@digdir/designsystemet-react 0.59.0 → 0.59.1-alpha.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 (121) hide show
  1. package/dist/cjs/components/Accordion/Accordion.js +1 -4
  2. package/dist/cjs/components/Accordion/AccordionContent/AccordionContent.js +1 -2
  3. package/dist/cjs/components/Accordion/AccordionHeader/AccordionHeader.js +1 -2
  4. package/dist/cjs/components/Accordion/AccordionItem/AccordionItem.js +1 -4
  5. package/dist/cjs/components/Button/Button.js +2 -2
  6. package/dist/cjs/components/DropdownMenu/DropdownMenuContent.js +4 -4
  7. package/dist/cjs/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
  8. package/dist/cjs/components/Modal/ModalDialog.js +1 -1
  9. package/dist/cjs/components/Modal/ModalHeader/ModalHeader.js +1 -1
  10. package/dist/cjs/components/Modal/ModalHeader/ModalHeader.module.css.js +1 -1
  11. package/dist/cjs/components/Popover/PopoverContent.js +6 -6
  12. package/dist/cjs/components/Popover/PopoverTrigger.js +1 -1
  13. package/dist/cjs/components/Tooltip/Tooltip.js +6 -6
  14. package/dist/cjs/components/form/Checkbox/Checkbox.js +1 -1
  15. package/dist/cjs/components/form/Combobox/Combobox.js +72 -199
  16. package/dist/cjs/components/form/Combobox/Combobox.module.css.js +1 -1
  17. package/dist/cjs/components/form/Combobox/ComboboxContext.js +8 -0
  18. package/dist/cjs/components/form/Combobox/ComboboxIdContext.js +42 -0
  19. package/dist/cjs/components/form/Combobox/Custom/Custom.js +14 -9
  20. package/dist/cjs/components/form/Combobox/Empty/Empty.js +4 -4
  21. package/dist/cjs/components/form/Combobox/Option/Option.js +15 -33
  22. package/dist/cjs/components/form/Combobox/Option/useComboboxOption.js +47 -0
  23. package/dist/cjs/components/form/Combobox/internal/ComboboxChips.js +13 -10
  24. package/dist/cjs/components/form/Combobox/internal/ComboboxClearButton.js +5 -8
  25. package/dist/cjs/components/form/Combobox/internal/ComboboxInput.js +40 -69
  26. package/dist/cjs/components/form/Combobox/internal/ComboboxNative.js +2 -2
  27. package/dist/cjs/components/form/Combobox/useCombobox.js +77 -75
  28. package/dist/cjs/components/form/Combobox/useComboboxKeyboard.js +76 -0
  29. package/dist/cjs/components/form/Combobox/useFloatingCombobox.js +78 -0
  30. package/dist/cjs/components/form/Search/Search.js +1 -1
  31. package/dist/cjs/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
  32. package/dist/cjs/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
  33. package/dist/cjs/node_modules/clsx/dist/clsx.js +1 -1
  34. package/dist/cjs/node_modules/clsx/dist/lite.js +9 -0
  35. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
  36. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +83 -31
  37. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +307 -157
  38. package/dist/cjs/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -4
  39. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +22 -18
  40. package/dist/{esm → cjs/packages/react}/node_modules/tabbable/dist/index.esm.js +59 -13
  41. package/dist/cjs/react-css-modules.css +2 -136
  42. package/dist/cjs/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
  43. package/dist/cjs/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
  44. package/dist/esm/components/Accordion/Accordion.js +1 -4
  45. package/dist/esm/components/Accordion/AccordionContent/AccordionContent.js +1 -2
  46. package/dist/esm/components/Accordion/AccordionHeader/AccordionHeader.js +1 -2
  47. package/dist/esm/components/Accordion/AccordionItem/AccordionItem.js +1 -4
  48. package/dist/esm/components/Button/Button.js +2 -2
  49. package/dist/esm/components/DropdownMenu/DropdownMenuContent.js +3 -3
  50. package/dist/esm/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
  51. package/dist/esm/components/Modal/ModalDialog.js +1 -1
  52. package/dist/esm/components/Modal/ModalHeader/ModalHeader.js +1 -1
  53. package/dist/esm/components/Modal/ModalHeader/ModalHeader.module.css.js +1 -1
  54. package/dist/esm/components/Popover/PopoverContent.js +4 -4
  55. package/dist/esm/components/Popover/PopoverTrigger.js +1 -1
  56. package/dist/esm/components/Tooltip/Tooltip.js +4 -4
  57. package/dist/esm/components/form/Checkbox/Checkbox.js +1 -1
  58. package/dist/esm/components/form/Combobox/Combobox.js +76 -203
  59. package/dist/esm/components/form/Combobox/Combobox.module.css.js +1 -1
  60. package/dist/esm/components/form/Combobox/ComboboxContext.js +6 -0
  61. package/dist/esm/components/form/Combobox/ComboboxIdContext.js +35 -0
  62. package/dist/esm/components/form/Combobox/Custom/Custom.js +13 -8
  63. package/dist/esm/components/form/Combobox/Empty/Empty.js +3 -3
  64. package/dist/esm/components/form/Combobox/Option/Option.js +15 -33
  65. package/dist/esm/components/form/Combobox/Option/useComboboxOption.js +45 -0
  66. package/dist/esm/components/form/Combobox/internal/ComboboxChips.js +12 -9
  67. package/dist/esm/components/form/Combobox/internal/ComboboxClearButton.js +4 -7
  68. package/dist/esm/components/form/Combobox/internal/ComboboxInput.js +40 -69
  69. package/dist/esm/components/form/Combobox/internal/ComboboxNative.js +2 -2
  70. package/dist/esm/components/form/Combobox/useCombobox.js +77 -75
  71. package/dist/esm/components/form/Combobox/useComboboxKeyboard.js +74 -0
  72. package/dist/esm/components/form/Combobox/useFloatingCombobox.js +76 -0
  73. package/dist/esm/components/form/Search/Search.js +1 -1
  74. package/dist/esm/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
  75. package/dist/esm/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
  76. package/dist/esm/node_modules/clsx/dist/clsx.js +1 -1
  77. package/dist/esm/node_modules/clsx/dist/lite.js +4 -0
  78. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
  79. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +82 -30
  80. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +282 -135
  81. package/dist/esm/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -5
  82. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +19 -14
  83. package/dist/{cjs → esm/packages/react}/node_modules/tabbable/dist/index.esm.js +55 -15
  84. package/dist/esm/react-css-modules.css +2 -136
  85. package/dist/esm/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
  86. package/dist/esm/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
  87. package/dist/types/components/Accordion/Accordion.d.ts.map +1 -1
  88. package/dist/types/components/Accordion/AccordionContent/AccordionContent.d.ts.map +1 -1
  89. package/dist/types/components/Accordion/AccordionHeader/AccordionHeader.d.ts.map +1 -1
  90. package/dist/types/components/Accordion/AccordionItem/AccordionItem.d.ts.map +1 -1
  91. package/dist/types/components/Modal/ModalHeader/ModalHeader.d.ts.map +1 -1
  92. package/dist/types/components/form/Combobox/Combobox.d.ts +95 -48
  93. package/dist/types/components/form/Combobox/Combobox.d.ts.map +1 -1
  94. package/dist/types/components/form/Combobox/ComboboxContext.d.ts +41 -0
  95. package/dist/types/components/form/Combobox/ComboboxContext.d.ts.map +1 -0
  96. package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts +19 -0
  97. package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts.map +1 -0
  98. package/dist/types/components/form/Combobox/Custom/Custom.d.ts.map +1 -1
  99. package/dist/types/components/form/Combobox/Option/Option.d.ts +2 -2
  100. package/dist/types/components/form/Combobox/Option/Option.d.ts.map +1 -1
  101. package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts +14 -0
  102. package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts.map +1 -0
  103. package/dist/types/components/form/Combobox/internal/ComboboxChips.d.ts.map +1 -1
  104. package/dist/types/components/form/Combobox/internal/ComboboxClearButton.d.ts.map +1 -1
  105. package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts +9 -2
  106. package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts.map +1 -1
  107. package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts +3 -1
  108. package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts.map +1 -1
  109. package/dist/types/components/form/Combobox/useCombobox.d.ts +14 -9
  110. package/dist/types/components/form/Combobox/useCombobox.d.ts.map +1 -1
  111. package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts +19 -0
  112. package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts.map +1 -0
  113. package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts +41 -0
  114. package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts.map +1 -0
  115. package/package.json +3 -3
  116. package/dist/cjs/components/Accordion/Accordion.module.css.js +0 -6
  117. package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -6
  118. package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +0 -68
  119. package/dist/esm/components/Accordion/Accordion.module.css.js +0 -4
  120. package/dist/esm/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -4
  121. package/dist/esm/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +0 -57
