@digdir/designsystemet-react 0.59.0 → 0.59.1-alpha.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.
- package/dist/cjs/components/DropdownMenu/DropdownMenuContent.js +4 -4
- package/dist/cjs/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
- package/dist/cjs/components/Modal/ModalDialog.js +1 -1
- package/dist/cjs/components/Popover/PopoverContent.js +6 -6
- package/dist/cjs/components/Popover/PopoverTrigger.js +1 -1
- package/dist/cjs/components/Tooltip/Tooltip.js +6 -6
- package/dist/cjs/components/form/Checkbox/Checkbox.js +1 -1
- package/dist/cjs/components/form/Combobox/Combobox.js +60 -177
- package/dist/cjs/components/form/Combobox/ComboboxContext.js +8 -0
- package/dist/cjs/components/form/Combobox/ComboboxIdContext.js +42 -0
- package/dist/cjs/components/form/Combobox/Custom/Custom.js +14 -9
- package/dist/cjs/components/form/Combobox/Empty/Empty.js +4 -4
- package/dist/cjs/components/form/Combobox/Option/Option.js +15 -33
- package/dist/cjs/components/form/Combobox/Option/useComboboxOption.js +47 -0
- package/dist/cjs/components/form/Combobox/internal/ComboboxChips.js +14 -6
- package/dist/cjs/components/form/Combobox/internal/ComboboxClearButton.js +4 -4
- package/dist/cjs/components/form/Combobox/internal/ComboboxInput.js +40 -35
- package/dist/cjs/components/form/Combobox/internal/ComboboxNative.js +2 -2
- package/dist/cjs/components/form/Combobox/useCombobox.js +46 -32
- package/dist/cjs/components/form/Combobox/useComboboxKeyboard.js +79 -0
- package/dist/cjs/components/form/Combobox/useFloatingCombobox.js +78 -0
- package/dist/cjs/components/form/Search/Search.js +1 -1
- package/dist/cjs/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
- package/dist/cjs/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
- package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
- package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +83 -31
- package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +307 -157
- package/dist/cjs/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -4
- package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +22 -18
- package/dist/{esm → cjs/packages/react}/node_modules/tabbable/dist/index.esm.js +59 -13
- package/dist/cjs/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
- package/dist/cjs/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
- package/dist/esm/components/DropdownMenu/DropdownMenuContent.js +3 -3
- package/dist/esm/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
- package/dist/esm/components/Modal/ModalDialog.js +1 -1
- package/dist/esm/components/Popover/PopoverContent.js +4 -4
- package/dist/esm/components/Popover/PopoverTrigger.js +1 -1
- package/dist/esm/components/Tooltip/Tooltip.js +4 -4
- package/dist/esm/components/form/Checkbox/Checkbox.js +1 -1
- package/dist/esm/components/form/Combobox/Combobox.js +65 -182
- package/dist/esm/components/form/Combobox/ComboboxContext.js +6 -0
- package/dist/esm/components/form/Combobox/ComboboxIdContext.js +35 -0
- package/dist/esm/components/form/Combobox/Custom/Custom.js +13 -8
- package/dist/esm/components/form/Combobox/Empty/Empty.js +3 -3
- package/dist/esm/components/form/Combobox/Option/Option.js +15 -33
- package/dist/esm/components/form/Combobox/Option/useComboboxOption.js +45 -0
- package/dist/esm/components/form/Combobox/internal/ComboboxChips.js +13 -5
- package/dist/esm/components/form/Combobox/internal/ComboboxClearButton.js +3 -3
- package/dist/esm/components/form/Combobox/internal/ComboboxInput.js +39 -34
- package/dist/esm/components/form/Combobox/internal/ComboboxNative.js +2 -2
- package/dist/esm/components/form/Combobox/useCombobox.js +46 -32
- package/dist/esm/components/form/Combobox/useComboboxKeyboard.js +77 -0
- package/dist/esm/components/form/Combobox/useFloatingCombobox.js +76 -0
- package/dist/esm/components/form/Search/Search.js +1 -1
- package/dist/esm/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
- package/dist/esm/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
- package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
- package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +82 -30
- package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +282 -135
- package/dist/esm/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -5
- package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +19 -14
- package/dist/{cjs → esm/packages/react}/node_modules/tabbable/dist/index.esm.js +55 -15
- package/dist/esm/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
- package/dist/esm/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
- package/dist/types/components/form/Combobox/Combobox.d.ts +104 -39
- package/dist/types/components/form/Combobox/Combobox.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/ComboboxContext.d.ts +48 -0
- package/dist/types/components/form/Combobox/ComboboxContext.d.ts.map +1 -0
- package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts +19 -0
- package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts.map +1 -0
- package/dist/types/components/form/Combobox/Custom/Custom.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/Option/Option.d.ts +2 -2
- package/dist/types/components/form/Combobox/Option/Option.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts +14 -0
- package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts.map +1 -0
- package/dist/types/components/form/Combobox/internal/ComboboxChips.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts +0 -1
- package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts +3 -1
- package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/useCombobox.d.ts +13 -5
- package/dist/types/components/form/Combobox/useCombobox.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts +20 -0
- package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts.map +1 -0
- package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts +41 -0
- package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts.map +1 -0
- package/package.json +3 -3
- package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -6
- package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +0 -68
- package/dist/esm/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -4
- package/dist/esm/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +0 -57
|
@@ -1,150 +1,105 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useRef, useId, useState, useEffect
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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, cleanButtonLabel = 'Fjern alt', 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
|
|
32
|
-
const { selectedOptions,
|
|
30
|
+
/* const idDispatch = useComboboxIdDispatch(); */
|
|
31
|
+
const { selectedOptions, options, restChildren, interactiveChildren, optionValues, customIds, filteredOptionsChildren, filteredOptions, prevSelectedHash, setSelectedOptions, setPrevSelectedHash, } = useCombobox({
|
|
33
32
|
children,
|
|
34
33
|
inputValue,
|
|
35
34
|
filter,
|
|
36
35
|
multiple,
|
|
37
36
|
initialValue,
|
|
38
37
|
});
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
setInputValue(rest.inputValue);
|
|
43
|
-
}
|
|
44
|
-
}, [rest.inputValue]);
|
|
38
|
+
const { open, setOpen, refs, floatingStyles, context, getReferenceProps, getFloatingProps, getItemProps, } = useFloatingCombobox({
|
|
39
|
+
listRef,
|
|
40
|
+
});
|
|
45
41
|
const formFieldProps = useFormField({
|
|
46
42
|
disabled,
|
|
47
43
|
readOnly,
|
|
48
44
|
error,
|
|
49
45
|
errorId,
|
|
50
|
-
size
|
|
46
|
+
size,
|
|
51
47
|
description,
|
|
52
48
|
id,
|
|
53
49
|
}, 'combobox');
|
|
54
|
-
const listRef = useRef([]);
|
|
55
50
|
// if value is set, set input value to the label of the value
|
|
56
51
|
useEffect(() => {
|
|
57
|
-
if (value && value.length
|
|
58
|
-
const option = options
|
|
52
|
+
if (value && value.length >= 0 && !multiple) {
|
|
53
|
+
const option = options[value[0]];
|
|
59
54
|
setInputValue(option?.label || '');
|
|
60
55
|
}
|
|
61
56
|
}, [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
57
|
// Send new value if option was clicked
|
|
117
58
|
useEffect(() => {
|
|
118
59
|
const selectedHash = JSON.stringify(selectedOptions);
|
|
119
60
|
if (prevSelectedHash === selectedHash)
|
|
120
61
|
return;
|
|
121
|
-
const values =
|
|
62
|
+
const values = Object.keys(selectedOptions);
|
|
122
63
|
onValueChange?.(values);
|
|
123
64
|
setPrevSelectedHash(selectedHash);
|
|
124
65
|
}, [onValueChange, selectedOptions, prevSelectedHash, setPrevSelectedHash]);
|
|
125
66
|
useEffect(() => {
|
|
126
|
-
if (value && options.length
|
|
67
|
+
if (value && Object.keys(options).length >= 0) {
|
|
127
68
|
const updatedSelectedOptions = value.map((option) => {
|
|
128
|
-
const value = options
|
|
69
|
+
const value = options[option];
|
|
129
70
|
return value;
|
|
130
71
|
});
|
|
131
|
-
setSelectedOptions(updatedSelectedOptions)
|
|
72
|
+
setSelectedOptions(updatedSelectedOptions.reduce((acc, value) => {
|
|
73
|
+
acc[value.value] = value;
|
|
74
|
+
return acc;
|
|
75
|
+
}, {}));
|
|
132
76
|
}
|
|
133
77
|
}, [multiple, prevSelectedHash, value, options, setSelectedOptions]);
|
|
134
78
|
// handle click on option, either select or deselect - Handles single or multiple
|
|
135
79
|
const handleSelectOption = (option) => {
|
|
136
80
|
// if option is already selected, remove it
|
|
137
81
|
if (value && value.includes(option.value)) {
|
|
138
|
-
setSelectedOptions((prev) =>
|
|
82
|
+
setSelectedOptions((prev) => {
|
|
83
|
+
const updated = { ...prev };
|
|
84
|
+
delete updated[option.value];
|
|
85
|
+
return updated;
|
|
86
|
+
});
|
|
139
87
|
return;
|
|
140
88
|
}
|
|
141
89
|
if (multiple) {
|
|
142
|
-
setSelectedOptions([...selectedOptions, option]);
|
|
90
|
+
/* setSelectedOptions([...selectedOptions, option]); */
|
|
91
|
+
setSelectedOptions((prev) => {
|
|
92
|
+
const updated = { ...prev };
|
|
93
|
+
updated[option.value] = option;
|
|
94
|
+
return updated;
|
|
95
|
+
});
|
|
143
96
|
setInputValue('');
|
|
144
97
|
inputRef.current?.focus();
|
|
145
98
|
}
|
|
146
99
|
else {
|
|
147
|
-
setSelectedOptions(
|
|
100
|
+
setSelectedOptions({
|
|
101
|
+
[option.value]: option,
|
|
102
|
+
});
|
|
148
103
|
setInputValue(option?.label || '');
|
|
149
104
|
// move cursor to the end of the input
|
|
150
105
|
setTimeout(() => {
|
|
@@ -155,87 +110,24 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
|
|
|
155
110
|
refs.domReference.current?.focus();
|
|
156
111
|
};
|
|
157
112
|
const debouncedHandleSelectOption = useDebounce(handleSelectOption, 50);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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);
|
|
113
|
+
const handleKeyDown = useComboboxKeyboard({
|
|
114
|
+
filteredOptions,
|
|
115
|
+
selectedOptions,
|
|
116
|
+
readOnly: formFieldProps.readOnly || false,
|
|
117
|
+
disabled: disabled,
|
|
118
|
+
multiple,
|
|
119
|
+
inputValue,
|
|
120
|
+
options,
|
|
121
|
+
open,
|
|
122
|
+
interactiveChildren,
|
|
123
|
+
setOpen,
|
|
124
|
+
setInputValue,
|
|
125
|
+
setSelectedOptions,
|
|
126
|
+
handleSelectOption: debouncedHandleSelectOption,
|
|
127
|
+
});
|
|
236
128
|
const rowVirtualizer = useVirtualizer({
|
|
237
|
-
count:
|
|
238
|
-
getScrollElement: () => refs.floating.current,
|
|
129
|
+
count: Object.keys(filteredOptionsChildren).length,
|
|
130
|
+
getScrollElement: () => (virtual ? refs.floating.current : null),
|
|
239
131
|
estimateSize: () => 70,
|
|
240
132
|
measureElement: (elem) => {
|
|
241
133
|
return elem.getBoundingClientRect().height;
|
|
@@ -243,18 +135,16 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
|
|
|
243
135
|
overscan: 1,
|
|
244
136
|
});
|
|
245
137
|
return (jsxs(ComboboxContext.Provider, { value: {
|
|
246
|
-
size
|
|
138
|
+
size,
|
|
247
139
|
options,
|
|
248
140
|
selectedOptions,
|
|
249
141
|
multiple,
|
|
250
|
-
activeIndex,
|
|
251
142
|
disabled,
|
|
252
143
|
readOnly,
|
|
253
144
|
open,
|
|
254
145
|
inputRef,
|
|
255
146
|
refs,
|
|
256
147
|
inputValue,
|
|
257
|
-
activeDescendant,
|
|
258
148
|
error,
|
|
259
149
|
formFieldProps,
|
|
260
150
|
name,
|
|
@@ -264,40 +154,33 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
|
|
|
264
154
|
clearButtonLabel: cleanButtonLabel || clearButtonLabel,
|
|
265
155
|
hideClearButton,
|
|
266
156
|
listId,
|
|
157
|
+
customIds,
|
|
158
|
+
filteredOptions,
|
|
267
159
|
setInputValue,
|
|
268
|
-
setActiveIndex,
|
|
269
160
|
handleKeyDown,
|
|
270
161
|
setOpen,
|
|
271
162
|
getReferenceProps,
|
|
163
|
+
getItemProps,
|
|
272
164
|
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
|
-
},
|
|
282
165
|
/* Recieves the value of the option, and searches for it in our values lookup */
|
|
283
166
|
onOptionClick: (value) => {
|
|
284
167
|
if (readOnly)
|
|
285
168
|
return;
|
|
286
169
|
if (disabled)
|
|
287
170
|
return;
|
|
288
|
-
const option = options
|
|
171
|
+
const option = options[value];
|
|
289
172
|
debouncedHandleSelectOption(option);
|
|
290
173
|
},
|
|
291
174
|
handleSelectOption: debouncedHandleSelectOption,
|
|
292
175
|
chipSrLabel,
|
|
293
176
|
listRef,
|
|
294
177
|
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
|
|
178
|
+
}, 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), "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
179
|
ref: refs.setFloating,
|
|
297
180
|
style: {
|
|
298
181
|
...floatingStyles,
|
|
299
182
|
},
|
|
300
|
-
}), className: clsx(classes.optionsWrapper, classes[size
|
|
183
|
+
}), className: clsx(classes.optionsWrapper, classes[size]), children: [virtual && (jsx("div", { style: {
|
|
301
184
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
302
185
|
width: '100%',
|
|
303
186
|
position: 'relative',
|
|
@@ -307,9 +190,9 @@ const Combobox = forwardRef(({ value, initialValue = [], onValueChange, label, h
|
|
|
307
190
|
left: 0,
|
|
308
191
|
width: '100%',
|
|
309
192
|
transform: `translateY(${virtualRow.start}px)`,
|
|
310
|
-
}, children:
|
|
193
|
+
}, 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
194
|
});
|
|
312
|
-
const
|
|
195
|
+
const Combobox = forwardRef((props, ref) => (jsx(ComboboxIdProvider, { children: jsx(ComboboxComponent, { ...props, ref: ref }) })));
|
|
313
196
|
Combobox.displayName = 'Combobox';
|
|
314
197
|
|
|
315
|
-
export { Combobox,
|
|
198
|
+
export { Combobox, ComboboxComponent };
|
|
@@ -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 {
|
|
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,
|
|
21
|
-
const index = useMemo(() => id &&
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 '../
|
|
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 {
|
|
14
|
-
return (
|
|
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
|
|
3
|
+
import { memo, forwardRef, useId, useContext } from 'react';
|
|
4
4
|
import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
|
|
5
|
-
import {
|
|
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 },
|
|
13
|
+
const ComboboxOption = memo(forwardRef(({ value, description, children, className, ...rest }, forwardedRef) => {
|
|
15
14
|
const labelId = useId();
|
|
16
|
-
const
|
|
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 {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
},
|
|
42
|
-
|
|
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 };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useId, useContext, useMemo, useEffect } from 'react';
|
|
3
|
+
import { useMergeRefs } from '../../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
4
|
+
import { ComboboxContext } from '../ComboboxContext.js';
|
|
5
|
+
import useDebounce from '../../../../utilities/useDebounce.js';
|
|
6
|
+
import { useComboboxId, useComboboxIdDispatch } from '../ComboboxIdContext.js';
|
|
7
|
+
|
|
8
|
+
function useComboboxOption({ id, ref, value, }) {
|
|
9
|
+
const generatedId = useId();
|
|
10
|
+
const newId = id || generatedId;
|
|
11
|
+
const context = useContext(ComboboxContext);
|
|
12
|
+
const { activeIndex } = useComboboxId();
|
|
13
|
+
const dispatch = useComboboxIdDispatch();
|
|
14
|
+
if (!context) {
|
|
15
|
+
throw new Error('ComboboxOption must be used within a Combobox');
|
|
16
|
+
}
|
|
17
|
+
const { selectedOptions, onOptionClick, listRef, customIds, filteredOptions, } = context;
|
|
18
|
+
const index = useMemo(() => filteredOptions.indexOf(value) + customIds.length, [customIds.length, filteredOptions, value]);
|
|
19
|
+
const combinedRef = useMergeRefs([
|
|
20
|
+
(node) => {
|
|
21
|
+
listRef.current[index] = node;
|
|
22
|
+
},
|
|
23
|
+
ref,
|
|
24
|
+
]);
|
|
25
|
+
if (index === -1) {
|
|
26
|
+
throw new Error('Internal error: ComboboxOption did not find index');
|
|
27
|
+
}
|
|
28
|
+
const selected = selectedOptions[value];
|
|
29
|
+
const active = activeIndex === index;
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (active) {
|
|
32
|
+
dispatch?.({ type: 'SET_ACTIVE_INDEX', payload: index });
|
|
33
|
+
}
|
|
34
|
+
}, [generatedId, id, dispatch, active, index]);
|
|
35
|
+
const onOptionClickDebounced = useDebounce(() => onOptionClick(value), 50);
|
|
36
|
+
return {
|
|
37
|
+
id: newId,
|
|
38
|
+
ref: combinedRef,
|
|
39
|
+
selected,
|
|
40
|
+
active,
|
|
41
|
+
onOptionClick: onOptionClickDebounced,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { useComboboxOption as default };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { useContext } from 'react';
|
|
4
4
|
import '../../../Chip/index.js';
|
|
5
|
-
import { ComboboxContext } from '../
|
|
5
|
+
import { ComboboxContext } from '../ComboboxContext.js';
|
|
6
6
|
import { RemovableChip } from '../../../Chip/Removable/Removable.js';
|
|
7
7
|
|
|
8
8
|
const ComboboxChips = () => {
|
|
@@ -11,7 +11,7 @@ const ComboboxChips = () => {
|
|
|
11
11
|
throw new Error('ComboboxContext is missing');
|
|
12
12
|
}
|
|
13
13
|
const { size, readOnly, disabled, selectedOptions, setSelectedOptions, chipSrLabel, inputRef, } = context;
|
|
14
|
-
return (jsx(Fragment, { children: selectedOptions.map((
|
|
14
|
+
return (jsx(Fragment, { children: Object.keys(selectedOptions).map((value) => {
|
|
15
15
|
return (jsx(RemovableChip, { size: size, disabled: disabled, onKeyDown: (e) => {
|
|
16
16
|
if (readOnly)
|
|
17
17
|
return;
|
|
@@ -19,7 +19,11 @@ const ComboboxChips = () => {
|
|
|
19
19
|
return;
|
|
20
20
|
if (e.key === 'Enter') {
|
|
21
21
|
e.stopPropagation();
|
|
22
|
-
setSelectedOptions(
|
|
22
|
+
setSelectedOptions((prev) => {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
24
|
+
const { [value]: _, ...rest } = prev;
|
|
25
|
+
return rest;
|
|
26
|
+
});
|
|
23
27
|
inputRef.current?.focus();
|
|
24
28
|
}
|
|
25
29
|
}, onClick: () => {
|
|
@@ -28,11 +32,15 @@ const ComboboxChips = () => {
|
|
|
28
32
|
if (disabled)
|
|
29
33
|
return;
|
|
30
34
|
/* If we click a chip, filter the active values and remove the one we clicked */
|
|
31
|
-
setSelectedOptions(
|
|
35
|
+
setSelectedOptions((prev) => {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
37
|
+
const { [value]: _, ...rest } = prev;
|
|
38
|
+
return rest;
|
|
39
|
+
});
|
|
32
40
|
}, style: {
|
|
33
41
|
/* We already set the opacity on Combobox */
|
|
34
42
|
opacity: 1,
|
|
35
|
-
}, "aria-label": chipSrLabel(
|
|
43
|
+
}, "aria-label": chipSrLabel(selectedOptions[value]), children: selectedOptions[value].label }, value));
|
|
36
44
|
}) }));
|
|
37
45
|
};
|
|
38
46
|
ComboboxChips.displayName = 'ComboboxChips';
|