@equinor/eds-core-react 2.2.1-beta.0 → 2.3.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 (41) hide show
  1. package/dist/eds-core-react.cjs +754 -499
  2. package/dist/esm/components/Autocomplete/AddNewOption.js +31 -14
  3. package/dist/esm/components/Autocomplete/Autocomplete.js +54 -874
  4. package/dist/esm/components/Autocomplete/AutocompleteContext.js +12 -0
  5. package/dist/esm/components/Autocomplete/EmptyOption.js +21 -0
  6. package/dist/esm/components/Autocomplete/MultipleInput.js +85 -0
  7. package/dist/esm/components/Autocomplete/Option.js +42 -23
  8. package/dist/esm/components/Autocomplete/OptionList.js +124 -0
  9. package/dist/esm/components/Autocomplete/RightAdornments.js +48 -0
  10. package/dist/esm/components/Autocomplete/SelectAllOption.js +63 -0
  11. package/dist/esm/components/Autocomplete/SingleInput.js +28 -0
  12. package/dist/esm/components/Autocomplete/useAutocomplete.js +605 -0
  13. package/dist/esm/components/Autocomplete/utils.js +93 -0
  14. package/dist/esm/components/Datepicker/fields/FieldWrapper.js +10 -0
  15. package/dist/esm/components/Dialog/Dialog.js +6 -4
  16. package/dist/esm/components/next/Icon/Icon.js +57 -0
  17. package/dist/esm/components/next/Icon/icon.css.js +6 -0
  18. package/dist/esm/index.js +1 -1
  19. package/dist/esm/index.next.js +1 -0
  20. package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js +26 -0
  21. package/dist/index.next.cjs +82 -0
  22. package/dist/types/components/Autocomplete/AddNewOption.d.ts +4 -12
  23. package/dist/types/components/Autocomplete/Autocomplete.d.ts +22 -7
  24. package/dist/types/components/Autocomplete/AutocompleteContext.d.ts +228 -0
  25. package/dist/types/components/Autocomplete/EmptyOption.d.ts +1 -0
  26. package/dist/types/components/Autocomplete/MultipleInput.d.ts +1 -0
  27. package/dist/types/components/Autocomplete/Option.d.ts +7 -15
  28. package/dist/types/components/Autocomplete/OptionList.d.ts +2 -0
  29. package/dist/types/components/Autocomplete/RightAdornments.d.ts +1 -0
  30. package/dist/types/components/Autocomplete/SelectAllOption.d.ts +6 -0
  31. package/dist/types/components/Autocomplete/SingleInput.d.ts +1 -0
  32. package/dist/types/components/Autocomplete/useAutocomplete.d.ts +122 -0
  33. package/dist/types/components/Autocomplete/utils.d.ts +13 -0
  34. package/dist/types/components/Datepicker/DateRangePicker.d.ts +1 -1
  35. package/dist/types/components/SideBar/SideBarButton/index.d.ts +1 -1
  36. package/dist/types/components/next/Icon/Icon.d.ts +29 -0
  37. package/dist/types/components/next/Icon/Icon.types.d.ts +19 -0
  38. package/dist/types/components/next/Icon/index.d.ts +2 -0
  39. package/dist/types/components/next/Placeholder/Placeholder.figma.d.ts +16 -0
  40. package/dist/types/components/next/index.d.ts +2 -0
  41. package/package.json +5 -4
@@ -1,23 +1,13 @@
1
- import { forwardRef, useState, useMemo, useCallback, useRef, useImperativeHandle, useEffect } from 'react';
2
- import { useMultipleSelection, useCombobox } from 'downshift';
3
- import { useVirtualizer } from '@tanstack/react-virtual';
4
- import styled, { ThemeProvider, css } from 'styled-components';
1
+ import { useFloating, offset, flip, size } from '@floating-ui/react';
2
+ import styled, { css, ThemeProvider } from 'styled-components';
5
3
  import { Button } from '../Button/index.js';
6
- import { List } from '../List/index.js';
7
- import { Icon } from '../Icon/index.js';
8
- import { Progress } from '../Progress/index.js';
9
- import { close, arrow_drop_up, arrow_drop_down } from '@equinor/eds-icons';
10
- import { multiSelect, selectTokens } from './Autocomplete.tokens.js';
11
- import { useToken, useIsomorphicLayoutEffect, bordersTemplate } from '@equinor/eds-utils';
12
- import { AutocompleteOption } from './Option.js';
13
- import { useFloating, offset, flip, size, useInteractions, autoUpdate } from '@floating-ui/react';
14
- import { AddNewOption } from './AddNewOption.js';
15
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
- import pickBy from '../../node_modules/.pnpm/ramda@0.32.0/node_modules/ramda/es/pickBy.js';
17
- import mergeWith from '../../node_modules/.pnpm/ramda@0.32.0/node_modules/ramda/es/mergeWith.js';
18
- import { useEds } from '../EdsProvider/eds.context.js';
4
+ import { AutocompleteContext } from './AutocompleteContext.js';
5
+ import { MultipleInput } from './MultipleInput.js';
6
+ import { OptionList } from './OptionList.js';
7
+ import { SingleInput } from './SingleInput.js';
8
+ import { useAutocomplete } from './useAutocomplete.js';
9
+ import { jsx, jsxs } from 'react/jsx-runtime';
19
10
  import { Label } from '../Label/Label.js';
