@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.
Files changed (91) hide show
  1. package/dist/cjs/components/DropdownMenu/DropdownMenuContent.js +4 -4
  2. package/dist/cjs/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
  3. package/dist/cjs/components/Modal/ModalDialog.js +1 -1
  4. package/dist/cjs/components/Popover/PopoverContent.js +6 -6
  5. package/dist/cjs/components/Popover/PopoverTrigger.js +1 -1
  6. package/dist/cjs/components/Tooltip/Tooltip.js +6 -6
  7. package/dist/cjs/components/form/Checkbox/Checkbox.js +1 -1
  8. package/dist/cjs/components/form/Combobox/Combobox.js +60 -177
  9. package/dist/cjs/components/form/Combobox/ComboboxContext.js +8 -0
  10. package/dist/cjs/components/form/Combobox/ComboboxIdContext.js +42 -0
  11. package/dist/cjs/components/form/Combobox/Custom/Custom.js +14 -9
  12. package/dist/cjs/components/form/Combobox/Empty/Empty.js +4 -4
  13. package/dist/cjs/components/form/Combobox/Option/Option.js +15 -33
  14. package/dist/cjs/components/form/Combobox/Option/useComboboxOption.js +47 -0
  15. package/dist/cjs/components/form/Combobox/internal/ComboboxChips.js +14 -6
  16. package/dist/cjs/components/form/Combobox/internal/ComboboxClearButton.js +4 -4
  17. package/dist/cjs/components/form/Combobox/internal/ComboboxInput.js +40 -35
  18. package/dist/cjs/components/form/Combobox/internal/ComboboxNative.js +2 -2
  19. package/dist/cjs/components/form/Combobox/useCombobox.js +46 -32
  20. package/dist/cjs/components/form/Combobox/useComboboxKeyboard.js +79 -0
  21. package/dist/cjs/components/form/Combobox/useFloatingCombobox.js +78 -0
  22. package/dist/cjs/components/form/Search/Search.js +1 -1
  23. package/dist/cjs/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
  24. package/dist/cjs/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
  25. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
  26. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +83 -31
  27. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +307 -157
  28. package/dist/cjs/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -4
  29. package/dist/cjs/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +22 -18
  30. package/dist/{esm → cjs/packages/react}/node_modules/tabbable/dist/index.esm.js +59 -13
  31. package/dist/cjs/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
  32. package/dist/cjs/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
  33. package/dist/esm/components/DropdownMenu/DropdownMenuContent.js +3 -3
  34. package/dist/esm/components/DropdownMenu/DropdownMenuTrigger.js +1 -1
  35. package/dist/esm/components/Modal/ModalDialog.js +1 -1
  36. package/dist/esm/components/Popover/PopoverContent.js +4 -4
  37. package/dist/esm/components/Popover/PopoverTrigger.js +1 -1
  38. package/dist/esm/components/Tooltip/Tooltip.js +4 -4
  39. package/dist/esm/components/form/Checkbox/Checkbox.js +1 -1
  40. package/dist/esm/components/form/Combobox/Combobox.js +65 -182
  41. package/dist/esm/components/form/Combobox/ComboboxContext.js +6 -0
  42. package/dist/esm/components/form/Combobox/ComboboxIdContext.js +35 -0
  43. package/dist/esm/components/form/Combobox/Custom/Custom.js +13 -8
  44. package/dist/esm/components/form/Combobox/Empty/Empty.js +3 -3
  45. package/dist/esm/components/form/Combobox/Option/Option.js +15 -33
  46. package/dist/esm/components/form/Combobox/Option/useComboboxOption.js +45 -0
  47. package/dist/esm/components/form/Combobox/internal/ComboboxChips.js +13 -5
  48. package/dist/esm/components/form/Combobox/internal/ComboboxClearButton.js +3 -3
  49. package/dist/esm/components/form/Combobox/internal/ComboboxInput.js +39 -34
  50. package/dist/esm/components/form/Combobox/internal/ComboboxNative.js +2 -2
  51. package/dist/esm/components/form/Combobox/useCombobox.js +46 -32
  52. package/dist/esm/components/form/Combobox/useComboboxKeyboard.js +77 -0
  53. package/dist/esm/components/form/Combobox/useFloatingCombobox.js +76 -0
  54. package/dist/esm/components/form/Search/Search.js +1 -1
  55. package/dist/esm/node_modules/@floating-ui/utils/{dom/dist → dist}/floating-ui.utils.dom.js +7 -4
  56. package/dist/esm/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +5 -0
  57. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/core/dist/floating-ui.core.js +40 -16
  58. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/dom/dist/floating-ui.dom.js +82 -30
  59. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react/dist/floating-ui.react.js +282 -135
  60. package/dist/esm/{node_modules/@floating-ui/react/utils → packages/react/node_modules/@floating-ui/react}/dist/floating-ui.react.utils.js +9 -5
  61. package/dist/esm/{node_modules → packages/react/node_modules}/@floating-ui/react-dom/dist/floating-ui.react-dom.js +19 -14
  62. package/dist/{cjs → esm/packages/react}/node_modules/tabbable/dist/index.esm.js +55 -15
  63. package/dist/esm/utilities/RovingTabIndex/RovingTabindexItem.js +1 -1
  64. package/dist/esm/utilities/RovingTabIndex/RovingTabindexRoot.js +1 -1
  65. package/dist/types/components/form/Combobox/Combobox.d.ts +104 -39
  66. package/dist/types/components/form/Combobox/Combobox.d.ts.map +1 -1
  67. package/dist/types/components/form/Combobox/ComboboxContext.d.ts +48 -0
  68. package/dist/types/components/form/Combobox/ComboboxContext.d.ts.map +1 -0
  69. package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts +19 -0
  70. package/dist/types/components/form/Combobox/ComboboxIdContext.d.ts.map +1 -0
  71. package/dist/types/components/form/Combobox/Custom/Custom.d.ts.map +1 -1
  72. package/dist/types/components/form/Combobox/Option/Option.d.ts +2 -2
  73. package/dist/types/components/form/Combobox/Option/Option.d.ts.map +1 -1
  74. package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts +14 -0
  75. package/dist/types/components/form/Combobox/Option/useComboboxOption.d.ts.map +1 -0
  76. package/dist/types/components/form/Combobox/internal/ComboboxChips.d.ts.map +1 -1
  77. package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts +0 -1
  78. package/dist/types/components/form/Combobox/internal/ComboboxInput.d.ts.map +1 -1
  79. package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts +3 -1
  80. package/dist/types/components/form/Combobox/internal/ComboboxNative.d.ts.map +1 -1
  81. package/dist/types/components/form/Combobox/useCombobox.d.ts +13 -5
  82. package/dist/types/components/form/Combobox/useCombobox.d.ts.map +1 -1
  83. package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts +20 -0
  84. package/dist/types/components/form/Combobox/useComboboxKeyboard.d.ts.map +1 -0
  85. package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts +41 -0
  86. package/dist/types/components/form/Combobox/useFloatingCombobox.d.ts.map +1 -0
  87. package/package.json +3 -3
  88. package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -6
  89. package/dist/cjs/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +0 -68
  90. package/dist/esm/node_modules/@floating-ui/react/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -4
  91. 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 '../Combobox.js';
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 '../Combobox.js';
6
+ import { useMergeRefs } from '../../../../packages/react/node_modules/@floating-ui/react/dist/floating-ui.react.js';
7
+ import { ComboboxContext } from '../ComboboxContext.js';
8
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 { forwareddRef, listId, size, readOnly, disabled, open, inputRef, refs, inputValue, activeDescendant, error, multiple, selectedOptions, formFieldProps, htmlSize, options, hideChips, hideClearButton, setOpen, setActiveIndex, handleKeyDown, getReferenceProps, setInputValue, handleSelectOption, } = context;
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.find((option) => option.label === value);
56
+ const option = options[value];
52
57
  if (!option)
