@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
|
@@ -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 = () => {
|
|
@@ -17,7 +17,7 @@ const ComboboxClearButton = () => {
|
|
|
17
17
|
return;
|
|
18
18
|
if (disabled)
|
|
19
19
|
return;
|
|
20
|
-
setSelectedOptions(
|
|
20
|
+
setSelectedOptions({});
|
|
21
21
|
setInputValue('');
|
|
22
22
|
}, onKeyDown: (e) => {
|
|
23
23
|
if (readOnly)
|
|
@@ -26,7 +26,7 @@ const ComboboxClearButton = () => {
|
|
|
26
26
|
return;
|
|
27
27
|
if (e.key === 'Enter') {
|
|
28
28
|
e.stopPropagation();
|
|
29
|
-
setSelectedOptions(
|
|
29
|
+
setSelectedOptions({});
|
|
30
30
|
setInputValue('');
|
|
31
31
|
inputRef.current?.focus();
|
|
32
32
|
}
|
|
@@ -3,10 +3,11 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
3
3
|
import { useContext, useState, useEffect } 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';
|
|
@@ -14,10 +15,14 @@ import { omit } from '../../../../utilities/objectUtils.js';
|
|
|
14
15
|
|
|
15
16
|
const ComboboxInput = ({ ...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, listId, size, readOnly, disabled, open, inputRef, refs, inputValue, error, multiple, selectedOptions, formFieldProps, htmlSize, options, hideChips, hideClearButton, setOpen, handleKeyDown, getReferenceProps, setInputValue, handleSelectOption, } = context;
|
|
21
26
|
const mergedRefs = useMergeRefs([forwareddRef, inputRef]);
|
|
22
27
|
// we need to check if input is in focus, to add focus styles to the wrapper
|
|
23
28
|
const [inputInFocus, setInputInFocus] = useState(false);
|
|
@@ -48,10 +53,10 @@ const ComboboxInput = ({ ...rest }) => {
|
|
|
48
53
|
setOpen(false);
|
|
49
54
|
}
|
|
50
55
|
// check if input value is the same as a label, if so, select it
|
|
51
|
-
const option = options
|
|
56
|
+
const option = options[value];
|
|
52
57
|
if (!option)
|
|
53
58
|
return;
|
|
54
|
-
if (selectedOptions
|
|
59
|
+
if (selectedOptions[option.value])
|
|
55
60
|
return;
|
|
56
61
|
handleSelectOption(option);
|
|
57
62
|
if (multiple) {
|
|
@@ -64,36 +69,36 @@ const ComboboxInput = ({ ...rest }) => {
|
|
|
64
69
|
}, 0);
|
|
65
70
|
}
|
|
66
71
|
};
|
|
67
|
-
const showClearButton = multiple && !hideClearButton && selectedOptions.length > 0;
|
|
68
|
-
return (jsxs(Box
|
|
72
|
+
const showClearButton = multiple && !hideClearButton && Object.keys(selectedOptions).length > 0;
|
|
69
73
|
/* 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
|
-
|
|
74
|
+
const props = getReferenceProps({
|
|
75
|
+
ref: refs?.setReference,
|
|
76
|
+
role: null,
|
|
77
|
+
'aria-controls': null,
|
|
78
|
+
'aria-expanded': null,
|
|
79
|
+
'aria-haspopup': null,
|
|
80
|
+
/* If we click the wrapper, open the list, set index to first option, and focus the input */
|
|
81
|
+
onClick() {
|
|
82
|
+
if (disabled)
|
|
83
|
+
return;
|
|
84
|
+
if (readOnly)
|
|
85
|
+
return;
|
|
86
|
+
setOpen(true);
|
|
87
|
+
setActiveIndex(0);
|
|
88
|
+
inputRef.current?.focus();
|
|
89
|
+
},
|
|
90
|
+
/* Handles list navigation */
|
|
91
|
+
onKeyDown(event) {
|
|
92
|
+
handleKeyDown(event);
|
|
93
|
+
},
|
|
94
|
+
// preventDefault on keydown to avoid sending in form
|
|
95
|
+
onKeyPress(event) {
|
|
96
|
+
if (event.key === 'Enter') {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return (jsxs(Box, { ...props, "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": 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
102
|
onChange(e);
|
|
98
103
|
rest.onChange && rest.onChange(e);
|
|
99
104
|
} })] }), 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,9 +13,11 @@ 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
|
|
16
|
+
function useCombobox({ children, inputValue, multiple, filter = (inputValue, option) => {
|
|
17
|
+
return option.label.toLowerCase().startsWith(inputValue.toLowerCase());
|
|
18
|
+
}, initialValue, }) {
|
|
17
19
|
const options = useMemo(() => {
|
|
18
|
-
const allOptions =
|
|
20
|
+
const allOptions = {};
|
|
19
21
|
Children.forEach(children, (child) => {
|
|
20
22
|
if (isComboboxOption(child)) {
|
|
21
23
|
const props = child.props;
|
|
@@ -33,51 +35,57 @@ function useCombobox({ children, inputValue, multiple, filter, initialValue, })
|
|
|
33
35
|
});
|
|
34
36
|
label = childrenLabel;
|
|
35
37
|
}
|
|
36
|
-
allOptions.
|
|
38
|
+
allOptions[props.value] = {
|
|
37
39
|
value: props.value,
|
|
38
40
|
label,
|
|
39
41
|
displayValue: props.displayValue,
|
|
40
42
|
description: props.description,
|
|
41
|
-
}
|
|
43
|
+
};
|
|
42
44
|
}
|
|
43
45
|
});
|
|
44
46
|
return allOptions;
|
|
45
47
|
}, [children]);
|
|
46
|
-
const preSelectedOptions = (initialValue || [])
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const preSelectedOptions = useMemo(() => (initialValue || []).reduce((acc, value) => {
|
|
49
|
+
const option = options[value];
|
|
50
|
+
if (isOption(option)) {
|
|
51
|
+
acc[value] = option;
|
|
52
|
+
}
|
|
53
|
+
return acc;
|
|
54
|
+
}, {}), [initialValue, options]);
|
|
49
55
|
const [selectedOptions, setSelectedOptions] = useState(preSelectedOptions);
|
|
50
56
|
const [prevSelectedHash, setPrevSelectedHash] = useState(JSON.stringify(selectedOptions));
|
|
51
|
-
const optionsChildren = useMemo(() => {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const activeValue = valuesArray.find((item) => item.label === inputValue);
|
|
55
|
-
if (activeValue && !multiple)
|
|
56
|
-
return children_;
|
|
57
|
-
if (inputValue === '' && !multiple)
|
|
58
|
-
return children_;
|
|
59
|
-
return children_.filter((child) => {
|
|
60
|
-
const { value } = child.props;
|
|
61
|
-
const option = valuesArray.find((item) => item.value === value);
|
|
62
|
-
if (!option)
|
|
63
|
-
return false;
|
|
64
|
-
const isSelected = selectedOptions.some((selectedOption) => selectedOption.value === value);
|
|
65
|
-
// show what we search for, and all selected options
|
|
66
|
-
return filter(inputValue, { ...option }) || isSelected;
|
|
67
|
-
});
|
|
68
|
-
}, [options, children, multiple, inputValue, selectedOptions, filter]);
|
|
69
|
-
const customIds = useMemo(() => {
|
|
57
|
+
const { optionsChildren, customIds } = useMemo(() => {
|
|
58
|
+
const allChildren = Children.toArray(children);
|
|
59
|
+
const optionsChildren = allChildren.filter((child) => isComboboxOption(child));
|
|
70
60
|
// find all custom components with `interactive=true` and generate random values for them
|
|
71
|
-
const
|
|
61
|
+
const customChildren = allChildren.filter((child) => {
|
|
72
62
|
return isInteractiveComboboxCustom(child);
|
|
73
63
|
});
|
|
74
64
|
// return all ids
|
|
75
|
-
|
|
65
|
+
const customIds = customChildren.map((child) => {
|
|
76
66
|
if (!child.props.id)
|
|
77
67
|
throw new Error('If ComboboxCustom is interactive, it must have an id');
|
|
78
68
|
return child.props.id;
|
|
79
69
|
});
|
|
70
|
+
return { optionsChildren, customIds };
|
|
80
71
|
}, [children]);
|
|
72
|
+
const { filteredOptions, filteredOptionsChildren } = useMemo(() => {
|
|
73
|
+
const filteredOptions = [];
|
|
74
|
+
const filteredOptionsChildren = Object.keys(options)
|
|
75
|
+
.map((option, index) => {
|
|
76
|
+
if (multiple && selectedOptions[option]) {
|
|
77
|
+
filteredOptions.push(options[option].value);
|
|
78
|
+
return optionsChildren[index];
|
|
79
|
+
}
|
|
80
|
+
if (filter(inputValue, options[option])) {
|
|
81
|
+
filteredOptions.push(options[option].value);
|
|
82
|
+
return optionsChildren[index];
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.filter((child) => child);
|
|
86
|
+
return { filteredOptions, filteredOptionsChildren };
|
|
87
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
+
}, [inputValue, multiple, options, optionsChildren, selectedOptions]);
|
|
81
89
|
const optionValues = useMemo(() => {
|
|
82
90
|
// create an index map of values from optionsChildren
|
|
83
91
|
const options = optionsChildren.map((child) => {
|
|
@@ -86,20 +94,26 @@ function useCombobox({ children, inputValue, multiple, filter, initialValue, })
|
|
|
86
94
|
});
|
|
87
95
|
return [...customIds, ...options];
|
|
88
96
|
}, [customIds, optionsChildren]);
|
|
89
|
-
const restChildren = useMemo(() => {
|
|
90
|
-
|
|
97
|
+
const { restChildren, interactiveChildren } = useMemo(() => {
|
|
98
|
+
const restChildren = Children.toArray(children).filter((child) => {
|
|
91
99
|
return !isComboboxOption(child);
|
|
92
100
|
});
|
|
101
|
+
const interactiveChildren = restChildren.filter((child) => {
|
|
102
|
+
return isInteractiveComboboxCustom(child);
|
|
103
|
+
});
|
|
104
|
+
return { restChildren, interactiveChildren };
|
|
93
105
|
}, [children]);
|
|
94
106
|
return {
|
|
95
|
-
|
|
107
|
+
filteredOptionsChildren,
|
|
108
|
+
filteredOptions,
|
|
96
109
|
optionValues,
|
|
97
110
|
restChildren,
|
|
98
111
|
options,
|
|
99
112
|
customIds,
|
|
100
113
|
selectedOptions,
|
|
101
|
-
setSelectedOptions,
|
|
102
114
|
prevSelectedHash,
|
|
115
|
+
interactiveChildren,
|
|
116
|
+
setSelectedOptions,
|
|
103
117
|
setPrevSelectedHash,
|
|
104
118
|
};
|
|
105
119
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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, setInputValue, setSelectedOptions, 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
|
+
setOpen(true);
|
|
18
|
+
}
|
|
19
|
+
break;
|
|
20
|
+
case 'ArrowUp':
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
/* If we are on the first item, close */
|
|
23
|
+
if (activeIndex === 0) {
|
|
24
|
+
setOpen(false);
|
|
25
|
+
}
|
|
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
|
+
if (!multiple) {
|
|
46
|
+
// check if option is already selected, if so, deselect it
|
|
47
|
+
if (selectedOptions[option]) {
|
|
48
|
+
setSelectedOptions({});
|
|
49
|
+
setInputValue('');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
handleSelectOption(options[option]);
|
|
54
|
+
break;
|
|
55
|
+
case 'Backspace':
|
|
56
|
+
if (inputValue === '' &&
|
|
57
|
+
multiple &&
|
|
58
|
+
Object.keys(selectedOptions).length >= 0) {
|
|
59
|
+
setSelectedOptions((prev) => {
|
|
60
|
+
const updated = { ...prev };
|
|
61
|
+
const keys = Object.keys(updated);
|
|
62
|
+
delete updated[keys[keys.length - 1]];
|
|
63
|
+
return updated;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// if we are in single mode, we need to set activeValue to null
|
|
67
|
+
if (!multiple) {
|
|
68
|
+
setSelectedOptions({});
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const handleKeyDown = useDebounce(handleKeyDownFunc, 20);
|
|
74
|
+
return handleKeyDown;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { evaluate, getSide, getOppositePlacement, getExpandedPlacements, getOppositeAxisPlacements, getAlignmentSides, getSideAxis, clamp, getPaddingObject, rectToClientRect, getAlignmentAxis,
|
|
2
|
+
import { evaluate, getSide, getOppositePlacement, getExpandedPlacements, getOppositeAxisPlacements, getAlignmentSides, getAlignment, getSideAxis, clamp, getPaddingObject, rectToClientRect, getAlignmentAxis, getOppositeAxis, getAxisLength, min, max } from '../../../../../../node_modules/@floating-ui/utils/dist/floating-ui.utils.js';
|
|
3
3
|
|
|
4
4
|
function computeCoordsFromPlacement(_ref, placement, rtl) {
|
|
5
5
|
let {
|
|
@@ -59,7 +59,7 @@ function computeCoordsFromPlacement(_ref, placement, rtl) {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Computes the `x` and `y` coordinates that will place the floating element
|
|
62
|
-
* next to a reference element
|
|
62
|
+
* next to a given reference element.
|
|
63
63
|
*
|
|
64
64
|
* This export does not have any `platform` interface logic. You will need to
|
|
65
65
|
* write one for the platform you are using Floating UI with.
|
|
@@ -137,7 +137,6 @@ const computePosition = async (reference, floating, config) => {
|
|
|
137
137
|
} = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
|
|
138
138
|
}
|
|
139
139
|
i = -1;
|
|
140
|
-
continue;
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
142
|
return {
|
|
@@ -200,6 +199,7 @@ async function detectOverflow(state, options) {
|
|
|
200
199
|
y: 1
|
|
201
200
|
};
|
|
202
201
|
const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({
|
|
202
|
+
elements,
|
|
203
203
|
rect,
|
|
204
204
|
offsetParent,
|
|
205
205
|
strategy
|
|
@@ -227,7 +227,8 @@ const arrow = options => ({
|
|
|
227
227
|
placement,
|
|
228
228
|
rects,
|
|
229
229
|
platform,
|
|
230
|
-
elements
|
|
230
|
+
elements,
|
|
231
|
+
middlewareData
|
|
231
232
|
} = state;
|
|
232
233
|
// Since `element` is required, we don't Partial<> the type.
|
|
233
234
|
const {
|
|
@@ -275,16 +276,20 @@ const arrow = options => ({
|
|
|
275
276
|
|
|
276
277
|
// If the reference is small enough that the arrow's padding causes it to
|
|
277
278
|
// to point to nothing for an aligned placement, adjust the offset of the
|
|
278
|
-
// floating element itself.
|
|
279
|
-
//
|
|
280
|
-
const shouldAddOffset = getAlignment(placement) != null && center
|
|
281
|
-
const alignmentOffset = shouldAddOffset ? center < min$1 ? min$1
|
|
279
|
+
// floating element itself. To ensure `shift()` continues to take action,
|
|
280
|
+
// a single reset is performed when this is true.
|
|
281
|
+
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
282
|
+
const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;
|
|
282
283
|
return {
|
|
283
|
-
[axis]: coords[axis]
|
|
284
|
+
[axis]: coords[axis] + alignmentOffset,
|
|
284
285
|
data: {
|
|
285
286
|
[axis]: offset,
|
|
286
|
-
centerOffset: center - offset
|
|
287
|
-
|
|
287
|
+
centerOffset: center - offset - alignmentOffset,
|
|
288
|
+
...(shouldAddOffset && {
|
|
289
|
+
alignmentOffset
|
|
290
|
+
})
|
|
291
|
+
},
|
|
292
|
+
reset: shouldAddOffset
|
|
288
293
|
};
|
|
289
294
|
}
|
|
290
295
|
});
|
|
@@ -303,7 +308,7 @@ const flip = function (options) {
|
|
|
303
308
|
name: 'flip',
|
|
304
309
|
options,
|
|
305
310
|
async fn(state) {
|
|
306
|
-
var _middlewareData$flip;
|
|
311
|
+
var _middlewareData$arrow, _middlewareData$flip;
|
|
307
312
|
const {
|
|
308
313
|
placement,
|
|
309
314
|
middlewareData,
|
|
@@ -321,6 +326,14 @@ const flip = function (options) {
|
|
|
321
326
|
flipAlignment = true,
|
|
322
327
|
...detectOverflowOptions
|
|
323
328
|
} = evaluate(options, state);
|
|
329
|
+
|
|
330
|
+
// If a reset by the arrow was caused due to an alignment offset being
|
|
331
|
+
// added, we should skip any logic now since `flip()` has already done its
|
|
332
|
+
// work.
|
|
333
|
+
// https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643
|
|
334
|
+
if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
|
|
335
|
+
return {};
|
|
336
|
+
}
|
|
324
337
|
const side = getSide(placement);
|
|
325
338
|
const isBasePlacement = getSide(initialPlacement) === initialPlacement;
|
|
326
339
|
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
|
|
@@ -398,6 +411,7 @@ const flip = function (options) {
|
|
|
398
411
|
|
|
399
412
|
// For type backwards-compatibility, the `OffsetOptions` type was also
|
|
400
413
|
// Derivable.
|
|
414
|
+
|
|
401
415
|
async function convertValueToCoords(state, options) {
|
|
402
416
|
const {
|
|
403
417
|
placement,
|
|
@@ -411,8 +425,6 @@ async function convertValueToCoords(state, options) {
|
|
|
411
425
|
const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
|
|
412
426
|
const crossAxisMulti = rtl && isVertical ? -1 : 1;
|
|
413
427
|
const rawValue = evaluate(options, state);
|
|
414
|
-
|
|
415
|
-
// eslint-disable-next-line prefer-const
|
|
416
428
|
let {
|
|
417
429
|
mainAxis,
|
|
418
430
|
crossAxis,
|
|
@@ -454,15 +466,27 @@ const offset = function (options) {
|
|
|
454
466
|
name: 'offset',
|
|
455
467
|
options,
|
|
456
468
|
async fn(state) {
|
|
469
|
+
var _middlewareData$offse, _middlewareData$arrow;
|
|
457
470
|
const {
|
|
458
471
|
x,
|
|
459
|
-
y
|
|
472
|
+
y,
|
|
473
|
+
placement,
|
|
474
|
+
middlewareData
|
|
460
475
|
} = state;
|
|
461
476
|
const diffCoords = await convertValueToCoords(state, options);
|
|
477
|
+
|
|
478
|
+
// If the placement is the same and the arrow caused an alignment offset
|
|
479
|
+
// then we don't need to change the positioning coordinates.
|
|
480
|
+
if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
|
|
481
|
+
return {};
|
|
482
|
+
}
|
|
462
483
|
return {
|
|
463
484
|
x: x + diffCoords.x,
|
|
464
485
|
y: y + diffCoords.y,
|
|
465
|
-
data:
|
|
486
|
+
data: {
|
|
487
|
+
...diffCoords,
|
|
488
|
+
placement
|
|
489
|
+
}
|
|
466
490
|
};
|
|
467
491
|
}
|
|
468
492
|
};
|