1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-01 11:12:42 +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-sql'
import 'prismjs/components/prism-tsx' import 'prismjs/components/prism-tsx'
import 'prismjs/components/prism-typescript' import 'prismjs/components/prism-typescript'
import React, { ChangeEvent, MouseEvent, useCallback, useState } from 'react' import React, { ChangeEvent, PointerEvent, useCallback, useState } from 'react'
import { import {
Editor, Editor,
Element, Element,
@@ -143,10 +143,10 @@ const CodeBlockButton = () => {
<Button <Button
data-test-id="code-block-button" data-test-id="code-block-button"
active active
onMouseDown={(event: MouseEvent<HTMLButtonElement>) => { onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
event.preventDefault() event.preventDefault()
handleClick()
}} }}
onClick={handleClick}
> >
<Icon>code</Icon> <Icon>code</Icon>
</Button> </Button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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