@@ -3,11 +3,11 @@ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
  import * as React from 'react';
4
4
  import { forwardRef, useState, cloneElement } from 'react';
5
5
  import { clsx } from '../../node_modules/clsx/dist/clsx.js';
6
- import { useFloating, useTransitionStyles, useInteractions, useHover, useFocus, useDismiss, useRole, useMergeRefs, FloatingPortal, FloatingArrow } from '../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
6
+ import { useFloating, useTransitionStyles, useInteractions, useHover, useFocus, useDismiss, useRole, useMergeRefs, FloatingPortal, FloatingArrow } from '../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
7
7
  import classes from './Tooltip.module.css.js';
8
- import { autoUpdate } from '../../node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
9
- import { offset, flip, shift } from '../../node_modules/@floating-ui/core/dist/floating-ui.core.js';
10
- import { arrow } from '../../node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js';
8
+ import { autoUpdate, flip, shift } from '../../packages/react/node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
9
+ import { offset } from '../../packages/react/node_modules/@floating-ui/core/dist/floating-ui.core.js';
10
+ import { arrow } from '../../packages/react/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js';
11
11
 
12
12
  const ARROW_HEIGHT = 7;
13
13
  const ARROW_GAP = 4;
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { forwardRef } from 'react';
4
4
  import { clsx } from '../../../node_modules/clsx/dist/clsx.js';
5
- import { useMergeRefs } from '../../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
5
+ import { useMergeRefs } from '../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
6
6
  import classes from './Checkbox.module.css.js';
7
7
  import { useCheckbox } from './useCheckbox.js';
8
8
  import { Paragraph } from '../../Typography/Paragraph/Paragraph.js';
@@ -1,241 +1,131 @@
1
1
  'use client';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import { forwardRef, useRef, useId, useState, useEffect, createContext } from 'react';