53
58
  return;
54
- if (selectedOptions.find((selectedOption) => selectedOption.value === option.value))
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
- , { ...getReferenceProps({
71
- ref: refs?.setReference,
72
- role: null,
73
- 'aria-controls': null,
74
- 'aria-expanded': null,
75
- 'aria-haspopup': null,
76
- /* If we click the wrapper, open the list, set index to first option, and focus the input */
77
- onClick() {
78
- if (disabled)
79
- return;
80
- if (readOnly)
81
- return;
82
- setOpen(true);
83
- setActiveIndex(0);
84
- inputRef.current?.focus();
85
- },
86
- /* Handles list navigation */
87
- onKeyDown(event) {
88
- handleKeyDown(event);
89
- },
90
- // preventDefault on keydown to avoid sending in form
91
- onKeyPress(event) {
92
- if (event.key === 'Enter') {
93
- event.preventDefault();
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) => {
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
- ? selectedOptions.map((option) => option.value)
7
- : selectedOptions[0]?.value, onChange: () => { }, children: selectedOptions.map((option) => (jsx("option", { value: option.value }, option.value))) }));
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, initialValue, }) {
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.push({
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
- .map((value) => options.find((option) => option.value === value))
48
- .filter(isOption);
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 valuesArray = Array.from(options);
53
- const children_ = Children.toArray(children).filter((child) => isComboboxOption(child));
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 children_ = Children.toArray(children).filter((child) => {
61
+ const customChildren = allChildren.filter((child) => {
72
62
  return isInteractiveComboboxCustom(child);
73
63
  });
74
64
  // return all ids
75
- return children_.map((child) => {
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
- return Children.toArray(children).filter((child) => {
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
- optionsChildren,
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 ? void 0 : (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
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,4 +1,9 @@
1
1
  'use client';
2
+ /**
3
+ * Custom positioning reference element.
4
+ * @see https://floating-ui.com/docs/virtual-elements
5
+ */
6
+
2
7
  const min = Math.min;
3
8
  const max = Math.max;
4
9
  const round = Math.round;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { evaluate, getSide, getOppositePlacement, getExpandedPlacements, getOppositeAxisPlacements, getAlignmentSides, getSideAxis, clamp, getPaddingObject, rectToClientRect, getAlignmentAxis, getAlignment, getOppositeAxis, max, getAxisLength, min } from '../../utils/dist/floating-ui.utils.js';
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 when it is given a certain positioning strategy.
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. This stops `shift()` from taking action, but can
279
- // be worked around by calling it again after the `arrow()` if desired.
280
- const shouldAddOffset = getAlignment(placement) != null && center != offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
281
- const alignmentOffset = shouldAddOffset ? center < min$1 ? min$1 - center : max - center : 0;
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] - alignmentOffset,
284
+ [axis]: coords[axis] + alignmentOffset,
284
285
  data: {
285
286
  [axis]: offset,
286
- centerOffset: center - offset + alignmentOffset
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: diffCoords
486
+ data: {
487
+ ...diffCoords,
488
+ placement
489
+ }
466
490
  };
467
491
  }
468
492
  };