@douyinfe/semi-foundation 2.7.0 → 2.8.0-beta.1

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 (42) hide show
  1. package/button/button.scss +14 -6
  2. package/datePicker/_utils/getDefaultPickerDate.ts +54 -0
  3. package/datePicker/datePicker.scss +2 -2
  4. package/form/form.scss +12 -4
  5. package/inputNumber/foundation.ts +1 -1
  6. package/lib/cjs/button/button.css +5 -5
  7. package/lib/cjs/button/button.scss +14 -6
  8. package/lib/cjs/datePicker/_utils/getDefaultPickerDate.d.ts +15 -0
  9. package/lib/cjs/datePicker/_utils/getDefaultPickerDate.js +73 -0
  10. package/lib/cjs/datePicker/datePicker.css +14 -14
  11. package/lib/cjs/datePicker/datePicker.scss +2 -2
  12. package/lib/cjs/form/form.css +11 -2
  13. package/lib/cjs/form/form.scss +12 -4
  14. package/lib/cjs/inputNumber/foundation.js +1 -1
  15. package/lib/cjs/tooltip/foundation.d.ts +27 -1
  16. package/lib/cjs/tooltip/foundation.js +159 -3
  17. package/lib/cjs/utils/getHighlight.js +1 -1
  18. package/lib/cjs/utils/isEscPress.d.ts +4 -0
  19. package/lib/cjs/utils/isEscPress.js +22 -0
  20. package/lib/cjs/utils/keyCode.d.ts +1 -0
  21. package/lib/cjs/utils/keyCode.js +3 -1
  22. package/lib/es/button/button.css +5 -5
  23. package/lib/es/button/button.scss +14 -6
  24. package/lib/es/datePicker/_utils/getDefaultPickerDate.d.ts +15 -0
  25. package/lib/es/datePicker/_utils/getDefaultPickerDate.js +57 -0
  26. package/lib/es/datePicker/datePicker.css +14 -14
  27. package/lib/es/datePicker/datePicker.scss +2 -2
  28. package/lib/es/form/form.css +11 -2
  29. package/lib/es/form/form.scss +12 -4
  30. package/lib/es/inputNumber/foundation.js +1 -1
  31. package/lib/es/tooltip/foundation.d.ts +27 -1
  32. package/lib/es/tooltip/foundation.js +159 -3
  33. package/lib/es/utils/getHighlight.js +1 -1
  34. package/lib/es/utils/isEscPress.d.ts +4 -0
  35. package/lib/es/utils/isEscPress.js +8 -0
  36. package/lib/es/utils/keyCode.d.ts +1 -0
  37. package/lib/es/utils/keyCode.js +1 -0
  38. package/package.json +3 -3
  39. package/tooltip/foundation.ts +131 -3
  40. package/utils/getHighlight.ts +1 -1
  41. package/utils/isEscPress.ts +8 -0
  42. package/utils/keyCode.ts +1 -0
@@ -175,6 +175,42 @@ export default class Tooltip extends BaseFoundation {
175
175
  }
176
176
  };
177
177
 
178
+ this.handleContainerKeydown = event => {
179
+ const {
180
+ guardFocus,
181
+ closeOnEsc
182
+ } = this.getProps();
183
+
184
+ switch (event && event.key) {
185
+ case "Escape":
186
+ closeOnEsc && this._handleEscKeyDown(event);
187
+ break;
188
+
189
+ case "Tab":
190
+ if (guardFocus) {
191
+ const container = this._adapter.getContainer();
192
+
193
+ const focusableElements = this._adapter.getFocusableElements(container);
194
+
195
+ const focusableNum = focusableElements.length;
196
+
197
+ if (focusableNum) {
198
+ // Shift + Tab will move focus backward
199
+ if (event.shiftKey) {
200
+ this._handleContainerShiftTabKeyDown(focusableElements, event);
201
+ } else {
202
+ this._handleContainerTabKeyDown(focusableElements, event);
203
+ }
204
+ }
205
+ }
206
+
207
+ break;
208
+
209
+ default:
210
+ break;
211
+ }
212
+ };
213
+
178
214
  this._timer = null;
179
215
  }
180
216
 