4
- import { useFloating, useRole, useDismiss, useListNavigation, useInteractions, FloatingPortal, FloatingFocusManager } from '../../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
3
+ import { forwardRef, useRef, useId, useState, useEffect } from 'react';
4
+ import { FloatingPortal, FloatingFocusManager } from '../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
5
5
  import { clsx } from '../../../node_modules/clsx/dist/clsx.js';
6
6
  import { useVirtualizer } from '../../../node_modules/@tanstack/react-virtual/dist/esm/index.js';
7
- import { flushSync } from 'react-dom';
8
7
  import { useFormField } from '../useFormField.js';
9
8
  import useDebounce from '../../../utilities/useDebounce.js';
10
- import useCombobox, { isInteractiveComboboxCustom, isComboboxOption } from './useCombobox.js';
9
+ import useCombobox from './useCombobox.js';
11
10
  import classes from './Combobox.module.css.js';
12
11
  import ComboboxInput from './internal/ComboboxInput.js';
13
12
  import ComboboxLabel from './internal/ComboboxLabel.js';
14
13
  import ComboboxError from './internal/ComboboxError.js';
15
14
  import ComboboxNative from './internal/ComboboxNative.js';
16
15
  import ComboboxCustom from './Custom/Custom.js';
16
+ import { useFloatingCombobox } from './useFloatingCombobox.js';
17
+ import { useComboboxKeyboard } from './useComboboxKeyboard.js';
18
+ import { ComboboxIdProvider } from './ComboboxIdContext.js';
19
+ import { ComboboxContext } from './ComboboxContext.js';
17
20
  import { Box } from '../../Box/Box.js';
18
21
  import { Spinner } from '../../Spinner/Spinner.js';
19
- import { autoUpdate } from '../../../node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
20
- import { flip, size, offset } from '../../../node_modules/@floating-ui/core/dist/floating-ui.core.js';
21
22
  import { omit } from '../../../utilities/objectUtils.js';
22
23
 
