Skip to content

Commit a4f8207

Browse files
authored
Merge 80b42da into cd3859a
2 parents cd3859a + 80b42da commit a4f8207

File tree

10 files changed

+699
-748
lines changed

10 files changed

+699
-748
lines changed

.changeset/weak-dodos-attend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@navikt/ds-react": patch
3+
---
4+
5+
Added useVirtualFocus hook - used in Combobox for now

@navikt/core/react/src/form/combobox/Combobox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const Combobox = forwardRef<
3333

3434
const toggleListButtonRef = useRef<HTMLButtonElement>(null);
3535

36-
const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
36+
const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
3737
const { selectedOptions } = useSelectedOptionsContext();
3838

3939
const {
@@ -92,7 +92,7 @@ export const Combobox = forwardRef<
9292
"navds-combobox__wrapper-inner navds-text-field__input",
9393
{
9494
"navds-combobox__wrapper-inner--virtually-unfocused":
95-
currentOption !== null,
95+
activeDecendantId !== null,
9696
}
9797
)}
9898
onClick={focusInput}

@navikt/core/react/src/form/combobox/ComboboxProvider.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
6565
size,
6666
}}
6767
>
68-
<CustomOptionsProvider>
68+
<CustomOptionsProvider value={{ isMultiSelect }}>
6969
<SelectedOptionsProvider
7070
value={{
7171
allowNewValues,
@@ -81,7 +81,6 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
8181
filteredOptions,
8282
isListOpen,
8383
isLoading,
84-
isMultiSelect,
8584
options,
8685
}}
8786
>

@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsCon
66
import { useInputContext } from "../Input/inputContext";
77
import { Loader } from "../../../loader";
88
import { BodyShort, Label } from "../../../typography";
9+
import filteredOptionsUtil from "./filtered-options-util";
910

1011
const FilteredOptions = () => {
1112
const {
@@ -18,25 +19,25 @@ const FilteredOptions = () => {
1819
isLoading,
1920
isListOpen,
2021
filteredOptions,
21-
filteredOptionsIndex,
22-
filteredOptionsRef,
22+
setFilteredOptionsRef,
2323
isMouseLastUsedInputDevice,
2424
setIsMouseLastUsedInputDevice,
2525
isValueNew,
26-
setFilteredOptionsIndex,
2726
toggleIsListOpen,
27+
activeDecendantId,
28+
virtualFocus,
2829
} = useFilteredOptionsContext();
2930
const { isMultiSelect, selectedOptions, toggleOption } =
3031
useSelectedOptionsContext();
3132

3233
return (
3334
<ul
34-
ref={filteredOptionsRef}
35+
ref={setFilteredOptionsRef}
3536
className={cl("navds-combobox__list", {
3637
"navds-combobox__list--closed": !isListOpen,
3738
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
3839
})}
39-
id={`${id}-filtered-options`}
40+
id={filteredOptionsUtil.getFilteredOptionsId(id)}
4041
role="listbox"
4142
tabIndex={-1}
4243
>
@@ -45,7 +46,8 @@ const FilteredOptions = () => {
4546
className="navds-combobox__list-item--loading"
4647
role="option"
4748
aria-selected={false}
48-
id={`${id}-is-loading`}
49+
id={filteredOptionsUtil.getIsLoadingId(id)}
50+
data-no-focus="true"
4951
>
5052
<Loader aria-label="Søker..." />
5153
</li>
@@ -54,8 +56,12 @@ const FilteredOptions = () => {
5456
<li
5557
tabIndex={-1}
5658
onMouseMove={() => {
57-
if (filteredOptionsIndex !== -1) {
58-
setFilteredOptionsIndex(-1);
59+
if (
60+
activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
61+
) {
62+
virtualFocus.moveFocusToElement(
63+
filteredOptionsUtil.getAddNewOptionId(id)
64+
);
5965
setIsMouseLastUsedInputDevice(true);
6066
}
6167
}}
@@ -64,10 +70,10 @@ const FilteredOptions = () => {
6470
if (!isMultiSelect && !selectedOptions.includes(value))
6571
toggleIsListOpen(false);
6672
}}
67-
id={`${id}-combobox-new-option`}
73+
id={filteredOptionsUtil.getAddNewOptionId(id)}
6874
className={cl("navds-combobox__list-item__new-option", {
6975
"navds-combobox__list-item__new-option--focus":
70-
filteredOptionsIndex === -1,
76+
activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
7177
})}
7278
role="option"
7379
aria-selected={false}
@@ -86,24 +92,30 @@ const FilteredOptions = () => {
8692
className="navds-combobox__list-item__no-options"
8793
role="option"
8894
aria-selected={false}
89-
id={`${id}-no-hits`}
95+
id={filteredOptionsUtil.getNoHitsId(id)}
96+
data-no-focus="true"
9097
>
9198
Ingen søketreff
9299
</li>
93100
)}
94-
{filteredOptions.map((option, index) => (
101+
{filteredOptions.map((option) => (
95102
<li
96103
className={cl("navds-combobox__list-item", {
97-
"navds-combobox__list-item--focus": index === filteredOptionsIndex,
104+
"navds-combobox__list-item--focus":
105+
activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
98106
"navds-combobox__list-item--selected":
99107
selectedOptions.includes(option),
100108
})}
101-
id={`${id}-option-${option.replace(" ", "-")}`}
109+
id={filteredOptionsUtil.getOptionId(id, option)}
102110
key={option}
103111
tabIndex={-1}
104112
onMouseMove={() => {
105-
if (filteredOptionsIndex !== index) {
106-
setFilteredOptionsIndex(index);
113+
if (
114+
activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
115+
) {
116+
virtualFocus.moveFocusToElement(
117+
filteredOptionsUtil.getOptionId(id, option)
118+
);
107119
setIsMouseLastUsedInputDevice(true);
108120
}
109121
}}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const normalizeText = (text: string): string =>
2+
typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
3+
4+
const isPartOfText = (value, text) =>
5+
normalizeText(text).startsWith(normalizeText(value ?? ""));
6+
7+
const isValueInList = (value, list) =>
8+
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
9+
10+
const getMatchingValuesFromList = (value, list) =>
11+
list?.filter((listItem) => isPartOfText(value, listItem));
12+
13+
const getFilteredOptionsId = (comboboxId: string) =>
14+
`${comboboxId}-filtered-options`;
15+
16+
const getOptionId = (comboboxId: string, option: string) =>
17+
`${comboboxId.toLocaleLowerCase()}-option-${option
18+
.replace(" ", "-")
19+
.toLocaleLowerCase()}`;
20+
21+
const getAddNewOptionId = (comboboxId: string) =>
22+
`${comboboxId}-combobox-new-option`;
23+
24+
const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`;
25+
26+
const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`;
27+
28+
export default {
29+
normalizeText,
30+
isPartOfText,
31+
isValueInList,
32+
getMatchingValuesFromList,
33+
getFilteredOptionsId,
34+
getAddNewOptionId,
35+
getOptionId,
36+
getIsLoadingId,
37+
getNoHitsId,
38+
};

0 commit comments

Comments
 (0)