@workday/canvas-kit-preview-react 15.0.0-alpha.0074-next.0 → 15.0.0-alpha.0076-next.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 (125) hide show
  1. package/dist/commonjs/index.d.ts +0 -2
  2. package/dist/commonjs/index.d.ts.map +1 -1
  3. package/dist/commonjs/index.js +0 -2
  4. package/dist/commonjs/multi-select/lib/MultiSelectedItem.js +1 -1
  5. package/dist/es6/index.d.ts +0 -2
  6. package/dist/es6/index.d.ts.map +1 -1
  7. package/dist/es6/index.js +0 -2
  8. package/dist/es6/multi-select/lib/MultiSelectedItem.js +1 -1
  9. package/index.ts +0 -2
  10. package/multi-select/lib/MultiSelectedItem.tsx +1 -1
  11. package/package.json +4 -4
  12. package/dist/commonjs/pill/index.d.ts +0 -8
  13. package/dist/commonjs/pill/index.d.ts.map +0 -1
  14. package/dist/commonjs/pill/index.js +0 -23
  15. package/dist/commonjs/pill/lib/Pill.d.ts +0 -265
  16. package/dist/commonjs/pill/lib/Pill.d.ts.map +0 -1
  17. package/dist/commonjs/pill/lib/Pill.js +0 -147
  18. package/dist/commonjs/pill/lib/PillAvatar.d.ts +0 -526
  19. package/dist/commonjs/pill/lib/PillAvatar.d.ts.map +0 -1
  20. package/dist/commonjs/pill/lib/PillAvatar.js +0 -22
  21. package/dist/commonjs/pill/lib/PillCount.d.ts +0 -12
  22. package/dist/commonjs/pill/lib/PillCount.d.ts.map +0 -1
  23. package/dist/commonjs/pill/lib/PillCount.js +0 -21
  24. package/dist/commonjs/pill/lib/PillIcon.d.ts +0 -54
  25. package/dist/commonjs/pill/lib/PillIcon.d.ts.map +0 -1
  26. package/dist/commonjs/pill/lib/PillIcon.js +0 -20
  27. package/dist/commonjs/pill/lib/PillIconButton.d.ts +0 -148
  28. package/dist/commonjs/pill/lib/PillIconButton.d.ts.map +0 -1
  29. package/dist/commonjs/pill/lib/PillIconButton.js +0 -21
  30. package/dist/commonjs/pill/lib/PillLabel.d.ts +0 -14
  31. package/dist/commonjs/pill/lib/PillLabel.d.ts.map +0 -1
  32. package/dist/commonjs/pill/lib/PillLabel.js +0 -17
  33. package/dist/commonjs/pill/lib/usePillModel.d.ts +0 -46
  34. package/dist/commonjs/pill/lib/usePillModel.d.ts.map +0 -1
  35. package/dist/commonjs/pill/lib/usePillModel.js +0 -24
  36. package/dist/commonjs/select/index.d.ts +0 -3
  37. package/dist/commonjs/select/index.d.ts.map +0 -1
  38. package/dist/commonjs/select/index.js +0 -17
  39. package/dist/commonjs/select/lib/Select.d.ts +0 -31
  40. package/dist/commonjs/select/lib/Select.d.ts.map +0 -1
  41. package/dist/commonjs/select/lib/Select.js +0 -486
  42. package/dist/commonjs/select/lib/SelectBase.d.ts +0 -165
  43. package/dist/commonjs/select/lib/SelectBase.d.ts.map +0 -1
  44. package/dist/commonjs/select/lib/SelectBase.js +0 -235
  45. package/dist/commonjs/select/lib/SelectMenu.d.ts +0 -45
  46. package/dist/commonjs/select/lib/SelectMenu.d.ts.map +0 -1
  47. package/dist/commonjs/select/lib/SelectMenu.js +0 -195
  48. package/dist/commonjs/select/lib/SelectOption.d.ts +0 -38
  49. package/dist/commonjs/select/lib/SelectOption.d.ts.map +0 -1
  50. package/dist/commonjs/select/lib/SelectOption.js +0 -73
  51. package/dist/commonjs/select/lib/scrolling.d.ts +0 -5
  52. package/dist/commonjs/select/lib/scrolling.d.ts.map +0 -1
  53. package/dist/commonjs/select/lib/scrolling.js +0 -36
  54. package/dist/commonjs/select/lib/types.d.ts +0 -37
  55. package/dist/commonjs/select/lib/types.d.ts.map +0 -1
  56. package/dist/commonjs/select/lib/types.js +0 -2
  57. package/dist/commonjs/select/lib/utils.d.ts +0 -10
  58. package/dist/commonjs/select/lib/utils.d.ts.map +0 -1
  59. package/dist/commonjs/select/lib/utils.js +0 -27
  60. package/dist/es6/pill/index.d.ts +0 -8
  61. package/dist/es6/pill/index.d.ts.map +0 -1
  62. package/dist/es6/pill/index.js +0 -7
  63. package/dist/es6/pill/lib/Pill.d.ts +0 -265
  64. package/dist/es6/pill/lib/Pill.d.ts.map +0 -1
  65. package/dist/es6/pill/lib/Pill.js +0 -144
  66. package/dist/es6/pill/lib/PillAvatar.d.ts +0 -526
  67. package/dist/es6/pill/lib/PillAvatar.d.ts.map +0 -1
  68. package/dist/es6/pill/lib/PillAvatar.js +0 -19
  69. package/dist/es6/pill/lib/PillCount.d.ts +0 -12
  70. package/dist/es6/pill/lib/PillCount.d.ts.map +0 -1
  71. package/dist/es6/pill/lib/PillCount.js +0 -18
  72. package/dist/es6/pill/lib/PillIcon.d.ts +0 -54
  73. package/dist/es6/pill/lib/PillIcon.d.ts.map +0 -1
  74. package/dist/es6/pill/lib/PillIcon.js +0 -17
  75. package/dist/es6/pill/lib/PillIconButton.d.ts +0 -148
  76. package/dist/es6/pill/lib/PillIconButton.d.ts.map +0 -1
  77. package/dist/es6/pill/lib/PillIconButton.js +0 -18
  78. package/dist/es6/pill/lib/PillLabel.d.ts +0 -14
  79. package/dist/es6/pill/lib/PillLabel.d.ts.map +0 -1
  80. package/dist/es6/pill/lib/PillLabel.js +0 -14
  81. package/dist/es6/pill/lib/usePillModel.d.ts +0 -46
  82. package/dist/es6/pill/lib/usePillModel.d.ts.map +0 -1
  83. package/dist/es6/pill/lib/usePillModel.js +0 -21
  84. package/dist/es6/select/index.d.ts +0 -3
  85. package/dist/es6/select/index.d.ts.map +0 -1
  86. package/dist/es6/select/index.js +0 -1
  87. package/dist/es6/select/lib/Select.d.ts +0 -31
  88. package/dist/es6/select/lib/Select.d.ts.map +0 -1
  89. package/dist/es6/select/lib/Select.js +0 -460
  90. package/dist/es6/select/lib/SelectBase.d.ts +0 -165
  91. package/dist/es6/select/lib/SelectBase.d.ts.map +0 -1
  92. package/dist/es6/select/lib/SelectBase.js +0 -208
  93. package/dist/es6/select/lib/SelectMenu.d.ts +0 -45
  94. package/dist/es6/select/lib/SelectMenu.d.ts.map +0 -1
  95. package/dist/es6/select/lib/SelectMenu.js +0 -191
  96. package/dist/es6/select/lib/SelectOption.d.ts +0 -38
  97. package/dist/es6/select/lib/SelectOption.d.ts.map +0 -1
  98. package/dist/es6/select/lib/SelectOption.js +0 -69
  99. package/dist/es6/select/lib/scrolling.d.ts +0 -5
  100. package/dist/es6/select/lib/scrolling.d.ts.map +0 -1
  101. package/dist/es6/select/lib/scrolling.js +0 -32
  102. package/dist/es6/select/lib/types.d.ts +0 -37
  103. package/dist/es6/select/lib/types.d.ts.map +0 -1
  104. package/dist/es6/select/lib/types.js +0 -1
  105. package/dist/es6/select/lib/utils.d.ts +0 -10
  106. package/dist/es6/select/lib/utils.d.ts.map +0 -1
  107. package/dist/es6/select/lib/utils.js +0 -22
  108. package/pill/index.ts +0 -7
  109. package/pill/lib/Pill.tsx +0 -285
  110. package/pill/lib/PillAvatar.tsx +0 -35
  111. package/pill/lib/PillCount.tsx +0 -44
  112. package/pill/lib/PillIcon.tsx +0 -32
  113. package/pill/lib/PillIconButton.tsx +0 -86
  114. package/pill/lib/PillLabel.tsx +0 -32
  115. package/pill/lib/usePillModel.tsx +0 -23
  116. package/pill/package.json +0 -6
  117. package/select/index.ts +0 -8
  118. package/select/lib/Select.tsx +0 -595
  119. package/select/lib/SelectBase.tsx +0 -493
  120. package/select/lib/SelectMenu.tsx +0 -304
  121. package/select/lib/SelectOption.tsx +0 -133
  122. package/select/lib/scrolling.ts +0 -42
  123. package/select/lib/types.ts +0 -37
  124. package/select/lib/utils.ts +0 -30
  125. package/select/package.json +0 -6
