浏览代码

feat: inputNumber a11y keyboard #205 (#890)

走鹃 3 年之前
父节点
当前提交
9f0ae270cf

+ 16 - 10
content/input/inputnumber/index-en-US.md

@@ -69,16 +69,12 @@ class App extends React.Component {
                 <InputNumber defaultValue={2} disabled />
                 <br/><br/>
 
-                <label>Set autofocus to true </label>
-                <InputNumber defaultValue={3} autofocus />
-                <br/><br/>
-
                 <label>Set precision to 2 </label>
                 <InputNumber precision={2} defaultValue={1.234} />
                 <br/><br/>
 
                 <label>Set innerButtons=true </label>
-                <InputNumber innerButtons={true} suffix={'Hour'} defaultValue={1} style={{ width: 190}} />
+                <InputNumber innerButtons={true} suffix={'Hour'} defaultValue={1} style={{ width: 190 }} />
                 <br/>
 
             </div>
@@ -97,7 +93,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber innerButtons style={{ width: 190}} />
+    <InputNumber innerButtons style={{ width: 190 }} />
 );
 ```
 
@@ -108,7 +104,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber hideButtons style={{ width: 190}} />
+    <InputNumber hideButtons style={{ width: 190 }} />
 );
 
 ```
@@ -221,7 +217,7 @@ function Demo () {
 | prefixCls    | Prefix content                                                                                  | string\|ReactNode                 |           |            |
 | pressInterval| How often will the click event be triggered when the button is long pressed, in milliseconds                                   | number                 |   250        |           |
 | pressTimeout | When the button is long pressed, how long will the click event be triggered after the delay, in milliseconds                                               | number                 |     250      |           |
-| shiftStep    | Step size for pressing the shift key, it can be a decimal.                            | number                            | 1         | **1.5.0** |
+| shiftStep    | Step size for pressing the shift key, it can be a decimal. The default value was adjusted from 1 to 10 in v2.13                     | number                            | 10         | **1.5.0** |
 | showClear    | Do you show the clear button?                                                                   | boolean                           | false     | **0.35.0** |
 | size         | Enter box size, optional value: "default"\|"small"\|"large"                                     | string                            | 'default' |            |
 | step         | Each time you change the number of steps, it can be a decimal.                                  | number                            | 1         |            |
@@ -241,10 +237,20 @@ function Demo () {
 | focus() | Get the focus.  |
 
 ## Accessibility
+
+Guideline: https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
+
 ### ARIA
 
-- Added button role to the increase and decrease buttons to indicate that it is a button that can be clicked
-- Use aria-valuenow for the current value, aria-valuemax for the maximum acceptable value, and aria-valuemin for the minimum acceptable value
+- InputNumber has `spinbutton` role
+- spinbutton uses `aria-valuenow` for current value, `aria-valuemax` for acceptable maximum value, and `aria-valuemin` for acceptable minimum value
+- When InputNumber is used in Form, the value of the input box's `aria-labeledby` reference is Field label
+
+### Keyboard and Focus
+
+- InputNumber can get focus, keyboard users can use `Tab` and `Shift + Tab` to switch focus (Increase and decrease buttons are not focusable)
+- Keyboard users can press up key ⬆️ or down key ⬇️ and the input value will increase or decrease by `step` (default is 1)
+- Hold down Shift + Up ⬆️ or Down ⬇️ , the input value will increase or decrease by `shiftStep` (default is 10)
 
 ## Design Tokens
 <DesignToken/>

+ 15 - 10
content/input/inputnumber/index.md

@@ -55,16 +55,12 @@ import { InputNumber } from '@douyinfe/semi-ui';
         <InputNumber defaultValue={2} disabled />
         <br/><br/>
 
-        <label>自动获得焦点 autofocus=true </label>
-        <InputNumber defaultValue={3} autofocus />
-        <br/><br/>
-
         <label>设置了小数位数 precision=2 </label>
         <InputNumber precision={2} defaultValue={1.234} />
         <br/><br/>
 
         <label>设置了 innerButtons=true </label>
-        <InputNumber innerButtons={true} suffix={'小时'} defaultValue={1} style={{ width: 190}} />
+        <InputNumber innerButtons={true} suffix={'小时'} defaultValue={1} style={{ width: 190 }} />
         <br/>
     </div>
 );
@@ -80,7 +76,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber innerButtons style={{ width: 190}} />
+    <InputNumber innerButtons style={{ width: 190 }} />
 );
 ```
 
@@ -91,7 +87,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber hideButtons style={{ width: 190}} />
+    <InputNumber hideButtons style={{ width: 190 }} />
 );
 ```
 
