Browse Source

feat(a11y): select #205

走鹃 3 years ago
parent
commit
222cdbb531

+ 2 - 3
packages/semi-foundation/input/foundation.ts

@@ -2,8 +2,7 @@ import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation
 import { strings } from './constants';
 import { noop, set, isNumber, isString, isFunction } from 'lodash-es';
 import isEnterPress from '../utils/isEnterPress';
-
-const ENTER_KEY_CODE = 'Enter';
+import { ENTER_KEY } from './../utils/keyCode';
 
 export interface InputDefaultAdapter {
     notifyChange: noopFunction;
@@ -262,7 +261,7 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
 
     handleKeyPress(e: any) {
         this._adapter.notifyKeyPress(e);
-        if (e.key === ENTER_KEY_CODE) {
+        if (e.key === ENTER_KEY) {
             this._adapter.notifyEnterPress(e);
         }
     }

+ 15 - 1
packages/semi-foundation/select/foundation.ts

@@ -1,11 +1,12 @@
 /* argus-disable unPkgSensitiveInfo */
 /* eslint-disable max-len */
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
-import KeyCode from '../utils/keyCode';
+import KeyCode, { ENTER_KEY } from '../utils/keyCode';
 import { isNumber, isString, isEqual } from 'lodash-es';
 import warning from '../utils/warning';
 import isNullOrUndefined from '../utils/isNullOrUndefined';
 import { BasicOptionProps } from './optionFoundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface SelectAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     getTriggerWidth(): number;
@@ -649,6 +650,7 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
                 this._handleEnterKeyDown(event);
                 break;
             case KeyCode.ESC:
+            case KeyCode.TAB:
                 this.close(event);
                 break;
             default:
@@ -867,6 +869,18 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
         e.stopPropagation();
     }
 
+    handleKeyPress(e: KeyboardEvent) {
+        if (e && e.key === ENTER_KEY) {
+            this.handleClick(e);
+        }
+    }
+
+    handleClearBtnEnterPress(e: KeyboardEvent) {
+        if (isEnterPress(e)) {
+            this.handleClearClick(e as any);
+        }
+    }
+
     handleOptionMouseEnter(optionIndex: number) {
         this._adapter.updateFocusIndex(optionIndex);
     }

+ 2 - 2
packages/semi-foundation/utils/isEnterPress.ts

@@ -1,8 +1,8 @@
 import { get } from 'lodash-es';
-import { ENTER_KEY_CODE } from './keyCode';
+import { ENTER_KEY } from './keyCode';
 
 function isEnterPress<T extends { key: string }>(e: T) {
-    return get(e, 'key') === ENTER_KEY_CODE ? true : false;
+    return get(e, 'key') === ENTER_KEY ? true : false;
 }
 
 export default isEnterPress;

+ 2 - 1
packages/semi-foundation/utils/keyCode.ts

@@ -426,6 +426,7 @@ const keyCode = {
     WIN_IME: 229,
 };
 
-export const ENTER_KEY_CODE = 'Enter';
+export const ENTER_KEY = 'Enter';
+export const TAB_KEY = 'Tab';
 
 export default keyCode;

+ 29 - 2
packages/semi-ui/select/index.tsx

@@ -326,6 +326,8 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.onMouseEnter = this.onMouseEnter.bind(this);
         this.onMouseLeave = this.onMouseLeave.bind(this);
         this.renderOption = this.renderOption.bind(this);
+        this.onKeyPress = this.onKeyPress.bind(this);
+        this.onClearBtnEnterPress = this.onClearBtnEnterPress.bind(this);
 
         this.foundation = new SelectFoundation(this.adapter);
 
@@ -622,6 +624,10 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.foundation.handleClearClick(e as any);
     }
 
+    onClearBtnEnterPress(e: React.KeyboardEvent) {
+        this.foundation.handleClearBtnEnterPress(e as any);
+    }
+
     renderEmpty() {
         return <Option empty={true} emptyContent={this.props.emptyContent} />;
     }
@@ -701,7 +707,8 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         const customCreateItem = renderCreateItem(option.value, isFocused);
 
         return (
-            <div onClick={e => this.onSelect(option, optionIndex, e)} key={new Date().valueOf()}>
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus
+            <div role="button" aria-label="Use the input box to create an optional item" onClick={e => this.onSelect(option, optionIndex, e)} key={new Date().valueOf()}>
                 {customCreateItem}
             </div>
         );
@@ -767,6 +774,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             innerBottomSlot,
             loading,
             virtualize,
+            multiple,
         } = this.props;
 
         // Do a filter first, instead of directly judging in forEach, so that the focusIndex can correspond to
@@ -792,6 +800,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                     style={{ maxHeight: `${maxHeight}px` }}
                     className={optionListCls}
                     role="listbox"
+                    aria-multiselectable={multiple}
                     onScroll={e => this.foundation.handleListScroll(e)}
                 >
                     {innerTopSlot}
@@ -919,6 +928,10 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         this.foundation.handleMouseLeave(e as any);
     }
 
+    onKeyPress(e: React.KeyboardEvent) {
+        this.foundation.handleKeyPress(e as any);
+    }
+
     /* Processing logic when popover visible changes */
     handlePopoverVisibleChange(status) {
         const { virtualize } = this.props;
@@ -1036,7 +1049,14 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                 </Fragment>,
                 <Fragment key="clearicon">
                     {showClear ? (
-                        <div className={cls(`${prefixcls}-clear`)} onClick={this.onClear}>
+                        <div
+                            role="button"
+                            aria-label="Clear selected value"
+                            tabIndex={0}
+                            className={cls(`${prefixcls}-clear`)}
+                            onClick={this.onClear}
+                            onKeyPress={this.onClearBtnEnterPress}
+                        >
                             <IconClear />
                         </div>
                     ) : arrowContent}
@@ -1048,6 +1068,12 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         const tabIndex = disabled ? null : 0;
         return (
             <div
+                role="combobox"
+                aria-disabled={disabled}
+                aria-expanded={isOpen}
+                aria-controls={`${prefixcls}-${this.selectOptionListID}`}
+                aria-haspopup="listbox"
+                aria-label="select value"
                 className={selectionCls}
                 ref={ref => ((this.triggerRef as any).current = ref)}
                 onClick={e => this.foundation.handleClick(e)}
@@ -1057,6 +1083,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                 onMouseLeave={this.onMouseLeave}
                 // onFocus={e => this.foundation.handleTriggerFocus(e)}
                 onBlur={e => this.foundation.handleTriggerBlur(e as any)}
+                onKeyPress={this.onKeyPress}
                 {...keyboardEventSet}
             >
                 {inner}

+ 3 - 0
packages/semi-ui/select/option.tsx

@@ -134,6 +134,7 @@ class Option extends PureComponent<OptionProps> {
             }
         };
         return (
+            // eslint-disable-next-line jsx-a11y/interactive-supports-focus,jsx-a11y/click-events-have-key-events
             <div
                 className={optionClassName}
                 onClick={e => {
@@ -141,6 +142,8 @@ class Option extends PureComponent<OptionProps> {
                 }}
                 onMouseEnter={e => onMouseEnter && onMouseEnter(e)}
                 role="option"
+                aria-selected={selected ? "true" : "false"}
+                aria-disabled={disabled ? "true" : "false"}
                 style={style}
             >
                 {showTick ? (