@@ -272,7 +308,12 @@ export default class Tooltip extends BaseFoundation {
272
308
  _generateEvent(types) {
273
309
  const eventNames = this._adapter.getEventName();
274
310
 
275
- const triggerEventSet = {};
311
+ const triggerEventSet = {
312
+ // bind esc keydown on trigger for a11y
313
+ [eventNames.keydown]: event => {
314
+ this._handleTriggerKeydown(event);
315
+ }
316
+ };
276
317
  let portalEventSet = {};
277
318
 
278
319
  switch (types) {
@@ -308,6 +349,15 @@ export default class Tooltip extends BaseFoundation {
308
349
  triggerEventSet[eventNames.mouseLeave] = () => {
309
350
  // console.log(e);
310
351
  this.delayHide(); // this.hide('trigger');
352
+ }; // bind focus to hover trigger for a11y
353
+
354
+
355
+ triggerEventSet[eventNames.focus] = () => {
356
+ this.delayShow();
357
+ };
358
+
359
+ triggerEventSet[eventNames.blur] = () => {
360
+ this.delayHide();
311
361
  };
312
362
 
313
363
  portalEventSet = _Object$assign({}, triggerEventSet);
@@ -331,7 +381,7 @@ export default class Tooltip extends BaseFoundation {
331
381
 
332
382
  case 'custom':
333
383
  // when trigger type is 'custom', no need to bind eventHandler
334
- // show/hide completely depond on props.visible which change by user
384
+ // show/hide completely depend on props.visible which change by user
335
385
  break;
336
386
 
337
387
  default:
@@ -357,7 +407,13 @@ export default class Tooltip extends BaseFoundation {
357
407
  const nowVisible = this.getState('visible');
358
408
 
359
409
  if (nowVisible !== isVisible) {
360
- this._adapter.togglePortalVisible(isVisible, () => this._adapter.notifyVisibleChange(isVisible));
410
+ this._adapter.togglePortalVisible(isVisible, () => {
411
+ if (isVisible) {
412
+ this._adapter.setInitialFocus();
413
+ }
414
+
415
+ this._adapter.notifyVisibleChange(isVisible);
416
+ });
361
417
  }
362
418
  }
363
419
 
@@ -860,4 +916,104 @@ export default class Tooltip extends BaseFoundation {
860
916
  this._adapter.updateContainerPosition();
861
917
  }
862
918
 
919
+ _handleTriggerKeydown(event) {
920
+ const {
921
+ closeOnEsc
922
+ } = this.getProps();
923
+
924
+ const container = this._adapter.getContainer();
925
+
926
+ const focusableElements = this._adapter.getFocusableElements(container);
927
+
928
+ const focusableNum = focusableElements.length;
929
+
930
+ switch (event && event.key) {
931
+ case "Escape":
932
+ closeOnEsc && this._handleEscKeyDown(event);
933
+ break;
934
+
935
+ case "ArrowUp":
936
+ focusableNum && this._handleTriggerArrowUpKeydown(focusableElements, event);
937
+ break;
938
+
939
+ case "ArrowDown":
940
+ focusableNum && this._handleTriggerArrowDownKeydown(focusableElements, event);
941
+ break;
942
+
943
+ default:
944
+ break;
945
+ }
946
+ }
947
+ /**
948
+ * focus trigger
949
+ *
950
+ * when trigger is 'focus' or 'hover', onFocus is bind to show popup
951
+ * if we focus trigger, popup will show again
952
+ *
953
+ * 如果 trigger 是 focus 或者 hover,则它绑定了 onFocus,这里我们如果重新 focus 的话,popup 会再次打开
954
+ * 因此 returnFocusOnClose 只支持 click trigger
955
+ */
956
+
957
+
958
+ _focusTrigger() {
959
+ const {
960
+ trigger,
961
+ returnFocusOnClose
962
+ } = this.getProps();
963
+
964
+ if (returnFocusOnClose && trigger === 'click') {
965
+ const triggerNode = this._adapter.getTriggerNode();
966
+
967
+ if (triggerNode && 'focus' in triggerNode) {
968
+ triggerNode.focus();
969
+ }
970
+ }
971
+ }
972
+
973
+ _handleEscKeyDown(event) {
974
+ const {
975
+ trigger
976
+ } = this.getProps();
977
+
978
+ if (trigger !== 'custom') {
979
+ this.hide();
980
+
981
+ this._focusTrigger();
982
+ }
983
+
984
+ this._adapter.notifyEscKeydown(event);
985
+ }
986
+
987
+ _handleContainerTabKeyDown(focusableElements, event) {
988
+ const activeElement = this._adapter.getActiveElement();
989
+
990
+ const isLastCurrentFocus = focusableElements[focusableElements.length - 1] === activeElement;
991
+
992
+ if (isLastCurrentFocus) {
993
+ focusableElements[0].focus();
994
+ event.preventDefault(); // prevent browser default tab move behavior
995
+ }
996
+ }
997
+
998
+ _handleContainerShiftTabKeyDown(focusableElements, event) {
999
+ const activeElement = this._adapter.getActiveElement();
1000
+
1001
+ const isFirstCurrentFocus = focusableElements[0] === activeElement;
1002
+
1003
+ if (isFirstCurrentFocus) {
1004
+ focusableElements[focusableElements.length - 1].focus();
1005
+ event.preventDefault(); // prevent browser default tab move behavior
1006
+ }
1007
+ }
1008
+
1009
+ _handleTriggerArrowDownKeydown(focusableElements, event) {
1010
+ focusableElements[0].focus();
1011
+ event.preventDefault(); // prevent browser default scroll behavior
1012
+ }
1013
+
1014
+ _handleTriggerArrowUpKeydown(focusableElements, event) {
1015
+ focusableElements[focusableElements.length - 1].focus();
1016
+ event.preventDefault(); // prevent browser default scroll behavior
1017
+ }
1018
+
863
1019
  }
@@ -168,7 +168,7 @@ const fillInChunks = _ref3 => {
168
168
 
169
169
  const findAll = _ref4 => {
170
170
  let {
171
- autoEscape,
171
+ autoEscape = true,
172
172
  caseSensitive = false,
173
173
  searchWords,
174
174
  sourceString
@@ -0,0 +1,4 @@
1
+ declare function isEscPress<T extends {
2
+ key: string;
3
+ }>(e: T): boolean;
4
+ export default isEscPress;
@@ -0,0 +1,8 @@
1
+ import _get from "lodash/get";
2
+ import { ESC_KEY } from './keyCode';
3
+
4
+ function isEscPress(e) {
5
+ return _get(e, 'key') === ESC_KEY ? true : false;
6
+ }
7
+
8
+ export default isEscPress;
@@ -426,4 +426,5 @@ declare const keyCode: {
426
426
  };
427
427
  export declare const ENTER_KEY = "Enter";
428
428
  export declare const TAB_KEY = "Tab";
429
+ export declare const ESC_KEY = "Escape";
429
430
  export default keyCode;
@@ -530,4 +530,5 @@ const keyCode = {
530
530
  };
531
531
  export const ENTER_KEY = 'Enter';
532
532
  export const TAB_KEY = 'Tab';
533
+ export const ESC_KEY = 'Escape';
533
534
  export default keyCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-foundation",
3
- "version": "2.7.0",
3
+ "version": "2.8.0-beta.1",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build:lib": "node ./scripts/compileLib.js",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@babel/runtime-corejs3": "^7.15.4",
11
- "@douyinfe/semi-animation": "2.7.0",
11
+ "@douyinfe/semi-animation": "2.8.0-beta.1",
12
12
  "async-validator": "^3.5.0",
13
13
  "classnames": "^2.2.6",
14
14
  "date-fns": "^2.9.0",
@@ -24,7 +24,7 @@
24
24
  "*.scss",
25
25
  "*.css"
26
26
  ],
27
- "gitHead": "b6e4e0a1e22d4dabe68cbf7e3d2cfd0202da0424",
27
+ "gitHead": "1d03945ec34f2ebd37ab7cc67c4b11786b46cb1f",
28
28
  "devDependencies": {
29
29
  "@babel/plugin-proposal-decorators": "^7.15.8",
30
30
  "@babel/plugin-transform-runtime": "^7.15.8",
@@ -46,6 +46,7 @@ export interface TooltipAdapter<P = Record<string, any>, S = Record<string, any>
46
46
  click: string;
47
47
  focus: string;
48
48
  blur: string;
49
+ keydown: string;
49
50
  };
50
51
  registerTriggerEvent(...args: any[]): void;
51
52
  getTriggerBounding(...args: any[]): DOMRect;
@@ -61,6 +62,12 @@ export interface TooltipAdapter<P = Record<string, any>, S = Record<string, any>
61
62
  updateContainerPosition(): void;
62
63
  updatePlacementAttr(placement: Position): void;
63
64
  getContainerPosition(): string;
65
+ getFocusableElements(node: any): any[];
66
+ getActiveElement(): any;
67
+ getContainer(): any;
68
+ setInitialFocus(): void;
69
+ notifyEscKeydown(event: any): void;
70
+ getTriggerNode(): any;
64
71
  }
65
72
 
66
73
  export type Position = ArrayElement<typeof strings.POSITION_SET>;
@@ -154,7 +161,12 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
154
161
 
155
162
  _generateEvent(types: ArrayElement<typeof strings.TRIGGER_SET>) {
156
163
  const eventNames = this._adapter.getEventName();
157
- const triggerEventSet = {};
164
+ const triggerEventSet = {
165
+ // bind esc keydown on trigger for a11y
166
+ [eventNames.keydown]: (event) => {
167
+ this._handleTriggerKeydown(event);
168
+ },
169
+ };
158
170
  let portalEventSet = {};
159
171
  switch (types) {
160
172
  case 'focus':
@@ -186,6 +198,13 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
186
198
  this.delayHide();
187
199
  // this.hide('trigger');
188
200
  };
201
+ // bind focus to hover trigger for a11y
202
+ triggerEventSet[eventNames.focus] = () => {
203
+ this.delayShow();
204
+ };
205
+ triggerEventSet[eventNames.blur] = () => {
206
+ this.delayHide();
207
+ };
189
208
 
190
209
  portalEventSet = { ...triggerEventSet };
191
210
  if (this.getProp('clickToHide')) {
@@ -205,7 +224,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
205
224
  break;
206
225
  case 'custom':
207
226
  // when trigger type is 'custom', no need to bind eventHandler
208
- // show/hide completely depond on props.visible which change by user
227
+ // show/hide completely depend on props.visible which change by user
209
228
  break;
210
229
  default:
211
230
  break;
@@ -292,7 +311,12 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
292
311
  _togglePortalVisible(isVisible: boolean) {
293
312
  const nowVisible = this.getState('visible');
294
313
  if (nowVisible !== isVisible) {
295
- this._adapter.togglePortalVisible(isVisible, () => this._adapter.notifyVisibleChange(isVisible));
314
+ this._adapter.togglePortalVisible(isVisible, () => {
315
+ if (isVisible) {
316
+ this._adapter.setInitialFocus();
317
+ }
318
+ this._adapter.notifyVisibleChange(isVisible);
319
+ });
296
320
  }
297
321
  }
298
322
 
@@ -797,4 +821,108 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
797
821
  _initContainerPosition() {
798
822
  this._adapter.updateContainerPosition();
799
823
  }
824
+
825
+ handleContainerKeydown = (event: any) => {
826
+ const { guardFocus, closeOnEsc } = this.getProps();
827
+ switch (event && event.key) {
828
+ case "Escape":
829
+ closeOnEsc && this._handleEscKeyDown(event);
830
+ break;
831
+ case "Tab":
832
+ if (guardFocus) {
833
+ const container = this._adapter.getContainer();
834
+ const focusableElements = this._adapter.getFocusableElements(container);
835
+ const focusableNum = focusableElements.length;
836
+
837
+ if (focusableNum) {
838
+ // Shift + Tab will move focus backward
839
+ if (event.shiftKey) {
840
+ this._handleContainerShiftTabKeyDown(focusableElements, event);
841
+ } else {
842
+ this._handleContainerTabKeyDown(focusableElements, event);
843
+ }
844
+ }
845
+ }
846
+ break;
847
+ default:
848
+ break;
849
+ }
850
+ }
851
+
852
+ _handleTriggerKeydown(event: any) {
853
+ const { closeOnEsc } = this.getProps();
854
+ const container = this._adapter.getContainer();
855
+ const focusableElements = this._adapter.getFocusableElements(container);
856
+ const focusableNum = focusableElements.length;
857
+
858
+ switch (event && event.key) {
859
+ case "Escape":
860
+ closeOnEsc && this._handleEscKeyDown(event);
861
+ break;
862
+ case "ArrowUp":
863
+ focusableNum && this._handleTriggerArrowUpKeydown(focusableElements, event);
864
+ break;
865
+ case "ArrowDown":
866
+ focusableNum && this._handleTriggerArrowDownKeydown(focusableElements, event);
867
+ break;
868
+ default:
869
+ break;
870
+ }
871
+ }
872
+
873
+ /**
874
+ * focus trigger
875
+ *
876
+ * when trigger is 'focus' or 'hover', onFocus is bind to show popup
877
+ * if we focus trigger, popup will show again
878
+ *
879
+ * 如果 trigger 是 focus 或者 hover,则它绑定了 onFocus,这里我们如果重新 focus 的话,popup 会再次打开
880
+ * 因此 returnFocusOnClose 只支持 click trigger
881
+ */
882
+ _focusTrigger() {
883
+ const { trigger, returnFocusOnClose } = this.getProps();
884
+ if (returnFocusOnClose && trigger === 'click') {
885
+ const triggerNode = this._adapter.getTriggerNode();
886
+ if (triggerNode && 'focus' in triggerNode) {
887
+ triggerNode.focus();
888
+ }
889
+ }
890
+ }
891
+
892
+ _handleEscKeyDown(event: any) {
893
+ const { trigger } = this.getProps();
894
+ if (trigger !== 'custom') {
895
+ this.hide();
896
+ this._focusTrigger();
897
+ }
898
+ this._adapter.notifyEscKeydown(event);
899
+ }
900
+
901
+ _handleContainerTabKeyDown(focusableElements: any[], event: any) {
902
+ const activeElement = this._adapter.getActiveElement();
903
+ const isLastCurrentFocus = focusableElements[focusableElements.length - 1] === activeElement;
904
+ if (isLastCurrentFocus) {
905
+ focusableElements[0].focus();
906
+ event.preventDefault(); // prevent browser default tab move behavior
907
+ }
908
+ }
909
+
910
+ _handleContainerShiftTabKeyDown(focusableElements: any[], event: any) {
911
+ const activeElement = this._adapter.getActiveElement();
912
+ const isFirstCurrentFocus = focusableElements[0] === activeElement;
913
+ if (isFirstCurrentFocus) {
914
+ focusableElements[focusableElements.length - 1].focus();
915
+ event.preventDefault(); // prevent browser default tab move behavior
916
+ }
917
+ }
918
+
919
+ _handleTriggerArrowDownKeydown(focusableElements: any[], event: any) {
920
+ focusableElements[0].focus();
921
+ event.preventDefault(); // prevent browser default scroll behavior
922
+ }
923
+
924
+ _handleTriggerArrowUpKeydown(focusableElements: any[], event: any) {
925
+ focusableElements[focusableElements.length - 1].focus();
926
+ event.preventDefault(); // prevent browser default scroll behavior
927
+ }
800
928
  }
@@ -152,7 +152,7 @@ const fillInChunks = ({ chunksToHighlight, totalLength }: { chunksToHighlight: C
152
152
  */
153
153
 
154
154
  const findAll = ({
155
- autoEscape,
155
+ autoEscape = true,
156
156
  caseSensitive = false,
157
157
  searchWords,
158
158
  sourceString
@@ -0,0 +1,8 @@
1
+ import { get } from 'lodash';
2
+ import { ESC_KEY } from './keyCode';
3
+
4
+ function isEscPress<T extends { key: string }>(e: T) {
5
+ return get(e, 'key') === ESC_KEY ? true : false;
6
+ }
7
+
8
+ export default isEscPress;
package/utils/keyCode.ts CHANGED
@@ -428,5 +428,6 @@ const keyCode = {
428
428
 
429
429
  export const ENTER_KEY = 'Enter';
430
430
  export const TAB_KEY = 'Tab';
431
+ export const ESC_KEY = 'Escape';
431
432
 
432
433
  export default keyCode;