23
- const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, hideLabel = false, description, multiple = false, size: size$1 = 'medium', disabled = false, readOnly = false, hideChips = false, cleanButtonLabel = 'Fjern alt', clearButtonLabel = 'Fjern alt', hideClearButton = false, error, errorId, id, name, portal = true, htmlSize = 0, virtual = false, children, style, loading, loadingLabel = 'Laster...', filter = (inputValue, option) => {
24
- return option.label.toLowerCase().startsWith(inputValue.toLowerCase());
25
- }, chipSrLabel = (option) => 'Slett ' + option.label, className, ...rest }, forwareddRef) => {
24
+ const ComboboxComponent = forwardRef(({ value, initialValue = [], onValueChange, label, hideLabel = false, description, multiple = false, size = 'medium', disabled = false, readOnly = false, hideChips = false, clearButtonLabel = 'Fjern alt', hideClearButton = false, error, errorId, id, name, portal = true, htmlSize = 0, virtual = false, children, style, loading, loadingLabel = 'Laster...', filter, chipSrLabel = (option) => 'Slett ' + option.label, className, ...rest }, forwareddRef) => {
26
25
  const inputRef = useRef(null);
27
26
  const portalRef = useRef(null);
27
+ const listRef = useRef([]);
28
28
  const listId = useId();
29
- const [open, setOpen] = useState(false);
30
29
  const [inputValue, setInputValue] = useState(rest.inputValue || '');
31
- const [activeIndex, setActiveIndex] = useState(null);
32
- const { selectedOptions, setSelectedOptions, options, optionsChildren, restChildren, optionValues, customIds, prevSelectedHash, setPrevSelectedHash, } = useCombobox({
30
+ const { selectedOptions, options, restChildren, interactiveChildren, customIds, filteredOptionsChildren, filteredOptions, setSelectedOptions, } = useCombobox({
33
31
  children,
34
32
  inputValue,
35
33
  filter,
36
34
  multiple,
37
35
  initialValue,
38
36
  });
39
- const [activeDescendant, setActiveDescendant] = useState(undefined);
40
- useEffect(() => {
41
- if (rest.inputValue !== undefined) {
42
- setInputValue(rest.inputValue);
43
- }
44
- }, [rest.inputValue]);
37
+ const { open, setOpen, refs, floatingStyles, context, getReferenceProps, getFloatingProps, getItemProps, } = useFloatingCombobox({
38
+ listRef,
39
+ });
45
40
  const formFieldProps = useFormField({
46
41
  disabled,
47
42
  readOnly,
48
43
  error,
49
44
  errorId,
50
- size: size$1,
45
+ size,
51
46
  description,
52
47
  id,
53
48
  }, 'combobox');
54
- const listRef = useRef([]);
55
49
  // if value is set, set input value to the label of the value
56
50
  useEffect(() => {
57
51
  if (value && value.length > 0 && !multiple) {
58
- const option = options.find((option) => option.value === value[0]);
52
+ const option = options[value[0]];
59
53
  setInputValue(option?.label || '');
60
54
  }
61
55
  }, [multiple, value, options]);
62
- // floating UI
63
- const { refs, floatingStyles, context } = useFloating({
64
- open,
65
- onOpenChange: (newOpen) => {
66
- flushSync(() => {
67
- if (refs.floating.current && !newOpen) {
68
- refs.floating.current.scrollTop = 0;
69
- }
70
- setTimeout(() => {
71
- setOpen(newOpen);
72
- }, 1);
73
- });
74
- },
75
- whileElementsMounted: (reference, floating, update) => {
76
- autoUpdate(reference, floating, update);
77
- return () => {
78
- floating.scrollTop = 0;
79
- };
80
- },
81
- middleware: [
82
- flip({ padding: 10 }),
83
- size({
84
- apply({ rects, elements }) {
85
- requestAnimationFrame(() => {
86
- Object.assign(elements.floating.style, {
87
- width: `calc(${rects.reference.width}px - calc(var(--fds-spacing-2) * 2))`,
88
- maxHeight: `200px`,
89
- });
90
- });
91
- },
92
- }),
93
- offset(10),
94
- ],
95
- });
96
- const role = useRole(context, { role: 'listbox' });
97
- const dismiss = useDismiss(context);
98
- const listNav = useListNavigation(context, {
99
- listRef,
100
- activeIndex,
101
- virtual: true,
102
- scrollItemIntoView: true,
103
- enabled: open,
104
- });
105
- const { getReferenceProps, getFloatingProps } = useInteractions([
106
- role,
107
- dismiss,
108
- listNav,
109
- ]);
110
- // remove active index if combobox is closed
111
- useEffect(() => {
112
- if (!open) {
113
- setActiveIndex(null);
114
- }
115
- }, [open]);
116
- // Send new value if option was clicked
117
- useEffect(() => {
118
- const selectedHash = JSON.stringify(selectedOptions);
119
- if (prevSelectedHash === selectedHash)
120
- return;
121
- const values = selectedOptions.map((option) => option.value);
122
- onValueChange?.(values);
123
- setPrevSelectedHash(selectedHash);
124
- }, [onValueChange, selectedOptions, prevSelectedHash, setPrevSelectedHash]);
125
56
  useEffect(() => {
126
- if (value && options.length > 0) {
57
+ if (value && Object.keys(options).length >= 0) {
127
58
  const updatedSelectedOptions = value.map((option) => {
128
- const value = options.find((value) => value.value === option);
59
+ const value = options[option];
129
60
  return value;
130
61
  });
131
- setSelectedOptions(updatedSelectedOptions);
62
+ setSelectedOptions(updatedSelectedOptions.reduce((acc, value) => {
63
+ acc[value.value] = value;
64
+ return acc;
65
+ }, {}));
132
66
  }
133
- }, [multiple, prevSelectedHash, value, options, setSelectedOptions]);
67
+ }, [multiple, value, options, setSelectedOptions]);
134
68
  // handle click on option, either select or deselect - Handles single or multiple
135
- const handleSelectOption = (option) => {
136
- // if option is already selected, remove it
137
- if (value && value.includes(option.value)) {
138
- setSelectedOptions((prev) => prev.filter((i) => i.value !== option.value));
69
+ const handleSelectOption = (args) => {
70
+ const { option, clear, remove } = args;
71
+ if (clear) {
72
+ setSelectedOptions({});
73
+ setInputValue('');
74
+ onValueChange?.([]);
139
75
  return;
140
76
  }
77
+ if (!option)
78
+ return;
79
+ if (remove) {
80
+ const newSelectedOptions = { ...selectedOptions };
81
+ delete newSelectedOptions[option.value];
82
+ setSelectedOptions(newSelectedOptions);
83
+ onValueChange?.(Object.keys(newSelectedOptions));
84
+ return;
85
+ }
86
+ const newSelectedOptions = { ...selectedOptions };
141
87
  if (multiple) {
142
- setSelectedOptions([...selectedOptions, option]);
88
+ if (newSelectedOptions[option.value]) {
89
+ delete newSelectedOptions[option.value];
90
+ }
91
+ else {
92
+ newSelectedOptions[option.value] = option;
93
+ }
143
94
  setInputValue('');
144
95
  inputRef.current?.focus();
145
96
  }
146
97
  else {
147
- setSelectedOptions([option]);
98
+ newSelectedOptions[option.value] = option;
148
99
  setInputValue(option?.label || '');
149
100
  // move cursor to the end of the input
150
101
  setTimeout(() => {
151
102
  inputRef.current?.setSelectionRange(option?.label?.length || 0, option?.label?.length || 0);
152
103
  }, 0);
153
104
  }
105
+ setSelectedOptions(newSelectedOptions);
106
+ console.log('calling new value with: ', Object.keys(newSelectedOptions));
107
+ onValueChange?.(Object.keys(newSelectedOptions));
154
108
  !multiple && setOpen(false);
155
109
  refs.domReference.current?.focus();
156
110
  };
157
111
  const debouncedHandleSelectOption = useDebounce(handleSelectOption, 50);
158
- // handle keyboard navigation in the list
159
- const handleKeyDownFunc = (event) => {
160
- const navigateable = customIds.length + optionsChildren.length;
161
- if (formFieldProps.readOnly || disabled)
162
- return;
163
- if (!event)
164
- return;
165
- switch (event.key) {
166
- case 'ArrowDown':
167
- event.preventDefault();
168
- if (!open)
169
- setOpen(true);
170
- setActiveIndex((prevActiveIndex) => {
171
- if (prevActiveIndex === null) {
172
- return 0;
173
- }
174
- return Math.min(prevActiveIndex + 1, navigateable - 1);
175
- });
176
- break;
177
- case 'ArrowUp':
178
- event.preventDefault();
179
- /* If we are on the first item, close */
180
- setActiveIndex((prevActiveIndex) => {
181
- if (prevActiveIndex === 0) {
182
- setOpen(false);
183
- return null;
184
- }
185
- if (prevActiveIndex === null) {
186
- return null;
187
- }
188
- return Math.max(prevActiveIndex - 1, 0);
189
- });
190
- break;
191
- case 'Enter':
192
- event.preventDefault();
193
- if (activeIndex !== null &&
194
- (optionsChildren[activeIndex] || customIds.length > 0)) {
195
- // check if we are in the custom components
196
- if (activeIndex <= customIds.length) {
197
- // send `onSelect` event to the custom component
198
- const selectedId = customIds[activeIndex];
199
- const selectedComponent = restChildren.find((component) => isInteractiveComboboxCustom(component) &&
200
- component.props?.id === selectedId);
201
- if (isInteractiveComboboxCustom(selectedComponent) &&
202
- selectedComponent.props.onSelect) {
203
- selectedComponent.props.onSelect();
204
- }
205
- }
206
- // if we are in the options, find the actual index
207
- const valueIndex = activeIndex - customIds.length;
208
- const child = optionsChildren[valueIndex];
209
- if (isComboboxOption(child)) {
210
- const props = child.props;
211
- const option = options.find((option) => option.value === props.value);
212
- if (!multiple) {
213
- // check if option is already selected, if so, deselect it
214
- if (selectedOptions.find((i) => i.value === option?.value)) {
215
- setSelectedOptions([]);
216
- setInputValue('');
217
- return;
218
- }
219
- }
220
- debouncedHandleSelectOption(option);
221
- }
222
- }
223
- break;
224
- case 'Backspace':
225
- if (inputValue === '' && multiple && selectedOptions.length > 0) {
226
- setSelectedOptions((prev) => prev.slice(0, prev.length - 1));
227
- }
228
- // if we are in single mode, we need to set activeValue to null
229
- if (!multiple) {
230
- setSelectedOptions([]);
231
- }
232
- break;
233
- }
234
- };
235
- const handleKeyDown = useDebounce(handleKeyDownFunc, 20);
112
+ const handleKeyDown = useComboboxKeyboard({
113
+ filteredOptions,
114
+ selectedOptions,
115
+ readOnly: formFieldProps.readOnly || false,
116
+ disabled: disabled,
117
+ multiple,
118
+ inputValue,
119
+ options,
120
+ open,
121
+ interactiveChildren,
122
+ setOpen,
123
+ setInputValue,
124
+ handleSelectOption: debouncedHandleSelectOption,
125
+ });
236
126
  const rowVirtualizer = useVirtualizer({
237
- count: optionsChildren.length,
238
- getScrollElement: () => refs.floating.current,
127
+ count: Object.keys(filteredOptionsChildren).length,
128
+ getScrollElement: () => (virtual ? refs.floating.current : null),
239
129
  estimateSize: () => 70,
240
130
  measureElement: (elem) => {
241
131
  return elem.getBoundingClientRect().height;
@@ -243,61 +133,44 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
243
133
  overscan: 1,
244
134
  });
245
135
  return (jsxs(ComboboxContext.Provider, { value: {
246
- size: size$1,
136
+ size,
247
137
  options,
248
138
  selectedOptions,
249
139
  multiple,
250
- activeIndex,
251
140
  disabled,
252
141
  readOnly,
253
142
  open,
254
143
  inputRef,
255
144
  refs,
256
145
  inputValue,
257
- activeDescendant,
258
- error,
259
146
  formFieldProps,
260
- name,
261
147
  htmlSize,
262
- optionValues,
263
- hideChips,
264
- clearButtonLabel: cleanButtonLabel || clearButtonLabel,
265
- hideClearButton,
266
- listId,
148
+ clearButtonLabel,
149
+ customIds,
150
+ filteredOptions,
267
151
  setInputValue,
268
- setActiveIndex,
269
- handleKeyDown,
270
152
  setOpen,
271
153
  getReferenceProps,
272
- setSelectedOptions,
273
- /* Recieves index of option, and the ID of the button element */
274
- setActiveOption: (index, id) => {
275
- if (readOnly)
276
- return;
277
- if (disabled)
278
- return;
279
- setActiveIndex(index);
280
- setActiveDescendant(id);
281
- },
154
+ getItemProps,
282
155
  /* Recieves the value of the option, and searches for it in our values lookup */
283
156
  onOptionClick: (value) => {
284
157
  if (readOnly)
285
158
  return;
286
159
  if (disabled)
287
160
  return;
288
- const option = options.find((option) => option.value === value);
289
- debouncedHandleSelectOption(option);
161
+ const option = options[value];
162
+ debouncedHandleSelectOption({ option: option });
290
163
  },
291
164
  handleSelectOption: debouncedHandleSelectOption,
292
165
  chipSrLabel,
293
166
  listRef,
294
167
  forwareddRef,
295
- }, children: [jsxs(Box, { className: clsx(classes.combobox, disabled && classes.disabled, className), style: style, ref: portalRef, children: [name && (jsx(ComboboxNative, { name: name, selectedOptions: selectedOptions, multiple: multiple })), jsx(ComboboxLabel, { label: label, description: description, size: size$1, readOnly: readOnly, hideLabel: hideLabel, formFieldProps: formFieldProps }), jsx(ComboboxInput, { ...omit(['inputValue'], rest), "aria-busy": loading }), jsx(ComboboxError, { size: size$1, error: error, formFieldProps: formFieldProps })] }), open && (jsx(FloatingPortal, { root: portal ? null : portalRef, children: jsx(FloatingFocusManager, { context: context, initialFocus: -1, visuallyHiddenDismiss: true, children: jsxs(Box, { id: listId, shadow: 'medium', borderRadius: 'medium', borderColor: 'default', "aria-labelledby": formFieldProps.inputProps.id, "aria-autocomplete": 'list', tabIndex: -1, ...getFloatingProps({
168
+ }, children: [jsxs(Box, { className: clsx(classes.combobox, disabled && classes.disabled, className), style: style, ref: portalRef, children: [name && (jsx(ComboboxNative, { name: name, selectedOptions: selectedOptions, multiple: multiple })), jsx(ComboboxLabel, { label: label, description: description, size: size, readOnly: readOnly, hideLabel: hideLabel, formFieldProps: formFieldProps }), jsx(ComboboxInput, { ...omit(['inputValue'], rest), hideClearButton: hideClearButton, listId: listId, error: error, hideChips: hideChips, handleKeyDown: handleKeyDown, "aria-busy": loading }), jsx(ComboboxError, { size: size, error: error, formFieldProps: formFieldProps })] }), open && (jsx(FloatingPortal, { root: portal ? null : portalRef, children: jsx(FloatingFocusManager, { context: context, initialFocus: -1, visuallyHiddenDismiss: true, children: jsxs(Box, { id: listId, shadow: 'medium', borderRadius: 'medium', borderColor: 'default', "aria-labelledby": formFieldProps.inputProps.id, "aria-autocomplete": 'list', tabIndex: -1, ...getFloatingProps({
296
169
  ref: refs.setFloating,
297
170
  style: {
298
171
  ...floatingStyles,
299
172
  },
300
- }), className: clsx(classes.optionsWrapper, classes[size$1]), children: [virtual && (jsx("div", { style: {
173
+ }), className: clsx(classes.optionsWrapper, classes[size]), children: [virtual && (jsx("div", { style: {
301
174
  height: `${rowVirtualizer.getTotalSize()}px`,
302
175
  width: '100%',
303
176
  position: 'relative',
@@ -307,9 +180,9 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
307
180
  left: 0,
308
181
  width: '100%',
309
182
  transform: `translateY(${virtualRow.start}px)`,
310
- }, children: optionsChildren[virtualRow.index] }, virtualRow.index))) })), loading ? (jsxs(ComboboxCustom, { className: classes.loading, children: [jsx(Spinner, { title: 'Laster', size: 'small' }), loadingLabel] })) : (jsxs(Fragment, { children: [restChildren, !virtual && optionsChildren] }))] }) }) }))] }));
183
+ }, children: filteredOptionsChildren[virtualRow.index] }, virtualRow.index))) })), loading ? (jsxs(ComboboxCustom, { className: classes.loading, children: [jsx(Spinner, { title: 'Laster', size: 'small' }), loadingLabel] })) : (jsxs(Fragment, { children: [restChildren, !virtual && filteredOptionsChildren] }))] }) }) }))] }));
311
184
  });