@@ -1,595 +0,0 @@
1
- import * as React from 'react';
2
- import {
3
- ErrorType,
4
- StyledType,
5
- createComponent,
6
- useForkRef,
7
- generateUniqueId,
8
- } from '@workday/canvas-kit-react/common';
9
- import {menuAnimationDuration} from './SelectMenu';
10
- import {SelectBase, CoreSelectBaseProps, Option, NormalizedOption} from './SelectBase';
11
- import {MenuVisibility} from './types';
12
- import {getCorrectedIndexByValue} from './utils';
13
- /**
14
- * @deprecated ⚠️ `SelectProps` in Preview has been deprecated and will be removed in a future major version. Please use [`Select` in Main](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs) instead.
15
- */
16
- export interface SelectProps extends CoreSelectBaseProps {
17
- /**
18
- * The options of the Select. `options` may be an array of objects, an array of strings,
19
- * or an array that contains both objects and strings.
20
- *
21
- * If `options` includes objects, each included object must adhere to the `Option` interface:
22
- *
23
- * * `data: object` (optional)
24
- * * `disabled: boolean` (optional)
25
- * * `id: string` (optional, a random `id` will be assigned to the object if one isn't provided)
26
- * * `label: string` (optional, analogous to the text content of an `<option>`)
27
- * * `value: string` (required, analogous to the `value` attribute of an `<option>`)
28
- *
29
- * If `label` is omitted, the `value` will be used to render the option.
30
- *
31
- * The `data` object is carried over to the `option` passed into the `renderOption` function where it may then be used to customize how each option is rendered.
32
- */
33
- options: (Option | string)[];
34
- }
35
-
36
- interface SelectContainerProps extends SelectProps, StyledType {
37
- /**
38
- * The ref passed from `createComponent` to be forwarded to the underlying button element.
39
- */
40
- buttonRef?: React.Ref<HTMLButtonElement>;
41
- }
42
-
43
- interface SelectContainerState {
44
- focusedOptionIndex: number;
45
- menuVisibility: MenuVisibility;
46
- }
47
-
48
- class SelectContainer extends React.Component<SelectContainerProps, SelectContainerState> {
49
- static ErrorType = ErrorType;
50
-
51
- state: Readonly<SelectContainerState> = {
52
- focusedOptionIndex: 0,
53
- menuVisibility: 'closed',
54
- };
55
-
56
- private forwardedButtonRef: React.Ref<HTMLButtonElement>;
57
- private localButtonRef = React.createRef<HTMLButtonElement>();
58
- private inputRef = React.createRef<HTMLInputElement>();
59
- private menuRef = React.createRef<HTMLUListElement>();
60
-
61
- private menuAnimationTimer!: ReturnType<typeof setTimeout>;
62
-
63
- // For type-ahead functionality
64
- private keysSoFar = '';
65
- private clearKeysSoFarTimeout = 500;
66
- private clearKeysSoFarTimer!: ReturnType<typeof setTimeout>;
67
-
68
- // Cached values
69
- private normalizedOptions: NormalizedOption[] = [];
70
-
71
- private areOptionsDefined = (): boolean => {
72
- const {options} = this.props;
73
-
74
- if (!options || options.length === 0) {
75
- return false;
76
- }
77
-
78
- return true;
79
- };
80
-
81
- // Store normalized options since the options prop can take on multiple
82
- // forms. It can be an array of strings, an array of objects (sometimes with
83
- // arbitrary keys), or an array that contains both strings and objects
84
- private setNormalizedOptions = (): void => {
85
- const {options} = this.props;
86
-
87
- // Abort if options weren't defined
88
- if (!this.areOptionsDefined()) {
89
- return;
90
- }
91
-
92
- this.normalizedOptions = options.map(option => {
93
- let data = {};
94
- let disabled, id, label, value;
95
-
96
- if (typeof option === 'string') {
97
- disabled = false;
98
- id = generateUniqueId();
99
- value = option;
100
- label = option;
101
- } else {
102
- data = option.data || data;
103
- disabled = !!option.disabled;
104
- id = option.id || generateUniqueId();
105
- value = option.value;
106
- label = option.label || option.value;
107
- }
108
-
109
- return {
110
- data,
111
- disabled,
112
- id,
113
- label,
114
- value,
115
- };
116
- });
117
- };
118
-
119
- private getIndexByStartString = (
120
- startIndex: number,
121
- startString: string,
122
- endIndex: number = this.normalizedOptions.length,
123
- ignoreDisabled: boolean = true
124
- ): number => {
125
- for (let i = startIndex; i < endIndex; i++) {
126
- const label = this.normalizedOptions[i].label.toLowerCase();
127
- if (label.indexOf(startString.toLowerCase()) === 0) {
128
- if (!ignoreDisabled || (ignoreDisabled && !this.normalizedOptions[i].disabled)) {
129
- return i;
130
- }
131
- }
132
- }
133
-
134
- return -1;
135
- };
136
-
137
- // This helper focuses the next enabled option given a startIndex and a
138
- // direction. If startIndex refers to an enabled option, we focus that option
139
- // immediately. Otherwise, we advance `direction` number of spaces in the options
140
- // array and check again if that index refers to an enabled option.
141
- //
142
- // This is useful for manipulating focus using the keyboard where pressing the
143
- // Up/Down key means "focus the first enabled option above/below the currently
144
- // focused option", or pressing the Home/End key means "focus the first/last
145
- // enabled option on the menu."
146
- private focusNextEnabledOption = (startIndex: number, direction: number) => {
147
- // Ensure direction is non-zero
148
- if (direction === 0) {
149
- return;
150
- }
151
-
152
- const numOptions = this.normalizedOptions.length;
153
-
154
- let currentIndex = startIndex;
155
-
156
- // Check if currentIndex refers to an enabled option. If not, keep moving
157
- // the index in the prescribed direction until we find an enabled option.
158
- while (
159
- currentIndex >= 0 &&
160
- currentIndex < numOptions &&
161
- this.normalizedOptions[currentIndex].disabled
162
- ) {
163
- currentIndex += direction;
164
- }
165
- // Update the focused index only if currentIndex is inbounds and
166
- // refers to an enabled option
167
- if (
168
- currentIndex >= 0 &&
169
- currentIndex < numOptions &&
170
- !this.normalizedOptions[currentIndex].disabled
171
- ) {
172
- this.setState({focusedOptionIndex: currentIndex});
173
- }
174
- };
175
-
176
- private updateStateFromValue = () => {
177
- this.setState({
178
- focusedOptionIndex: getCorrectedIndexByValue(this.normalizedOptions, this.props.value),
179
- });
180
- };
181
-
182
- private focusButton = () => {
183
- if (this.localButtonRef.current) {
184
- this.localButtonRef.current.focus();
185
- }
186
- };
187
-
188
- private setMenuAnimationTimeout = (callback: () => void) => {
189
- if (this.menuAnimationTimer) {
190
- clearTimeout(this.menuAnimationTimer);
191
- }
192
- this.menuAnimationTimer = setTimeout(callback, menuAnimationDuration);
193
- };
194
-
195
- private openMenu = () => {
196
- this.setState({menuVisibility: 'opening'});
197
- this.setMenuAnimationTimeout(() => {
198
- this.setState({menuVisibility: 'opened'});
199
- });
200
- };
201
-
202
- private closeMenu = () => {
203
- this.setState({menuVisibility: 'closing'});
204
- this.setMenuAnimationTimeout(() => {
205
- this.setState({
206
- // Reset the focused option to the currently selected option in case
207
- // the user focused a different option but chose not to select it. The
208
- // next time the menu opens, focus should begin on the selected option.
209
- focusedOptionIndex: getCorrectedIndexByValue(this.normalizedOptions, this.props.value),
210
- menuVisibility: 'closed',
211
- });
212
- });
213
- };
214
-
215
- private toggleMenu = (open: boolean): void => {
216
- const {menuVisibility} = this.state;
217
-
218
- if (open) {
219
- switch (menuVisibility) {
220
- // We're opening a menu which is currently closed: set the menu state
221
- // to open before kicking off openMenu. This allows us to transition
222
- // from 0 opacity in the open state to the targeted 1.0 opacity in
223
- // the opening state.
224
- case 'closed':
225
- this.setState({menuVisibility: 'open'}, this.openMenu);
226
- break;
227
- // We're opening a menu which is in the process of closing. Since the
228
- // menu isn't closed, there's no need to set the open state: kick off
229
- // openMenu immediately.
230
- case 'close':
231
- case 'closing':
232
- this.openMenu();
233
- break;
234
- // Otherwise, we're opening a menu is already opened or in the process of
235
- // opening; no need to do anything further.
236
- default:
237
- break;
238
- }
239
- } else {
240
- switch (menuVisibility) {
241
- // We're closing a menu which is currently opened: set the menu state to
242
- // close before kicking off closeMenu.
243
- case 'opened':
244
- this.setState({menuVisibility: 'close'}, this.closeMenu);
245
- break;
246
- // We're closing a menu which is in the process of opening. Since the
247
- // menu isn't opened, there's no need to set the close state: kick off
248
- // closeMenu immediately.
249
- case 'open':
250
- case 'opening':
251
- this.closeMenu();
252
- break;
253
- // Otherwise, we're closing a menu which is already closed or in the process
254
- // of closing; no need to do anything further.
255
- default:
256
- break;
257
- }
258
- }
259
- };
260
-
261
- // Code inspired by: https://stackoverflow.com/a/46012210
262
- // In order for Select to be usable as a controlled component, we
263
- // need to programmatically change the value of the SelectInput
264
- // in such a way that triggers its change event
265
- private fireChangeEvent = (value: string): void => {
266
- if (this.inputRef && this.inputRef.current) {
267
- const nativeInputValue = Object.getOwnPropertyDescriptor(
268
- Object.getPrototypeOf(this.inputRef.current),
269
- 'value'
270
- );
271
- if (nativeInputValue && nativeInputValue.set) {
272
- nativeInputValue.set.call(this.inputRef.current, value);
273
- }
274
-
275
- let event: Event;
276
- if (typeof Event === 'function') {
277
- // Modern browsers
278
- event = new Event('change', {bubbles: true});
279
- } else {
280
- // IE 11
281
- event = document.createEvent('Event');
282
- event.initEvent('change', true, true);
283
- }
284
-
285
- this.inputRef.current.dispatchEvent(event);
286
- }
287
- };
288
-
289
- private handleKeyboardTypeAhead = (key: string, numOptions: number) => {
290
- // Abort immediately if the menu is the process of closing
291
- if (this.state.menuVisibility === 'closing') {
292
- return;
293
- }
294
-
295
- // Set the starting point of the search to one of two locations
296
- // based on the search string so far (keysSoFar):
297
- //
298
- // 1. If the search string is empty, start the search from the
299
- // next option AFTER the currently focused option. For example,
300
- // if the Select is currently focused on "San Francisco", typing
301
- // "s" again advances focus to the next option that begins with "s".
302
- //
303
- // 2. If the search string is populated, start the search from the
304
- // CURRENTLY focused option. For example, if the Select is currently
305
- // focused on "San Francisco", typing "san j" retains focus on
306
- // "San Francisco" as you type "san " (because "san " still matches
307
- // "San Francisco") and then advances focus to "San Jose" after you
308
- // type the "j" at the end.
309
- let start =
310
- this.keysSoFar.length === 0
311
- ? this.state.focusedOptionIndex + 1
312
- : this.state.focusedOptionIndex;
313
-
314
- // If the starting point is beyond the list of options, reset it
315
- // to the beginning of the list
316
- start = start === numOptions ? 0 : start;
317
-
318
- this.keysSoFar += key;
319
- this.startClearKeysSoFarTimer();
320
-
321
- // First, look for a match from start to end
322
- let matchIndex;
323
- matchIndex = this.getIndexByStartString(start, this.keysSoFar);
324
-
325
- // If a match isn't found between start and end, wrap the search
326
- // around and search again from the beginning (0) to start
327
- if (matchIndex === -1) {
328
- matchIndex = this.getIndexByStartString(0, this.keysSoFar, start);
329
- }
330
-
331
- // A match was found...
332
- if (matchIndex > -1) {
333
- if (this.state.menuVisibility === 'closed') {
334
- // If the menu is closed, fire the change event
335
- this.fireChangeEvent(this.normalizedOptions[matchIndex].value);
336
- } else {
337
- // Otherwise the menu is visible (or at least partially visible);
338
- // focus the matched option
339
- this.setState({focusedOptionIndex: matchIndex});
340
- }
341
- }
342
- };
343
-
344
- private startClearKeysSoFarTimer = () => {
345
- if (this.clearKeysSoFarTimer) {
346
- clearTimeout(this.clearKeysSoFarTimer);
347
- }
348
- this.clearKeysSoFarTimer = setTimeout(() => {
349
- this.keysSoFar = '';
350
- }, this.clearKeysSoFarTimeout);
351
- };
352
-
353
- constructor(props: SelectContainerProps) {
354
- super(props);
355
- this.setNormalizedOptions();
356
-
357
- // We need a local ref (RefObject) to the Select component's underlying
358
- // button to manage focus within the component and to serve as its Popper
359
- // Menu's anchorElement. If the buttonRef prop (to be forwarded to the
360
- // underlying button) passed in through createComponent was a ref object,
361
- // we could reuse it for our internal purposes. buttonRef may be a callback
362
- // ref, however, or it may not even be defined.
363
- //
364
- // To guarantee we have access to a ref object, we created one earlier when
365
- // declaring the localButtonRef instance variable. We then use useForkRef
366
- // to combine localButtonRef and buttonRef into a single callback ref
367
- // (forwardedButtonRef) which can be forwarded to the underlying button.
368
- // When the component mounts/unmounts, this callback will both:
369
- //
370
- // (1) Update the current value of localButtonRef, and;
371
- // (2) Either update the current value of buttonRef if it was a ref object,
372
- // or call buttonRef with the underlying button element if it was a
373
- // callback ref.
374
- // eslint-disable-next-line react-hooks/rules-of-hooks
375
- this.forwardedButtonRef = useForkRef(props.buttonRef, this.localButtonRef);
376
- }
377
-
378
- componentDidMount() {
379
- this.updateStateFromValue();
380
- }
381
-
382
- componentDidUpdate(prevProps: SelectProps) {
383
- const {options, value} = this.props;
384
-
385
- if (options !== prevProps.options) {
386
- this.setNormalizedOptions();
387
- this.updateStateFromValue();
388
- }
389
-
390
- if (value !== prevProps.value) {
391
- this.updateStateFromValue();
392
- }
393
- }
394
-
395
- componentWillUnmount() {
396
- // Clear timers
397
- if (this.menuAnimationTimer) {
398
- clearTimeout(this.menuAnimationTimer);
399
- }
400
- if (this.clearKeysSoFarTimer) {
401
- clearTimeout(this.clearKeysSoFarTimer);
402
- }
403
- }
404
-
405
- handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {
406
- const {menuVisibility} = this.state;
407
-
408
- switch (menuVisibility) {
409
- // If we click the button while the menu is in the process of closing,
410
- // we want to toggle the menu back on. However, we also need to focus
411
- // the menu since it won't be focused using Popper's onFirstUpdate
412
- // callback (because the menu already exists).
413
- case 'close':
414
- case 'closing':
415
- if (this.menuRef.current) {
416
- this.menuRef.current.focus();
417
- }
418
- this.toggleMenu(true);
419
- break;
420
- case 'closed':
421
- this.toggleMenu(true);
422
- break;
423
- // Otherwise, the menu is opened or in the process of opening; toggle
424
- // the menu off.
425
- default:
426
- this.toggleMenu(false);
427
- break;
428
- }
429
- };
430
-
431
- handleOptionSelection = (index: number): void => {
432
- // Abort if a disabled option was clicked (we ignore these clicks)
433
- if (this.normalizedOptions[index].disabled) {
434
- return;
435
- }
436
-
437
- // Toggle menu off, shift focus back to the button, and fire change event
438
- this.toggleMenu(false);
439
- this.focusButton();
440
- this.fireChangeEvent(this.normalizedOptions[index].value);
441
- };
442
-
443
- handleMenuCloseOnKeyPress = (): void => {
444
- // Toggle menu off and shift focus back to the button
445
- this.handleClose();
446
- this.focusButton();
447
- };
448
-
449
- handleClose = (): void => {
450
- this.toggleMenu(false);
451
- };
452
-
453
- handleKeyDown = (event: React.KeyboardEvent): void => {
454
- const {options} = this.props;
455
- const numOptions = options.length;
456
-
457
- const {focusedOptionIndex, menuVisibility} = this.state;
458
-
459
- let isShortcut = false;
460
-
461
- // Check for type-ahead first
462
- if (event.key.length === 1 && event.key.match(/\S/)) {
463
- isShortcut = true;
464
- this.handleKeyboardTypeAhead(event.key, numOptions);
465
- } else {
466
- switch (event.key) {
467
- case 'ArrowUp':
468
- case 'Up': // IE/Edge specific value
469
- case 'ArrowDown':
470
- case 'Down': // IE/Edge specific value
471
- isShortcut = true;
472
- if (menuVisibility === 'closed' || menuVisibility === 'closing') {
473
- this.toggleMenu(true);
474
- } else {
475
- const direction = event.key === 'ArrowUp' || event.key === 'Up' ? -1 : 1;
476
- const startIndex = focusedOptionIndex + direction;
477
- this.focusNextEnabledOption(startIndex, direction);
478
- }
479
- break;
480
-
481
- case 'Home':
482
- case 'End':
483
- isShortcut = true;
484
- const direction = event.key === 'Home' ? 1 : -1;
485
- const startIndex = event.key === 'Home' ? 0 : numOptions - 1;
486
- this.focusNextEnabledOption(startIndex, direction);
487
- break;
488
-
489
- case 'Tab':
490
- if (menuVisibility !== 'closed') {
491
- isShortcut = true;
492
- this.handleMenuCloseOnKeyPress();
493
- }
494
- break;
495
-
496
- case 'Spacebar':
497
- case ' ':
498
- isShortcut = true;
499
- // If the user is in the middle of typing a string, treat
500
- // space key as type-ahead rather than option selection
501
- if (this.keysSoFar !== '') {
502
- this.handleKeyboardTypeAhead(' ', numOptions);
503
- } else if (menuVisibility === 'closed' || menuVisibility === 'closing') {
504
- this.toggleMenu(true);
505
- } else {
506
- this.handleOptionSelection(focusedOptionIndex);
507
- }
508
- break;
509
-
510
- case 'Enter':
511
- isShortcut = true;
512
- if (menuVisibility === 'closed' || menuVisibility === 'closing') {
513
- // allow the click
514
- isShortcut = false;
515
- } else {
516
- this.handleOptionSelection(focusedOptionIndex);
517
- }
518
- break;
519
-
520
- default:
521
- }
522
- }
523
-
524
- if (isShortcut) {
525
- // Call stopPropagation here to limit shortcut key handling to the Select
526
- // component. Otherwise, for example, using the typeahead feature of the
527
- // Select would end up triggering a number of undesired if actions if the
528
- // containing application supports its own keyboard shortcuts.
529
- event.stopPropagation();
530
-
531
- // Call preventDefault here to maintain control of what happens when
532
- // handling shortcut keys. For example, without this call, pressing the
533
- // down arrow key would scroll the menu down (since the menu has DOM
534
- // focus while its visible and scrolling is the default behavior of the
535
- // down arrow key). Instead, we want to provide our own custom behavior
536
- // of assistively focusing the next option.
537
- event.preventDefault();
538
- }
539
- };
540
-
541
- public render() {
542
- const {
543
- value,
544
-
545
- // Strip props we don't want to pass down from elemProps
546
- buttonRef,
547
- options,
548
- onKeyDown,
549
- onBlur,
550
-
551
- ...elemProps
552
- } = this.props;
553
-
554
- const {focusedOptionIndex, menuVisibility} = this.state;
555
-
556
- // Don't pass in event handlers if options weren't defined
557
- const eventHandlers = this.areOptionsDefined()
558
- ? {
559
- onClick: this.handleClick,
560
- onKeyDown: this.handleKeyDown,
561
- onClose: this.handleClose,
562
- onOptionSelection: this.handleOptionSelection,
563
- }
564
- : {};
565
-
566
- return (
567
- <SelectBase
568
- forwardedButtonRef={this.forwardedButtonRef}
569
- localButtonRef={this.localButtonRef}
570
- focusedOptionIndex={focusedOptionIndex}
571
- inputRef={this.inputRef}
572
- menuRef={this.menuRef}
573
- menuVisibility={menuVisibility}
574
- options={this.normalizedOptions}
575
- value={value}
576
- {...eventHandlers}
577
- {...elemProps}
578
- />
579
- );
580
- }
581
- }
582
- /**
583
- * @deprecated ⚠️ `Select` in Preview has been deprecated and will be removed in a future major version. Please use [`Select` in Main](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs) instead.
584
- */
585
- export const Select = createComponent('button')({
586
- displayName: 'Select',
587
- Component: (props: SelectProps, ref, Element) => (
588
- // Select is still a class component, so we render a renamed version of it
589
- // (SelectContainer) and pass it ref and Element
590
- <SelectContainer as={Element} buttonRef={ref} {...props} />
591
- ),
592
- subComponents: {
593
- ErrorType,
594
- },
595
- });