@douyinfe/semi-ui 2.4.1 → 2.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/calendar/monthCalendar.tsx +14 -13
  2. package/cascader/__test__/cascader.test.js +24 -0
  3. package/cascader/_story/cascader.stories.js +73 -0
  4. package/cascader/index.tsx +26 -5
  5. package/cascader/item.tsx +25 -5
  6. package/datePicker/_story/v2/FixInputRangeFocus.jsx +25 -0
  7. package/datePicker/_story/v2/index.js +2 -1
  8. package/datePicker/dateInput.tsx +8 -5
  9. package/datePicker/datePicker.tsx +9 -1
  10. package/datePicker/month.tsx +14 -7
  11. package/datePicker/monthsGrid.tsx +17 -5
  12. package/datePicker/navigation.tsx +8 -4
  13. package/datePicker/quickControl.tsx +1 -0
  14. package/datePicker/yearAndMonth.tsx +1 -1
  15. package/dist/css/semi.css +71 -35
  16. package/dist/css/semi.min.css +1 -1
  17. package/dist/umd/semi-ui.js +696 -263
  18. package/dist/umd/semi-ui.js.map +1 -1
  19. package/dist/umd/semi-ui.min.js +1 -1
  20. package/dist/umd/semi-ui.min.js.map +1 -1
  21. package/form/__test__/formApi.test.js +182 -0
  22. package/form/_story/FormApi/arrayDemo.jsx +4 -7
  23. package/form/_story/Layout/slotDemo.jsx +2 -2
  24. package/form/_story/demo.jsx +18 -1
  25. package/form/_story/form.stories.js +6 -6
  26. package/form/baseForm.tsx +2 -2
  27. package/form/hoc/withField.tsx +1 -1
  28. package/lib/cjs/_base/base.css +5 -5
  29. package/lib/cjs/autoComplete/index.d.ts +1 -1
  30. package/lib/cjs/calendar/monthCalendar.js +21 -5
  31. package/lib/cjs/cascader/index.d.ts +9 -2
  32. package/lib/cjs/cascader/index.js +20 -1
  33. package/lib/cjs/cascader/item.d.ts +6 -2
  34. package/lib/cjs/cascader/item.js +33 -4
  35. package/lib/cjs/datePicker/dateInput.d.ts +0 -2
  36. package/lib/cjs/datePicker/dateInput.js +17 -6
  37. package/lib/cjs/datePicker/datePicker.js +19 -12
  38. package/lib/cjs/datePicker/month.d.ts +1 -0
  39. package/lib/cjs/datePicker/month.js +18 -2
  40. package/lib/cjs/datePicker/monthsGrid.js +16 -4
  41. package/lib/cjs/datePicker/navigation.js +8 -0
  42. package/lib/cjs/datePicker/quickControl.js +1 -0
  43. package/lib/cjs/datePicker/yearAndMonth.js +1 -0
  44. package/lib/cjs/dropdown/index.d.ts +1 -1
  45. package/lib/cjs/form/baseForm.d.ts +1 -1
  46. package/lib/cjs/form/baseForm.js +2 -2
  47. package/lib/cjs/form/field.d.ts +1 -1
  48. package/lib/cjs/form/hoc/withField.js +1 -1
  49. package/lib/cjs/scrollList/scrollItem.d.ts +2 -1
  50. package/lib/cjs/scrollList/scrollItem.js +13 -3
  51. package/lib/cjs/select/index.d.ts +3 -3
  52. package/lib/cjs/select/index.js +32 -28
  53. package/lib/cjs/select/option.js +2 -2
  54. package/lib/cjs/select/virtualRow.js +2 -2
  55. package/lib/cjs/table/Table.d.ts +1 -1
  56. package/lib/cjs/table/Table.js +8 -2
  57. package/lib/cjs/table/interface.d.ts +1 -0
  58. package/lib/cjs/tabs/interface.d.ts +1 -1
  59. package/lib/cjs/tooltip/index.d.ts +1 -1
  60. package/lib/cjs/tooltip/index.js +6 -2
  61. package/lib/cjs/tree/index.d.ts +2 -0
  62. package/lib/cjs/tree/index.js +15 -8
  63. package/lib/cjs/treeSelect/index.d.ts +2 -0
  64. package/lib/cjs/treeSelect/index.js +64 -27
  65. package/lib/cjs/upload/fileCard.js +31 -22
  66. package/lib/cjs/upload/index.d.ts +6 -0
  67. package/lib/cjs/upload/index.js +15 -8
  68. package/lib/cjs/upload/interface.d.ts +8 -6
  69. package/lib/es/_base/base.css +5 -5
  70. package/lib/es/autoComplete/index.d.ts +1 -1
  71. package/lib/es/calendar/monthCalendar.js +22 -5
  72. package/lib/es/cascader/index.d.ts +9 -2
  73. package/lib/es/cascader/index.js +19 -1
  74. package/lib/es/cascader/item.d.ts +6 -2
  75. package/lib/es/cascader/item.js +31 -4
  76. package/lib/es/datePicker/dateInput.d.ts +0 -2
  77. package/lib/es/datePicker/dateInput.js +17 -6
  78. package/lib/es/datePicker/datePicker.js +19 -12
  79. package/lib/es/datePicker/month.d.ts +1 -0
  80. package/lib/es/datePicker/month.js +18 -2
  81. package/lib/es/datePicker/monthsGrid.js +16 -4
  82. package/lib/es/datePicker/navigation.js +8 -0
  83. package/lib/es/datePicker/quickControl.js +2 -0
  84. package/lib/es/datePicker/yearAndMonth.js +1 -0
  85. package/lib/es/dropdown/index.d.ts +1 -1
  86. package/lib/es/form/baseForm.d.ts +1 -1
  87. package/lib/es/form/baseForm.js +2 -2
  88. package/lib/es/form/field.d.ts +1 -1
  89. package/lib/es/form/hoc/withField.js +1 -1
  90. package/lib/es/scrollList/scrollItem.d.ts +2 -1
  91. package/lib/es/scrollList/scrollItem.js +13 -3
  92. package/lib/es/select/index.d.ts +3 -3
  93. package/lib/es/select/index.js +30 -26
  94. package/lib/es/select/option.js +2 -2
  95. package/lib/es/select/virtualRow.js +2 -2
  96. package/lib/es/table/Table.d.ts +1 -1
  97. package/lib/es/table/Table.js +10 -2
  98. package/lib/es/table/interface.d.ts +1 -0
  99. package/lib/es/tabs/interface.d.ts +1 -1
  100. package/lib/es/tooltip/index.d.ts +1 -1
  101. package/lib/es/tooltip/index.js +6 -2
  102. package/lib/es/tree/index.d.ts +2 -0
  103. package/lib/es/tree/index.js +15 -8
  104. package/lib/es/treeSelect/index.d.ts +2 -0
  105. package/lib/es/treeSelect/index.js +64 -27
  106. package/lib/es/upload/fileCard.js +31 -24
  107. package/lib/es/upload/index.d.ts +6 -0
  108. package/lib/es/upload/index.js +14 -8
  109. package/lib/es/upload/interface.d.ts +8 -6
  110. package/package.json +9 -9
  111. package/scrollList/_story/ScrollList/index.js +3 -0
  112. package/scrollList/_story/WheelList/index.js +3 -0
  113. package/scrollList/scrollItem.tsx +30 -9
  114. package/select/index.tsx +18 -19
  115. package/select/option.tsx +2 -2
  116. package/select/virtualRow.tsx +2 -2
  117. package/table/Table.tsx +7 -2
  118. package/table/_story/Perf/Virtualized/index.jsx +6 -0
  119. package/table/_story/table.stories.js +1 -2
  120. package/table/_story/v2/FixedHeaderMerge/index.jsx +98 -0
  121. package/table/_story/v2/FixedResizable/index.jsx +114 -0
  122. package/table/_story/v2/defaultFilteredValue.tsx +114 -0
  123. package/table/_story/v2/index.js +5 -0
  124. package/table/interface.ts +1 -0
  125. package/tabs/interface.ts +1 -1
  126. package/tooltip/__test__/tooltip.test.js +48 -4
  127. package/tooltip/_story/tooltip.stories.js +83 -1
  128. package/tooltip/index.tsx +4 -4
  129. package/tree/__test__/treeMultiple.test.js +94 -0
  130. package/tree/_story/tree.stories.js +169 -0
  131. package/tree/index.tsx +12 -5
  132. package/treeSelect/__test__/treeMultiple.test.js +94 -0
  133. package/treeSelect/_story/treeSelect.stories.js +242 -0
  134. package/treeSelect/index.tsx +72 -40
  135. package/upload/_story/upload.stories.js +22 -6
  136. package/upload/fileCard.tsx +23 -23
  137. package/upload/index.tsx +15 -6
  138. package/upload/interface.ts +7 -5
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */
1
2
  import React, { ReactInstance } from 'react';
