@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.
Files changed (156) hide show
  1. package/cjs/dropdown/Menu/index.js +1 -1
  2. package/cjs/dropdown/Menu/index.js.map +1 -1
  3. package/cjs/form/checkbox/useCheckbox.js +3 -2
  4. package/cjs/form/checkbox/useCheckbox.js.map +1 -1
  5. package/cjs/form/combobox/ComboboxProvider.js +4 -1
  6. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  7. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +3 -1
  8. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  9. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
  10. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  11. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
  12. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +33 -10
  13. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  14. package/cjs/form/combobox/Input/Input.context.js +1 -1
  15. package/cjs/form/combobox/Input/Input.context.js.map +1 -1
  16. package/cjs/form/combobox/Input/Input.js +23 -10
  17. package/cjs/form/combobox/Input/Input.js.map +1 -1
  18. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +2 -1
  19. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  20. package/cjs/form/combobox/customOptionsContext.js +2 -2
  21. package/cjs/form/combobox/customOptionsContext.js.map +1 -1
  22. package/cjs/form/file-upload/index.d.ts +4 -4
  23. package/cjs/form/file-upload/index.js +5 -5
  24. package/cjs/form/file-upload/index.js.map +1 -1
  25. package/cjs/form/radio/useRadio.js +3 -2
  26. package/cjs/form/radio/useRadio.js.map +1 -1
  27. package/cjs/form/search/Search.js +1 -1
  28. package/cjs/form/search/Search.js.map +1 -1
  29. package/cjs/form/switch/Switch.js +2 -1
  30. package/cjs/form/switch/Switch.js.map +1 -1
  31. package/cjs/index.d.ts +1 -1
  32. package/cjs/index.js +2 -2
  33. package/cjs/index.js.map +1 -1
  34. package/cjs/list/List.js +7 -2
  35. package/cjs/list/List.js.map +1 -1
  36. package/cjs/list/types.d.ts +1 -1
  37. package/cjs/modal/Modal.js +2 -2
  38. package/cjs/modal/Modal.js.map +1 -1
  39. package/cjs/overlays/dismissablelayer/DismissableLayer.js +2 -0
  40. package/cjs/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  41. package/cjs/overlays/floating-menu/parts/RovingFocus.js +4 -4
  42. package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  43. package/cjs/progress-bar/ProgressBar.js +6 -2
  44. package/cjs/progress-bar/ProgressBar.js.map +1 -1
  45. package/cjs/table/AnimateHeight.js +12 -14
  46. package/cjs/table/AnimateHeight.js.map +1 -1
  47. package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -1
  48. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  49. package/cjs/tabs/parts/tablist/useTabList.js +4 -4
  50. package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
  51. package/cjs/timeline/TimelineRow.js +9 -10
  52. package/cjs/timeline/TimelineRow.js.map +1 -1
  53. package/cjs/toggle-group/parts/useToggleItem.js +4 -4
  54. package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
  55. package/cjs/util/TextareaAutoSize.js +3 -2
  56. package/cjs/util/TextareaAutoSize.js.map +1 -1
  57. package/cjs/util/create-context.js +3 -3
  58. package/cjs/util/create-context.js.map +1 -1
  59. package/cjs/util/hooks/descendants/descendant.js +1 -1
  60. package/cjs/util/hooks/descendants/descendant.js.map +1 -1
  61. package/cjs/util/hooks/descendants/useDescendant.js +1 -1
  62. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  63. package/esm/dropdown/Menu/index.js +1 -1
  64. package/esm/dropdown/Menu/index.js.map +1 -1
  65. package/esm/form/checkbox/useCheckbox.js +3 -2
  66. package/esm/form/checkbox/useCheckbox.js.map +1 -1
  67. package/esm/form/combobox/ComboboxProvider.js +4 -1
  68. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  69. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +3 -1
  70. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  71. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
  72. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  73. package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
  74. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +34 -11
  75. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  76. package/esm/form/combobox/Input/Input.context.js +1 -1
  77. package/esm/form/combobox/Input/Input.context.js.map +1 -1
  78. package/esm/form/combobox/Input/Input.js +23 -10
  79. package/esm/form/combobox/Input/Input.js.map +1 -1
  80. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +2 -1
  81. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  82. package/esm/form/combobox/customOptionsContext.js +2 -2
  83. package/esm/form/combobox/customOptionsContext.js.map +1 -1
  84. package/esm/form/file-upload/index.d.ts +4 -4
  85. package/esm/form/file-upload/index.js +4 -4
  86. package/esm/form/file-upload/index.js.map +1 -1
  87. package/esm/form/radio/useRadio.js +3 -2
  88. package/esm/form/radio/useRadio.js.map +1 -1
  89. package/esm/form/search/Search.js +1 -1
  90. package/esm/form/search/Search.js.map +1 -1
  91. package/esm/form/switch/Switch.js +2 -1
  92. package/esm/form/switch/Switch.js.map +1 -1
  93. package/esm/index.d.ts +1 -1
  94. package/esm/index.js +1 -1
  95. package/esm/index.js.map +1 -1
  96. package/esm/list/List.js +7 -2
  97. package/esm/list/List.js.map +1 -1
  98. package/esm/list/types.d.ts +1 -1
  99. package/esm/modal/Modal.js +2 -2
  100. package/esm/modal/Modal.js.map +1 -1
  101. package/esm/overlays/dismissablelayer/DismissableLayer.js +2 -0
  102. package/esm/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  103. package/esm/overlays/floating-menu/parts/RovingFocus.js +4 -4
  104. package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  105. package/esm/progress-bar/ProgressBar.js +6 -2
  106. package/esm/progress-bar/ProgressBar.js.map +1 -1
  107. package/esm/table/AnimateHeight.js +12 -14
  108. package/esm/table/AnimateHeight.js.map +1 -1
  109. package/esm/tabs/parts/tablist/useScrollButtons.js +1 -1
  110. package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  111. package/esm/tabs/parts/tablist/useTabList.js +4 -4
  112. package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
  113. package/esm/timeline/TimelineRow.js +9 -10
  114. package/esm/timeline/TimelineRow.js.map +1 -1
  115. package/esm/toggle-group/parts/useToggleItem.js +4 -4
  116. package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
  117. package/esm/util/TextareaAutoSize.js +3 -2
  118. package/esm/util/TextareaAutoSize.js.map +1 -1
  119. package/esm/util/create-context.js +3 -3
  120. package/esm/util/create-context.js.map +1 -1
  121. package/esm/util/hooks/descendants/descendant.js +1 -1
  122. package/esm/util/hooks/descendants/descendant.js.map +1 -1
  123. package/esm/util/hooks/descendants/useDescendant.js +1 -1
  124. package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
  125. package/package.json +5 -5
  126. package/src/dropdown/Menu/index.tsx +1 -1
  127. package/src/form/checkbox/useCheckbox.ts +2 -2
  128. package/src/form/combobox/ComboboxProvider.tsx +9 -1
  129. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +1 -0
  130. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +1 -1
  131. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +42 -11
  132. package/src/form/combobox/Input/Input.context.tsx +1 -1
  133. package/src/form/combobox/Input/Input.tsx +19 -10
  134. package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +2 -1
  135. package/src/form/combobox/__tests__/combobox.test.tsx +36 -0
  136. package/src/form/combobox/customOptionsContext.tsx +2 -2
  137. package/src/form/file-upload/index.ts +4 -4
  138. package/src/form/radio/useRadio.ts +2 -2
  139. package/src/form/search/Search.tsx +1 -1
  140. package/src/form/switch/Switch.tsx +1 -1
  141. package/src/index.ts +1 -1
  142. package/src/list/List.tsx +12 -6
  143. package/src/list/types.ts +1 -1
  144. package/src/modal/Modal.tsx +2 -2
  145. package/src/overlays/dismissablelayer/DismissableLayer.tsx +3 -0
  146. package/src/overlays/floating-menu/parts/RovingFocus.tsx +4 -4
  147. package/src/progress-bar/ProgressBar.tsx +2 -0
  148. package/src/table/AnimateHeight.tsx +15 -16
  149. package/src/tabs/parts/tablist/useScrollButtons.ts +1 -1
  150. package/src/tabs/parts/tablist/useTabList.ts +4 -4
  151. package/src/timeline/TimelineRow.tsx +20 -21
  152. package/src/toggle-group/parts/useToggleItem.ts +4 -4
  153. package/src/util/TextareaAutoSize.tsx +3 -2
  154. package/src/util/create-context.tsx +3 -2
  155. package/src/util/hooks/descendants/descendant.ts +1 -1
  156. 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["index"]);
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,OAAO,CAAC,CAAC,CAAC;YACvD,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"}
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.2",
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 ts-node ../../../scripts/docgen.ts",
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.2",
598
- "@navikt/ds-tokens": "^7.1.2",
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
  },