312
- const ComboboxContext = createContext(undefined);
185
+ const Combobox = forwardRef((props, ref) => (jsx(ComboboxIdProvider, { children: jsx(ComboboxComponent, { ...props, ref: ref }) })));
313
186
  Combobox.displayName = 'Combobox';
314
187
 
315
- export { Combobox, ComboboxContext };
188
+ export { Combobox, ComboboxComponent };
@@ -1,4 +1,4 @@
1
1
  'use client';
2
- var classes = {"combobox":"fds-combobox-combobox-249a725c","optionsWrapper":"fds-combobox-optionsWrapper-249a725c","readOnly":"fds-combobox-readOnly-249a725c","inputWrapper":"fds-combobox-inputWrapper-249a725c","small":"fds-combobox-small-249a725c","medium":"fds-combobox-medium-249a725c","large":"fds-combobox-large-249a725c","error":"fds-combobox-error-249a725c","chipAndInput":"fds-combobox-chipAndInput-249a725c","chips":"fds-combobox-chips-249a725c","arrow":"fds-combobox-arrow-249a725c","readonly":"fds-combobox-readonly-249a725c","label":"fds-combobox-label-249a725c","description":"fds-combobox-description-249a725c","clearButton":"fds-combobox-clearButton-249a725c","disabled":"fds-combobox-disabled-249a725c","padlock":"fds-combobox-padlock-249a725c","errorMessage":"fds-combobox-errorMessage-249a725c","loading":"fds-combobox-loading-249a725c","inFocus":"fds-combobox-inFocus-249a725c","showChecked":"fds-combobox-showChecked-249a725c"};
2
+ var classes = {"combobox":"fds-combobox-combobox-249a725c","optionsWrapper":"fds-combobox-optionsWrapper-249a725c","readOnly":"fds-combobox-readOnly-249a725c","inputWrapper":"fds-combobox-inputWrapper-249a725c","small":"fds-combobox-small-249a725c","medium":"fds-combobox-medium-249a725c","large":"fds-combobox-large-249a725c","error":"fds-combobox-error-249a725c","chipAndInput":"fds-combobox-chipAndInput-249a725c","chips":"fds-combobox-chips-249a725c","arrow":"fds-combobox-arrow-249a725c","readonly":"fds-combobox-readonly-249a725c","label":"fds-combobox-label-249a725c","description":"fds-combobox-description-249a725c","clearButton":"fds-combobox-clearButton-249a725c","disabled":"fds-combobox-disabled-249a725c","padlock":"fds-combobox-padlock-249a725c","errorMessage":"fds-combobox-errorMessage-249a725c","loading":"fds-combobox-loading-249a725c","showChecked":"fds-combobox-showChecked-249a725c"};
3
3
 
