-
-
Notifications
You must be signed in to change notification settings - Fork 338
feat: Improve accessibility (keyboard navigation, ARIA semantics, translations) #972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,7 +45,8 @@ export default function Footer(props: FooterProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prefixCls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| locale, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button: Button = 'button', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nowButton: NowButton = 'button', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| okButton: OkButton = 'button', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| classNames, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| styles, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = React.useContext(PickerContext); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -70,35 +71,24 @@ export default function Footer(props: FooterProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nowPrefixCls = `${prefixCls}-now`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nowBtnPrefixCls = `${nowPrefixCls}-btn`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const presetNode = showNow && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <li className={nowPrefixCls}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={clsx(nowBtnPrefixCls, nowDisabled && `${nowBtnPrefixCls}-disabled`)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-disabled={nowDisabled} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={onInternalNow} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {internalMode === 'date' ? locale.today : locale.now} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <NowButton className={`${prefixCls}-now`} disabled={nowDisabled} onClick={onInternalNow}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {internalMode === 'date' ? locale.today : locale.now} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </NowButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // >>> OK | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const okNode = needConfirm && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <li className={`${prefixCls}-ok`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button disabled={invalid} onClick={onSubmit}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {locale.ok} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <OkButton disabled={invalid} className={`${prefixCls}-ok`} onClick={onSubmit}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {locale.ok} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </OkButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add
Suggested change
Comment on lines
+75
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 给默认按钮补上
建议修改- <NowButton className={`${prefixCls}-now`} disabled={nowDisabled} onClick={onInternalNow}>
+ <NowButton
+ type="button"
+ className={`${prefixCls}-now`}
+ disabled={nowDisabled}
+ onClick={onInternalNow}
+ >
{internalMode === 'date' ? locale.today : locale.now}
</NowButton>
@@
- <OkButton disabled={invalid} className={`${prefixCls}-ok`} onClick={onSubmit}>
+ <OkButton type="button" disabled={invalid} className={`${prefixCls}-ok`} onClick={onSubmit}>
{locale.ok}
</OkButton>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rangeNode = (presetNode || okNode) && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ul className={`${prefixCls}-ranges`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`${prefixCls}-ranges`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {presetNode} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {okNode} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ======================== Render ======================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,24 +23,22 @@ export default function PresetPanel<DateType extends object = any>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`${prefixCls}-presets`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {presets.map(({ label, value }, index) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <li | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={index} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick(executeValue(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMouseEnter={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHover(executeValue(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMouseLeave={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHover(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {label} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {presets.map(({ label, value }, index) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={index} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick(executeValue(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMouseEnter={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHover(executeValue(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMouseLeave={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHover(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {label} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preset 按钮也需要显式声明 现在这些 preset 项已经变成原生 建议修改 <button
key={index}
+ type="button"
onClick={() => {
onClick(executeValue(value));
}}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,10 +20,12 @@ export type PopupShowTimeConfig<DateType extends object = any> = Omit< | |
| Pick<SharedTimeProps<DateType>, 'disabledTime'>; | ||
|
|
||
| export interface PopupProps<DateType extends object = any, PresetValue = DateType> | ||
| extends Pick<React.InputHTMLAttributes<HTMLDivElement>, 'onFocus' | 'onBlur'>, | ||
| extends | ||
| Pick<React.InputHTMLAttributes<HTMLDivElement>, 'onFocus' | 'onBlur'>, | ||
| FooterProps<DateType>, | ||
| PopupPanelProps<DateType> { | ||
| panelRender?: SharedPickerProps['panelRender']; | ||
| containerRef?: React.Ref<HTMLDivElement>; | ||
|
|
||
| // Presets | ||
| presets: ValueDate<DateType>[]; | ||
|
|
@@ -45,6 +47,7 @@ export interface PopupProps<DateType extends object = any, PresetValue = DateTyp | |
| onOk: VoidFunction; | ||
|
|
||
| onPanelMouseDown?: React.MouseEventHandler<HTMLDivElement>; | ||
| onEscapeKeyDown?: VoidFunction; | ||
|
|
||
| classNames?: SharedPickerProps['classNames']; | ||
| styles?: SharedPickerProps['styles']; | ||
|
|
@@ -71,6 +74,8 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D | |
| onFocus, | ||
| onBlur, | ||
| onPanelMouseDown, | ||
| containerRef, | ||
| onEscapeKeyDown, | ||
|
|
||
| // Direction | ||
| direction, | ||
|
|
@@ -213,11 +218,38 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D | |
| const marginLeft = 'marginLeft'; | ||
| const marginRight = 'marginRight'; | ||
|
|
||
| const onContainerKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => { | ||
| if (e.key === 'Tab') { | ||
| const container = e.currentTarget; | ||
| const focusable = Array.from( | ||
| container.querySelectorAll<HTMLElement>( | ||
| 'button:not([disabled]), a:not([aria-disabled="true"]), td[tabindex="0"], li[tabindex="0"]', | ||
| ), | ||
| ).filter((el) => window.getComputedStyle(el).visibility !== 'hidden'); | ||
|
Comment on lines
+221
to
+228
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 焦点循环的选择器写死了内置 DOM,定制内容会被跳过。 这里现在只会收集 建议把选择器改成通用的 focusable 集合(表单控件、 可参考的修正方向- const focusable = Array.from(
- container.querySelectorAll<HTMLElement>(
- 'button:not([disabled]), a:not([aria-disabled="true"]), td[tabindex="0"], li[tabindex="0"]',
- ),
- ).filter((el) => window.getComputedStyle(el).visibility !== 'hidden');
+ const focusable = Array.from(
+ container.querySelectorAll<HTMLElement>(
+ [
+ 'button',
+ 'a[href]',
+ 'input',
+ 'select',
+ 'textarea',
+ '[tabindex]:not([tabindex="-1"])',
+ ].join(', '),
+ ),
+ ).filter((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ !el.hasAttribute('disabled') &&
+ el.getAttribute('aria-disabled') !== 'true' &&
+ style.display !== 'none' &&
+ style.visibility !== 'hidden'
+ );
+ });🤖 Prompt for AI Agents |
||
|
|
||
| if (!focusable.length) return; | ||
|
|
||
| e.preventDefault(); | ||
| const idx = focusable.indexOf(document.activeElement as HTMLElement); | ||
| const next = e.shiftKey | ||
| ? (idx - 1 + focusable.length) % focusable.length | ||
| : (idx + 1) % focusable.length; | ||
| focusable[next].focus(); | ||
|
Comment on lines
+233
to
+237
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||
| } else if (e.key === 'Escape') { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| onEscapeKeyDown?.(); | ||
| } | ||
| }; | ||
|
|
||
| // Container | ||
| let renderNode = ( | ||
| <div | ||
| ref={containerRef} | ||
| onMouseDown={onPanelMouseDown} | ||
| onKeyDown={onContainerKeyDown} | ||
| tabIndex={-1} | ||
| role="dialog" | ||
| className={clsx( | ||
| containerPrefixCls, | ||
| // Used for Today Button style, safe to remove if no need | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add
type="button"to theNowButtonto prevent accidental form submissions when the picker is placed inside an HTML<form>.