@navikt/ds-react 7.1.2 → 7.2.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/cjs/dropdown/Menu/index.js +1 -1
- package/cjs/dropdown/Menu/index.js.map +1 -1
- package/cjs/form/checkbox/useCheckbox.js +3 -2
- package/cjs/form/checkbox/useCheckbox.js.map +1 -1
- package/cjs/form/combobox/ComboboxProvider.js +4 -1
- package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +3 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +33 -10
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/cjs/form/combobox/Input/Input.context.js +1 -1
- package/cjs/form/combobox/Input/Input.context.js.map +1 -1
- package/cjs/form/combobox/Input/Input.js +23 -10
- package/cjs/form/combobox/Input/Input.js.map +1 -1
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +2 -1
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
- package/cjs/form/combobox/customOptionsContext.js +2 -2
- package/cjs/form/combobox/customOptionsContext.js.map +1 -1
- package/cjs/form/file-upload/index.d.ts +4 -4
- package/cjs/form/file-upload/index.js +5 -5
- package/cjs/form/file-upload/index.js.map +1 -1
- package/cjs/form/radio/useRadio.js +3 -2
- package/cjs/form/radio/useRadio.js.map +1 -1
- package/cjs/form/search/Search.js +1 -1
- package/cjs/form/search/Search.js.map +1 -1
- package/cjs/form/switch/Switch.js +2 -1
- package/cjs/form/switch/Switch.js.map +1 -1
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +2 -2
- package/cjs/index.js.map +1 -1
- package/cjs/list/List.js +7 -2
- package/cjs/list/List.js.map +1 -1
- package/cjs/list/types.d.ts +1 -1
- package/cjs/modal/Modal.js +2 -2
- package/cjs/modal/Modal.js.map +1 -1
- package/cjs/overlays/dismissablelayer/DismissableLayer.js +2 -0
- package/cjs/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
- package/cjs/overlays/floating-menu/parts/RovingFocus.js +4 -4
- package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
- package/cjs/progress-bar/ProgressBar.js +6 -2
- package/cjs/progress-bar/ProgressBar.js.map +1 -1
- package/cjs/table/AnimateHeight.js +12 -14
- package/cjs/table/AnimateHeight.js.map +1 -1
- package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -1
- package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
- package/cjs/tabs/parts/tablist/useTabList.js +4 -4
- package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
- package/cjs/timeline/TimelineRow.js +9 -10
- package/cjs/timeline/TimelineRow.js.map +1 -1
- package/cjs/toggle-group/parts/useToggleItem.js +4 -4
- package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
- package/cjs/util/TextareaAutoSize.js +3 -2
- package/cjs/util/TextareaAutoSize.js.map +1 -1
- package/cjs/util/create-context.js +3 -3
- package/cjs/util/create-context.js.map +1 -1
- package/cjs/util/hooks/descendants/descendant.js +1 -1
- package/cjs/util/hooks/descendants/descendant.js.map +1 -1
- package/cjs/util/hooks/descendants/useDescendant.js +1 -1
- package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
- package/esm/dropdown/Menu/index.js +1 -1
- package/esm/dropdown/Menu/index.js.map +1 -1
- package/esm/form/checkbox/useCheckbox.js +3 -2
- package/esm/form/checkbox/useCheckbox.js.map +1 -1
- package/esm/form/combobox/ComboboxProvider.js +4 -1
- package/esm/form/combobox/ComboboxProvider.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +3 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +34 -11
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/esm/form/combobox/Input/Input.context.js +1 -1
- package/esm/form/combobox/Input/Input.context.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +23 -10
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +2 -1
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
- package/esm/form/combobox/customOptionsContext.js +2 -2
- package/esm/form/combobox/customOptionsContext.js.map +1 -1
- package/esm/form/file-upload/index.d.ts +4 -4
- package/esm/form/file-upload/index.js +4 -4
- package/esm/form/file-upload/index.js.map +1 -1
- package/esm/form/radio/useRadio.js +3 -2
- package/esm/form/radio/useRadio.js.map +1 -1
- package/esm/form/search/Search.js +1 -1
- package/esm/form/search/Search.js.map +1 -1
- package/esm/form/switch/Switch.js +2 -1
- package/esm/form/switch/Switch.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/list/List.js +7 -2
- package/esm/list/List.js.map +1 -1
- package/esm/list/types.d.ts +1 -1
- package/esm/modal/Modal.js +2 -2
- package/esm/modal/Modal.js.map +1 -1
- package/esm/overlays/dismissablelayer/DismissableLayer.js +2 -0
- package/esm/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
- package/esm/overlays/floating-menu/parts/RovingFocus.js +4 -4
- package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
- package/esm/progress-bar/ProgressBar.js +6 -2
- package/esm/progress-bar/ProgressBar.js.map +1 -1
- package/esm/table/AnimateHeight.js +12 -14
- package/esm/table/AnimateHeight.js.map +1 -1
- package/esm/tabs/parts/tablist/useScrollButtons.js +1 -1
- package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -1
- package/esm/tabs/parts/tablist/useTabList.js +4 -4
- package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
- package/esm/timeline/TimelineRow.js +9 -10
- package/esm/timeline/TimelineRow.js.map +1 -1
- package/esm/toggle-group/parts/useToggleItem.js +4 -4
- package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
- package/esm/util/TextareaAutoSize.js +3 -2
- package/esm/util/TextareaAutoSize.js.map +1 -1
- package/esm/util/create-context.js +3 -3
- package/esm/util/create-context.js.map +1 -1
- package/esm/util/hooks/descendants/descendant.js +1 -1
- package/esm/util/hooks/descendants/descendant.js.map +1 -1
- package/esm/util/hooks/descendants/useDescendant.js +1 -1
- package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
- package/package.json +5 -5
- package/src/dropdown/Menu/index.tsx +1 -1
- package/src/form/checkbox/useCheckbox.ts +2 -2
- package/src/form/combobox/ComboboxProvider.tsx +9 -1
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +1 -0
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +1 -1
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +42 -11
- package/src/form/combobox/Input/Input.context.tsx +1 -1
- package/src/form/combobox/Input/Input.tsx +19 -10
- package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +2 -1
- package/src/form/combobox/__tests__/combobox.test.tsx +36 -0
- package/src/form/combobox/customOptionsContext.tsx +2 -2
- package/src/form/file-upload/index.ts +4 -4
- package/src/form/radio/useRadio.ts +2 -2
- package/src/form/search/Search.tsx +1 -1
- package/src/form/switch/Switch.tsx +1 -1
- package/src/index.ts +1 -1
- package/src/list/List.tsx +12 -6
- package/src/list/types.ts +1 -1
- package/src/modal/Modal.tsx +2 -2
- package/src/overlays/dismissablelayer/DismissableLayer.tsx +3 -0
- package/src/overlays/floating-menu/parts/RovingFocus.tsx +4 -4
- package/src/progress-bar/ProgressBar.tsx +2 -0
- package/src/table/AnimateHeight.tsx +15 -16
- package/src/tabs/parts/tablist/useScrollButtons.ts +1 -1
- package/src/tabs/parts/tablist/useTabList.ts +4 -4
- package/src/timeline/TimelineRow.tsx +20 -21
- package/src/toggle-group/parts/useToggleItem.ts +4 -4
- package/src/util/TextareaAutoSize.tsx +3 -2
- package/src/util/create-context.tsx +3 -2
- package/src/util/hooks/descendants/descendant.ts +1 -1
- package/src/util/hooks/descendants/useDescendant.tsx +1 -1
|
@@ -37,7 +37,7 @@ export function createDescendantContext() {
|
|
|
37
37
|
useClientLayoutEffect(() => {
|
|
38
38
|
if (!ref.current)
|
|
39
39
|
return;
|
|
40
|
-
const dataIndex = Number(ref.current.dataset
|
|
40
|
+
const dataIndex = Number(ref.current.dataset.index);
|
|
41
41
|
if (index !== dataIndex && !Number.isNaN(dataIndex)) {
|
|
42
42
|
setIndex(dataIndex);
|
|
43
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDescendant.js","sourceRoot":"","sources":["../../../../src/util/hooks/descendants/useDescendant.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAqB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B;;GAEG;AACH,MAAM,UAAU,uBAAuB;IAIrC,MAAM,CAAC,0BAA0B,EAAE,qBAAqB,CAAC,GAAG,aAAa,CAEvE;QACA,IAAI,EAAE,qBAAqB;QAC3B,YAAY,EACV,+DAA+D;KAClE,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,CAC1B,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,oBAAC,0BAA0B,oBAAK,KAAK,CAAC,KAAK,GACxC,KAAK,CAAC,QAAQ,CACY,CAC9B,CACF,CAAC;IAEF;;;;;;OAMG;IACH,SAAS,cAAc,CAAC,OAA8B;QACpD,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;QAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAI,IAAI,CAAC,CAAC;QAE5B,qBAAqB,CAAC,GAAG,EAAE;YACzB,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,GAAG,CAAC,OAAO;oBAAE,OAAO;gBACzB,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC,CAAC;QACJ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,qBAAqB,CAAC,GAAG,EAAE;YACzB,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO;YACzB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"useDescendant.js","sourceRoot":"","sources":["../../../../src/util/hooks/descendants/useDescendant.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAqB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B;;GAEG;AACH,MAAM,UAAU,uBAAuB;IAIrC,MAAM,CAAC,0BAA0B,EAAE,qBAAqB,CAAC,GAAG,aAAa,CAEvE;QACA,IAAI,EAAE,qBAAqB;QAC3B,YAAY,EACV,+DAA+D;KAClE,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,CAC1B,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,oBAAC,0BAA0B,oBAAK,KAAK,CAAC,KAAK,GACxC,KAAK,CAAC,QAAQ,CACY,CAC9B,CACF,CAAC;IAEF;;;;;;OAMG;IACH,SAAS,cAAc,CAAC,OAA8B;QACpD,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;QAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAI,IAAI,CAAC,CAAC;QAE5B,qBAAqB,CAAC,GAAG,EAAE;YACzB,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,GAAG,CAAC,OAAO;oBAAE,OAAO;gBACzB,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC,CAAC;QACJ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,qBAAqB,CAAC,GAAG,EAAE;YACzB,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO;YACzB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;gBACpD,QAAQ,CAAC,SAAS,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,OAAO;YACzB,CAAC,CAAC,IAAI,CAAuB,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC3D,CAAC,CAAC,IAAI,CAAuB,WAAW,CAAC,QAAQ,CAAC,CAAC;QAErD,OAAO;YACL,WAAW;YACX,KAAK;YACL,YAAY,EAAE,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;YACrD,QAAQ,EAAE,SAAS,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;SACxC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,SAAS,eAAe;QACtB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,kBAAkB,EAAQ,CAAC,CAAC,OAAO,CAAC;QAEnE,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,OAAO;QACL,mBAAmB;QACnB,eAAe;QACf,+CAA+C;QAC/C,qBAAqB;QACrB,8EAA8E;QAC9E,eAAe;QACf,+BAA+B;QAC/B,cAAc;KACN,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.2.1",
|
|
4
4
|
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
|
5
5
|
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -583,7 +583,7 @@
|
|
|
583
583
|
"./package.json": "./package.json"
|
|
584
584
|
},
|
|
585
585
|
"scripts": {
|
|
586
|
-
"docgen": "yarn
|
|
586
|
+
"docgen": "yarn tsx ../../../scripts/docgen.ts",
|
|
587
587
|
"write-packagejson": "echo '{\"type\": \"module\"}' > esm/package.json",
|
|
588
588
|
"clean": "rimraf cjs esm",
|
|
589
589
|
"build": "concurrently \"tsc -p tsconfig.build.json\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" ",
|
|
@@ -594,8 +594,8 @@
|
|
|
594
594
|
"dependencies": {
|
|
595
595
|
"@floating-ui/react": "0.25.4",
|
|
596
596
|
"@floating-ui/react-dom": "^2.0.9",
|
|
597
|
-
"@navikt/aksel-icons": "^7.1
|
|
598
|
-
"@navikt/ds-tokens": "^7.1
|
|
597
|
+
"@navikt/aksel-icons": "^7.2.1",
|
|
598
|
+
"@navikt/ds-tokens": "^7.2.1",
|
|
599
599
|
"clsx": "^2.1.0",
|
|
600
600
|
"date-fns": "^3.0.0",
|
|
601
601
|
"react-day-picker": "8.10.0"
|
|
@@ -614,8 +614,8 @@
|
|
|
614
614
|
"react-router-dom": "^6.3.0",
|
|
615
615
|
"rimraf": "6.0.1",
|
|
616
616
|
"swr": "^1.1.2",
|
|
617
|
-
"ts-node": "^10.9.1",
|
|
618
617
|
"tsc-alias": "1.8.8",
|
|
618
|
+
"tsx": "^4.19.1",
|
|
619
619
|
"typescript": "^5.1.6",
|
|
620
620
|
"vitest": "^1.2.2"
|
|
621
621
|
},
|
|
@@ -44,8 +44,8 @@ const useCheckbox = (props: CheckboxProps) => {
|
|
|
44
44
|
if (readOnly) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
props.onChange
|
|
48
|
-
checkboxGroup
|
|
47
|
+
props.onChange?.(e);
|
|
48
|
+
checkboxGroup?.toggleValue(props.value);
|
|
49
49
|
},
|
|
50
50
|
onClick: (e) => {
|
|
51
51
|
if (readOnly) {
|
|
@@ -51,13 +51,21 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
|
|
51
51
|
value,
|
|
52
52
|
onChange,
|
|
53
53
|
onClear,
|
|
54
|
-
shouldAutocomplete,
|
|
54
|
+
shouldAutocomplete: externalShouldAutocomplete,
|
|
55
55
|
size,
|
|
56
56
|
...rest
|
|
57
57
|
} = props;
|
|
58
58
|
const options = mapToComboboxOptionArray(externalOptions) || [];
|
|
59
59
|
const filteredOptions = mapToComboboxOptionArray(externalFilteredOptions);
|
|
60
60
|
const selectedOptions = mapToComboboxOptionArray(externalSelectedOptions);
|
|
61
|
+
|
|
62
|
+
const userAgent =
|
|
63
|
+
typeof navigator === "undefined" ? "" : navigator.userAgent;
|
|
64
|
+
const isFirefoxOnAndroid =
|
|
65
|
+
userAgent.includes("Android") && userAgent.includes("Firefox/");
|
|
66
|
+
const shouldAutocomplete =
|
|
67
|
+
!isFirefoxOnAndroid && externalShouldAutocomplete;
|
|
68
|
+
|
|
61
69
|
return (
|
|
62
70
|
<InputContextProvider
|
|
63
71
|
value={{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
|
|
3
3
|
export type VirtualFocusType = {
|
|
4
4
|
activeElement: HTMLElement | undefined;
|
|
@@ -10,6 +10,9 @@ export type VirtualFocusType = {
|
|
|
10
10
|
moveFocusToElement: (id: string) => void;
|
|
11
11
|
moveFocusToTop: () => void;
|
|
12
12
|
moveFocusToBottom: () => void;
|
|
13
|
+
moveFocusUpBy: (numberOfElements: number) => void;
|
|
14
|
+
moveFocusDownBy: (numberOfElements: number) => void;
|
|
15
|
+
resetFocus: () => void;
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
const useVirtualFocus = (
|
|
@@ -40,11 +43,6 @@ const useVirtualFocus = (
|
|
|
40
43
|
: false;
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
const _moveFocusAndScrollTo = (_element?: HTMLElement) => {
|
|
44
|
-
setActiveElement(_element);
|
|
45
|
-
_element?.scrollIntoView?.({ block: "nearest" });
|
|
46
|
-
};
|
|
47
|
-
|
|
48
46
|
const moveFocusUp = () => {
|
|
49
47
|
if (!activeElement) {
|
|
50
48
|
return;
|
|
@@ -55,14 +53,14 @@ const useVirtualFocus = (
|
|
|
55
53
|
if (_currentIndex === 0) {
|
|
56
54
|
setActiveElement(undefined);
|
|
57
55
|
} else {
|
|
58
|
-
|
|
56
|
+
setActiveElement(elementAbove);
|
|
59
57
|
}
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
const moveFocusDown = () => {
|
|
63
61
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
64
62
|
if (!activeElement) {
|
|
65
|
-
|
|
63
|
+
setActiveElement(elementsAbleToReceiveFocus[0]);
|
|
66
64
|
return;
|
|
67
65
|
}
|
|
68
66
|
const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
|
@@ -70,13 +68,17 @@ const useVirtualFocus = (
|
|
|
70
68
|
return;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
setActiveElement(elementsAbleToReceiveFocus[_currentIndex + 1]);
|
|
74
72
|
};
|
|
75
73
|
|
|
76
|
-
const
|
|
74
|
+
const resetFocus = () => setActiveElement(undefined);
|
|
75
|
+
const moveFocusToTop = () => {
|
|
76
|
+
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
77
|
+
setActiveElement(elementsAbleToReceiveFocus[0]);
|
|
78
|
+
};
|
|
77
79
|
const moveFocusToBottom = () => {
|
|
78
80
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
79
|
-
|
|
81
|
+
setActiveElement(
|
|
80
82
|
elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1],
|
|
81
83
|
);
|
|
82
84
|
};
|
|
@@ -89,6 +91,32 @@ const useVirtualFocus = (
|
|
|
89
91
|
}
|
|
90
92
|
};
|
|
91
93
|
|
|
94
|
+
const moveFocusUpBy = (numberOfElements: number) => {
|
|
95
|
+
if (!activeElement) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
99
|
+
const currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
|
100
|
+
const newIndex = Math.max(currentIndex - numberOfElements, 0);
|
|
101
|
+
setActiveElement(elementsAbleToReceiveFocus[newIndex]);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const moveFocusDownBy = (numberOfElements: number) => {
|
|
105
|
+
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
106
|
+
const currentIndex = activeElement
|
|
107
|
+
? elementsAbleToReceiveFocus.indexOf(activeElement)
|
|
108
|
+
: -1;
|
|
109
|
+
const newIndex = Math.min(
|
|
110
|
+
currentIndex + numberOfElements,
|
|
111
|
+
elementsAbleToReceiveFocus.length - 1,
|
|
112
|
+
);
|
|
113
|
+
setActiveElement(elementsAbleToReceiveFocus[newIndex]);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
activeElement?.scrollIntoView?.({ block: "nearest" });
|
|
118
|
+
}, [activeElement]);
|
|
119
|
+
|
|
92
120
|
return {
|
|
93
121
|
activeElement,
|
|
94
122
|
getElementById,
|
|
@@ -99,6 +127,9 @@ const useVirtualFocus = (
|
|
|
99
127
|
moveFocusToElement,
|
|
100
128
|
moveFocusToTop,
|
|
101
129
|
moveFocusToBottom,
|
|
130
|
+
moveFocusUpBy,
|
|
131
|
+
moveFocusDownBy,
|
|
132
|
+
resetFocus,
|
|
102
133
|
};
|
|
103
134
|
};
|
|
104
135
|
|
|
@@ -99,7 +99,7 @@ const InputProvider = ({ children, value: props }: Props) => {
|
|
|
99
99
|
setInternalValue("");
|
|
100
100
|
setSearchTerm("");
|
|
101
101
|
},
|
|
102
|
-
[externalOnChange, onClear
|
|
102
|
+
[externalOnChange, onClear],
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
const focusInput = useCallback(() => {
|
|
@@ -134,14 +134,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
134
134
|
case "Accept":
|
|
135
135
|
onEnter(e);
|
|
136
136
|
break;
|
|
137
|
-
case "Home":
|
|
138
|
-
toggleIsListOpen(false);
|
|
139
|
-
virtualFocus.moveFocusToTop();
|
|
140
|
-
break;
|
|
141
|
-
case "End":
|
|
142
|
-
toggleIsListOpen(true);
|
|
143
|
-
virtualFocus.moveFocusToBottom();
|
|
144
|
-
break;
|
|
145
137
|
default:
|
|
146
138
|
break;
|
|
147
139
|
}
|
|
@@ -202,6 +194,24 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
202
194
|
}
|
|
203
195
|
virtualFocus.moveFocusUp();
|
|
204
196
|
}
|
|
197
|
+
} else if (e.key === "Home") {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
virtualFocus.moveFocusToTop();
|
|
200
|
+
} else if (e.key === "End") {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
if (virtualFocus.activeElement === null || !isListOpen) {
|
|
203
|
+
toggleIsListOpen(true);
|
|
204
|
+
}
|
|
205
|
+
virtualFocus.moveFocusToBottom();
|
|
206
|
+
} else if (e.key === "PageUp") {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
virtualFocus.moveFocusUpBy(6);
|
|
209
|
+
} else if (e.key === "PageDown") {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
if (virtualFocus.activeElement === null || !isListOpen) {
|
|
212
|
+
toggleIsListOpen(true);
|
|
213
|
+
}
|
|
214
|
+
virtualFocus.moveFocusDownBy(6);
|
|
205
215
|
}
|
|
206
216
|
},
|
|
207
217
|
[
|
|
@@ -230,10 +240,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
230
240
|
} else if (filteredOptions.length === 0) {
|
|
231
241
|
toggleIsListOpen(false);
|
|
232
242
|
}
|
|
233
|
-
virtualFocus.moveFocusToTop();
|
|
234
243
|
onChange(newValue);
|
|
235
244
|
},
|
|
236
|
-
[filteredOptions.length,
|
|
245
|
+
[filteredOptions.length, onChange, toggleIsListOpen],
|
|
237
246
|
);
|
|
238
247
|
|
|
239
248
|
return (
|
|
@@ -105,9 +105,10 @@ const SelectedOptionsProvider = ({
|
|
|
105
105
|
(!!maxSelected?.limit && selectedOptions.length >= maxSelected.limit) ||
|
|
106
106
|
(!isMultiSelect && selectedOptions.length > 0);
|
|
107
107
|
|
|
108
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: We explicitly want to run this effect when selectedOptions changes to match the view with the selected options.
|
|
108
109
|
useEffect(() => {
|
|
109
110
|
setHideCaret(isLimitReached);
|
|
110
|
-
}, [selectedOptions, setHideCaret
|
|
111
|
+
}, [isLimitReached, selectedOptions, setHideCaret]);
|
|
111
112
|
|
|
112
113
|
const toggleOption = useCallback(
|
|
113
114
|
(
|
|
@@ -291,4 +291,40 @@ describe("Render combobox", () => {
|
|
|
291
291
|
);
|
|
292
292
|
});
|
|
293
293
|
});
|
|
294
|
+
|
|
295
|
+
describe("has keyboard navigation", () => {
|
|
296
|
+
test("for PageDown and PageUp", async () => {
|
|
297
|
+
render(<App options={options} />);
|
|
298
|
+
|
|
299
|
+
const combobox = screen.getByRole("combobox", {
|
|
300
|
+
name: "Hva er dine favorittfrukter?",
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const pressKey = async (key: string) => {
|
|
304
|
+
await act(async () => {
|
|
305
|
+
await userEvent.keyboard(`{${key}}`);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const hasVirtualFocus = (option: string) =>
|
|
310
|
+
expect(combobox.getAttribute("aria-activedescendant")).toBe(
|
|
311
|
+
screen.getByRole("option", { name: option }).id,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
await act(async () => {
|
|
315
|
+
await userEvent.click(combobox);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await pressKey("ArrowDown");
|
|
319
|
+
hasVirtualFocus("apple");
|
|
320
|
+
await pressKey("PageDown");
|
|
321
|
+
hasVirtualFocus("mango");
|
|
322
|
+
await pressKey("PageDown");
|
|
323
|
+
hasVirtualFocus("watermelon");
|
|
324
|
+
await pressKey("PageUp");
|
|
325
|
+
hasVirtualFocus("mango");
|
|
326
|
+
await pressKey("PageUp");
|
|
327
|
+
hasVirtualFocus("apple");
|
|
328
|
+
});
|
|
329
|
+
});
|
|
294
330
|
});
|
|
@@ -35,7 +35,7 @@ const CustomOptionsProvider = ({
|
|
|
35
35
|
);
|
|
36
36
|
focusInput();
|
|
37
37
|
},
|
|
38
|
-
[focusInput
|
|
38
|
+
[focusInput],
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
const addCustomOption = useCallback(
|
|
@@ -47,7 +47,7 @@ const CustomOptionsProvider = ({
|
|
|
47
47
|
}
|
|
48
48
|
focusInput();
|
|
49
49
|
},
|
|
50
|
-
[focusInput, isMultiSelect
|
|
50
|
+
[focusInput, isMultiSelect],
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
const customOptionsState = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as
|
|
2
|
+
export { default as FileUpload } from "./FileUpload";
|
|
3
|
+
export { default as FileUploadDropzone } from "./parts/dropzone/Dropzone";
|
|
4
4
|
export { type FileUploadDropzoneProps } from "./parts/dropzone/dropzone.types";
|
|
5
5
|
export {
|
|
6
|
-
default as
|
|
6
|
+
default as FileUploadTrigger,
|
|
7
7
|
type FileUploadTriggerProps,
|
|
8
8
|
} from "./parts/Trigger";
|
|
9
9
|
export {
|
|
@@ -15,7 +15,7 @@ export {
|
|
|
15
15
|
type FileRejectionReason,
|
|
16
16
|
} from "./FileUpload.types";
|
|
17
17
|
export {
|
|
18
|
-
default as
|
|
18
|
+
default as FileUploadItem,
|
|
19
19
|
type FileUploadItemProps,
|
|
20
20
|
} from "./parts/item/Item";
|
|
21
21
|
export { type FileItem, type FileMetadata } from "./parts/item/Item.types";
|
|
@@ -41,8 +41,8 @@ export const useRadio = (props: RadioProps) => {
|
|
|
41
41
|
if (readOnly) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
props.onChange
|
|
45
|
-
radioGroup?.onChange
|
|
44
|
+
props.onChange?.(e);
|
|
45
|
+
radioGroup?.onChange?.(props.value);
|
|
46
46
|
},
|
|
47
47
|
onClick: (e) => {
|
|
48
48
|
if (readOnly) {
|
|
@@ -144,7 +144,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
144
144
|
(event: SearchClearEvent) => {
|
|
145
145
|
onClear?.(event);
|
|
146
146
|
handleChange("");
|
|
147
|
-
searchRef.current
|
|
147
|
+
searchRef.current?.focus?.();
|
|
148
148
|
},
|
|
149
149
|
[handleChange, onClear],
|
|
150
150
|
);
|
package/src/index.ts
CHANGED
|
@@ -127,7 +127,7 @@ export {
|
|
|
127
127
|
export { ErrorSummary, type ErrorSummaryProps } from "./form/error-summary";
|
|
128
128
|
export { Fieldset, type FieldsetProps } from "./form/fieldset";
|
|
129
129
|
export {
|
|
130
|
-
|
|
130
|
+
FileUpload,
|
|
131
131
|
type FileAccepted,
|
|
132
132
|
type FileItem,
|
|
133
133
|
type FileMetadata,
|
package/src/list/List.tsx
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
2
|
import React, { forwardRef, useContext } from "react";
|
|
3
|
-
import { BodyLong, BodyShort, Heading } from "../typography";
|
|
3
|
+
import { BodyLong, BodyShort, Heading, HeadingProps } from "../typography";
|
|
4
4
|
import { ListItem } from "./ListItem";
|
|
5
5
|
import { ListContext } from "./context";
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
7
|
import type { ListItemProps, ListProps } from "./types";
|
|
8
8
|
|
|
9
|
+
const headingSizeMap: Record<
|
|
10
|
+
Exclude<ListProps["size"], undefined>,
|
|
11
|
+
HeadingProps["size"]
|
|
12
|
+
> = {
|
|
13
|
+
small: "xsmall",
|
|
14
|
+
medium: "small",
|
|
15
|
+
large: "medium",
|
|
16
|
+
};
|
|
17
|
+
|
|
9
18
|
export interface ListComponent
|
|
10
19
|
extends React.ForwardRefExoticComponent<
|
|
11
20
|
ListProps & React.RefAttributes<HTMLDivElement>
|
|
@@ -58,15 +67,12 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
|
|
|
58
67
|
<BodyLong
|
|
59
68
|
as="div"
|
|
60
69
|
{...rest}
|
|
61
|
-
size={
|
|
70
|
+
size={listSize}
|
|
62
71
|
ref={ref}
|
|
63
72
|
className={cl("navds-list", `navds-list--${listSize}`, className)}
|
|
64
73
|
>
|
|
65
74
|
{title && (
|
|
66
|
-
<Heading
|
|
67
|
-
size={listSize === "medium" ? "small" : "xsmall"}
|
|
68
|
-
as={headingTag}
|
|
69
|
-
>
|
|
75
|
+
<Heading size={headingSizeMap[listSize]} as={headingTag}>
|
|
70
76
|
{title}
|
|
71
77
|
</Heading>
|
|
72
78
|
)}
|
package/src/list/types.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
22
22
|
* Changes margin-block on list and font size on items.
|
|
23
23
|
* @default "medium"
|
|
24
24
|
*/
|
|
25
|
-
size?: "medium" | "
|
|
25
|
+
size?: "small" | "medium" | "large";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
package/src/modal/Modal.tsx
CHANGED
|
@@ -127,7 +127,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
|
|
127
127
|
// We have to use JS because it doesn't work to set it with a prop (React bug?)
|
|
128
128
|
// Currently doesn't seem to work in Chrome. See also Tooltip.tsx
|
|
129
129
|
if (modalRef.current && portalNode) modalRef.current.autofocus = true;
|
|
130
|
-
}, [
|
|
130
|
+
}, [portalNode]);
|
|
131
131
|
|
|
132
132
|
useEffect(() => {
|
|
133
133
|
// We need to have this in a useEffect so that the content renders before the modal is displayed,
|
|
@@ -140,7 +140,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
|
|
140
140
|
modalRef.current.close();
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
}, [
|
|
143
|
+
}, [portalNode, open]);
|
|
144
144
|
|
|
145
145
|
useBodyScrollLock(modalRef, portalNode, isNested);
|
|
146
146
|
|
|
@@ -337,6 +337,8 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
|
|
|
337
337
|
* If `disableOutsidePointerEvents` is true,
|
|
338
338
|
* we want to disable pointer events on the body when the first layer is opened.
|
|
339
339
|
*/
|
|
340
|
+
|
|
341
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Every time the descendants change, we want to update the body pointer events since we might have added or removed a layer.
|
|
340
342
|
useEffect(() => {
|
|
341
343
|
if (!node || !enabled || !disableOutsidePointerEvents) return;
|
|
342
344
|
|
|
@@ -362,6 +364,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
|
|
|
362
364
|
/**
|
|
363
365
|
* To make sure pointerEvents are enabled for all parents and siblings when the layer is removed from the DOM
|
|
364
366
|
*/
|
|
367
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: We explicitly want to run this on unmount, including every time the node updates to make sure we don't lock the application behind pointer-events: none.
|
|
365
368
|
useEffect(() => {
|
|
366
369
|
return () => descendants.values().forEach((x) => x.forceUpdate());
|
|
367
370
|
}, [descendants, node]);
|
|
@@ -55,19 +55,19 @@ const RovingFocus = forwardRef<HTMLDivElement, RovingFocusProps>(
|
|
|
55
55
|
|
|
56
56
|
const nextItem = () => {
|
|
57
57
|
const next = descendants.nextEnabled(idx, loop);
|
|
58
|
-
next
|
|
58
|
+
next?.node?.focus();
|
|
59
59
|
};
|
|
60
60
|
const prevItem = () => {
|
|
61
61
|
const prev = descendants.prevEnabled(idx, loop);
|
|
62
|
-
prev
|
|
62
|
+
prev?.node?.focus();
|
|
63
63
|
};
|
|
64
64
|
const firstItem = () => {
|
|
65
65
|
const first = descendants.firstEnabled();
|
|
66
|
-
first
|
|
66
|
+
first?.node?.focus();
|
|
67
67
|
};
|
|
68
68
|
const lastItem = () => {
|
|
69
69
|
const last = descendants.lastEnabled();
|
|
70
|
-
last
|
|
70
|
+
last?.node?.focus();
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const keyMap: Record<string, React.KeyboardEventHandler> = {
|
|
@@ -107,6 +107,7 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
107
107
|
}, [simulated?.seconds]);
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
|
+
/* biome-ignore lint/a11y/useFocusableInteractive: Progressbar is not interactive. */
|
|
110
111
|
<div
|
|
111
112
|
ref={ref}
|
|
112
113
|
className={cl(
|
|
@@ -121,6 +122,7 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
121
122
|
? `Fremdrift kan ikke beregnes, antatt tid er: ${simulated?.seconds} sekunder`
|
|
122
123
|
: `${Math.round(value)} av ${Math.round(valueMax)}`
|
|
123
124
|
}
|
|
125
|
+
// biome-ignore lint/a11y/useAriaPropsForRole: We found that adding valueMin was not needed
|
|
124
126
|
role="progressbar"
|
|
125
127
|
aria-labelledby={ariaLabelledBy}
|
|
126
128
|
aria-label={ariaLabel}
|
|
@@ -74,37 +74,36 @@ const AnimateHeight: React.FC<AnimateHeightProps> = ({
|
|
|
74
74
|
const animationClassesTimeoutID = useRef<Timeout>();
|
|
75
75
|
const timeoutID = useRef<Timeout>();
|
|
76
76
|
|
|
77
|
-
const
|
|
77
|
+
const initialHeight = useRef<Height>(height);
|
|
78
|
+
const initialOverflow = useRef<Overflow>("visible");
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
let initOverflow: Overflow = "visible";
|
|
80
|
+
const duration = prefersReducedMotion ? 0 : userDuration;
|
|
81
81
|
|
|
82
|
-
if (typeof
|
|
82
|
+
if (typeof initialHeight.current === "number") {
|
|
83
83
|
// Reset negative height to 0
|
|
84
84
|
if (typeof height !== "string") {
|
|
85
|
-
|
|
85
|
+
initialHeight.current = height < 0 ? 0 : height;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
} else if (isPercentage(
|
|
87
|
+
initialOverflow.current = "hidden";
|
|
88
|
+
} else if (isPercentage(initialHeight.current)) {
|
|
89
89
|
// If value is string "0%" make sure we convert it to number 0
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
initialHeight.current = height === "0%" ? 0 : height;
|
|
91
|
+
initialOverflow.current = "hidden";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const [currentHeight, setCurrentHeight] = useState<Height>(
|
|
95
|
-
|
|
94
|
+
const [currentHeight, setCurrentHeight] = useState<Height>(
|
|
95
|
+
initialHeight.current,
|
|
96
|
+
);
|
|
97
|
+
const [overflow, setOverflow] = useState<Overflow>(initialOverflow.current);
|
|
96
98
|
const [useTransitions, setUseTransitions] = useState<boolean>(false);
|
|
97
99
|
|
|
98
|
-
// ------------------ Did mount
|
|
99
100
|
useEffect(() => {
|
|
100
101
|
// Hide content if height is 0 (to prevent tabbing into it)
|
|
101
|
-
hideContent(contentElement.current,
|
|
102
|
-
|
|
103
|
-
// This should be explicitly run only on mount
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
102
|
+
hideContent(contentElement.current, initialHeight.current);
|
|
105
103
|
}, []);
|
|
106
104
|
|
|
107
105
|
// ------------------ Height update
|
|
106
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: This should be explicitly run only on height change
|
|
108
107
|
useEffect(() => {
|
|
109
108
|
if (height !== prevHeight.current && contentElement.current) {
|
|
110
109
|
showContent(contentElement.current, prevHeight.current);
|
|
@@ -41,7 +41,7 @@ export function useScrollButtons(listRef: React.RefObject<HTMLDivElement>) {
|
|
|
41
41
|
|
|
42
42
|
return () => {
|
|
43
43
|
win.removeEventListener("resize", handleResize);
|
|
44
|
-
resizeObserver
|
|
44
|
+
resizeObserver?.disconnect();
|
|
45
45
|
updateScrollButtonState.clear();
|
|
46
46
|
};
|
|
47
47
|
}, [listRef, updateScrollButtonState]);
|
|
@@ -26,19 +26,19 @@ export function useTabList() {
|
|
|
26
26
|
|
|
27
27
|
const nextTab = () => {
|
|
28
28
|
const next = descendants.nextEnabled(idx, loop);
|
|
29
|
-
next
|
|
29
|
+
next?.node?.focus();
|
|
30
30
|
};
|
|
31
31
|
const prevTab = () => {
|
|
32
32
|
const prev = descendants.prevEnabled(idx, loop);
|
|
33
|
-
prev
|
|
33
|
+
prev?.node?.focus();
|
|
34
34
|
};
|
|
35
35
|
const firstTab = () => {
|
|
36
36
|
const first = descendants.firstEnabled();
|
|
37
|
-
first
|
|
37
|
+
first?.node?.focus();
|
|
38
38
|
};
|
|
39
39
|
const lastTab = () => {
|
|
40
40
|
const last = descendants.lastEnabled();
|
|
41
|
-
last
|
|
41
|
+
last?.node?.focus();
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
const keyMap: Record<string, React.KeyboardEventHandler> = {
|