1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-01 03:11:44 +02:00

fix: keyboard accessibility for example site (#5946)

* fix: keyboard accessibility for example site

* chore: remove redundant assignment of DOM attribute

Co-authored-by: Joe Anderson <joe@anderbell.studio>

* refactor: replace MouseEvent with PointerEvent for improved button interactions

---------

Co-authored-by: Joe Anderson <joe@anderbell.studio>
This commit is contained in:
Hitesh Shetty
2025-08-28 02:14:52 +05:30
committed by GitHub
parent a8fc9a4158
commit 61ee2adc0f
8 changed files with 79 additions and 29 deletions

View File

@@ -10,7 +10,7 @@ import 'prismjs/components/prism-python'
import 'prismjs/components/prism-sql'
import 'prismjs/components/prism-tsx'
import 'prismjs/components/prism-typescript'
import React, { ChangeEvent, MouseEvent, useCallback, useState } from 'react'
import React, { ChangeEvent, PointerEvent, useCallback, useState } from 'react'
import {
Editor,
Element,
@@ -143,10 +143,10 @@ const CodeBlockButton = () => {
<Button
data-test-id="code-block-button"
active
onMouseDown={(event: MouseEvent<HTMLButtonElement>) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
event.preventDefault()
handleClick()
}}
onClick={handleClick}
>
<Icon>code</Icon>
</Button>

View File

@@ -20,14 +20,16 @@ export const Button = React.forwardRef(
reversed: boolean
} & BaseProps
>,
ref: Ref<HTMLSpanElement>
ref: Ref<HTMLButtonElement>
) => (
<span
<button
{...props}
ref={ref}
className={cx(
className,
css`
border: none;
background: none;
padding: 0;
cursor: pointer;
color: ${reversed
? active
@@ -36,7 +38,8 @@ export const Button = React.forwardRef(
: active
? 'black'
: '#ccc'};
`
`,
className
)}
/>
)

View File

@@ -1,5 +1,5 @@
import { css } from '@emotion/css'
import React, { MouseEvent, useMemo, useState } from 'react'
import React, { PointerEvent, useMemo, useState } from 'react'
import { createEditor, Descendant, Transforms } from 'slate'
import { withHistory } from 'slate-history'
import {
@@ -130,10 +130,10 @@ const InsertEditableVoidButton = () => {
const editor = useSlateStatic()
return (
<Button
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
event.preventDefault()
insertEditableVoid(editor)
}}
onClick={() => insertEditableVoid(editor)}
>
<Icon>add</Icon>
</Button>

View File

@@ -1,5 +1,5 @@
import isHotkey from 'is-hotkey'
import React, { MouseEvent, useCallback, useMemo, useState } from 'react'
import React, { PointerEvent, useCallback, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
import { Editor, createEditor, Descendant } from 'slate'
import { withHistory } from 'slate-history'
@@ -115,10 +115,10 @@ const MarkButton = ({ format, icon }: MarkButtonProps) => {
return (
<Button
active={isMarkActive(editor, format)}
onMouseDown={(event: MouseEvent) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
event.preventDefault()
toggleMark(editor, format)
}}
onClick={() => toggleMark(editor, format)}
>
<Icon>{icon}</Icon>
</Button>

View File

@@ -2,7 +2,7 @@ import { css } from '@emotion/css'
import imageExtensions from 'image-extensions'
import isHotkey from 'is-hotkey'
import isUrl from 'is-url'
import React, { MouseEvent, useMemo } from 'react'
import React, { PointerEvent, useMemo } from 'react'
import { Descendant, Transforms, createEditor } from 'slate'
import { withHistory } from 'slate-history'
import {
@@ -135,6 +135,9 @@ const Image = ({
/>
<Button
active
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
event.preventDefault()
}}
onClick={() => Transforms.removeNodes(editor, { at: path })}
className={css`
display: ${selected && focused ? 'inline' : 'none'};
@@ -155,8 +158,10 @@ const InsertImageButton = () => {
const editor = useSlateStatic()
return (
<Button
onMouseDown={(event: MouseEvent) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
}
onClick={() => {
const url = window.prompt('Enter the URL of the image:')
if (url && !isImageUrl(url)) {
alert('URL is not an image')

View File

@@ -1,7 +1,7 @@
import { css } from '@emotion/css'
import { isKeyHotkey } from 'is-hotkey'
import isUrl from 'is-url'
import React, { MouseEvent, useMemo } from 'react'
import React, { PointerEvent, useMemo } from 'react'
import {
createEditor,
Descendant,
@@ -400,8 +400,10 @@ const AddLinkButton = () => {
return (
<Button
active={isLinkActive(editor)}
onMouseDown={(event: MouseEvent) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
}
onClick={() => {
const url = window.prompt('Enter the URL of the link:')
if (!url) return
insertLink(editor, url)
@@ -418,7 +420,10 @@ const RemoveLinkButton = () => {
return (
<Button
active={isLinkActive(editor)}
onMouseDown={(event: MouseEvent) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
}
onClick={() => {
if (isLinkActive(editor)) {
unwrapLink(editor)
}
@@ -434,8 +439,10 @@ const ToggleEditableButtonButton = () => {
return (
<Button
active
onMouseDown={(event: MouseEvent) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
}
onClick={() => {
if (isButtonActive(editor)) {
unwrapButton(editor)
} else {

View File

@@ -1,5 +1,5 @@
import isHotkey from 'is-hotkey'
import React, { KeyboardEvent, MouseEvent, useCallback, useMemo } from 'react'
import React, { KeyboardEvent, PointerEvent, useCallback, useMemo } from 'react'
import {
Descendant,
Editor,
@@ -247,10 +247,10 @@ const BlockButton = ({ format, icon }: BlockButtonProps) => {
format,
isAlignType(format) ? 'align' : 'type'
)}
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
toggleBlock(editor, format)
}}
}
onClick={() => toggleBlock(editor, format)}
>
<Icon>{icon}</Icon>
</Button>
@@ -267,10 +267,10 @@ const MarkButton = ({ format, icon }: MarkButtonProps) => {
return (
<Button
active={isMarkActive(editor, format)}
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
event.preventDefault()
toggleMark(editor, format)
}}
}
onClick={() => toggleMark(editor, format)}
>
<Icon>{icon}</Icon>
</Button>

View File

@@ -135,6 +135,9 @@ const TabList = ({
...props
}: React.HTMLAttributes<HTMLDivElement> & { isVisible?: boolean }) => (
<div
role="menu"
aria-label="Examples navigation"
aria-hidden={!isVisible}
{...props}
className={css`
background-color: #222;
@@ -143,11 +146,14 @@ const TabList = ({
overflow: auto;
padding-top: 0.2em;
position: absolute;
transition: width 0.2s;
transition:
width 0.2s,
visibility 0.2s;
width: ${isVisible ? '200px' : '0'};
white-space: nowrap;
max-height: 70vh;
z-index: 3; /* To appear above the underlay */
visibility: ${isVisible ? 'visible' : 'hidden'};
`}
/>
)
@@ -171,10 +177,16 @@ const TabListUnderlay = ({
)
const TabButton = (props: React.HTMLAttributes<HTMLSpanElement>) => (
<span
<button
{...props}
aria-label="Toggle examples menu"
aria-haspopup="menu"
className={css`
margin-left: 0.8em;
background: none;
border: none;
cursor: pointer;
padding: 0;
&:hover {
cursor: pointer;
@@ -204,6 +216,8 @@ const Tab = React.forwardRef(
<a
ref={ref}
href={href}
role="menuitem"
aria-current={active ? 'page' : undefined}
{...props}
className={css`
display: inline-block;
@@ -342,6 +356,12 @@ const ExamplePage = ({ example }: { example: string }) => {
e.stopPropagation()
setShowTabs(!showTabs)
}}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
setShowTabs(false)
}
}}
aria-expanded={showTabs}
>
<Icon>menu</Icon>
</TabButton>
@@ -368,7 +388,17 @@ const ExamplePage = ({ example }: { example: string }) => {
legacyBehavior
passHref
>
<Tab onClick={() => setShowTabs(false)}>{n}</Tab>
<Tab
onClick={() => setShowTabs(false)}
active={p === path}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
setShowTabs(false)
}
}}
>
{n}
</Tab>
</Link>
))}
</TabList>
@@ -393,6 +423,11 @@ const ExamplePage = ({ example }: { example: string }) => {
<TabListUnderlay
isVisible={showTabs}
onClick={() => setShowTabs(false)}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
setShowTabs(false)
}
}}
/>
</div>
</ErrorBoundary>