@@ -195,7 +191,7 @@ function Demo () {
 | prefixCls    | 前缀内容                                                       | string\|ReactNode                 |           |           |
 | pressInterval| 长按按钮时,多久触发一次点击事件,单位毫秒                                   | number                 |   250        |           |
 | pressTimeout | 长按按钮时,延迟多久后触发点击事件,单位毫秒                                                      | number                 |     250      |           |
-| shiftStep    | 按住 shift 键每次改变步数,可以为小数                           | number                            | 1         | **1.5.0** |
+| shiftStep    | 按住 shift 键每次改变步数,可以为小数,v2.13 默认值由 1 调整为 10                           | number                            | 10         | **1.5.0** |
 | showClear    | 是否显示清除按钮                                               | boolean                           | false     | **0.35.0**   |
 | size         | 输入框大小,可选值:"default"\|"small"\|"large"                | string                            | 'default' |           |
 | step         | 每次改变步数,可以为小数                                       | number                            | 1         |           |
@@ -217,10 +213,19 @@ function Demo () {
 
 ## Accessibility
 
+参考标准:https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
+
 ### ARIA
 
-- 增减按钮添加了 button role,以表示是可以点击的按钮
-- 使用 aria-valuenow 表示当前值,aria-valuemax 表示可以接受的最大值,aria-valuemin 表示可以接受的最小值
+- 数字输入框具有 spinbutton role
+- spinbutton 使用 aria-valuenow 表示当前值,aria-valuemax 表示可以接受的最大值,aria-valuemin 表示可以接受的最小值
+- 当 InputNumber 在 Form 中使用时,输入框的 aria-labeledby 指向 Field label
+
+### 键盘和焦点
+
+- InputNumber 可被获取焦点,键盘用户可以使用 Tab 及 Shift + Tab 切换焦点(增加/减少按钮不可以被键盘聚焦)
+- 键盘用户可以按上键 ⬆️ 或下键 ⬇️ ,输入值将增加或减少 step(默认值为 1)
+- 按住 Shift + 上键 ⬆️ 或下键 ⬇️ ,输入值将增加或减少 shiftStep(默认值为 10)
 
 ## 设计变量
 <DesignToken/>

+ 20 - 1
cypress/integration/inputNumber.spec.js

@@ -12,5 +12,24 @@ describe('inputNumber', () => {
         cy.get('[data-cy=fix-precision-786] .semi-input').type('aaa');
         cy.get('[data-cy=fix-precision-786] .semi-input').blur();
         cy.get('[data-cy=fix-precision-786] .semi-input').should('have.value', '');
-    })
+    });
+
+    it('a11y', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=inputnumber--input-number-a-11-y&args=&viewMode=story');
+        cy.get('input[data-cy=default]').click();
+        cy.get('input[data-cy=default]').type('{upArrow}');
+        cy.get('input[data-cy=default]').should('have.value', '1');
+        cy.get('input[data-cy=default]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'upArrow', keyCode: 38, shiftKey: true });
+        cy.get('input[data-cy=default]').should('have.value', '11');
+        cy.get('input[data-cy=default]').type('{downArrow}');
+        cy.get('input[data-cy=default]').should('have.value', '10');
+
+        cy.get('input[data-cy=step]').click();
+        cy.get('input[data-cy=step]').type('{upArrow}');
+        cy.get('input[data-cy=step]').should('have.value', '5');
+        cy.get('input[data-cy=step]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'upArrow', keyCode: 38, shiftKey: true });
+        cy.get('input[data-cy=step]').should('have.value', '105');
+        cy.get('input[data-cy=step]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'downArrow', keyCode: 40, shiftKey: true });
+        cy.get('input[data-cy=step]').should('have.value', '5');
+    });
 });

+ 1 - 1
packages/semi-foundation/inputNumber/constants.ts

@@ -7,7 +7,7 @@ const cssClasses = {
 const numbers = {
     ...inputNumbers,
     DEFAULT_STEP: 1,
-    DEFAULT_SHIFT_STEP: 1,
+    DEFAULT_SHIFT_STEP: 10,
     DEFAULT_PRESS_TIMEOUT: 250,
     DEFAULT_PRESS_INTERVAL: 0,
     MOUSE_BUTTON_LEFT: 0, // left mouse button

+ 1 - 1
packages/semi-foundation/inputNumber/foundation.ts

@@ -215,7 +215,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         if (code === keyCode.UP || code === keyCode.DOWN) {
             this._adapter.setClickUpOrDown(true);
             this._adapter.recordCursorPosition();
-            const formattedVal = code === keyCode.UP ? this.add() : this.minus();
+            const formattedVal = code === keyCode.UP ? this.add(null, event) : this.minus(null, event);
 
             this._doInput(formattedVal, event, () => {
                 this._adapter.restoreCursor();

+ 24 - 0
packages/semi-ui/inputNumber/_story/inputNumber.stories.js

@@ -5,6 +5,7 @@ import InputNumber from '../index';
 import Button from '../../button/index';
 import { withField, Form } from '../../index';
 import { useFormApi } from '../../form';
+import { Space } from '../../index';
 
 export default {
   title: 'InputNumber',
@@ -741,3 +742,26 @@ FixPrecision786.storyName = 'fix precision 删除后输入非法值会显示 0.0
   );
 }
 FixFormValidate.storyName = 'fix form validate';
+
+export const InputNumberA11y = () => {
+  return (
+    <Space vertical align="start" data-cy="a11y">
+      <label for="default">
+        step=1, shiftStep=10
+      </label>
+      <InputNumber id="default" data-cy="default" />
+      <label for="step">
+        step=5, shiftStep=100
+      </label>
+      <InputNumber id="step" data-cy="step" step={5} shiftStep={100} />
+      <label for="max">
+        step=1, shiftStep=10, max=10
+      </label>
+      <InputNumber id="max" data-cy="max" max={10} />
+      <Form>
+        <Form.InputNumber field="test" label="item number" />
+      </Form>
+    </Space>
+  );
+}
+InputNumberA11y.storyName = "inputNumber a11y";

+ 0 - 4
packages/semi-ui/inputNumber/index.tsx

@@ -397,8 +397,6 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
         return (
             <div className={suffixChildrenCls}>
                 <span
-                    role="button"
-                    tabIndex={-1}
                     className={upClassName}
                     onMouseDown={notAllowedUp ? noop : this.handleUpClick}
                     onMouseUp={this.handleMouseUp}
@@ -407,8 +405,6 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                     <IconChevronUp size="extra-small" />
                 </span>
                 <span
-                    role="button"
-                    tabIndex={-1}
                     className={downClassName}
                     onMouseDown={notAllowedDown ? noop : this.handleDownClick}
                     onMouseUp={this.handleMouseUp}