4
4
  export { classes as default };
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+ import { createContext } from 'react';
3
+
4
+ const ComboboxContext = createContext(undefined);
5
+
6
+ export { ComboboxContext };
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { createContext, useContext, useReducer } from 'react';
4
+
5
+ const ComboboxIdContext = createContext({
6
+ activeIndex: 0,
7
+ });
8
+ const ComboboxIdReducer = (state, action) => {
9
+ switch (action.type) {
10
+ case 'SET_ACTIVE_INDEX':
11
+ return {
12
+ ...state,
13
+ activeIndex: action.payload,
14
+ };
15
+ default:
16
+ return state;
17
+ }
18
+ };
19
+ const ComboboxIdDispatch = createContext(() => {
20
+ throw new Error('ComboboxIdDispatch must be used within a provider');
21
+ });
22
+ const ComboboxIdProvider = ({ children, }) => {
23
+ const [state, dispatch] = useReducer(ComboboxIdReducer, {
24
+ activeIndex: 0,
25
+ });
26
+ return (jsx(ComboboxIdContext.Provider, { value: state, children: jsx(ComboboxIdDispatch.Provider, { value: dispatch, children: children }) }));
27
+ };
28
+ function useComboboxIdDispatch() {
29
+ return useContext(ComboboxIdDispatch);
30
+ }
31
+ function useComboboxId() {
32
+ return useContext(ComboboxIdContext);
33
+ }
34
+
35
+ export { ComboboxIdContext, ComboboxIdDispatch, ComboboxIdProvider, ComboboxIdReducer, useComboboxId, useComboboxIdDispatch };
@@ -3,7 +3,9 @@ import { jsx } from 'react/jsx-runtime';
3
3
  import { forwardRef, useId, useContext, useMemo } from 'react';