2
3
  import ReactDOM from 'react-dom';
3
4
  import cls from 'classnames';
@@ -180,16 +181,16 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
180
181
  const { markWeekend, displayValue } = this.props;
181
182
  this.monthlyData = this.foundation.getMonthlyData(displayValue, dateFnsLocale);
182
183
  return (
183
- <div className={`${prefixCls}-header`}>
184
- <div role="gridcell" className={`${prefixCls}-grid`}>
185
- <ul className={`${prefixCls}-grid-row`}>
184
+ <div className={`${prefixCls}-header`} role="presentation">
185
+ <div role="presentation" className={`${prefixCls}-grid`}>
186
+ <ul role="row" className={`${prefixCls}-grid-row`}>
186
187
  {this.monthlyData[0].map(day => {
187
188
  const { weekday } = day;
188
189
  const listCls = cls({
189
190
  [`${cssClasses.PREFIX}-weekend`]: markWeekend && day.isWeekend,
190
191
  });
191
192
  return (
192
- <li key={`${weekday}-monthheader`} className={listCls}>
193
+ <li role="columnheader" aria-label={weekday} key={`${weekday}-monthheader`} className={listCls}>
193
194
  <span>{weekday}</span>
194
195
  </li>
195
196
  );
@@ -268,7 +269,7 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
268
269
  const pos = showCard && showCard[key] ? showCard[key][1] : 'leftTopOver';
269
270
  const text = (
270
271
  <LocaleConsumer componentName="Calendar">
271
- {(locale: Locale['Calendar']) => (
272
+ {(locale: Locale['Calendar']) => (// eslint-disable-next-line jsx-a11y/no-static-element-interactions
272
273
  <div
273
274
  className={`${cardCls}-wrapper`}
274
275
  style={{ bottom: 0 }}
@@ -330,8 +331,8 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
330
331
  const { itemLimit } = this.state;
331
332
  const { display, day } = events;
332
333
  return (
333
- <div role="gridcell" className={`${prefixCls}-weekrow`} ref={this.cellDom} key={`${index}-weekrow`}>
334
- <ul className={`${prefixCls}-skeleton`}>
334
+ <div role="presentation" className={`${prefixCls}-weekrow`} ref={this.cellDom} key={`${index}-weekrow`}>
335
+ <ul role="row" className={`${prefixCls}-skeleton`}>
335
336
  {weekDay.map(each => {
336
337
  const { date, dayString, isToday, isSameMonth, isWeekend, month, ind } = each;
337
338
  const listCls = cls({
@@ -341,7 +342,7 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
341
342
  });
342
343
  const shouldRenderCollapsed = Boolean(day && day[ind] && day[ind].length > itemLimit);
343
344
  const inner = (
344
- <li key={`${date}-weeksk`} className={listCls} onClick={e => this.handleClick(e, [date])}>
345
+ <li role="gridcell" aria-label={date.toLocaleDateString()} aria-current={isToday ? "date" : false} key={`${date}-weeksk`} className={listCls} onClick={e => this.handleClick(e, [date])}>
345
346
  {this.formatDayString(month, dayString)}
346
347
  {this.renderCusDateGrid(date)}
347
348
  </li>
@@ -362,8 +363,8 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
362
363
  renderMonthGrid = () => {
363
364
  const { parsedEvents } = this.state;
364
365
  return (
365
- <div role="gridcell" className={`${prefixCls}-week`}>
366
- <ul className={`${prefixCls}-grid-col`}>
366
+ <div role="presentation" className={`${prefixCls}-week`}>
367
+ <ul role="presentation" className={`${prefixCls}-grid-col`}>
367
368
  {Object.keys(this.monthlyData).map(weekInd =>
368
369
  this.renderWeekRow(weekInd, this.monthlyData[weekInd], parsedEvents[weekInd])
369
370
  )}
@@ -383,12 +384,12 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
383
384
  return (
384
385
  <LocaleConsumer componentName="Calendar">
385
386
  {(locale: Locale['Calendar'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => (
386
- <div className={monthCls} key={this.state.itemLimit} style={monthStyle}>
387
- <div className={`${prefixCls}-sticky-top`}>
387
+ <div role="grid" className={monthCls} key={this.state.itemLimit} style={monthStyle}>
388
+ <div role="presentation" className={`${prefixCls}-sticky-top`}>
388
389
  {header}
389
390
  {this.renderHeader(dateFnsLocale)}
390
391
  </div>
391
- <div className={`${prefixCls}-grid-wrapper`}>
392
+ <div role="presentation" className={`${prefixCls}-grid-wrapper`}>
392
393
  {this.renderMonthGrid()}
393
394
  </div>
394
395
  </div>
@@ -195,6 +195,30 @@ describe('Cascader', () => {
195
195
  // done();
196
196
  });
197
197
 
198
+ it('dynamic treeData in multiple and uncontrolled mode', () => {
199
+ const cascader = render({
200
+ defaultValue: 'Yazhou',
201
+ multiple: true,
202
+ });
203
+ const opt = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-cascader-selection-multiple .${BASE_CLASS_PREFIX}-tag`);
204
+ expect(opt.length).toEqual(1);
205
+ cascader.setProps({ treeData: treeDataWithDisabled });
206
+ cascader.update();
207
+ expect(opt.length).toEqual(1);
208
+ });
209
+
210
+ it('dynamic treeData in multiple and controlled mode', () => {
211
+ const cascader = render({
212
+ value: 'Yazhou',
213
+ multiple: true,
214
+ });
215
+ const opt = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-cascader-selection-multiple .${BASE_CLASS_PREFIX}-tag`);
216
+ expect(opt.length).toEqual(1);
217
+ cascader.setProps({ treeData: treeDataWithDisabled });
218
+ cascader.update();
219
+ expect(opt.length).toEqual(1);
220
+ });
221
+
198
222
  it('getPopupContainer', () => {
199
223
  let cascader = render({
200
224
  getPopupContainer: getPopupContainer,
@@ -1348,4 +1348,77 @@ export const LeafOnly = () => {
1348
1348
  />
1349
1349
  </div>
1350
1350
  );
1351
+ }
1352
+
1353
+ export const DynamicTreeData = () => {
1354
+ const [treeDataDemo1,setTreeData1]=useState(treeData2);
1355
+ const [treeDataDemo2,setTreeData2]=useState(treeData2);
1356
+ const [treeDataDemo3,setTreeData3]=useState(treeData2);
1357
+ const [treeDataDemo4,setTreeData4]=useState(treeData2);
1358
+ const [treeDataDemo5,setTreeData5]=useState(treeData2);
1359
+ const [value3,setValue3]=useState();
1360
+ const [value4,setValue4]=useState();
1361
+ return (
1362
+ <div>
1363
+ <div>多选 + 动态更新 tree</div>
1364
+ <Cascader
1365
+ style={{ width: 300 }}
1366
+ treeData={treeDataDemo1}
1367
+ multiple
1368
+ placeholder="请选择所在地区"
1369
+ />
1370
+ <Button onClick={()=>{setTreeData1(treeData3)}}>改变treeData</Button>
1371
+ <br />
1372
+ <br />
1373
+ <div>单选 + 动态更新 tree</div>
1374
+ <Cascader
1375
+ style={{ width: 300 }}
1376
+ treeData={treeDataDemo2}
1377
+ placeholder="请选择所在地区"
1378
+ />
1379
+ <Button onClick={()=>{setTreeData2(treeData3)}}>改变treeData</Button>
1380
+ <br />
1381
+ <br />
1382
+ <div>多选 + 动态更新 tree + 受控</div>
1383
+ <Cascader
1384
+ style={{ width: 300 }}
1385
+ treeData={treeDataDemo3}
1386
+ multiple
1387
+ value={value3}
1388
+ onChange={v=>{
1389
+ console.log(v);
1390
+ setValue3(v);
1391
+ }}
1392
+ placeholder="请选择所在地区"
1393
+ />
1394
+ <Button onClick={()=>{setTreeData3(treeData3)}}>改变treeData</Button>
1395
+ <br />
1396
+ <br />
1397
+ <div>单选 + 动态更新 tree + 受控</div>
1398
+ <Cascader
1399
+ style={{ width: 300 }}
1400
+ treeData={treeDataDemo4}
1401
+ value={value4}
1402
+ onChange={v=>{
1403
+ console.log(v);
1404
+ setValue4(v);
1405
+ }}
1406
+ placeholder="请选择所在地区"
1407
+ />
1408
+ <Button onClick={()=>{setTreeData4(treeData3)}}>改变treeData</Button>
1409
+ <br />
1410
+ <br />
1411
+ <div>多选 + 动态更新 tree + defaultValue 为亚洲</div>
1412
+ <Cascader
1413
+ style={{ width: 300 }}
1414
+ treeData={treeDataDemo5}
1415
+ multiple
1416
+ defaultValue='yazhou'
1417
+ placeholder="请选择所在地区"
1418
+ />
1419
+ <Button onClick={()=>{setTreeData5(treeData3)}}>改变treeData</Button>
1420
+ <br />
1421
+ <br />
1422
+ </div>
1423
+ );
1351
1424
  }
@@ -1,4 +1,4 @@
1
- import React, { Fragment, ReactNode, CSSProperties, MouseEvent } from 'react';
1
+ import React, { Fragment, ReactNode, CSSProperties, MouseEvent, KeyboardEvent } from 'react';
2
2
  import ReactDOM from 'react-dom';
3
3
  import cls from 'classnames';
4
4
  import PropTypes from 'prop-types';
@@ -14,7 +14,7 @@ import CascaderFoundation, {
14
14
  } from '@douyinfe/semi-foundation/cascader/foundation';
15
15
  import { cssClasses, strings } from '@douyinfe/semi-foundation/cascader/constants';
16
16
  import { numbers as popoverNumbers } from '@douyinfe/semi-foundation/popover/constants';
17
- import { isEqual, isString, isEmpty, isFunction, isNumber, noop, flatten } from 'lodash';
17
+ import { isSet, isEqual, isString, isEmpty, isFunction, isNumber, noop, flatten } from 'lodash';
18
18
  import '@douyinfe/semi-foundation/cascader/cascader.scss';
19
19
  import { IconClear, IconChevronDown } from '@douyinfe/semi-icons';
20
20
  import { findKeysForValues, convertDataToEntities, calcMergeType } from '@douyinfe/semi-foundation/cascader/util';
@@ -54,6 +54,7 @@ export interface CascaderProps extends BasicCascaderProps {
54
54
  'aria-invalid'?: React.AriaAttributes['aria-invalid'];
55
55
  'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];
56
56
  'aria-required'?: React.AriaAttributes['aria-required'];
57
+ 'aria-label'?: React.AriaAttributes['aria-label'];
57
58
  arrowIcon?: ReactNode;
58
59
  defaultValue?: Value;
59
60
  dropdownStyle?: CSSProperties;
@@ -100,6 +101,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
100
101
  'aria-errormessage': PropTypes.string,
101
102
  'aria-describedby': PropTypes.string,
102
103
  'aria-required': PropTypes.bool,
104
+ 'aria-label': PropTypes.string,
103
105
  arrowIcon: PropTypes.node,
104
106
  changeOnSelect: PropTypes.bool,
105
107
  defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
@@ -197,6 +199,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
197
199
  onDropdownVisibleChange: noop,
198
200
  onListScroll: noop,
199
201
  enableLeafClick: false,
202
+ 'aria-label': 'Cascader'
200
203
  };
201
204
 
202
205
  options: any;
@@ -426,7 +429,10 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
426
429
  });
427
430
  realKeys = formatKeys;
428
431
  }
429
- const calRes = calcCheckedKeys(flatten(realKeys as string[]), keyEntities);
432
+ if (isSet(realKeys)) {
433
+ realKeys = [...realKeys];
434
+ }
435
+ const calRes = calcCheckedKeys(flatten(realKeys), keyEntities);
430
436
  const checkedKeys = new Set(calRes.checkedKeys);
431
437
  const halfCheckedKeys = new Set(calRes.halfCheckedKeys);
432
438
  // disableStrictly
@@ -581,7 +587,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
581
587
  );
582
588
  }
583
589
 
584
- handleItemClick = (e: MouseEvent, item: Entity | Data) => {
590
+ handleItemClick = (e: MouseEvent | KeyboardEvent, item: Entity | Data) => {
585
591
  this.foundation.handleItemClick(e, item);
586
592
  };
587
593
 
@@ -795,6 +801,14 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
795
801
  this.foundation.handleClear();
796
802
  };
797
803
 
804
+ /**
805
+ * A11y: simulate clear button click
806
+ */
807
+ handleClearEnterPress = (e: KeyboardEvent) => {
808
+ e && e.stopPropagation();
809
+ this.foundation.handleClearEnterPress();
810
+ };
811
+
798
812
  showClearBtn = () => {
799
813
  const { showClear, disabled, multiple } = this.props;
800
814
  const { selectedKeys, isOpen, isHovering, checkedKeys } = this.state;
@@ -808,7 +822,13 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
808
822
  const allowClear = this.showClearBtn();
809
823
  if (allowClear) {
810
824
  return (
811
- <div className={clearCls} onClick={this.handleClear} role='button' tabIndex={0}>
825
+ <div
826
+ className={clearCls}
827
+ onClick={this.handleClear}
828
+ onKeyPress={this.handleClearEnterPress}
829
+ role='button'
830
+ tabIndex={0}
831
+ >
812
832
  <IconClear />
813
833
  </div>
814
834
  );
@@ -888,6 +908,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
888
908
  style={style}
889
909
  ref={this.triggerRef}
890
910
  onClick={e => this.foundation.handleClick(e)}
911
+ onKeyPress={e => this.foundation.handleSelectionEnterPress(e)}
891
912
  aria-invalid={this.props['aria-invalid']}
892
913
  aria-errormessage={this.props['aria-errormessage']}
893
914
  aria-label={this.props['aria-label']}
package/cascader/item.tsx CHANGED
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
2
2
  import cls from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import { cssClasses, strings } from '@douyinfe/semi-foundation/cascader/constants';
5
+ import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
5
6
  import { includes } from 'lodash';
6
7
  import ConfigContext from '../configProvider/context';
7
8
  import LocaleConsumer from '../locale/localeConsumer';
@@ -43,7 +44,7 @@ export interface CascaderItemProps {
43
44
  selectedKeys: Set<string>;
44
45
  loadedKeys: Set<string>;
45
46
  loadingKeys: Set<string>;
46
- onItemClick: (e: React.MouseEvent, item: Entity | Data) => void;
47
+ onItemClick: (e: React.MouseEvent | React.KeyboardEvent, item: Entity | Data) => void;
47
48
  onItemHover: (e: React.MouseEvent, item: Entity) => void;
48
49
  showNext: ShowNextType;
49
50
  onItemCheckboxClick: (item: Entity | Data) => void;
@@ -84,7 +85,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
84
85
  empty: false,
85
86
  };
86
87
 
87
- onClick = (e: React.MouseEvent, item: Entity | Data) => {
88
+ onClick = (e: React.MouseEvent | React.KeyboardEvent, item: Entity | Data) => {
88
89
  const { onItemClick } = this.props;
89
90
  if (item.data.disabled || ('disabled' in item && item.disabled)) {
90
91
  return;
@@ -92,6 +93,15 @@ export default class Item extends PureComponent<CascaderItemProps> {
92
93
  onItemClick(e, item);
93
94
  };
94
95
 
96
+ /**
97
+ * A11y: simulate item click
98
+ */
99
+ handleItemEnterPress = (keyboardEvent: React.KeyboardEvent, item: Entity | Data) => {
100
+ if (isEnterPress(keyboardEvent)) {
101
+ this.onClick(keyboardEvent, item);
102
+ }
103
+ }
104
+
95
105
  onHover = (e: React.MouseEvent, item: Entity) => {
96
106
  const { showNext, onItemHover } = this.props;
97
107
  if (item.data.disabled) {
@@ -136,7 +146,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
136
146
  case 'loading':
137
147
  return <Spin wrapperClassName={`${prefixcls}-spin-icon`} />;
138
148
  case 'empty':
139
- return (<span className={`${prefixcls}-icon ${prefixcls}-icon-empty`} />);
149
+ return (<span aria-hidden={true} className={`${prefixcls}-icon ${prefixcls}-icon-empty`} />);
140
150
  default:
141
151
  return null;
142
152
  }
@@ -179,11 +189,13 @@ export default class Item extends PureComponent<CascaderItemProps> {
179
189
  });
180
190
  return (
181
191
  <li
192
+ role='menuitem'
182
193
  className={className}
183
194
  key={key}
184
195
  onClick={e => {
185
196
  this.onClick(e, item);
186
197
  }}
198
+ onKeyPress={e => this.handleItemEnterPress(e, item)}
187
199
  >
188
200
  <span className={`${prefixcls}-label`}>
189
201
  {!multiple && this.renderIcon('empty')}
@@ -211,9 +223,9 @@ export default class Item extends PureComponent<CascaderItemProps> {
211
223
  let showChildItem: Entity;
212
224
  const ind = content.length;
213
225
  content.push(
214
- <ul className={`${prefixcls}-list`} key={renderData[0].key} onScroll={e => this.props.onListScroll(e, ind)}>
226
+ <ul role='menu' className={`${prefixcls}-list`} key={renderData[0].key} onScroll={e => this.props.onListScroll(e, ind)}>
215
227
  {renderData.map(item => {
216
- const { data, key } = item;
228
+ const { data, key, parentKey } = item;
217
229
  const { children, label, disabled, isLeaf } = data;
218
230
  const { active, selected, loading } = this.getItemStatus(key);
219
231
  const hasChild = Boolean(children) && children.length;
@@ -226,13 +238,21 @@ export default class Item extends PureComponent<CascaderItemProps> {
226
238
  [`${prefixcls}-select`]: selected && !multiple,
227
239
  [`${prefixcls}-disabled`]: disabled
228
240
  });
241
+ const otherAriaProps = parentKey ? { ['aria-owns']: `cascaderItem-${parentKey}` } : {};
229
242
  return (
230
243
  <li
244
+ role='menuitem'
245
+ id={`cascaderItem-${key}`}
246
+ aria-expanded={active}
247
+ aria-haspopup={Boolean(showExpand)}
248
+ aria-disabled={disabled}
249
+ {...otherAriaProps}
231
250
  className={className}
232
251
  key={key}
233
252
  onClick={e => {
234
253
  this.onClick(e, item);
235
254
  }}
255
+ onKeyPress={e => this.handleItemEnterPress(e, item)}
236
256
  onMouseEnter={e => {
237
257
  this.onHover(e, item);
238
258
  }}
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { DatePicker, Button } from '../../../index';
3
+
4
+ /**
5
+ * fix gitlab #1375
6
+ */
7
+ App.storyName = 'fixed input range focus';
8
+ export default function App() {
9
+ const [visible, setVisible] = React.useState(false);
10
+ return (
11
+ <div>
12
+ {/* <Button onClick={() => { setVisible(false); }}>关闭</Button> */}
13
+ <DatePicker
14
+ type="dateTimeRange"
15
+ bottomSlot={<Button onClick={() => { setVisible(false); }}>关闭</Button>}
16
+ onFocus={() => {
17
+ console.log('focus');
18
+ setVisible(true);
19
+ }}
20
+ open={visible}
21
+ showClear
22
+ />
23
+ </div>
24
+ );
25
+ }
@@ -1,2 +1,3 @@
1
1
  export { default as YearButton } from './YearButton';
2
- export { default as PanelOpen } from './PanelOpen';
2
+ export { default as PanelOpen } from './PanelOpen';
3
+ export { default as FixInputRangeFocus } from './FixInputRangeFocus';
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions,jsx-a11y/interactive-supports-focus */
1
2
  /* eslint-disable max-lines-per-function */
2
3
  /* eslint-disable no-unused-vars */
3
4
  import React from 'react';
@@ -40,7 +41,6 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
40
41
  value: PropTypes.array,
41
42
  disabled: PropTypes.bool,
42
43
  type: PropTypes.oneOf(strings.TYPE_SET),
43
- multiple: PropTypes.bool,
44
44
  showClear: PropTypes.bool,
45
45
  format: PropTypes.string, // Attributes not used
46
46
  inputStyle: PropTypes.object,
@@ -65,7 +65,6 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
65
65
  onBlur: noop,
66
66
  onClear: noop,
67
67
  onFocus: noop,
68
- multiple: false,
69
68
  type: 'date',
70
69
  inputStyle: {},
71
70
  inputReadOnly: false,
@@ -177,9 +176,12 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
177
176
  const allowClear = (rangeStart || rangeEnd) && showClear;
178
177
  return allowClear && !disabled ? (
179
178
  <div
179
+ role="button"
180
+ tabIndex={0}
181
+ aria-label="Clear range input value"
180
182
  className={`${prefixCls}-range-input-clearbtn`}
181
183
  onMouseDown={e => !disabled && this.handleRangeInputClear(e)}>
182
- <IconClear />
184
+ <IconClear aria-hidden />
183
185
  </div>
184
186
  ) : null;
185
187
  }
@@ -293,6 +295,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
293
295
  validateStatus,
294
296
  block,
295
297
  prefixCls,
298
+ multiple, // Whether to allow multiple values for email and file types
296
299
  dateFnsLocale, // No need to pass to input
297
300
  onBlur,
298
301
  onClear,
@@ -310,8 +313,8 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
310
313
  rangeSeparator,
311
314
  ...rest
312
315
  } = this.props;
313
- const dateIcon = <IconCalendar />;
314
- const dateTimeIcon = <IconCalendarClock />;
316
+ const dateIcon = <IconCalendar aria-hidden />;
317
+ const dateTimeIcon = <IconCalendarClock aria-hidden />;
315
318
  const suffix = type.includes('Time') ? dateTimeIcon : dateIcon;
316
319
  let text = '';
317
320
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus */
1
2
  /* eslint-disable max-len */
2
3
  import React from 'react';
3
4
  import classnames from 'classnames';
@@ -331,6 +332,9 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
331
332
 
332
333
  if (prevProps.open !== this.props.open) {
333
334
  this.foundation.initPanelOpenStatus();
335
+ if (!this.props.open) {
336
+ this.foundation.clearRangeInputFocus();
337
+ }
334
338
  }
335
339
  }
336
340
 
@@ -525,8 +529,12 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
525
529
  };
526
530
 
527
531
  return (
528
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
529
532
  <div
533
+ // tooltip will mount a11y props to children
534
+ // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
535
+ role="combobox"
536
+ aria-label={Array.isArray(value) && value.length ? "Change date" : "Choose date"}
537
+ aria-disabled={disabled}
530
538
  onClick={this.handleTriggerWrapperClick}
531
539
  className={inputCls}>
532
540
  {typeof triggerRender === 'function' ? (
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions */
1
2
  /* eslint-disable max-len */
2
3
  import React from 'react';
3
4
  import classNames from 'classnames';
@@ -40,7 +41,8 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
40
41
  startDateOffset: PropTypes.func,
41
42
  endDateOffset: PropTypes.func,
42
43
  rangeInputFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
43
- focusRecordsRef: PropTypes.object
44
+ focusRecordsRef: PropTypes.object,
45
+ multiple: PropTypes.bool,
44
46
  };
45
47
 
46
48
  static defaultProps = {
@@ -275,9 +277,9 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
275
277
  // i18n
276
278
  const weekdaysText = weekdays.map(key => locale.weeks[key]);
277
279
  return (
278
- <div className={weekdayCls}>
280
+ <div role="row" className={weekdayCls}>
279
281
  {weekdaysText.map((E, i) => (
280
- <div key={E + i} className={weekdayItemCls}>
282
+ <div role="columnheader" key={E + i} className={weekdayItemCls}>
281
283
  {E}
282
284
  </div>
283
285
  ))}
@@ -305,7 +307,7 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
305
307
  renderWeek(week: MonthDayInfo[], weekIndex: number) {
306
308
  const weekCls = cssClasses.WEEK;
307
309
  return (
308
- <div className={weekCls} key={weekIndex}>
310
+ <div role="row" className={weekCls} key={weekIndex}>
309
311
  {week.map((day, dayIndex) => this.renderDay(day, dayIndex))}
310
312
  </div>
311
313
  );
@@ -317,7 +319,7 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
317
319
  const { fullDate, dayNumber } = day;
318
320
  if (!fullDate) {
319
321
  return (
320
- <div key={(dayNumber as number) + dayIndex} className={cssClasses.DAY}>
322
+ <div role="gridcell" tabIndex={-1} key={(dayNumber as number) + dayIndex} className={cssClasses.DAY}>
321
323
  <span />
322
324
  </div>
323
325
  );
@@ -356,6 +358,11 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
356
358
 
357
359
  return (
358
360
  <div
361
+ role="gridcell"
362
+ tabIndex={dayStatus.isDisabled ? -1 : 0}
363
+ aria-disabled={dayStatus.isDisabled}
364
+ aria-selected={dayStatus.isSelected}
365
+ aria-label={fullDate}
359
366
  className={!customRender ? dayCls : cssClasses.DAY}
360
367
  title={fullDate}
361
368
  key={(dayNumber as number) + dayIndex}
@@ -373,13 +380,13 @@ export default class Month extends BaseComponent<MonthProps, MonthState> {
373
380
  }
374
381
 
375
382
  render() {
376
- const { forwardRef } = this.props;
383
+ const { forwardRef, multiple } = this.props;
377
384
  const weekday = this.renderDayOfWeek();
378
385
  const weeks = this.renderWeeks();
379
386
  const monthCls = classNames(cssClasses.MONTH);
380
387
  const ref = forwardRef || this.monthRef;
381
388
  return (
382
- <div ref={ref} className={monthCls}>
389
+ <div role="grid" aria-multiselectable={multiple} ref={ref} className={monthCls} >
383
390
  {weekday}
384
391
  {weeks}
385
392
  </div>
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsx-a11y/interactive-supports-focus,jsx-a11y/click-events-have-key-events */
1
2
  /* eslint-disable react/no-did-update-set-state */
2
3
  /* eslint-disable max-len */
3
4
  /* eslint-disable no-nested-ternary */
@@ -343,7 +344,7 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
343
344
 
344
345
  renderMonth(month: Date, panelType: PanelType) {
345
346
  const { selected, rangeStart, rangeEnd, hoverDay, maxWeekNum, offsetRangeStart, offsetRangeEnd } = this.state;
346
- const { weekStartsOn, disabledDate, locale, localeCode, renderDate, renderFullDate, startDateOffset, endDateOffset, density, rangeInputFocus, syncSwitchMonth } = this.props;
347
+ const { weekStartsOn, disabledDate, locale, localeCode, renderDate, renderFullDate, startDateOffset, endDateOffset, density, rangeInputFocus, syncSwitchMonth, multiple } = this.props;
347
348
  let monthText = '';
348
349
  // i18n monthText
349
350
  if (month) {
@@ -409,6 +410,7 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
409
410
  startDateOffset={startDateOffset}
410
411
  endDateOffset={endDateOffset}
411
412
  focusRecordsRef={this.props.focusRecordsRef}
413
+ multiple={multiple}
412
414
  />
413
415
  </div>
414
416
  );
@@ -580,12 +582,22 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
580
582
 
581
583
  return (
582
584
  <div className={switchCls} ref={current => this.adapter.setCache(`switch-${panelType}`, current)}>
583
- <div className={dateCls} onClick={e => this.foundation.showDatePanel(panelType)}>
584
- {showSwithIcon && <IconCalendar />}
585
+ <div
586
+ role="button"
587
+ aria-label="Switch to date panel"
588
+ className={dateCls}
589
+ onClick={e => this.foundation.showDatePanel(panelType)}
590
+ >
591
+ {showSwithIcon && <IconCalendar aria-hidden />}
585
592
  <span className={textCls}>{dateText || monthText}</span>
586
593
  </div>
587
- <div className={timeCls} onClick={e => this.foundation.showTimePicker(panelType, true)}>
588
- {showSwithIcon && <IconClock />}
594
+ <div
595
+ role="button"
596
+ aria-label="Switch to time panel"
597
+ className={timeCls}
598
+ onClick={e => this.foundation.showTimePicker(panelType, true)}
599
+ >
600
+ {showSwithIcon && <IconClock aria-hidden />}
589
601
  <span className={textCls}>{timeText}</span>
590
602
  </div>
591
603
  </div>
@@ -101,7 +101,8 @@ export default class Navigation extends PureComponent<NavigationProps> {
101
101
  <div className={prefixCls} ref={ref}>
102
102
  <IconButton
103
103
  key="double-chevron-left"
104
- icon={<IconDoubleChevronLeft size={iconBtnSize}/>}
104
+ aria-label="Previous year"
105
+ icon={<IconDoubleChevronLeft aria-hidden size={iconBtnSize} />}
105
106
  size={buttonSize}
106
107
  theme={btnTheme}
107
108
  noHorizontalPadding={btnNoHorizontalPadding}
@@ -110,7 +111,8 @@ export default class Navigation extends PureComponent<NavigationProps> {
110
111
  />
111
112
  <IconButton
112
113
  key="chevron-left"
113
- icon={<IconChevronLeft size={iconBtnSize} />}
114
+ aria-label="Previous month"
115
+ icon={<IconChevronLeft aria-hidden size={iconBtnSize} />}
114
116
  size={buttonSize}
115
117
  onClick={onPrevMonth}
116
118
  theme={btnTheme}
@@ -124,7 +126,8 @@ export default class Navigation extends PureComponent<NavigationProps> {
124
126
  </div>
125
127
  <IconButton
126
128
  key="chevron-right"
127
- icon={<IconChevronRight size={iconBtnSize} />}
129
+ aria-label="Next month"
130
+ icon={<IconChevronRight aria-hidden size={iconBtnSize} />}
128
131
  size={buttonSize}
129
132
  onClick={onNextMonth}
130
133
  theme={btnTheme}
@@ -133,7 +136,8 @@ export default class Navigation extends PureComponent<NavigationProps> {
133
136
  />
134
137
  <IconButton
135
138
  key="double-chevron-right"
136
- icon={<IconDoubleChevronRight size={iconBtnSize}/>}
139
+ aria-label="Next year"
140
+ icon={<IconDoubleChevronRight aria-hidden size={iconBtnSize} />}
137
141
  size={buttonSize}
138
142
  theme={btnTheme}
139
143
  noHorizontalPadding={btnNoHorizontalPadding}