20
- import { Input } from '../Input/Input.js';
21
11
  import { HelperText as TextfieldHelperText } from '../InputWrapper/HelperText/HelperText.js';
22
12
 
23
13
  const Container = styled.div.withConfig({
@@ -26,33 +16,13 @@ const Container = styled.div.withConfig({
26
16
  })(["position:relative;"]);
27
17
  const AllSymbol = Symbol('Select all');
28
18
  const AddSymbol = Symbol('Add new');
29
-
30
- // MARK: styled components
31
- const StyledList = styled(List).withConfig({
32
- displayName: "Autocomplete__StyledList",
33
- componentId: "sc-yvif0e-1"
34
- })(({
35
- theme
36
- }) => css(["background-color:", ";box-shadow:", ";", " overflow-y:auto;overflow-x:hidden;padding:0;display:grid;@supports (-moz-appearance:none){scrollbar-width:thin;}"], theme.background, theme.boxShadow, bordersTemplate(theme.border)));
37
- const StyledPopover = styled('div').withConfig({
38
- shouldForwardProp: () => true //workaround to avoid warning until popover gets added to react types
39
- }).withConfig({
40
- displayName: "Autocomplete__StyledPopover",
41
- componentId: "sc-yvif0e-2"
42
- })(["inset:unset;border:0;padding:0;margin:0;overflow:visible;&::backdrop{background-color:transparent;}"]);
43
19
  const HelperText = styled(TextfieldHelperText).withConfig({
44
20
  displayName: "Autocomplete__HelperText",
45
- componentId: "sc-yvif0e-3"
21
+ componentId: "sc-yvif0e-1"
46
22
  })(["margin-top:8px;margin-left:8px;"]);
47
- const AutocompleteNoOptions = styled(AutocompleteOption).withConfig({
48
- displayName: "Autocomplete__AutocompleteNoOptions",
49
- componentId: "sc-yvif0e-4"
50
- })(({
51
- theme
52
- }) => css(["color:", ";"], theme.entities.noOptions.typography.color));
53
23
  const StyledButton = styled(Button).withConfig({
54
24
  displayName: "Autocomplete__StyledButton",
55
- componentId: "sc-yvif0e-5"
25
+ componentId: "sc-yvif0e-2"
56
26
  })(({
57
27
  theme: {
58
28
  entities: {
@@ -60,641 +30,35 @@ const StyledButton = styled(Button).withConfig({
60
30
  }
61
31
  }
62
32
  }) => css(["height:", ";width:", ";"], button.height, button.height));
63
- // MARK: outside functions
64
- // Typescript can struggle with parsing generic arrow functions in a .tsx file (see https://github.com/microsoft/TypeScript/issues/15713)
65
- // Workaround is to add a trailing , after T, which tricks the compiler, but also have to ignore prettier rule.
66
- // prettier-ignore
67
-
68
- const findIndex = ({
69
- calc,
70
- index,
71
- optionDisabled,
72
- availableItems
73
- }) => {
74
- const nextItem = availableItems[index];
75
- if (optionDisabled(nextItem) && index >= 0 && index < availableItems.length) {
76
- const nextIndex = calc(index);
77
- return findIndex({
78
- calc,
79
- index: nextIndex,
80
- availableItems,
81
- optionDisabled
82
- });
83
- }
84
- return index;
85
- };
86
- const isEvent = (val, key) => /^on[A-Z](.*)/.test(key) && typeof val === 'function';
87
- function mergeEventsFromRight(props1, props2) {
88
- const events1 = pickBy(isEvent, props1);
89
- const events2 = pickBy(isEvent, props2);
90
- return mergeWith((event1, event2) => {
91
- return (...args) => {
92
- event1(...args);
93
- event2(...args);
94
- };
95
- }, events1, events2);
96
- }
97
- const findNextIndex = ({
98
- index,
99
- optionDisabled,
100
- availableItems,
101
- allDisabled
102
- }) => {
103
- if (allDisabled) return 0;
104
- const options = {
105
- index,
106
- optionDisabled,
107
- availableItems,
108
- calc: num => num + 1
109
- };
110
- const nextIndex = findIndex(options);
111
- if (nextIndex > availableItems.length - 1) {
112
- // jump to start of list
113
- return findIndex({
114
- ...options,
115
- index: 0
116
- });
117
- }
118
- return nextIndex;
119
- };
120
- const findPrevIndex = ({
121
- index,
122
- optionDisabled,
123
- availableItems,
124
- allDisabled
125
- }) => {
126
- if (allDisabled) return 0;
127
- const options = {
128
- index,
129
- optionDisabled,
130
- availableItems,
131
- calc: num => num - 1
132
- };
133
- const prevIndex = findIndex(options);
134
- if (prevIndex < 0) {
135
- // jump to end of list
136
- return findIndex({
137
- ...options,
138
- index: availableItems.length - 1
139
- });
140
- }
141
- return prevIndex;
142
- };
143
-
144
- /*When a user clicks the StyledList scrollbar, the input looses focus which breaks downshift
145
- * keyboard navigation in the list. This code returns focus to the input on mouseUp
146
- */
147
- const handleListFocus = e => {
148
- e.preventDefault();
149
- e.stopPropagation();
150
- window?.addEventListener('mouseup', () => {
151
- e.relatedTarget?.focus();
152
- }, {
153
- once: true
154
- });
155
- };
156
33
  const defaultOptionDisabled = () => false;
157
34
  // MARK: types
158
35
 
159
36
  // MARK: component
160
- function AutocompleteInner(props, ref) {
161
- const [controlledHighlightedIndex, setControlledHighlightedIndex] = useState(0);
162
- const [lastScrollOffset, setLastScrollOffset] = useState(0);
37
+ function Autocomplete({
38
+ ...props
39
+ }) {
40
+ const autocompleteProps = useAutocomplete({
41
+ ...props,
42
+ ref: props.ref
43
+ });
163
44
  const {
164
- options = [],
165
- totalOptions,
166
- label,
167
- meta,
45
+ getLabelProps,
46
+ token,
47
+ tokens,
48
+ autoWidth,
168
49
  className,
169
50
  style,
170
- disabled = false,
171
- readOnly = false,
172
- loading = false,
173
- hideClearButton = false,
174
- onOptionsChange,
175
- onAddNewOption,
176
- onInputChange,
177
- selectedOptions: _selectedOptions,
51
+ label,
52
+ meta,
178
53
  multiple,
179
- itemToKey: _itemToKey,
180
- itemCompare: _itemCompare,
181
- allowSelectAll,
182
- initialSelectedOptions: _initialSelectedOptions = [],
183
- optionDisabled = defaultOptionDisabled,
184
- optionsFilter,
185
- autoWidth,
186
- placeholder,
187
- optionLabel,
188
- clearSearchOnChange = true,
189
- multiline = false,
190
- dropdownHeight = 300,
191
- optionComponent,
192
- helperText,
193
- helperIcon,
194
- noOptionsText = 'No options',
54
+ disabled,
195
55
  variant,
196
- onClear,
197
- ...other
198
- } = props;
199
- const itemCompare = useMemo(() => {
200
- if (_itemCompare && _itemToKey) {
201
- console.error('Error: Specifying both itemCompare and itemToKey. itemCompare is deprecated, while itemToKey should be used instead of it. Please only use one.');
202
- return _itemCompare;
203
- }
204
- if (_itemToKey) {
205
- return (o1, o2) => _itemToKey(o1) === _itemToKey(o2);
206
- }
207
- return _itemCompare;
208
- }, [_itemCompare, _itemToKey]);
209
- const itemToKey = useCallback(item => {
210
- return _itemToKey ? _itemToKey(item) : item;
211
- }, [_itemToKey]);
212
-
213
- // MARK: initializing data/setup
214
- const selectedOptions = _selectedOptions ? itemCompare ? options.filter(item => _selectedOptions.some(compare => itemCompare(item, compare))) : _selectedOptions : undefined;
215
- const initialSelectedOptions = _initialSelectedOptions ? itemCompare ? options.filter(item => _initialSelectedOptions.some(compare => itemCompare(item, compare))) : _initialSelectedOptions : undefined;
216
- const isControlled = Boolean(selectedOptions);
217
- const [inputOptions, setInputOptions] = useState(options);
218
- const [_availableItems, setAvailableItems] = useState(inputOptions);
219
- const [typedInputValue, setTypedInputValue] = useState('');
220
- const inputRef = useRef(null);
221
- useImperativeHandle(ref, () => inputRef.current);
222
- const showSelectAll = useMemo(() => {
223
- if (!multiple && allowSelectAll) {
224
- throw new Error(`allowSelectAll can only be used with multiple`);
225
- }
226
- return allowSelectAll && !typedInputValue;
227
- }, [allowSelectAll, multiple, typedInputValue]);
228
- const availableItems = useMemo(() => {
229
- if (showSelectAll && onAddNewOption) return [AddSymbol, AllSymbol, ..._availableItems];
230
- if (showSelectAll) return [AllSymbol, ..._availableItems];
231
- if (onAddNewOption) return [AddSymbol, ..._availableItems];
232
- return _availableItems;
233
- }, [_availableItems, showSelectAll, onAddNewOption]);
234
- const getSelectedIndex = useCallback(selectedItem => availableItems.findIndex(item => itemCompare ? itemCompare(item, selectedItem) : item === selectedItem), [availableItems, itemCompare]);
235
-
236
- //issue 2304, update dataset when options are added dynamically
237
- useEffect(() => {
238
- const availableHash = JSON.stringify(inputOptions);
239
- const optionsHash = JSON.stringify(options);
240
- if (availableHash !== optionsHash) {
241
- setInputOptions(options);
242
- }
243
- }, [options, inputOptions]);
244
- useEffect(() => {
245
- setAvailableItems(inputOptions);
246
- }, [inputOptions]);
247
- const {
248
- density
249
- } = useEds();
250
- const token = useToken({
251
- density
252
- }, multiple ? multiSelect : selectTokens);
253
- const tokens = token();
254
- let placeholderText = placeholder;
255
- let multipleSelectionProps = {
256
- itemToKey,
257
- initialSelectedItems: multiple ? initialSelectedOptions : initialSelectedOptions[0] ? [initialSelectedOptions[0]] : []
258
- };
259
- if (multiple) {
260
- multipleSelectionProps = {
261
- ...multipleSelectionProps,
262
- onSelectedItemsChange: changes => {
263
- if (onOptionsChange) {
264
- let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol || item !== AddSymbol);
265
- if (itemCompare) {
266
- selectedItems = inputOptions.filter(item => selectedItems.some(compare => itemCompare(item, compare)));
267
- }
268
- onOptionsChange({
269
- selectedItems
270
- });
271
- }
272
- }
273
- };
274
- if (isControlled) {
275
- multipleSelectionProps = {
276
- ...multipleSelectionProps,
277
- selectedItems: selectedOptions
278
- };
279
- }
280
- }
281
- const {
282
- getDropdownProps,
283
- addSelectedItem,
284
- removeSelectedItem,
285
- selectedItems,
286
- setSelectedItems
287
- } = useMultipleSelection(multipleSelectionProps);
288
-
289
- // MARK: select all logic
290
- const enabledItems = useMemo(() => {
291
- const disabledItemsSet = new Set(inputOptions.filter(optionDisabled));
292
- return inputOptions.filter(x => !disabledItemsSet.has(x));
293
- }, [inputOptions, optionDisabled]);
294
- const allDisabled = enabledItems.length === 0;
295
- const selectedDisabledItemsSet = useMemo(() => new Set(selectedItems.filter(x => x !== null && optionDisabled(x))), [selectedItems, optionDisabled]);
296
- const selectedEnabledItems = useMemo(() => selectedItems.filter(x => !selectedDisabledItemsSet.has(x)), [selectedItems, selectedDisabledItemsSet]);
297
- const allSelectedState = useMemo(() => {
298
- if (!enabledItems || !selectedEnabledItems) return 'NONE';
299
- if (enabledItems.length === selectedEnabledItems.length) return 'ALL';
300
- if (enabledItems.length != selectedEnabledItems.length && selectedEnabledItems.length > 0) return 'SOME';
301
- return 'NONE';
302
- }, [enabledItems, selectedEnabledItems]);
303
- const toggleAllSelected = () => {
304
- if (selectedEnabledItems.length === enabledItems.length) {
305
- setSelectedItems([...selectedDisabledItemsSet]);
306
- } else {
307
- setSelectedItems([...enabledItems, ...selectedDisabledItemsSet]);
308
- }
309
- };
310
-
311
- // MARK: getLabel
312
- const getLabel = useCallback(item => {
313
- //note: non strict check for null or undefined to allow 0
314
- if (item == null) {
315
- return '';
316
- }
317
- if (optionLabel) {
318
- return optionLabel(item);
319
- } else if (typeof item === 'object') {
320
- throw new Error('Missing label. When using objects for options make sure to define the `optionLabel` property');
321
- }
322
- if (typeof item === 'string') {
323
- return item;
324
- }
325
- try {
326
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
327
- return item?.toString();
328
- } catch {
329
- throw new Error('Unable to find label, make sure your are using options as documented');
330
- }
331
- }, [optionLabel]);
332
-
333
- // MARK: setup virtualizer
334
- const scrollContainer = useRef(null);
335
- const rowVirtualizer = useVirtualizer({
336
- count: availableItems.length,
337
- getScrollElement: () => scrollContainer.current,
338
- estimateSize: useCallback(() => {
339
- return parseInt(token().entities.label.minHeight);
340
- }, [token]),
341
- overscan: 25
342
- });
343
-
344
- //https://github.com/TanStack/virtual/discussions/379#discussioncomment-3501037
345
- useIsomorphicLayoutEffect(() => {
346
- rowVirtualizer?.measure?.();
347
- }, [rowVirtualizer, density]);
348
-
349
- // MARK: downshift state
350
- let comboBoxProps = {
351
- items: availableItems,
352
- //can not pass readonly type to downshift so we cast it to regular T[]
353
- initialSelectedItem: initialSelectedOptions[0],
354
- isItemDisabled(item) {
355
- if (item === AddSymbol) return !typedInputValue.trim();
356
- return optionDisabled(item);
357
- },
358
- itemToKey,
359
- itemToString: getLabel,
360
- onInputValueChange: ({
361
- inputValue
362
- }) => {
363
- onInputChange && onInputChange(inputValue);
364
- setAvailableItems(options.filter(item => {
365
- if (optionsFilter) {
366
- return optionsFilter(item, inputValue);
367
- }
368
- return getLabel(item).toLowerCase().includes(inputValue.toLowerCase());
369
- }));
370
- },
371
- onHighlightedIndexChange({
372
- highlightedIndex
373
- }) {
374
- if (highlightedIndex >= 0 && rowVirtualizer.getVirtualItems) {
375
- const visibleIndexes = rowVirtualizer.getVirtualItems().map(v => v.index);
376
- if (!visibleIndexes.includes(highlightedIndex)) {
377
- rowVirtualizer.scrollToIndex(highlightedIndex, {
378
- align: allowSelectAll ? 'center' : 'auto'
379
- });
380
- }
381
- }
382
- if (typeof rowVirtualizer.scrollOffset === 'number') {
383
- setLastScrollOffset(rowVirtualizer.scrollOffset);
384
- }
385
- },
386
- onIsOpenChange: ({
387
- selectedItem
388
- }) => {
389
- if (!multiple && selectedItem !== null) {
390
- setAvailableItems(options);
391
- setTimeout(() => {
392
- if (controlledHighlightedIndex === 0) {
393
- rowVirtualizer.scrollToOffset?.(0);
394
- } else if (rowVirtualizer.scrollToOffset && lastScrollOffset > 0) {
395
- rowVirtualizer.scrollToOffset(lastScrollOffset);
396
- }
397
- const visibleIndexes = rowVirtualizer.getVirtualItems?.().map(v => v.index) || [];
398
- if (!visibleIndexes.includes(controlledHighlightedIndex)) {
399
- rowVirtualizer.scrollToIndex(controlledHighlightedIndex, {
400
- align: allowSelectAll ? 'center' : 'auto'
401
- });
402
- }
403
- }, 10);
404
- }
405
- },
406
- onStateChange: ({
407
- type,
408
- selectedItem
409
- }) => {
410
- switch (type) {
411
- case useCombobox.stateChangeTypes.InputChange:
412
- case useCombobox.stateChangeTypes.InputBlur:
413
- break;
414
- case useCombobox.stateChangeTypes.InputKeyDownEnter:
415
- case useCombobox.stateChangeTypes.ItemClick:
416
- //note: non strict check for null or undefined to allow 0
417
- if (selectedItem != null && !optionDisabled(selectedItem)) {
418
- if (selectedItem === AllSymbol) {
419
- toggleAllSelected();
420
- } else if (selectedItem === AddSymbol && typedInputValue.trim()) {
421
- onAddNewOption?.(typedInputValue);
422
- } else if (multiple) {
423
- const shouldRemove = itemCompare ? selectedItems.some(i => itemCompare(selectedItem, i)) : selectedItems.includes(selectedItem);
424
- if (shouldRemove) {
425
- removeSelectedItem(selectedItem);
426
- } else {
427
- addSelectedItem(selectedItem);
428
- }
429
- } else {
430
- setSelectedItems([selectedItem]);
431
- }
432
- }
433
- break;
434
- }
435
- }
436
- };
437
- // MARK: singleselect specific
438
- if (!multiple) {
439
- comboBoxProps = {
440
- ...comboBoxProps,
441
- onSelectedItemChange: changes => {
442
- if (changes.selectedItem === AddSymbol) return;
443
- const idx = getSelectedIndex(changes.selectedItem);
444
- setControlledHighlightedIndex(idx >= 0 ? idx : 0);
445
- if (onOptionsChange) {
446
- let {
447
- selectedItem
448
- } = changes;
449
- if (itemCompare) {
450
- selectedItem = inputOptions.find(item => itemCompare(item, selectedItem));
451
- }
452
- onOptionsChange({
453
- selectedItems: selectedItem ? [selectedItem] : []
454
- });
455
- }
456
- },
457
- stateReducer: (state, actionAndChanges) => {
458
- const {
459
- changes,
460
- type
461
- } = actionAndChanges;
462
- switch (type) {
463
- case useCombobox.stateChangeTypes.InputClick:
464
- return {
465
- ...changes,
466
- isOpen: !(disabled || readOnly),
467
- highlightedIndex: controlledHighlightedIndex
468
- };
469
- case useCombobox.stateChangeTypes.InputKeyDownEnter:
470
- case useCombobox.stateChangeTypes.ItemClick:
471
- {
472
- if (changes.selectedItem === AddSymbol) {
473
- return {
474
- ...changes,
475
- inputValue: ''
476
- };
477
- }
478
- const idx = getSelectedIndex(changes.selectedItem);
479
- setControlledHighlightedIndex(idx >= 0 ? idx : 0);
480
- return {
481
- ...changes,
482
- highlightedIndex: idx >= 0 ? idx : 0
483
- };
484
- }
485
- case useCombobox.stateChangeTypes.InputBlur:
486
- return {
487
- ...changes,
488
- inputValue: changes.selectedItem ? getLabel(changes.selectedItem) : ''
489
- };
490
- case useCombobox.stateChangeTypes.InputChange:
491
- setTypedInputValue(changes.inputValue);
492
- return {
493
- ...changes
494
- };
495
- case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
496
- if (readOnly) {
497
- return {
498
- ...changes,
499
- isOpen: false
500
- };
501
- }
502
- if (state.isOpen === false) {
503
- return {
504
- ...changes,
505
- isOpen: true,
506
- highlightedIndex: controlledHighlightedIndex
507
- };
508
- }
509
- return {
510
- ...changes,
511
- highlightedIndex: findNextIndex({
512
- index: changes.highlightedIndex,
513
- availableItems,
514
- optionDisabled,
515
- allDisabled
516
- })
517
- };
518
- case useCombobox.stateChangeTypes.InputKeyDownHome:
519
- if (readOnly) {
520
- return {
521
- ...changes,
522
- isOpen: false
523
- };
524
- }
525
- return {
526
- ...changes,
527
- highlightedIndex: findNextIndex({
528
- index: 0,
529
- availableItems,
530
- optionDisabled,
531
- allDisabled
532
- })
533
- };
534
- case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
535
- if (readOnly) {
536
- return {
537
- ...changes,
538
- isOpen: false
539
- };
540
- }
541
- if (state.isOpen === false) {
542
- return {
543
- ...changes,
544
- isOpen: true,
545
- highlightedIndex: controlledHighlightedIndex
546
- };
547
- }
548
- return {
549
- ...changes,
550
- highlightedIndex: findPrevIndex({
551
- index: changes.highlightedIndex,
552
- availableItems,
553
- optionDisabled,
554
- allDisabled
555
- })
556
- };
557
- case useCombobox.stateChangeTypes.InputKeyDownEnd:
558
- if (readOnly) {
559
- return {
560
- ...changes,
561
- isOpen: false
562
- };
563
- }
564
- return {
565
- ...changes,
566
- highlightedIndex: findPrevIndex({
567
- index: availableItems.length - 1,
568
- availableItems,
569
- optionDisabled,
570
- allDisabled
571
- })
572
- };
573
- case useCombobox.stateChangeTypes.ControlledPropUpdatedSelectedItem:
574
- setSelectedItems([changes.selectedItem]);
575
- return {
576
- ...changes,
577
- highlightedIndex: controlledHighlightedIndex
578
- };
579
- default:
580
- return changes;
581
- }
582
- }
583
- };
584
- if (isControlled) {
585
- comboBoxProps = {
586
- ...comboBoxProps,
587
- selectedItem: selectedOptions[0] || null
588
- };
589
- }
590
- }
591
- // MARK: multiselect specific
592
- if (multiple) {
593
- const showPlaceholder = placeholderText && selectedItems.length === 0;
594
- const optionCount = totalOptions || inputOptions.length;
595
- placeholderText = showPlaceholder ? placeholderText : `${selectedItems.length}/${optionCount} selected`;
596
- comboBoxProps = {
597
- ...comboBoxProps,
598
- selectedItem: null,
599
- stateReducer: (state, actionAndChanges) => {
600
- const {
601
- changes,
602
- type
603
- } = actionAndChanges;
604
- switch (type) {
605
- case useCombobox.stateChangeTypes.InputClick:
606
- return {
607
- ...changes,
608
- isOpen: !(disabled || readOnly)
609
- };
610
- case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
611
- case useCombobox.stateChangeTypes.InputKeyDownHome:
612
- if (readOnly) {
613
- return {
614
- ...changes,
615
- isOpen: false
616
- };
617
- }
618
- return {
619
- ...changes,
620
- highlightedIndex: findNextIndex({
621
- index: changes.highlightedIndex,
622
- availableItems,
623
- optionDisabled,
624
- allDisabled
625
- })
626
- };
627
- case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
628
- case useCombobox.stateChangeTypes.InputKeyDownEnd:
629
- if (readOnly) {
630
- return {
631
- ...changes,
632
- isOpen: false
633
- };
634
- }
635
- return {
636
- ...changes,
637
- highlightedIndex: findPrevIndex({
638
- index: changes.highlightedIndex,
639
- availableItems,
640
- optionDisabled,
641
- allDisabled
642
- })
643
- };
644
- case useCombobox.stateChangeTypes.InputKeyDownEnter:
645
- case useCombobox.stateChangeTypes.ItemClick:
646
- if (clearSearchOnChange) {
647
- setTypedInputValue('');
648
- }
649
- return {
650
- ...changes,
651
- isOpen: true,
652
- // keep menu open after selection.
653
- highlightedIndex: state.highlightedIndex,
654
- inputValue: !clearSearchOnChange ? typedInputValue : ''
655
- };
656
- case useCombobox.stateChangeTypes.InputChange:
657
- setTypedInputValue(changes.inputValue);
658
- return {
659
- ...changes
660
- };
661
- case useCombobox.stateChangeTypes.InputBlur:
662
- setTypedInputValue('');
663
- return {
664
- ...changes,
665
- inputValue: ''
666
- };
667
- case useCombobox.stateChangeTypes.ControlledPropUpdatedSelectedItem:
668
- return {
669
- ...changes,
670
- inputValue: !clearSearchOnChange ? typedInputValue : changes.inputValue
671
- };
672
- default:
673
- return changes;
674
- }
675
- }
676
- };
677
- }
678
- const {
679
- isOpen,
680
- getToggleButtonProps,
681
- getLabelProps,
682
- getMenuProps,
683
- getInputProps,
684
- highlightedIndex,
685
- getItemProps,
686
- inputValue,
687
- reset: resetCombobox
688
- } = useCombobox(comboBoxProps);
56
+ helperIcon,
57
+ helperText
58
+ } = autocompleteProps;
689
59
 
690
60
  // MARK: floating-ui setup
691
- const {
692
- x,
693
- y,
694
- refs,
695
- update,
696
- strategy
697
- } = useFloating({
61
+ const floatingProps = useFloating({
698
62
  placement: 'bottom-start',
699
63
  middleware: [offset(4), flip({
700
64
  boundary: typeof document === 'undefined' ? undefined : document?.body
@@ -711,219 +75,35 @@ function AutocompleteInner(props, ref) {
711
75
  padding: 10
712
76
  })]
713
77
  });
714
- const {
715
- getFloatingProps
716
- } = useInteractions([]);
717
- useEffect(() => {
718
- if (refs.reference.current && refs.floating.current && isOpen) {
719
- return autoUpdate(refs.reference.current, refs.floating.current, update);
720
- }
721
- }, [refs.reference, refs.floating, update, isOpen]);
722
-
723
- // MARK: popover toggle
724
- useIsomorphicLayoutEffect(() => {
725
- if (isOpen) {
726
- refs.floating.current?.showPopover();
727
- } else {
728
- refs.floating.current?.hidePopover();
729
- }
730
- }, [isOpen, refs.floating]);
731
- const clear = () => {
732
- if (onClear) onClear();
733
- resetCombobox();
734
- //dont clear items if they are selected and disabled
735
- setSelectedItems([...selectedDisabledItemsSet]);
736
- setTypedInputValue('');
737
- inputRef.current?.focus();
738
- };
739
- const showClearButton = (selectedItems.length > 0 || inputValue) && !readOnly && !hideClearButton;
740
- const showNoOptions = isOpen && !availableItems.length && noOptionsText.length > 0;
741
- const selectedItemsLabels = useMemo(() => selectedItems.map(getLabel), [selectedItems, getLabel]);
742
-
743
- // MARK: optionsList
744
- const optionsList = /*#__PURE__*/jsx(StyledPopover, {
745
- popover: "manual",
746
- ...getFloatingProps({
747
- ref: refs.setFloating,
748
- onFocus: handleListFocus,
749
- style: {
750
- position: strategy,
751
- top: y || 0,
752
- left: x || 0
753
- }
754
- }),
755
- children: /*#__PURE__*/jsxs(StyledList, {
756
- ...getMenuProps({
757
- 'aria-multiselectable': multiple ? 'true' : null,
758
- ref: scrollContainer,
759
- style: {
760
- maxHeight: `${dropdownHeight}px`
761
- }
762
- }, {
763
- suppressRefError: true
764
- }),
765
- children: [showNoOptions && /*#__PURE__*/jsx(AutocompleteNoOptions, {
766
- value: noOptionsText,
767
- multiple: false,
768
- multiline: false,
769
- highlighted: 'false',
770
- isSelected: false,
771
- isDisabled: true
772
- }), isOpen && /*#__PURE__*/jsx("li", {
773
- role: "presentation",
774
- style: {
775
- height: `${rowVirtualizer.getTotalSize()}px`,
776
- margin: '0',
777
- gridArea: '1 / -1'
778
- }
779
- }, "total-size"), !isOpen ? null : rowVirtualizer.getVirtualItems().map(virtualItem => {
780
- const index = virtualItem.index;
781
- const item = availableItems[index];
782
- const label = getLabel(item);
783
- const isDisabled = optionDisabled(item);
784
- const isSelected = selectedItemsLabels.includes(label);
785
- if (item === AllSymbol) {
786
- return /*#__PURE__*/jsx(AutocompleteOption, {
787
- "data-index": 0,
788
- "data-testid": 'select-all',
789
- value: 'Select all',
790
- "aria-setsize": availableItems.length,
791
- multiple: true,
792
- isSelected: allSelectedState === 'ALL',
793
- indeterminate: allSelectedState === 'SOME',
794
- highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
795
- isDisabled: false,
796
- multiline: multiline,
797
- onClick: toggleAllSelected,
798
- style: {
799
- position: 'sticky',
800
- top: 0,
801
- zIndex: 99
802
- },
803
- ...getItemProps({
804
- ...(multiline && {
805
- ref: rowVirtualizer.measureElement
806
- }),
807
- item,
808
- index: index
809
- })
810
- }, 'select-all');
811
- }
812
- if (item === AddSymbol && onAddNewOption) {
813
- const isDisabled = !typedInputValue.trim();
814
- return /*#__PURE__*/jsx(AddNewOption, {
815
- "data-index": 0,
816
- "data-testid": 'add-item',
817
- "aria-setsize": availableItems.length,
818
- multiple: multiple,
819
- highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
820
- multiline: multiline,
821
- style: {
822
- position: 'sticky',
823
- top: 0,
824
- zIndex: 99
825
- },
826
- ...getItemProps({
827
- ...(multiline && {
828
- ref: rowVirtualizer.measureElement
829
- }),
830
- item,
831
- index: index
832
- }),
833
- value: typedInputValue.trim()
834
- }, 'add-item');
835
- }
836
- return /*#__PURE__*/jsx(AutocompleteOption, {
837
- "data-index": index,
838
- "aria-setsize": availableItems.length,
839
- "aria-posinset": index + 1,
840
- value: label,
841
- multiple: multiple,
842
- highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
843
- isSelected: isSelected,
844
- isDisabled: isDisabled,
845
- multiline: multiline,
846
- optionComponent: optionComponent?.(item, isSelected),
847
- ...getItemProps({
848
- ...(multiline && {
849
- ref: rowVirtualizer.measureElement
850
- }),
851
- item,
852
- index,
853
- style: {
854
- transform: `translateY(${virtualItem.start}px)`,
855
- ...(!multiline && {
856
- height: `${virtualItem.size}px`
857
- })
858
- }
859
- })
860
- }, virtualItem.key);
861
- })]
862
- })
863
- });
864
- const inputProps = getInputProps(getDropdownProps({
865
- preventKeyAction: multiple ? isOpen : undefined,
866
- disabled,
867
- ref: inputRef
868
- }));
869
- const consolidatedEvents = mergeEventsFromRight(other, inputProps);
870
78
 
871
79
  // MARK: input
872
- return /*#__PURE__*/jsx(ThemeProvider, {
873
- theme: token,
874
- children: /*#__PURE__*/jsxs(Container, {
875
- className: className,
876
- style: style,
877
- children: [/*#__PURE__*/jsx(Label, {
878
- ...getLabelProps(),
879
- label: label,
880
- meta: meta,
881
- disabled: disabled
882
- }), /*#__PURE__*/jsx(Container, {
883
- ref: refs.setReference,
884
- children: /*#__PURE__*/jsx(Input, {
885
- ...inputProps,
886
- variant: variant,
887
- placeholder: placeholderText,
888
- readOnly: readOnly,
889
- rightAdornmentsWidth: hideClearButton ? 24 + 8 : 24 * 2 + 8,
890
- rightAdornments: /*#__PURE__*/jsxs(Fragment, {
891
- children: [loading && /*#__PURE__*/jsx(Progress.Circular, {
892
- size: 16
893
- }), showClearButton && /*#__PURE__*/jsx(StyledButton, {
894
- variant: "ghost_icon",
895
- disabled: disabled || readOnly,
896
- "aria-label": 'clear options',
897
- title: "clear",
898
- onClick: clear,
899
- children: /*#__PURE__*/jsx(Icon, {
900
- data: close,
901
- size: 16
902
- })
903
- }), !readOnly && /*#__PURE__*/jsx(StyledButton, {
904
- variant: "ghost_icon",
905
- ...getToggleButtonProps({
906
- disabled: disabled || readOnly
907
- }),
908
- "aria-label": 'toggle options',
909
- title: "open",
910
- children: /*#__PURE__*/jsx(Icon, {
911
- data: isOpen ? arrow_drop_up : arrow_drop_down
912
- })
913
- })]
914
- }),
915
- ...other,
916
- ...consolidatedEvents
917
- })
918
- }), helperText && /*#__PURE__*/jsx(HelperText, {
919
- color: variant ? tokens.variants[variant].typography.color : undefined,
920
- text: helperText,
921
- icon: helperIcon
922
- }), optionsList]
80
+ return /*#__PURE__*/jsx(AutocompleteContext.Provider, {
81
+ value: {
82
+ ...autocompleteProps
83
+ },
84
+ children: /*#__PURE__*/jsx(ThemeProvider, {
85
+ theme: token,
86
+ children: /*#__PURE__*/jsxs(Container, {
87
+ className: className,
88
+ style: style,
89
+ children: [/*#__PURE__*/jsx(Label, {
90
+ ...getLabelProps(),
91
+ label: label,
92
+ meta: meta,
93
+ disabled: disabled
94
+ }), /*#__PURE__*/jsx(Container, {
95
+ ref: floatingProps.refs.setReference,
96
+ children: multiple ? /*#__PURE__*/jsx(MultipleInput, {}) : /*#__PURE__*/jsx(SingleInput, {})
97
+ }), helperText && /*#__PURE__*/jsx(HelperText, {
98
+ color: variant ? tokens.variants[variant].typography.color : undefined,
99
+ text: helperText,
100
+ icon: helperIcon
101
+ }), /*#__PURE__*/jsx(OptionList, {
102
+ ...floatingProps
103
+ })]
104
+ })
923
105
  })
924
106
  });
925
107
  }
926
- // MARK: exported component
927
- const Autocomplete = /*#__PURE__*/forwardRef(AutocompleteInner);
928
108
 
929
- export { Autocomplete };
109
+ export { AddSymbol, AllSymbol, Autocomplete, StyledButton, defaultOptionDisabled };