@@ -82,7 +82,7 @@ export const Menu = forwardRef<HTMLDivElement, DropdownMenuProps>(
82
82
  open={isOpen}
83
83
  onClose={() => {
84
84
  handleToggle(false);
85
- onClose && onClose();
85
+ onClose?.();
86
86
  }}
87
87
  />
88
88
  );
@@ -44,8 +44,8 @@ const useCheckbox = (props: CheckboxProps) => {
44
44
  if (readOnly) {
45
45
  return;
46
46
  }
47
- props.onChange && props.onChange(e);
48
- checkboxGroup && checkboxGroup.toggleValue(props.value);
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={{
@@ -54,6 +54,7 @@ const FilteredOptions = () => {
54
54
  )}
55
55
 
56
56
  {shouldRenderFilteredOptionsList && (
57
+ /* biome-ignore lint/a11y/useFocusableInteractive: Interaction is not handeled by listbox itself. */
57
58
  <ul
58
59
  ref={setFilteredOptionsRef}
59
60
  role="listbox"
@@ -140,7 +140,7 @@ const FilteredOptionsProvider = ({
140
140
  if (disabled || readOnly) {
141
141
  return;
142
142
  }
143
- virtualFocus.moveFocusToTop();
143
+ virtualFocus.resetFocus();
144
144
  if (newState ?? !isInternalListOpen) {
145
145
  setHideCaret(!!maxSelected?.isLimitReached);
146
146
  }
@@ -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
- _moveFocusAndScrollTo(elementAbove);
56
+ setActiveElement(elementAbove);
59
57
  }
60
58
  };
61
59
 
62
60
  const moveFocusDown = () => {
63
61
  const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
64
62
  if (!activeElement) {
65
- _moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]);
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
- _moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]);
71
+ setActiveElement(elementsAbleToReceiveFocus[_currentIndex + 1]);
74
72
  };
75
73
 
76
- const moveFocusToTop = () => _moveFocusAndScrollTo(undefined);
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
- return _moveFocusAndScrollTo(
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, setInternalValue],
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, virtualFocus, onChange, toggleIsListOpen],
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, isLimitReached]);
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, setCustomOptions],
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, setCustomOptions],
50
+ [focusInput, isMultiSelect],
51
51
  );