4
4
  import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
5
5
  import { Slot as $5e63c961fc1ce211$export$8c6ed5c666ac1360 } from '../../../../node_modules/@radix-ui/react-slot/dist/index.js';
6
- import { ComboboxContext } from '../Combobox.js';
6
+ import { useMergeRefs } from '../../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
7
+ import { ComboboxContext } from '../ComboboxContext.js';
8
+ import { useComboboxId } from '../ComboboxIdContext.js';
7
9
  import classes from './Custom.module.css.js';
8
10
  import { omit } from '../../../../utilities/objectUtils.js';
9
11
 
@@ -13,17 +15,20 @@ const ComboboxCustom = forwardRef(({ asChild, interactive, id, className, ...res
13
15
  }
14
16
  const Component = asChild ? $5e63c961fc1ce211$export$8c6ed5c666ac1360 : 'div';
15
17
  const randomId = useId();
18
+ const { activeIndex } = useComboboxId();
16
19
  const context = useContext(ComboboxContext);
17
20
  if (!context) {
18
21
  throw new Error('ComboboxCustom must be used within a Combobox');
19
22
  }
20
- const { size, activeIndex, optionValues, setActiveIndex } = context;
21
- const index = useMemo(() => id && optionValues.indexOf(id), [optionValues, id]);
22
- return (jsx(Component, { ref: ref, tabIndex: -1, className: clsx(classes.custom, classes[size], className), id: id || randomId, role: 'option', "aria-selected": activeIndex === index, "data-active": activeIndex === index, onMouseEnter: () => {
23
- typeof index === 'number' && setActiveIndex(index);
24
- }, onFocus: () => {
25
- typeof index === 'number' && setActiveIndex(index);
26
- }, ...omit(['interactive'], rest) }));
23
+ const { size, customIds, listRef, getItemProps } = context;
24
+ const index = useMemo(() => (id && customIds.indexOf(id)) || 0, [id, customIds]);
25
+ const combinedRef = useMergeRefs([
26
+ (node) => {
27
+ listRef.current[index] = node;
28
+ },
29
+ ref,
30
+ ]);
31
+ return (jsx(Component, { ref: combinedRef, tabIndex: -1, className: clsx(classes.custom, classes[size], className), id: id || randomId, role: 'option', "aria-selected": activeIndex === index, "data-active": activeIndex === index, ...omit(['interactive'], rest), ...omit(['onClick', 'onPointerLeave'], getItemProps()) }));
27
32
  });
28
33
  var ComboboxCustom$1 = ComboboxCustom;
29
34
 
@@ -2,7 +2,7 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { forwardRef, useContext } from 'react';
4
4
  import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
5
- import { ComboboxContext } from '../Combobox.js';
5
+ import { ComboboxContext } from '../ComboboxContext.js';
6
6
  import classes from './Empty.module.css.js';
