@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.
- package/dist/cjs/components/Accordion/Accordion.js +1 -4
- package/dist/cjs/components/Accordion/AccordionContent/AccordionContent.js +1 -2
- package/dist/cjs/components/Accordion/AccordionHeader/AccordionHeader.js +1 -2
- package/dist/cjs/components/Accordion/AccordionItem/AccordionItem.js +1 -4
- package/dist/cjs/components/Button/Button.js +2 -2
- 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/Modal/ModalHeader/ModalHeader.js +1 -1
- package/dist/cjs/components/Modal/ModalHeader/ModalHeader.module.css.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 +72 -199
- package/dist/cjs/components/form/Combobox/Combobox.module.css.js +1 -1
- 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 +13 -10
- package/dist/cjs/components/form/Combobox/internal/ComboboxClearButton.js +5 -8
- package/dist/cjs/components/form/Combobox/internal/ComboboxInput.js +40 -69
- package/dist/cjs/components/form/Combobox/internal/ComboboxNative.js +2 -2
- package/dist/cjs/components/form/Combobox/useCombobox.js +77 -75
- package/dist/cjs/components/form/Combobox/useComboboxKeyboard.js +76 -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/clsx/dist/clsx.js +1 -1
- package/dist/cjs/node_modules/clsx/dist/lite.js +9 -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/react-css-modules.css +2 -136
- package/dist/cjs/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
- package/dist/cjs/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
- package/dist/esm/components/Accordion/Accordion.js +1 -4
- package/dist/esm/components/Accordion/AccordionContent/AccordionContent.js +1 -2
- package/dist/esm/components/Accordion/AccordionHeader/AccordionHeader.js +1 -2
- package/dist/esm/components/Accordion/AccordionItem/AccordionItem.js +1 -4
- package/dist/esm/components/Button/Button.js +2 -2
- 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/Modal/ModalHeader/ModalHeader.js +1 -1
- package/dist/esm/components/Modal/ModalHeader/ModalHeader.module.css.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 +76 -203
- package/dist/esm/components/form/Combobox/Combobox.module.css.js +1 -1
- 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 +12 -9
- package/dist/esm/components/form/Combobox/internal/ComboboxClearButton.js +4 -7
- package/dist/esm/components/form/Combobox/internal/ComboboxInput.js +40 -69
- package/dist/esm/components/form/Combobox/internal/ComboboxNative.js +2 -2
- package/dist/esm/components/form/Combobox/useCombobox.js +77 -75
- package/dist/esm/components/form/Combobox/useComboboxKeyboard.js +74 -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/clsx/dist/clsx.js +1 -1
- package/dist/esm/node_modules/clsx/dist/lite.js +4 -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/react-css-modules.css +2 -136
- package/dist/esm/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
- package/dist/esm/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
- package/dist/types/components/Accordion/Accordion.d.ts.map +1 -1
- package/dist/types/components/Accordion/AccordionContent/AccordionContent.d.ts.map +1 -1
- package/dist/types/components/Accordion/AccordionHeader/AccordionHeader.d.ts.map +1 -1
- package/dist/types/components/Accordion/AccordionItem/AccordionItem.d.ts.map +1 -1
- package/dist/types/components/Modal/ModalHeader/ModalHeader.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/Combobox.d.ts +95 -48
- package/dist/types/components/form/Combobox/Combobox.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/ComboboxContext.d.ts +41 -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/ComboboxClearButton.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts +9 -2
- 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 +14 -9
- package/dist/types/components/form/Combobox/useCombobox.d.ts.map +1 -1
- package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts +19 -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/components/Accordion/Accordion.module.css.js +0 -6
- 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/components/Accordion/Accordion.module.css.js +0 -4
- 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
|
@@ -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 = () => {
|
|
@@ -10,8 +10,8 @@ const ComboboxChips = () => {
|
|
|
10
10
|
if (!context) {
|
|
11
11
|
throw new Error('ComboboxContext is missing');
|
|
12
12
|
}
|
|
13
|
-
const { size, readOnly, disabled, selectedOptions,
|
|
14
|
-
return (jsx(Fragment, { children: selectedOptions.map((
|
|
13
|
+
const { size, readOnly, disabled, selectedOptions, chipSrLabel, handleSelectOption, inputRef, } = context;
|
|
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,10 @@ const ComboboxChips = () => {
|
|
|
19
19
|
return;
|
|
20
20
|
if (e.key === 'Enter') {
|
|
21
21
|
e.stopPropagation();
|
|
22
|
-
|
|
22
|
+
handleSelectOption({
|
|
23
|
+
option: selectedOptions[value],
|
|
24
|
+
remove: true,
|
|
25
|
+
});
|
|
23
26
|
inputRef.current?.focus();
|
|
24
27
|
}
|
|
25
28
|
}, onClick: () => {
|
|
@@ -28,11 +31,11 @@ const ComboboxChips = () => {
|
|
|
28
31
|
if (disabled)
|
|
29
32
|
return;
|
|
30
33
|
/* If we click a chip, filter the active values and remove the one we clicked */
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}, "aria-label": chipSrLabel(
|
|
34
|
+
handleSelectOption({
|
|
35
|
+
option: selectedOptions[value],
|
|
36
|
+
remove: true,
|
|
37
|
+
});
|
|
38
|
+
}, "aria-label": chipSrLabel(selectedOptions[value]), children: selectedOptions[value].label }, value));
|
|
36
39
|
}) }));
|
|
37
40
|
};
|
|
38
41
|
ComboboxChips.displayName = 'ComboboxChips';
|
|
@@ -3,7 +3,7 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
3
3
|
import { useContext } from 'react';
|
|
4
4
|
import { XMarkIcon } from '@navikt/aksel-icons';
|
|
5
5
|
import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
|
|
6
|
-
import { ComboboxContext } from '../
|
|
6
|
+
import { ComboboxContext } from '../ComboboxContext.js';
|
|
7
7
|
import classes from '../Combobox.module.css.js';
|
|
8
8
|
|
|
9
9
|
const ComboboxClearButton = () => {
|
|
@@ -11,14 +11,13 @@ const ComboboxClearButton = () => {
|
|
|
11
11
|
if (!context) {
|
|
12
12
|
throw new Error('ComboboxContext is missing');
|
|
13
13
|
}
|
|
14
|
-
const { size, readOnly, disabled, clearButtonLabel,
|
|
14
|
+
const { size, readOnly, disabled, clearButtonLabel, handleSelectOption } = context;
|
|
15
15
|
return (jsx("button", { disabled: disabled, className: clsx(classes.clearButton, classes[size], `fds-focus`), onClick: () => {
|
|
16
16
|
if (readOnly)
|
|
17
17
|
return;
|
|
18
18
|
if (disabled)
|
|
19
19
|
return;
|
|
20
|
-
|
|
21
|
-
setInputValue('');
|
|
20
|
+
handleSelectOption({ option: null, clear: true });
|
|
22
21
|
}, onKeyDown: (e) => {
|
|
23
22
|
if (readOnly)
|
|
24
23
|
return;
|
|
@@ -26,9 +25,7 @@ const ComboboxClearButton = () => {
|
|
|
26
25
|
return;
|
|
27
26
|
if (e.key === 'Enter') {
|
|
28
27
|
e.stopPropagation();
|
|
29
|
-
|
|
30
|
-
setInputValue('');
|
|
31
|
-
inputRef.current?.focus();
|
|
28
|
+
handleSelectOption({ option: null, clear: true });
|
|
32
29
|
}
|
|
33
30
|
}, type: 'button', "aria-label": clearButtonLabel, children: jsx(XMarkIcon, { fontSize: '1.5em', title: 'Clear selection' }) }));
|
|
34
31
|
};
|
|
@@ -1,99 +1,70 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { useContext
|
|
3
|
+
import { useContext } from 'react';
|
|
4
4
|
import { clsx } from '../../../../node_modules/clsx/dist/clsx.js';
|
|
5
5
|
import { ChevronUpIcon, ChevronDownIcon } from '@navikt/aksel-icons';
|
|
6
|
-
import { useMergeRefs } from '../../../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
7
|
-
import { ComboboxContext } from '../
|
|
6
|
+
import { useMergeRefs } from '../../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
7
|
+
import { ComboboxContext } from '../ComboboxContext.js';
|
|
8
8
|
import classes from '../Combobox.module.css.js';
|
|
9
9
|
import textFieldClasses from '../../Textfield/Textfield.module.css.js';
|
|
10
|
+
import { useComboboxIdDispatch } from '../ComboboxIdContext.js';
|
|
10
11
|
import ComboboxChips from './ComboboxChips.js';
|
|
11
12
|
import ComboboxClearButton from './ComboboxClearButton.js';
|
|
12
13
|
import { Box } from '../../../Box/Box.js';
|
|
13
14
|
import { omit } from '../../../../utilities/objectUtils.js';
|
|
14
15
|
|
|
15
|
-
const ComboboxInput = ({ ...rest }) => {
|
|
16
|
+
const ComboboxInput = ({ hideClearButton, listId, error, hideChips, handleKeyDown, ...rest }) => {
|
|
16
17
|
const context = useContext(ComboboxContext);
|
|
18
|
+
const idDispatch = useComboboxIdDispatch();
|
|
17
19
|
if (!context) {
|
|
18
20
|
throw new Error('ComboboxContext is missing');
|
|
19
21
|
}
|
|
20
|
-
const
|
|
22
|
+
const setActiveIndex = (id) => {
|
|
23
|
+
idDispatch?.({ type: 'SET_ACTIVE_INDEX', payload: id });
|
|
24
|
+
};
|
|
25
|
+
const { forwareddRef, size, readOnly, disabled, open, inputRef, refs, inputValue, multiple, selectedOptions, formFieldProps, htmlSize, options, setOpen, getReferenceProps, setInputValue, handleSelectOption, } = context;
|
|
21
26
|
const mergedRefs = useMergeRefs([forwareddRef, inputRef]);
|
|
22
|
-
// we need to check if input is in focus, to add focus styles to the wrapper
|
|
23
|
-
const [inputInFocus, setInputInFocus] = useState(false);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
const input = inputRef.current;
|
|
26
|
-
const onFocus = () => {
|
|
27
|
-
setInputInFocus(true);
|
|
28
|
-
};
|
|
29
|
-
const onBlur = () => {
|
|
30
|
-
setInputInFocus(false);
|
|
31
|
-
};
|
|
32
|
-
input?.addEventListener('focus', onFocus);
|
|
33
|
-
input?.addEventListener('blur', onBlur);
|
|
34
|
-
return () => {
|
|
35
|
-
input?.removeEventListener('focus', onFocus);
|
|
36
|
-
input?.removeEventListener('blur', onBlur);
|
|
37
|
-
};
|
|
38
|
-
}, [inputRef]);
|
|
39
27
|
// onChange function for the input
|
|
40
28
|
const onChange = (event) => {
|
|
41
29
|
const value = event.target.value;
|
|
42
30
|
setInputValue(value);
|
|
43
31
|
setActiveIndex(0);
|
|
44
|
-
if (typeof value === 'string') {
|
|
45
|
-
setOpen(true);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
setOpen(false);
|
|
49
|
-
}
|
|
50
32
|
// check if input value is the same as a label, if so, select it
|
|
51
|
-
const option = options.
|
|
33
|
+
const option = options[value.toLowerCase()];
|
|
52
34
|
if (!option)
|
|
53
35
|
return;
|
|
54
|
-
if (selectedOptions
|
|
36
|
+
if (selectedOptions[option.value])
|
|
55
37
|
return;
|
|
56
|
-
handleSelectOption(option);
|
|
57
|
-
if (multiple) {
|
|
58
|
-
inputRef.current?.focus();
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// move cursor to the end of the input
|
|
62
|
-
setTimeout(() => {
|
|
63
|
-
inputRef.current?.setSelectionRange(option?.label?.length || 0, option?.label?.length || 0);
|
|
64
|
-
}, 0);
|
|
65
|
-
}
|
|
38
|
+
handleSelectOption({ option: option });
|
|
66
39
|
};
|
|
67
|
-
const showClearButton = multiple && !hideClearButton && selectedOptions.length > 0;
|
|
68
|
-
return (jsxs(Box
|
|
40
|
+
const showClearButton = multiple && !hideClearButton && Object.keys(selectedOptions).length > 0;
|
|
69
41
|
/* Props from floating-ui */
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}), "aria-disabled": disabled, className: clsx(textFieldClasses.input, classes.inputWrapper, classes[size], inputInFocus && classes.inFocus, readOnly && classes.readonly, error && classes.error), children: [jsxs("div", { className: classes.chipAndInput, children: [multiple && !hideChips && jsx(ComboboxChips, {}), jsx("input", { ref: mergedRefs, "aria-activedescendant": activeDescendant, readOnly: readOnly, "aria-autocomplete": 'list', role: 'combobox', "aria-expanded": open, "aria-controls": listId, autoComplete: 'off', size: htmlSize, value: inputValue, ...omit(['style', 'className'], rest), ...formFieldProps.inputProps, onChange: (e) => {
|
|
42
|
+
const props = getReferenceProps({
|
|
43
|
+
ref: refs?.setReference,
|
|
44
|
+
role: null,
|
|
45
|
+
'aria-controls': null,
|
|
46
|
+
'aria-expanded': null,
|
|
47
|
+
'aria-haspopup': null,
|
|
48
|
+
/* If we click the wrapper, open the list, set index to first option, and focus the input */
|
|
49
|
+
onClick() {
|
|
50
|
+
if (disabled)
|
|
51
|
+
return;
|
|
52
|
+
if (readOnly)
|
|
53
|
+
return;
|
|
54
|
+
setOpen(true);
|
|
55
|
+
setActiveIndex(0);
|
|
56
|
+
inputRef.current?.focus();
|
|
57
|
+
},
|
|
58
|
+
/* Handles list navigation */
|
|
59
|
+
onKeyDown: handleKeyDown,
|
|
60
|
+
// preventDefault on keydown to avoid sending in form
|
|
61
|
+
onKeyPress(event) {
|
|
62
|
+
if (event.key === 'Enter') {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return (jsxs(Box, { ...props, "aria-disabled": disabled, className: clsx(textFieldClasses.input, classes.inputWrapper, classes[size], readOnly && classes.readonly, error && classes.error), children: [jsxs("div", { className: classes.chipAndInput, children: [multiple && !hideChips && jsx(ComboboxChips, {}), jsx("input", { ref: mergedRefs, "aria-activedescendant": props['aria-activedescendant'], readOnly: readOnly, "aria-autocomplete": 'list', role: 'combobox', "aria-expanded": open, "aria-controls": listId, autoComplete: 'off', size: htmlSize, value: inputValue, ...omit(['style', 'className'], rest), ...formFieldProps.inputProps, onChange: (e) => {
|
|
97
68
|
onChange(e);
|
|
98
69
|
rest.onChange && rest.onChange(e);
|
|
99
70
|
} })] }), showClearButton && jsx(ComboboxClearButton, {}), jsx("div", { className: classes.arrow, children: open ? (jsx(ChevronUpIcon, { title: 'arrow up', fontSize: '1.5em' })) : (jsx(ChevronDownIcon, { title: 'arrow down', fontSize: '1.5em' })) })] }));
|
|
@@ -3,8 +3,8 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
3
3
|
|
|
4
4
|
const ComboboxNative = ({ selectedOptions, multiple, name, }) => {
|
|
5
5
|
return (jsx("select", { name: name, multiple: multiple, style: { display: 'none' }, value: multiple
|
|
6
|
-
?
|
|
7
|
-
: selectedOptions[0]
|
|
6
|
+
? Object.keys(selectedOptions)
|
|
7
|
+
: Object.keys(selectedOptions)[0], onChange: () => { }, children: Object.keys(selectedOptions).map((value) => (jsx("option", { value: value }, value))) }));
|
|
8
8
|
};
|
|
9
9
|
ComboboxNative.displayName = 'ComboboxNative';
|
|
10
10
|
var ComboboxNative$1 = ComboboxNative;
|
|
@@ -13,94 +13,96 @@ function isComboboxCustom(child) {
|
|
|
13
13
|
function isInteractiveComboboxCustom(child) {
|
|
14
14
|
return isComboboxCustom(child) && child.props.interactive === true;
|
|
15
15
|
}
|
|
16
|
-
function useCombobox({ children, inputValue, multiple, filter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
function useCombobox({ children, inputValue, multiple, filter = (inputValue, option) => {
|
|
17
|
+
return option.label.toLowerCase().startsWith(inputValue.toLowerCase());
|
|
18
|
+
}, initialValue, }) {
|
|
19
|
+
const { optionsChildren, customIds, restChildren, interactiveChildren } = useMemo(() => {
|
|
20
|
+
const allChildren = Children.toArray(children);
|
|
21
|
+
const result = allChildren.reduce((acc, child) => {
|
|
20
22
|
if (isComboboxOption(child)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
label = childrenLabel;
|
|
23
|
+
acc.optionsChildren.push(child);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
acc.restChildren.push(child);
|
|
27
|
+
if (isInteractiveComboboxCustom(child)) {
|
|
28
|
+
const childElement = child;
|
|
29
|
+
acc.interactiveChildren.push(childElement);
|
|
30
|
+
if (!childElement.props.id) {
|
|
31
|
+
throw new Error('If ComboboxCustom is interactive, it must have an id');
|
|
32
|
+
}
|
|
33
|
+
acc.customIds.push(childElement.props.id);
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
}
|
|
36
|
+
return acc;
|
|
37
|
+
}, {
|
|
38
|
+
optionsChildren: [],
|
|
39
|
+
customIds: [],
|
|
40
|
+
restChildren: [],
|
|
41
|
+
interactiveChildren: [],
|
|
42
|
+
});
|
|
43
|
+
return result;
|
|
44
|
+
}, [children]);
|
|
45
|
+
const options = useMemo(() => {
|
|
46
|
+
const allOptions = {};
|
|
47
|
+
optionsChildren.map((child) => {
|
|
48
|
+
const props = child.props;
|
|
49
|
+
let label = props.displayValue || '';
|
|
50
|
+
if (!props.displayValue) {
|
|
51
|
+
let childrenLabel = '';
|
|
52
|
+
// go over children and find all strings
|
|
53
|
+
Children.forEach(props.children, (child) => {
|
|
54
|
+
if (typeof child === 'string') {
|
|
55
|
+
childrenLabel += child;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new Error('If ComboboxOption is not a string, it must have a displayValue prop');
|
|
59
|
+
}
|
|
41
60
|
});
|
|
61
|
+
label = childrenLabel;
|
|
42
62
|
}
|
|
63
|
+
allOptions[props.value] = {
|
|
64
|
+
value: props.value,
|
|
65
|
+
label,
|
|
66
|
+
displayValue: props.displayValue,
|
|
67
|
+
description: props.description,
|
|
68
|
+
};
|
|
43
69
|
});
|
|
44
70
|
return allOptions;
|
|
45
|
-
}, [
|
|
46
|
-
const preSelectedOptions = (initialValue || [])
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
}, [optionsChildren]);
|
|
72
|
+
const preSelectedOptions = useMemo(() => (initialValue || []).reduce((acc, value) => {
|
|
73
|
+
const option = options[value];
|
|
74
|
+
if (isOption(option)) {
|
|
75
|
+
acc[value] = option;
|
|
76
|
+
}
|
|
77
|
+
return acc;
|
|
78
|
+
}, {}), [initialValue, options]);
|
|
49
79
|
const [selectedOptions, setSelectedOptions] = useState(preSelectedOptions);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
}, [options, children, multiple, inputValue, selectedOptions, filter]);
|
|
69
|
-
const customIds = useMemo(() => {
|
|
70
|
-
// find all custom components with `interactive=true` and generate random values for them
|
|
71
|
-
const children_ = Children.toArray(children).filter((child) => {
|
|
72
|
-
return isInteractiveComboboxCustom(child);
|
|
73
|
-
});
|
|
74
|
-
// return all ids
|
|
75
|
-
return children_.map((child) => {
|
|
76
|
-
if (!child.props.id)
|
|
77
|
-
throw new Error('If ComboboxCustom is interactive, it must have an id');
|
|
78
|
-
return child.props.id;
|
|
79
|
-
});
|
|
80
|
-
}, [children]);
|
|
81
|
-
const optionValues = useMemo(() => {
|
|
82
|
-
// create an index map of values from optionsChildren
|
|
83
|
-
const options = optionsChildren.map((child) => {
|
|
84
|
-
const { value } = child.props;
|
|
85
|
-
return value;
|
|
86
|
-
});
|
|
87
|
-
return [...customIds, ...options];
|
|
88
|
-
}, [customIds, optionsChildren]);
|
|
89
|
-
const restChildren = useMemo(() => {
|
|
90
|
-
return Children.toArray(children).filter((child) => {
|
|
91
|
-
return !isComboboxOption(child);
|
|
92
|
-
});
|
|
93
|
-
}, [children]);
|
|
80
|
+
const { filteredOptions, filteredOptionsChildren } = useMemo(() => {
|
|
81
|
+
const filteredOptions = [];
|
|
82
|
+
const filteredOptionsChildren = Object.keys(options)
|
|
83
|
+
.map((option, index) => {
|
|
84
|
+
if (multiple && selectedOptions[option]) {
|
|
85
|
+
filteredOptions.push(options[option].value);
|
|
86
|
+
return optionsChildren[index];
|
|
87
|
+
}
|
|
88
|
+
if (filter(inputValue, options[option])) {
|
|
89
|
+
filteredOptions.push(options[option].value);
|
|
90
|
+
return optionsChildren[index];
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.filter((child) => child);
|
|
94
|
+
return { filteredOptions, filteredOptionsChildren };
|
|
95
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
96
|
+
}, [inputValue, multiple, options, optionsChildren, selectedOptions]);
|
|
94
97
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
filteredOptionsChildren,
|
|
99
|
+
filteredOptions,
|
|
97
100
|
restChildren,
|
|
98
101
|
options,
|
|
99
102
|
customIds,
|
|
100
103
|
selectedOptions,
|
|
104
|
+
interactiveChildren,
|
|
101
105
|
setSelectedOptions,
|
|
102
|
-
prevSelectedHash,
|
|
103
|
-
setPrevSelectedHash,
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import useDebounce from '../../../utilities/useDebounce.js';
|
|
3
|
+
import { useComboboxId } from './ComboboxIdContext.js';
|
|
4
|
+
|
|
5
|
+
const useComboboxKeyboard = ({ readOnly, disabled, interactiveChildren, filteredOptions, inputValue, selectedOptions, multiple, open, options, setOpen, handleSelectOption, }) => {
|
|
6
|
+
const { activeIndex } = useComboboxId();
|
|
7
|
+
// handle keyboard navigation in the list
|
|
8
|
+
const handleKeyDownFunc = (event) => {
|
|
9
|
+
if (readOnly || disabled)
|
|
10
|
+
return;
|
|
11
|
+
if (!event)
|
|
12
|
+
return;
|
|
13
|
+
switch (event.key) {
|
|
14
|
+
case 'ArrowDown':
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
if (open)
|
|
17
|
+
break;
|
|
18
|
+
setOpen(true);
|
|
19
|
+
break;
|
|
20
|
+
case 'ArrowUp':
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
/* If we are on the first item, close */
|
|
23
|
+
if (activeIndex !== 0)
|
|
24
|
+
break;
|
|
25
|
+
setOpen(false);
|
|
26
|
+
break;
|
|
27
|
+
case 'Enter':
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
// ignore if it is closed
|
|
30
|
+
if (!open)
|
|
31
|
+
break;
|
|
32
|
+
// check if we are in the custom components
|
|
33
|
+
if (activeIndex <= interactiveChildren.length - 1) {
|
|
34
|
+
const selectedComponent = interactiveChildren[activeIndex];
|
|
35
|
+
if (selectedComponent.props.onSelect) {
|
|
36
|
+
selectedComponent?.props.onSelect();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// if we are in the options, find the actual index
|
|
41
|
+
// eslint-disable-next-line no-case-declarations
|
|
42
|
+
const valueIndex = activeIndex - interactiveChildren.length;
|
|
43
|
+
// eslint-disable-next-line no-case-declarations
|
|
44
|
+
const option = filteredOptions[valueIndex];
|
|
45
|
+
handleSelectOption({ option: options[option] });
|
|
46
|
+
break;
|
|
47
|
+
case 'Backspace':
|
|
48
|
+
// if we are in single mode, we need to set selectedOptions to empty
|
|
49
|
+
if (!multiple) {
|
|
50
|
+
const lastOption = Object.keys(selectedOptions).pop();
|
|
51
|
+
lastOption &&
|
|
52
|
+
handleSelectOption({
|
|
53
|
+
option: selectedOptions[lastOption],
|
|
54
|
+
remove: true,
|
|
55
|
+
});
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
if (inputValue === '' && multiple) {
|
|
59
|
+
const lastOption = Object.keys(selectedOptions).pop();
|
|
60
|
+
/* Remove last option, and use handleSelectOption */
|
|
61
|
+
lastOption &&
|
|
62
|
+
handleSelectOption({
|
|
63
|
+
option: selectedOptions[lastOption],
|
|
64
|
+
remove: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const handleKeyDown = useDebounce(handleKeyDownFunc, 20);
|
|
71
|
+
return handleKeyDown;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { useComboboxKeyboard };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useFloating, useRole, useDismiss, useListNavigation, useInteractions } from '../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { flushSync } from 'react-dom';
|
|
5
|
+
import { useComboboxId, useComboboxIdDispatch } from './ComboboxIdContext.js';
|
|
6
|
+
import { autoUpdate, flip, size } from '../../../packages/react/node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
|
|
7
|
+
import { offset } from '../../../packages/react/node_modules/@floating-ui/core/dist/floating-ui.core.js';
|
|
8
|
+
|
|
9
|
+
const useFloatingCombobox = ({ listRef }) => {
|
|
10
|
+
const [open, setOpen] = useState(false);
|
|
11
|
+
const { activeIndex } = useComboboxId();
|
|
12
|
+
const dispatch = useComboboxIdDispatch();
|
|
13
|
+
// floating UI
|
|
14
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
15
|
+
open,
|
|
16
|
+
onOpenChange: (newOpen) => {
|
|
17
|
+
if (!newOpen)
|
|
18
|
+
dispatch?.({ type: 'SET_ACTIVE_INDEX', payload: 0 });
|
|
19
|
+
flushSync(() => {
|
|
20
|
+
if (refs.floating.current && !newOpen) {
|
|
21
|
+
refs.floating.current.scrollTop = 0;
|
|
22
|
+
}
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
setOpen(newOpen);
|
|
25
|
+
}, 1);
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
whileElementsMounted: (reference, floating, update) => {
|
|
29
|
+
autoUpdate(reference, floating, update);
|
|
30
|
+
return () => {
|
|
31
|
+
floating.scrollTop = 0;
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
middleware: [
|
|
35
|
+
flip({ padding: 10 }),
|
|
36
|
+
size({
|
|
37
|
+
apply({ rects, elements }) {
|
|
38
|
+
requestAnimationFrame(() => {
|
|
39
|
+
Object.assign(elements.floating.style, {
|
|
40
|
+
width: `calc(${rects.reference.width}px - calc(var(--fds-spacing-2) * 2))`,
|
|
41
|
+
maxHeight: `200px`,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
offset(10),
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
const role = useRole(context, { role: 'listbox' });
|
|
50
|
+
const dismiss = useDismiss(context);
|
|
51
|
+
const listNav = useListNavigation(context, {
|
|
52
|
+
listRef,
|
|
53
|
+
activeIndex,
|
|
54
|
+
virtual: true,
|
|
55
|
+
scrollItemIntoView: true,
|
|
56
|
+
enabled: open,
|
|
57
|
+
focusItemOnHover: true,
|
|
58
|
+
onNavigate: (index) => {
|
|
59
|
+
dispatch?.({ type: 'SET_ACTIVE_INDEX', payload: index || 0 });
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([role, dismiss, listNav]);
|
|
63
|
+
return {
|
|
64
|
+
open,
|
|
65
|
+
setOpen,
|
|
66
|
+
activeIndex,
|
|
67
|
+
refs,
|
|
68
|
+
floatingStyles,
|
|
69
|
+
context,
|
|
70
|
+
getReferenceProps,
|
|
71
|
+
getFloatingProps,
|
|
72
|
+
getItemProps,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { useFloatingCombobox };
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
3
3
|
import { forwardRef, useRef, useState, useCallback } from 'react';
|
|
4
4
|
import { clsx } from '../../../node_modules/clsx/dist/clsx.js';
|
|
5
5
|
import { MagnifyingGlassIcon, XMarkIcon } from '@navikt/aksel-icons';
|
|
6
|
-
import { useMergeRefs } from '../../../node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
6
|
+
import { useMergeRefs } from '../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
|
|
7
7
|
import { useSearch } from './useSearch.js';
|
|
8
8
|
import classes from './Search.module.css.js';
|
|
9
9
|
import { Button } from '../../Button/Button.js';
|
|
@@ -10,7 +10,7 @@ function getNodeName(node) {
|
|
|
10
10
|
}
|
|
11
11
|
function getWindow(node) {
|
|
12
12
|
var _node$ownerDocument;
|
|
13
|
-
return (node == null
|
|
13
|
+
return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
|
|
14
14
|
}
|
|
15
15
|
function getDocumentElement(node) {
|
|
16
16
|
var _ref;
|
|
@@ -109,18 +109,21 @@ function getNearestOverflowAncestor(node) {
|
|
|
109
109
|
}
|
|
110
110
|
return getNearestOverflowAncestor(parentNode);
|
|
111
111
|
}
|
|
112
|
-
function getOverflowAncestors(node, list) {
|
|
112
|
+
function getOverflowAncestors(node, list, traverseIframes) {
|
|
113
113
|
var _node$ownerDocument2;
|
|
114
114
|
if (list === void 0) {
|
|
115
115
|
list = [];
|
|
116
116
|
}
|
|
117
|
+
if (traverseIframes === void 0) {
|
|
118
|
+
traverseIframes = true;
|
|
119
|
+
}
|
|
117
120
|
const scrollableAncestor = getNearestOverflowAncestor(node);
|
|
118
121
|
const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body);
|
|
119
122
|
const win = getWindow(scrollableAncestor);
|
|
120
123
|
if (isBody) {
|
|
121
|
-
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []);
|
|
124
|
+
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], win.frameElement && traverseIframes ? getOverflowAncestors(win.frameElement) : []);
|
|
122
125
|
}
|
|
123
|
-
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor));
|
|
126
|
+
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
export { getComputedStyle, getContainingBlock, getDocumentElement, getNearestOverflowAncestor, getNodeName, getNodeScroll, getOverflowAncestors, getParentNode, getWindow, isContainingBlock, isElement, isHTMLElement, isLastTraversableNode, isNode, isOverflowElement, isShadowRoot, isTableElement, isWebKit };
|