52
52
 
53
53
  const customOptionsState = {
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- export { default as UNSAFE_FileUpload } from "./FileUpload";
3
- export { default as UNSAFE_FileUploadDropzone } from "./parts/dropzone/Dropzone";
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 UNSAFE_FileUploadTrigger,
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 UNSAFE_FileUploadItem,
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 && props.onChange(e);
45
- radioGroup?.onChange && radioGroup.onChange(props.value);
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 && searchRef.current?.focus?.();
147
+ searchRef.current?.focus?.();
148
148
  },
149
149
  [handleChange, onClear],
150
150
  );
@@ -122,7 +122,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
122
122
  return;
123
123
  }
124
124
  setChecked(e.target.checked);
125
- props.onChange && props.onChange(e);
125
+ props.onChange?.(e);
126
126
  }}
127
127
  onClick={(e) => {
128
128
  if (readOnly) {
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
- UNSAFE_FileUpload,
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={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" | "small";
25
+ size?: "small" | "medium" | "large";
26
26
  }
27
27
 
28
28
  export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
@@ -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
- }, [modalRef, portalNode]);
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
- }, [modalRef, portalNode, open]);
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 && next.node?.focus();
58
+ next?.node?.focus();
59
59
  };
60
60
  const prevItem = () => {
61
61
  const prev = descendants.prevEnabled(idx, loop);
62
- prev && prev.node?.focus();
62
+ prev?.node?.focus();
63
63
  };
64
64
  const firstItem = () => {
65
65
  const first = descendants.firstEnabled();
66
- first && first.node?.focus();
66
+ first?.node?.focus();
67
67
  };
68
68
  const lastItem = () => {
69
69
  const last = descendants.lastEnabled();
70
- last && last.node?.focus();
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 duration = prefersReducedMotion ? 0 : userDuration;
77
+ const initialHeight = useRef<Height>(height);
78
+ const initialOverflow = useRef<Overflow>("visible");
78
79
 
79
- let initHeight: Height = height;
80
- let initOverflow: Overflow = "visible";
80
+ const duration = prefersReducedMotion ? 0 : userDuration;
81
81
 
82
- if (typeof initHeight === "number") {
82
+ if (typeof initialHeight.current === "number") {
83
83
  // Reset negative height to 0
84
84
  if (typeof height !== "string") {
85
- initHeight = height < 0 ? 0 : height;
85
+ initialHeight.current = height < 0 ? 0 : height;
86
86
  }
87
- initOverflow = "hidden";
88
- } else if (isPercentage(initHeight)) {
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
- initHeight = height === "0%" ? 0 : height;
91
- initOverflow = "hidden";
90
+ initialHeight.current = height === "0%" ? 0 : height;
91
+ initialOverflow.current = "hidden";
92
92
  }
93
93
 
94
- const [currentHeight, setCurrentHeight] = useState<Height>(initHeight);
95
- const [overflow, setOverflow] = useState<Overflow>(initOverflow);
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, currentHeight);
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 && resizeObserver.disconnect();
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 && next.node?.focus();
29
+ next?.node?.focus();
30
30
  };
31
31
  const prevTab = () => {
32
32
  const prev = descendants.prevEnabled(idx, loop);
33
- prev && prev.node?.focus();
33
+ prev?.node?.focus();
34
34
  };
35
35
  const firstTab = () => {
36
36
  const first = descendants.firstEnabled();
37
- first && first.node?.focus();
37
+ first?.node?.focus();
38
38
  };
39
39
  const lastTab = () => {
40
40
  const last = descendants.lastEnabled();
41
- last && last.node?.focus();
41
+ last?.node?.focus();
42
42
  };
43
43
 
44
44
  const keyMap: Record<string, React.KeyboardEventHandler> = {