7
7
 
8
8
  const ComboboxEmpty = forwardRef(({ children, className, ...rest }, ref) => {
@@ -10,8 +10,8 @@ const ComboboxEmpty = forwardRef(({ children, className, ...rest }, ref) => {
10
10
  if (!context) {
11
11
  throw new Error('ComboboxEmpty must be used within a Combobox');
12
12
  }
13
- const { optionValues, size } = context;
14
- return (optionValues.length === 0 && (jsx("div", { ref: ref, className: clsx(classes.empty, classes[size], className), ...rest, children: children })));
13
+ const { filteredOptions, size } = context;
14
+ return (filteredOptions.length === 0 && (jsx("div", { ref: ref, className: clsx(classes.empty, classes[size], className), ...rest, children: children })));
15
15
  });
16
16
  ComboboxEmpty.displayName = 'ComboboxEmpty';
17
17
 
@@ -1,51 +1,33 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useId, useContext, useMemo, useEffect } from 'react';
3
+ import { memo, forwardRef, useId, useContext } from 'react';
4
4
  import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
5
- import { useMergeRefs } from '../../../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
6
- import { ComboboxContext } from '../Combobox.js';
7
- import useDebounce from '../../../../utilities/useDebounce.js';
5
+ import { ComboboxContext } from '../ComboboxContext.js';
8
6
  import { SelectedIcon } from './Icon/SelectedIcon.js';
9
7
  import classes from './Option.module.css.js';
10
8
  import ComboboxOptionDescription from './Description/Description.js';
9
+ import useComboboxOption from './useComboboxOption.js';
11
10
  import { omit } from '../../../../utilities/objectUtils.js';
12
11
  import { Label } from '../../../Typography/Label/Label.js';
13
12
 
14
- const ComboboxOption = forwardRef(({ value, description, children, className, ...rest }, ref) => {
13
+ const ComboboxOption = memo(forwardRef(({ value, description, children, className, ...rest }, forwardedRef) => {
15
14
  const labelId = useId();
16
- const generatedId = useId();
15
+ const { id, ref, selected, active, onOptionClick } = useComboboxOption({
16
+ id: rest.id,
17
+ ref: forwardedRef,
18
+ value,
19
+ });
17
20
  const context = useContext(ComboboxContext);
18
21
  if (!context) {
19
22
  throw new Error('ComboboxOption must be used within a Combobox');
20
23
  }
21
- const { selectedOptions, activeIndex, setActiveOption, onOptionClick, size, listRef, optionValues, multiple, } = context;
22
- const index = useMemo(() => optionValues.indexOf(value), [optionValues, value]);
23
- const combinedRef = useMergeRefs([
24
- (node) => {
25
- listRef.current[index] = node;
26
- },
27
- ref,
28
- ]);
29
- if (index === -1) {
30
- throw new Error('Internal error: ComboboxOption did not find index');
31
- }
32
- const selected = selectedOptions.find((option) => option.value === value);
33
- useEffect(() => {
34
- if (activeIndex === index)
35
- setActiveOption(index, rest.id || generatedId);
36
- }, [activeIndex, generatedId, index, rest.id, setActiveOption]);
37
- const onOptionClickDebounced = useDebounce(() => onOptionClick(value), 50);
38
- return (jsxs("button", { id: rest.id || generatedId, role: 'option', type: 'button', "aria-selected": !!selected, "aria-labelledby": labelId, tabIndex: -1, onClick: (e) => {
39
- onOptionClickDebounced();
24
+ const { size, multiple, getItemProps } = context;
25
+ const props = getItemProps();
26
+ return (jsxs("button", { ref: ref, id: id, role: 'option', type: 'button', "aria-selected": !!selected, "aria-labelledby": labelId, tabIndex: -1, onClick: (e) => {
27
+ onOptionClick();
40
28
  rest.onClick?.(e);
41
- }, onMouseEnter: (e) => {
42
- setActiveOption(index, labelId);
43
- rest.onMouseEnter?.(e);
44
- }, onFocus: (e) => {
45
- setActiveOption(index, labelId);
46
- rest.onFocus?.(e);
47
- }, className: clsx(classes.option, classes[size], activeIndex === index && classes.active, multiple && classes.multiple, className), ref: combinedRef, ...omit(['displayValue'], rest), children: [jsx(Label, { asChild: true, size: size, children: jsx("span", { children: jsx(SelectedIcon, { multiple: multiple, selected: !!selected }) }) }), jsxs(Label, { className: classes.optionText, size: size, id: labelId, children: [children, description && (jsx(ComboboxOptionDescription, { children: description }))] })] }));
48
- });
29
+ }, className: clsx(classes.option, classes[size], active && classes.active, multiple && classes.multiple, className), ...omit(['displayValue'], rest), ...omit(['onClick', 'onPointerLeave'], props), children: [jsx(Label, { asChild: true, size: size, children: jsx("span", { children: jsx(SelectedIcon, { multiple: multiple, selected: !!selected }) }) }), jsxs(Label, { className: classes.optionText, size: size, id: labelId, children: [children, description && (jsx(ComboboxOptionDescription, { children: description }))] })] }));
30
+ }));
49
31
  ComboboxOption.displayName = 'ComboboxOption';
50
32
 
51
33
  export { ComboboxOption };