@neo4j-ndl/react 4.18.12 → 4.19.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 (254) hide show
  1. package/lib/LICENSES.txt +54 -0
  2. package/lib/NOTICE.txt +8 -0
  3. package/lib/cjs/_common/use-semi-controlled-state.js +6 -1
  4. package/lib/cjs/_common/use-semi-controlled-state.js.map +1 -1
  5. package/lib/cjs/combobox/Combobox.js +1031 -0
  6. package/lib/cjs/combobox/Combobox.js.map +1 -0
  7. package/lib/cjs/combobox/combobox-types.js +23 -0
  8. package/lib/cjs/combobox/combobox-types.js.map +1 -0
  9. package/lib/cjs/combobox/combobox-utils.js +129 -0
  10. package/lib/cjs/combobox/combobox-utils.js.map +1 -0
  11. package/lib/cjs/combobox/index.js +26 -0
  12. package/lib/cjs/combobox/index.js.map +1 -0
  13. package/lib/cjs/combobox/stories/combobox-action-on-select.story.js +82 -0
  14. package/lib/cjs/combobox/stories/combobox-action-on-select.story.js.map +1 -0
  15. package/lib/cjs/combobox/stories/combobox-actions.story.js +78 -0
  16. package/lib/cjs/combobox/stories/combobox-actions.story.js.map +1 -0
  17. package/lib/cjs/combobox/stories/combobox-async.story.js +75 -0
  18. package/lib/cjs/combobox/stories/combobox-async.story.js.map +1 -0
  19. package/lib/cjs/combobox/stories/combobox-clean.story.js +38 -0
  20. package/lib/cjs/combobox/stories/combobox-clean.story.js.map +1 -0
  21. package/lib/cjs/combobox/stories/combobox-creatable.story.js +41 -0
  22. package/lib/cjs/combobox/stories/combobox-creatable.story.js.map +1 -0
  23. package/lib/cjs/combobox/stories/combobox-custom-multi.story.js +45 -0
  24. package/lib/cjs/combobox/stories/combobox-custom-multi.story.js.map +1 -0
  25. package/lib/cjs/combobox/stories/combobox-custom-single.story.js +44 -0
  26. package/lib/cjs/combobox/stories/combobox-custom-single.story.js.map +1 -0
  27. package/lib/cjs/combobox/stories/combobox-custom-trigger.story.js +60 -0
  28. package/lib/cjs/combobox/stories/combobox-custom-trigger.story.js.map +1 -0
  29. package/lib/cjs/combobox/stories/combobox-default.story.js +42 -0
  30. package/lib/cjs/combobox/stories/combobox-default.story.js.map +1 -0
  31. package/lib/cjs/combobox/stories/combobox-descriptions.story.js +45 -0
  32. package/lib/cjs/combobox/stories/combobox-descriptions.story.js.map +1 -0
  33. package/lib/cjs/combobox/stories/combobox-disabled.story.js +34 -0
  34. package/lib/cjs/combobox/stories/combobox-disabled.story.js.map +1 -0
  35. package/lib/cjs/combobox/stories/combobox-fluid.story.js +34 -0
  36. package/lib/cjs/combobox/stories/combobox-fluid.story.js.map +1 -0
  37. package/lib/cjs/combobox/stories/combobox-groups.story.js +49 -0
  38. package/lib/cjs/combobox/stories/combobox-groups.story.js.map +1 -0
  39. package/lib/cjs/combobox/stories/combobox-hidden-label.story.js +33 -0
  40. package/lib/cjs/combobox/stories/combobox-hidden-label.story.js.map +1 -0
  41. package/lib/cjs/combobox/stories/combobox-icons.story.js +46 -0
  42. package/lib/cjs/combobox/stories/combobox-icons.story.js.map +1 -0
  43. package/lib/cjs/combobox/stories/combobox-in-dialog.story.js +40 -0
  44. package/lib/cjs/combobox/stories/combobox-in-dialog.story.js.map +1 -0
  45. package/lib/cjs/combobox/stories/combobox-in-popover-dialog.story.js +40 -0
  46. package/lib/cjs/combobox/stories/combobox-in-popover-dialog.story.js.map +1 -0
  47. package/lib/cjs/combobox/stories/combobox-in-popover.story.js +36 -0
  48. package/lib/cjs/combobox/stories/combobox-in-popover.story.js.map +1 -0
  49. package/lib/cjs/combobox/stories/combobox-large-data-set.story.js +36 -0
  50. package/lib/cjs/combobox/stories/combobox-large-data-set.story.js.map +1 -0
  51. package/lib/cjs/combobox/stories/combobox-max-rows.story.js +42 -0
  52. package/lib/cjs/combobox/stories/combobox-max-rows.story.js.map +1 -0
  53. package/lib/cjs/combobox/stories/combobox-multi.story.js +45 -0
  54. package/lib/cjs/combobox/stories/combobox-multi.story.js.map +1 -0
  55. package/lib/cjs/combobox/stories/combobox-native-form.story.js +49 -0
  56. package/lib/cjs/combobox/stories/combobox-native-form.story.js.map +1 -0
  57. package/lib/cjs/combobox/stories/combobox-select-only.story.js +38 -0
  58. package/lib/cjs/combobox/stories/combobox-select-only.story.js.map +1 -0
  59. package/lib/cjs/combobox/stories/combobox-sizes.story.js +33 -0
  60. package/lib/cjs/combobox/stories/combobox-sizes.story.js.map +1 -0
  61. package/lib/cjs/combobox/stories/combobox-unlimited-rows.story.js +42 -0
  62. package/lib/cjs/combobox/stories/combobox-unlimited-rows.story.js.map +1 -0
  63. package/lib/cjs/combobox/stories/combobox-validation.story.js +33 -0
  64. package/lib/cjs/combobox/stories/combobox-validation.story.js.map +1 -0
  65. package/lib/cjs/combobox/stories/index.js +133 -0
  66. package/lib/cjs/combobox/stories/index.js.map +1 -0
  67. package/lib/cjs/combobox/use-combobox-multi-value-overflow.js +173 -0
  68. package/lib/cjs/combobox/use-combobox-multi-value-overflow.js.map +1 -0
  69. package/lib/cjs/combobox/use-combobox-popover.js +80 -0
  70. package/lib/cjs/combobox/use-combobox-popover.js.map +1 -0
  71. package/lib/cjs/date-picker/DatePicker.js.map +1 -1
  72. package/lib/cjs/dismissible-tag/DismissibleTag.js +1 -1
  73. package/lib/cjs/dismissible-tag/DismissibleTag.js.map +1 -1
  74. package/lib/cjs/index.js +1 -0
  75. package/lib/cjs/index.js.map +1 -1
  76. package/lib/cjs/next/tree-view/TreeView.js +1 -1
  77. package/lib/cjs/next/tree-view/TreeView.js.map +1 -1
  78. package/lib/cjs/select/Select.js +3 -0
  79. package/lib/cjs/select/Select.js.map +1 -1
  80. package/lib/cjs/text-area/TextArea.js.map +1 -1
  81. package/lib/cjs/text-input/TextInput.js.map +1 -1
  82. package/lib/cjs/tooltip/Tooltip.js.map +1 -1
  83. package/lib/cjs/tooltip/stories/tooltip-follow-cursor.story.js +1 -1
  84. package/lib/cjs/tooltip/stories/tooltip-follow-cursor.story.js.map +1 -1
  85. package/lib/cjs/tooltip/use-tooltip.js +37 -5
  86. package/lib/cjs/tooltip/use-tooltip.js.map +1 -1
  87. package/lib/esm/_common/use-semi-controlled-state.js +6 -1
  88. package/lib/esm/_common/use-semi-controlled-state.js.map +1 -1
  89. package/lib/esm/combobox/Combobox.js +1024 -0
  90. package/lib/esm/combobox/Combobox.js.map +1 -0
  91. package/lib/esm/combobox/combobox-types.js +22 -0
  92. package/lib/esm/combobox/combobox-types.js.map +1 -0
  93. package/lib/esm/combobox/combobox-utils.js +112 -0
  94. package/lib/esm/combobox/combobox-utils.js.map +1 -0
  95. package/lib/esm/combobox/index.js +22 -0
  96. package/lib/esm/combobox/index.js.map +1 -0
  97. package/lib/esm/combobox/stories/combobox-action-on-select.story.js +80 -0
  98. package/lib/esm/combobox/stories/combobox-action-on-select.story.js.map +1 -0
  99. package/lib/esm/combobox/stories/combobox-actions.story.js +76 -0
  100. package/lib/esm/combobox/stories/combobox-actions.story.js.map +1 -0
  101. package/lib/esm/combobox/stories/combobox-async.story.js +73 -0
  102. package/lib/esm/combobox/stories/combobox-async.story.js.map +1 -0
  103. package/lib/esm/combobox/stories/combobox-clean.story.js +36 -0
  104. package/lib/esm/combobox/stories/combobox-clean.story.js.map +1 -0
  105. package/lib/esm/combobox/stories/combobox-creatable.story.js +39 -0
  106. package/lib/esm/combobox/stories/combobox-creatable.story.js.map +1 -0
  107. package/lib/esm/combobox/stories/combobox-custom-multi.story.js +43 -0
  108. package/lib/esm/combobox/stories/combobox-custom-multi.story.js.map +1 -0
  109. package/lib/esm/combobox/stories/combobox-custom-single.story.js +42 -0
  110. package/lib/esm/combobox/stories/combobox-custom-single.story.js.map +1 -0
  111. package/lib/esm/combobox/stories/combobox-custom-trigger.story.js +58 -0
  112. package/lib/esm/combobox/stories/combobox-custom-trigger.story.js.map +1 -0
  113. package/lib/esm/combobox/stories/combobox-default.story.js +40 -0
  114. package/lib/esm/combobox/stories/combobox-default.story.js.map +1 -0
  115. package/lib/esm/combobox/stories/combobox-descriptions.story.js +43 -0
  116. package/lib/esm/combobox/stories/combobox-descriptions.story.js.map +1 -0
  117. package/lib/esm/combobox/stories/combobox-disabled.story.js +32 -0
  118. package/lib/esm/combobox/stories/combobox-disabled.story.js.map +1 -0
  119. package/lib/esm/combobox/stories/combobox-fluid.story.js +32 -0
  120. package/lib/esm/combobox/stories/combobox-fluid.story.js.map +1 -0
  121. package/lib/esm/combobox/stories/combobox-groups.story.js +47 -0
  122. package/lib/esm/combobox/stories/combobox-groups.story.js.map +1 -0
  123. package/lib/esm/combobox/stories/combobox-hidden-label.story.js +31 -0
  124. package/lib/esm/combobox/stories/combobox-hidden-label.story.js.map +1 -0
  125. package/lib/esm/combobox/stories/combobox-icons.story.js +44 -0
  126. package/lib/esm/combobox/stories/combobox-icons.story.js.map +1 -0
  127. package/lib/esm/combobox/stories/combobox-in-dialog.story.js +38 -0
  128. package/lib/esm/combobox/stories/combobox-in-dialog.story.js.map +1 -0
  129. package/lib/esm/combobox/stories/combobox-in-popover-dialog.story.js +38 -0
  130. package/lib/esm/combobox/stories/combobox-in-popover-dialog.story.js.map +1 -0
  131. package/lib/esm/combobox/stories/combobox-in-popover.story.js +34 -0
  132. package/lib/esm/combobox/stories/combobox-in-popover.story.js.map +1 -0
  133. package/lib/esm/combobox/stories/combobox-large-data-set.story.js +34 -0
  134. package/lib/esm/combobox/stories/combobox-large-data-set.story.js.map +1 -0
  135. package/lib/esm/combobox/stories/combobox-max-rows.story.js +40 -0
  136. package/lib/esm/combobox/stories/combobox-max-rows.story.js.map +1 -0
  137. package/lib/esm/combobox/stories/combobox-multi.story.js +43 -0
  138. package/lib/esm/combobox/stories/combobox-multi.story.js.map +1 -0
  139. package/lib/esm/combobox/stories/combobox-native-form.story.js +47 -0
  140. package/lib/esm/combobox/stories/combobox-native-form.story.js.map +1 -0
  141. package/lib/esm/combobox/stories/combobox-select-only.story.js +36 -0
  142. package/lib/esm/combobox/stories/combobox-select-only.story.js.map +1 -0
  143. package/lib/esm/combobox/stories/combobox-sizes.story.js +31 -0
  144. package/lib/esm/combobox/stories/combobox-sizes.story.js.map +1 -0
  145. package/lib/esm/combobox/stories/combobox-unlimited-rows.story.js +40 -0
  146. package/lib/esm/combobox/stories/combobox-unlimited-rows.story.js.map +1 -0
  147. package/lib/esm/combobox/stories/combobox-validation.story.js +31 -0
  148. package/lib/esm/combobox/stories/combobox-validation.story.js.map +1 -0
  149. package/lib/esm/combobox/stories/index.js +100 -0
  150. package/lib/esm/combobox/stories/index.js.map +1 -0
  151. package/lib/esm/combobox/use-combobox-multi-value-overflow.js +166 -0
  152. package/lib/esm/combobox/use-combobox-multi-value-overflow.js.map +1 -0
  153. package/lib/esm/combobox/use-combobox-popover.js +77 -0
  154. package/lib/esm/combobox/use-combobox-popover.js.map +1 -0
  155. package/lib/esm/date-picker/DatePicker.js.map +1 -1
  156. package/lib/esm/dismissible-tag/DismissibleTag.js +1 -1
  157. package/lib/esm/dismissible-tag/DismissibleTag.js.map +1 -1
  158. package/lib/esm/index.js +1 -0
  159. package/lib/esm/index.js.map +1 -1
  160. package/lib/esm/next/tree-view/TreeView.js +1 -1
  161. package/lib/esm/next/tree-view/TreeView.js.map +1 -1
  162. package/lib/esm/select/Select.js +3 -0
  163. package/lib/esm/select/Select.js.map +1 -1
  164. package/lib/esm/text-area/TextArea.js.map +1 -1
  165. package/lib/esm/text-input/TextInput.js.map +1 -1
  166. package/lib/esm/tooltip/Tooltip.js.map +1 -1
  167. package/lib/esm/tooltip/stories/tooltip-follow-cursor.story.js +1 -1
  168. package/lib/esm/tooltip/stories/tooltip-follow-cursor.story.js.map +1 -1
  169. package/lib/esm/tooltip/use-tooltip.js +38 -6
  170. package/lib/esm/tooltip/use-tooltip.js.map +1 -1
  171. package/lib/types/_common/use-semi-controlled-state.d.ts +5 -1
  172. package/lib/types/_common/use-semi-controlled-state.d.ts.map +1 -1
  173. package/lib/types/combobox/Combobox.d.ts +24 -0
  174. package/lib/types/combobox/Combobox.d.ts.map +1 -0
  175. package/lib/types/combobox/combobox-types.d.ts +419 -0
  176. package/lib/types/combobox/combobox-types.d.ts.map +1 -0
  177. package/lib/types/combobox/combobox-utils.d.ts +81 -0
  178. package/lib/types/combobox/combobox-utils.d.ts.map +1 -0
  179. package/lib/types/combobox/index.d.ts +23 -0
  180. package/lib/types/combobox/index.d.ts.map +1 -0
  181. package/lib/types/combobox/stories/combobox-action-on-select.story.d.ts +24 -0
  182. package/lib/types/combobox/stories/combobox-action-on-select.story.d.ts.map +1 -0
  183. package/lib/types/combobox/stories/combobox-actions.story.d.ts +24 -0
  184. package/lib/types/combobox/stories/combobox-actions.story.d.ts.map +1 -0
  185. package/lib/types/combobox/stories/combobox-async.story.d.ts +24 -0
  186. package/lib/types/combobox/stories/combobox-async.story.d.ts.map +1 -0
  187. package/lib/types/combobox/stories/combobox-clean.story.d.ts +24 -0
  188. package/lib/types/combobox/stories/combobox-clean.story.d.ts.map +1 -0
  189. package/lib/types/combobox/stories/combobox-creatable.story.d.ts +24 -0
  190. package/lib/types/combobox/stories/combobox-creatable.story.d.ts.map +1 -0
  191. package/lib/types/combobox/stories/combobox-custom-multi.story.d.ts +24 -0
  192. package/lib/types/combobox/stories/combobox-custom-multi.story.d.ts.map +1 -0
  193. package/lib/types/combobox/stories/combobox-custom-single.story.d.ts +24 -0
  194. package/lib/types/combobox/stories/combobox-custom-single.story.d.ts.map +1 -0
  195. package/lib/types/combobox/stories/combobox-custom-trigger.story.d.ts +24 -0
  196. package/lib/types/combobox/stories/combobox-custom-trigger.story.d.ts.map +1 -0
  197. package/lib/types/combobox/stories/combobox-default.story.d.ts +24 -0
  198. package/lib/types/combobox/stories/combobox-default.story.d.ts.map +1 -0
  199. package/lib/types/combobox/stories/combobox-descriptions.story.d.ts +24 -0
  200. package/lib/types/combobox/stories/combobox-descriptions.story.d.ts.map +1 -0
  201. package/lib/types/combobox/stories/combobox-disabled.story.d.ts +24 -0
  202. package/lib/types/combobox/stories/combobox-disabled.story.d.ts.map +1 -0
  203. package/lib/types/combobox/stories/combobox-fluid.story.d.ts +24 -0
  204. package/lib/types/combobox/stories/combobox-fluid.story.d.ts.map +1 -0
  205. package/lib/types/combobox/stories/combobox-groups.story.d.ts +24 -0
  206. package/lib/types/combobox/stories/combobox-groups.story.d.ts.map +1 -0
  207. package/lib/types/combobox/stories/combobox-hidden-label.story.d.ts +24 -0
  208. package/lib/types/combobox/stories/combobox-hidden-label.story.d.ts.map +1 -0
  209. package/lib/types/combobox/stories/combobox-icons.story.d.ts +24 -0
  210. package/lib/types/combobox/stories/combobox-icons.story.d.ts.map +1 -0
  211. package/lib/types/combobox/stories/combobox-in-dialog.story.d.ts +24 -0
  212. package/lib/types/combobox/stories/combobox-in-dialog.story.d.ts.map +1 -0
  213. package/lib/types/combobox/stories/combobox-in-popover-dialog.story.d.ts +24 -0
  214. package/lib/types/combobox/stories/combobox-in-popover-dialog.story.d.ts.map +1 -0
  215. package/lib/types/combobox/stories/combobox-in-popover.story.d.ts +24 -0
  216. package/lib/types/combobox/stories/combobox-in-popover.story.d.ts.map +1 -0
  217. package/lib/types/combobox/stories/combobox-large-data-set.story.d.ts +24 -0
  218. package/lib/types/combobox/stories/combobox-large-data-set.story.d.ts.map +1 -0
  219. package/lib/types/combobox/stories/combobox-max-rows.story.d.ts +24 -0
  220. package/lib/types/combobox/stories/combobox-max-rows.story.d.ts.map +1 -0
  221. package/lib/types/combobox/stories/combobox-multi.story.d.ts +24 -0
  222. package/lib/types/combobox/stories/combobox-multi.story.d.ts.map +1 -0
  223. package/lib/types/combobox/stories/combobox-native-form.story.d.ts +24 -0
  224. package/lib/types/combobox/stories/combobox-native-form.story.d.ts.map +1 -0
  225. package/lib/types/combobox/stories/combobox-select-only.story.d.ts +24 -0
  226. package/lib/types/combobox/stories/combobox-select-only.story.d.ts.map +1 -0
  227. package/lib/types/combobox/stories/combobox-sizes.story.d.ts +24 -0
  228. package/lib/types/combobox/stories/combobox-sizes.story.d.ts.map +1 -0
  229. package/lib/types/combobox/stories/combobox-unlimited-rows.story.d.ts +24 -0
  230. package/lib/types/combobox/stories/combobox-unlimited-rows.story.d.ts.map +1 -0
  231. package/lib/types/combobox/stories/combobox-validation.story.d.ts +24 -0
  232. package/lib/types/combobox/stories/combobox-validation.story.d.ts.map +1 -0
  233. package/lib/types/combobox/stories/index.d.ts +73 -0
  234. package/lib/types/combobox/stories/index.d.ts.map +1 -0
  235. package/lib/types/combobox/use-combobox-multi-value-overflow.d.ts +76 -0
  236. package/lib/types/combobox/use-combobox-multi-value-overflow.d.ts.map +1 -0
  237. package/lib/types/combobox/use-combobox-popover.d.ts +66 -0
  238. package/lib/types/combobox/use-combobox-popover.d.ts.map +1 -0
  239. package/lib/types/date-picker/DatePicker.d.ts.map +1 -1
  240. package/lib/types/dismissible-tag/DismissibleTag.d.ts.map +1 -1
  241. package/lib/types/index.d.ts +1 -0
  242. package/lib/types/index.d.ts.map +1 -1
  243. package/lib/types/select/Select.d.ts +3 -0
  244. package/lib/types/select/Select.d.ts.map +1 -1
  245. package/lib/types/text-input/TextInput.d.ts.map +1 -1
  246. package/lib/types/tooltip/Tooltip.d.ts +6 -2
  247. package/lib/types/tooltip/Tooltip.d.ts.map +1 -1
  248. package/lib/types/tooltip/stories/tooltip-follow-cursor.story.d.ts.map +1 -1
  249. package/lib/types/tooltip/use-tooltip.d.ts +6 -2
  250. package/lib/types/tooltip/use-tooltip.d.ts.map +1 -1
  251. package/package.json +10 -4
  252. package/skills/ndl-react/SKILL.md +20 -0
  253. package/skills/ndl-react/components/combobox.md +1487 -0
  254. package/skills/ndl-react/components/tooltip.md +39 -7
@@ -0,0 +1,1031 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Combobox = void 0;
18
+ const jsx_runtime_1 = require("react/jsx-runtime");
19
+ /**
20
+ *
21
+ * Copyright (c) "Neo4j"
22
+ * Neo4j Sweden AB [http://neo4j.com]
23
+ *
24
+ * This file is part of Neo4j.
25
+ *
26
+ * Neo4j is free software: you can redistribute it and/or modify
27
+ * it under the terms of the GNU General Public License as published by
28
+ * the Free Software Foundation, either version 3 of the License, or
29
+ * (at your option) any later version.
30
+ *
31
+ * This program is distributed in the hope that it will be useful,
32
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34
+ * GNU General Public License for more details.
35
+ *
36
+ * You should have received a copy of the GNU General Public License
37
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
38
+ */
39
+ const react_1 = require("@floating-ui/react");
40
+ const react_virtual_1 = require("@tanstack/react-virtual");
41
+ const classnames_1 = __importDefault(require("classnames"));
42
+ const react_2 = require("react");
43
+ const use_semi_controlled_state_1 = require("../_common/use-semi-controlled-state");
44
+ const utils_1 = require("../_common/utils");
45
+ const checkbox_1 = require("../checkbox");
46
+ const conditional_wrap_1 = require("../conditional-wrap");
47
+ const dismissible_tag_1 = require("../dismissible-tag");
48
+ const divider_1 = require("../divider");
49
+ const heroIcons_1 = require("../icons/generated/heroIcons");
50
+ const loading_spinner_1 = require("../loading-spinner");
51
+ const read_only_tag_1 = require("../read-only-tag");
52
+ const theme_1 = require("../theme");
53
+ const typography_1 = require("../typography");
54
+ const combobox_utils_1 = require("./combobox-utils");
55
+ const use_combobox_multi_value_overflow_1 = require("./use-combobox-multi-value-overflow");
56
+ const use_combobox_popover_1 = require("./use-combobox-popover");
57
+ const DEFAULT_SEARCH_PLACEHOLDER = 'Search';
58
+ const DEFAULT_PLACEHOLDER = 'Select...';
59
+ const DEFAULT_LOADING_MESSAGE = 'Loading...';
60
+ const NO_ACTIVE_INDEX = -1;
61
+ const OPENING_KEYS = ['ArrowDown', 'ArrowUp', 'Enter', ' '];
62
+ // Above this many rendered rows - options and group headers - the listbox
63
+ // switches to windowed rendering. Below it, the DOM, including the nested
64
+ // group semantics, is exactly what it always was.
65
+ const VIRTUALIZATION_ROW_THRESHOLD = 100;
66
+ // Starting guess only: every virtual row is measured once rendered.
67
+ const VIRTUAL_ROW_ESTIMATED_HEIGHT = 38;
68
+ // With a row limit, at most this many candidate tags per permitted row are
69
+ // rendered and measured; anything past them is overflowed arithmetically. Far
70
+ // more than fit a row even with the narrowest plausible tags.
71
+ const TAG_CANDIDATES_PER_ROW = 50;
72
+ // The counter's title and screen reader text name at most this many overflowed
73
+ // options before summarizing the rest as a count.
74
+ const OVERFLOW_LABEL_LIMIT = 20;
75
+ // The hidden form select mirrors the options so browser autofill can match
76
+ // against them, but past this many the DOM cost outweighs an autofill list
77
+ // nobody scrolls. Above it, only what submission, validation, and reset need
78
+ // is mirrored: the selection and the default value. Matches the limit React
79
+ // Aria's HiddenSelect uses for the same trade-off.
80
+ const FORM_OPTION_MIRROR_LIMIT = 300;
81
+ /**
82
+ * Flattens the grouped option items into display rows for the virtualizer,
83
+ * along with a lookup from an option's flat index - the one behind option ids
84
+ * and the active index - to its display row index. Pinned action rows sit at
85
+ * the end of the flat indices but get no display row, so the lookup simply
86
+ * misses for them.
87
+ */
88
+ const buildComboboxDisplayRows = (optionItems) => {
89
+ const displayRows = [];
90
+ const flatToDisplayIndex = [];
91
+ const pushOption = (row) => {
92
+ flatToDisplayIndex.push(displayRows.length);
93
+ displayRows.push({ kind: 'option', row });
94
+ };
95
+ optionItems.forEach((item) => {
96
+ if ((0, combobox_utils_1.isOptionGroup)(item)) {
97
+ displayRows.push({ kind: 'group-header', label: item.label });
98
+ item.options.forEach(pushOption);
99
+ return;
100
+ }
101
+ pushOption(item);
102
+ });
103
+ return { displayRows, flatToDisplayIndex };
104
+ };
105
+ const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVisuallyHiddenLabel = false, helpText, errorText, isRequired = false, hasRequiredIndicator = false, options, value, defaultValue, onChange, isMulti = false, isSearchable = true, searchValue, defaultSearchValue, onSearchChange, filterOption, searchPlaceholder = DEFAULT_SEARCH_PLACEHOLDER, isLoading = false, loadingMessage = DEFAULT_LOADING_MESSAGE, isCreatable = false, onCreateOption, createOption = combobox_utils_1.buildCreateOption, formatCreateLabel = combobox_utils_1.formatCreateOptionLabel, isValidNewOption = combobox_utils_1.isValidNewComboboxOption, actions, maxRows = 1, isOpen, onOpenChange, isClearable = false, placeholder = DEFAULT_PLACEHOLDER, isDisabled = false, size = 'medium', isFluid = false, isClean = false, isPortaled, portalTarget, floatingStrategy, renderTrigger, components = {}, className, style, htmlAttributes, ref, }) => {
106
+ var _a;
107
+ const baseId = (0, react_2.useId)();
108
+ const listboxId = `${baseId}-listbox`;
109
+ const panelId = `${baseId}-panel`;
110
+ const triggerId = `${baseId}-trigger`;
111
+ const labelId = `${baseId}-label`;
112
+ const helpTextId = `${baseId}-help-text`;
113
+ const errorTextId = `${baseId}-error-text`;
114
+ const initialValue = (0, combobox_utils_1.getInitialValue)(value, defaultValue, isMulti);
115
+ const [selectedValue, setSelectedValue, resetSelectedValue] = (0, use_semi_controlled_state_1.useSemicontrolledState)({
116
+ isControlled: value !== undefined,
117
+ onChange: (nextValue) => {
118
+ if (isMulti) {
119
+ onChange === null || onChange === void 0 ? void 0 : onChange(Array.isArray(nextValue) ? nextValue : []);
120
+ }
121
+ else if (!Array.isArray(nextValue)) {
122
+ onChange === null || onChange === void 0 ? void 0 : onChange(nextValue);
123
+ }
124
+ },
125
+ state: initialValue,
126
+ });
127
+ const [currentSearchValue, setCurrentSearchValue, resetSearchValue] = (0, use_semi_controlled_state_1.useSemicontrolledState)({
128
+ isControlled: searchValue !== undefined,
129
+ onChange: onSearchChange,
130
+ state: (_a = searchValue !== null && searchValue !== void 0 ? searchValue : defaultSearchValue) !== null && _a !== void 0 ? _a : '',
131
+ });
132
+ const [isPanelOpen, setIsPanelOpen, resetPanelOpen] = (0, use_semi_controlled_state_1.useSemicontrolledState)({
133
+ isControlled: isOpen !== undefined,
134
+ onChange: onOpenChange,
135
+ state: isOpen !== null && isOpen !== void 0 ? isOpen : false,
136
+ });
137
+ const { themeClassName } = (0, theme_1.useNeedleTheme)();
138
+ const internalTriggerRef = (0, react_2.useRef)(null);
139
+ const searchInputRef = (0, react_2.useRef)(null);
140
+ const { context, floatingStyles, getFloatingProps, getReferenceProps, isPortaled: isPanelPortaled, refs, } = (0, use_combobox_popover_1.useComboboxPopover)({
141
+ floatingStrategy,
142
+ isDisabled,
143
+ isOpen: isPanelOpen,
144
+ isPortaled,
145
+ onOpenChange: setIsPanelOpen,
146
+ });
147
+ const hasCustomTrigger = renderTrigger !== undefined;
148
+ const builtInTriggerRef = (0, react_1.useMergeRefs)([internalTriggerRef, triggerRef]);
149
+ const customTriggerRef = (0, react_1.useMergeRefs)([
150
+ refs.setReference,
151
+ internalTriggerRef,
152
+ triggerRef,
153
+ ]);
154
+ // Creating builds an option out of the search value, so there is nothing to
155
+ // create from without a search input.
156
+ const canCreate = isCreatable && isSearchable;
157
+ (0, react_2.useEffect)(() => {
158
+ if (isCreatable && !isSearchable) {
159
+ (0, utils_1.needleWarningMessage)('A creatable Combobox also needs isSearchable, since a new option is built from the search value.');
160
+ }
161
+ }, [isCreatable, isSearchable]);
162
+ const selectedOptions = getSelectedOptions(selectedValue);
163
+ const resetValue = (0, combobox_utils_1.getInitialValue)(undefined, defaultValue, isMulti);
164
+ const resetSelectedOptions = getSelectedOptions(resetValue);
165
+ const selectedValuesKey = selectedOptions
166
+ .map((option) => option.value)
167
+ .join('\u0000');
168
+ // Selection checks run once per rendered option, so with large lists they
169
+ // must not scan the selection array each time.
170
+ const selectedValuesSet = (0, react_2.useMemo)(() => new Set(getSelectedOptions(selectedValue).map(({ value }) => value)), [selectedValue]);
171
+ const allOptions = (0, react_2.useMemo)(() => (0, combobox_utils_1.flattenComboboxOptions)(options), [options]);
172
+ const [nativeValidationMessage, setNativeValidationMessage] = (0, react_2.useState)(undefined);
173
+ const formOptions = (0, react_2.useMemo)(() => {
174
+ const seenValues = new Set();
175
+ // The default value options are mirrored even above the limit, since the
176
+ // native select can only reset to options it renders.
177
+ const mirroredOptions = allOptions.length > FORM_OPTION_MIRROR_LIMIT
178
+ ? [
179
+ ...getSelectedOptions(selectedValue),
180
+ ...getSelectedOptions((0, combobox_utils_1.getInitialValue)(undefined, defaultValue, isMulti)),
181
+ ]
182
+ : [...allOptions, ...getSelectedOptions(selectedValue)];
183
+ return mirroredOptions.filter((option) => {
184
+ if (seenValues.has(option.value)) {
185
+ return false;
186
+ }
187
+ seenValues.add(option.value);
188
+ return true;
189
+ });
190
+ }, [allOptions, selectedValue, defaultValue, isMulti]);
191
+ (0, react_2.useEffect)(() => {
192
+ setNativeValidationMessage(undefined);
193
+ }, [selectedValuesKey]);
194
+ const resetFromForm = () => {
195
+ resetSelectedValue(resetValue);
196
+ resetSearchValue(defaultSearchValue !== null && defaultSearchValue !== void 0 ? defaultSearchValue : '');
197
+ resetPanelOpen(false);
198
+ setNativeValidationMessage(undefined);
199
+ };
200
+ const filteredOptions = (0, react_2.useMemo)(() => isSearchable
201
+ ? (0, combobox_utils_1.filterComboboxOptions)(options, currentSearchValue, filterOption)
202
+ : options, [isSearchable, options, currentSearchValue, filterOption]);
203
+ // Created options are never added to `options`, so the selection is offered
204
+ // for deduplication as well.
205
+ const createRow = canCreate &&
206
+ isValidNewOption(currentSearchValue, [...allOptions, ...selectedOptions])
207
+ ? (0, combobox_utils_1.asCreateOption)(createOption(currentSearchValue))
208
+ : undefined;
209
+ // The create row is appended after any groups rather than inside one, since it
210
+ // belongs to the list as a whole.
211
+ const optionItems = createRow === undefined ? filteredOptions : [...filteredOptions, createRow];
212
+ // Actions are not data, so they survive filtering and close the list off at
213
+ // the end, after anything that selects.
214
+ const actionRows = (actions !== null && actions !== void 0 ? actions : []).map(combobox_utils_1.asActionRow);
215
+ const flatOptions = [
216
+ ...(0, combobox_utils_1.flattenComboboxOptions)(optionItems),
217
+ ...actionRows,
218
+ ];
219
+ const optionsKey = flatOptions.map((option) => option.value).join('\u0000');
220
+ // Ids and hover both need a row's index; one O(n) map build per render
221
+ // replaces an O(n) scan per rendered row.
222
+ const rowIndexes = new Map(flatOptions.map((row, index) => [row, index]));
223
+ const getOptionId = (row) => { var _a; return `${baseId}-option-${(_a = rowIndexes.get(row)) !== null && _a !== void 0 ? _a : NO_ACTIVE_INDEX}`; };
224
+ const groupHeaderCount = optionItems.reduce((count, item) => ((0, combobox_utils_1.isOptionGroup)(item) ? count + 1 : count), 0);
225
+ // Only the options listbox is windowed; the pinned action rows below it
226
+ // are always rendered, so they do not count towards the threshold.
227
+ const displayRowCount = flatOptions.length - actionRows.length + groupHeaderCount;
228
+ const isListVirtualized = displayRowCount > VIRTUALIZATION_ROW_THRESHOLD;
229
+ const virtualRows = isListVirtualized
230
+ ? buildComboboxDisplayRows(optionItems)
231
+ : undefined;
232
+ // Registered by the virtualized listbox while it is mounted, since scrolling
233
+ // a windowed list works by index rather than by element.
234
+ const scrollToActiveRowRef = (0, react_2.useRef)(null);
235
+ // No option is active until the user navigates or hovers, so nothing is
236
+ // highlighted and Enter is inert before then.
237
+ const [activeIndex, setActiveIndex] = (0, react_2.useState)(NO_ACTIVE_INDEX);
238
+ const [renderedOptionsKey, setRenderedOptionsKey] = (0, react_2.useState)(optionsKey);
239
+ const wasActivatedByPointerRef = (0, react_2.useRef)(false);
240
+ // Indices change meaning when the list is filtered, so a stale one would
241
+ // point at the wrong option.
242
+ if (renderedOptionsKey !== optionsKey) {
243
+ setRenderedOptionsKey(optionsKey);
244
+ setActiveIndex(NO_ACTIVE_INDEX);
245
+ }
246
+ const activeOption = flatOptions[activeIndex];
247
+ const activeOptionId = activeOption === undefined ? undefined : getOptionId(activeOption);
248
+ // The listbox scrolls, so keep the virtually focused option visible.
249
+ // Pointer activation is skipped, otherwise hovering a partially visible
250
+ // option would nudge the list out from under the cursor. Pinned action rows
251
+ // sit outside the scrolling listbox and are always visible, so scrolling
252
+ // for them is a harmless no-op in both paths.
253
+ (0, react_2.useEffect)(() => {
254
+ var _a;
255
+ if (wasActivatedByPointerRef.current) {
256
+ wasActivatedByPointerRef.current = false;
257
+ return;
258
+ }
259
+ if (activeOptionId === undefined) {
260
+ return;
261
+ }
262
+ // A windowed listbox scrolls by index, since the active option's element
263
+ // may not be mounted yet. Scrolling is also what mounts it, keeping
264
+ // aria-activedescendant resolvable.
265
+ if (scrollToActiveRowRef.current !== null) {
266
+ scrollToActiveRowRef.current(activeIndex);
267
+ return;
268
+ }
269
+ (_a = document
270
+ .getElementById(activeOptionId)) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
271
+ }, [activeOptionId, activeIndex]);
272
+ // Reopening the panel starts with nothing highlighted, the same as the first
273
+ // open, so a stale active option can never be one Enter away from selection.
274
+ (0, react_2.useEffect)(() => {
275
+ if (!isPanelOpen) {
276
+ setActiveIndex(NO_ACTIVE_INDEX);
277
+ }
278
+ }, [isPanelOpen]);
279
+ const isOptionSelected = (option) => selectedValuesSet.has(option.value);
280
+ const selectOption = (option) => {
281
+ if (isMulti) {
282
+ const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
283
+ const isSelected = selectedValuesSet.has(option.value);
284
+ const nextValue = isSelected
285
+ ? currentValue.filter((selected) => selected.value !== option.value)
286
+ : [...currentValue, option];
287
+ setSelectedValue(nextValue);
288
+ // Multi select keeps the panel open so several options can be picked in
289
+ // one pass, matching Select.
290
+ return;
291
+ }
292
+ if (Array.isArray(selectedValue) || (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue.value) !== option.value) {
293
+ setSelectedValue(option);
294
+ }
295
+ setIsPanelOpen(false);
296
+ };
297
+ const createFromSearch = () => {
298
+ const created = createOption(currentSearchValue);
299
+ selectOption(created);
300
+ onCreateOption === null || onCreateOption === void 0 ? void 0 : onCreateOption(created);
301
+ // Multi select stays open, so the query is reset to leave the full list
302
+ // usable for the next pick.
303
+ setCurrentSearchValue('');
304
+ };
305
+ const handleOptionChange = (row) => {
306
+ if (isDisabled || row.isDisabled) {
307
+ return;
308
+ }
309
+ // An action leaves the selection alone, so it only runs and closes.
310
+ if ((0, combobox_utils_1.isActionRow)(row)) {
311
+ (0, combobox_utils_1.getAction)(row).onSelect({ searchValue: currentSearchValue });
312
+ setIsPanelOpen(false);
313
+ return;
314
+ }
315
+ if ((0, combobox_utils_1.isCreateOption)(row)) {
316
+ createFromSearch();
317
+ return;
318
+ }
319
+ selectOption(row);
320
+ };
321
+ const clearSelection = () => {
322
+ if (isDisabled) {
323
+ return;
324
+ }
325
+ setSelectedValue(isMulti ? [] : null);
326
+ };
327
+ const removeOption = (option) => {
328
+ if (isDisabled) {
329
+ return;
330
+ }
331
+ if (!isMulti) {
332
+ clearSelection();
333
+ return;
334
+ }
335
+ setSelectedValue(selectedOptions.filter((selected) => selected.value !== option.value));
336
+ };
337
+ // Hover and the keyboard share one active option, so exactly one option is
338
+ // ever highlighted. Unlike the keyboard, hover skips disabled options: a
339
+ // pointer user sees the disabled state directly, so there is no
340
+ // discoverability to preserve.
341
+ const handleOptionHover = (row) => {
342
+ var _a;
343
+ if (row.isDisabled === true) {
344
+ return;
345
+ }
346
+ const index = (_a = rowIndexes.get(row)) !== null && _a !== void 0 ? _a : NO_ACTIVE_INDEX;
347
+ if (index === activeIndex) {
348
+ return;
349
+ }
350
+ wasActivatedByPointerRef.current = true;
351
+ setActiveIndex(index);
352
+ };
353
+ // Disabled options are included so they stay discoverable to screen reader
354
+ // users, per the APG guidance on disabled controls in composite widgets.
355
+ // Activating one is a no-op.
356
+ const moveActiveIndex = (direction) => {
357
+ if (activeIndex === NO_ACTIVE_INDEX) {
358
+ setActiveIndex(direction === 1
359
+ ? (0, combobox_utils_1.getFirstOptionIndex)(flatOptions)
360
+ : (0, combobox_utils_1.getLastOptionIndex)(flatOptions));
361
+ return;
362
+ }
363
+ setActiveIndex((0, combobox_utils_1.getNextOptionIndex)(flatOptions, activeIndex, direction));
364
+ };
365
+ /**
366
+ * Shared by the search input and the select-only trigger, since both drive
367
+ * the same virtual focus. Returns whether the key was consumed.
368
+ */
369
+ const handleNavigationKeyDown = (event) => {
370
+ switch (event.key) {
371
+ case 'ArrowDown':
372
+ event.preventDefault();
373
+ moveActiveIndex(1);
374
+ return true;
375
+ case 'ArrowUp':
376
+ event.preventDefault();
377
+ moveActiveIndex(-1);
378
+ return true;
379
+ case 'Home':
380
+ event.preventDefault();
381
+ setActiveIndex((0, combobox_utils_1.getFirstOptionIndex)(flatOptions));
382
+ return true;
383
+ case 'End':
384
+ event.preventDefault();
385
+ setActiveIndex((0, combobox_utils_1.getLastOptionIndex)(flatOptions));
386
+ return true;
387
+ case 'Enter':
388
+ if (activeOption === undefined) {
389
+ return false;
390
+ }
391
+ event.preventDefault();
392
+ handleOptionChange(activeOption);
393
+ return true;
394
+ default:
395
+ return false;
396
+ }
397
+ };
398
+ const handleSearchKeyDown = (event) => {
399
+ // Home and End belong to the text cursor while typing.
400
+ if (event.key === 'Home' || event.key === 'End') {
401
+ return;
402
+ }
403
+ if (handleNavigationKeyDown(event)) {
404
+ return;
405
+ }
406
+ if (event.key === 'Escape') {
407
+ // Escape belongs to this combobox: close immediately and keep it from
408
+ // dismissing an enclosing dialog, popover, or other composite widget.
409
+ event.preventDefault();
410
+ event.stopPropagation();
411
+ if (currentSearchValue !== '') {
412
+ setCurrentSearchValue('');
413
+ }
414
+ setIsPanelOpen(false);
415
+ }
416
+ };
417
+ const handleTriggerKeyDown = (event) => {
418
+ if (isDisabled) {
419
+ return;
420
+ }
421
+ if (!isPanelOpen) {
422
+ if (!OPENING_KEYS.includes(event.key)) {
423
+ return;
424
+ }
425
+ // Preventing the default keeps the button from synthesizing a click,
426
+ // which would let useClick toggle the panel straight back shut.
427
+ event.preventDefault();
428
+ setIsPanelOpen(true);
429
+ if (!isSearchable && event.key === 'ArrowDown') {
430
+ setActiveIndex((0, combobox_utils_1.getFirstOptionIndex)(flatOptions));
431
+ }
432
+ else if (!isSearchable && event.key === 'ArrowUp') {
433
+ setActiveIndex((0, combobox_utils_1.getLastOptionIndex)(flatOptions));
434
+ }
435
+ return;
436
+ }
437
+ if (event.key === 'Escape') {
438
+ event.preventDefault();
439
+ event.stopPropagation();
440
+ setIsPanelOpen(false);
441
+ return;
442
+ }
443
+ // A searchable panel holds DOM focus in its search input, so the trigger
444
+ // only ever sees keys while the panel is closed.
445
+ if (isSearchable) {
446
+ return;
447
+ }
448
+ if (event.key === ' ' && activeOption !== undefined) {
449
+ event.preventDefault();
450
+ handleOptionChange(activeOption);
451
+ return;
452
+ }
453
+ if (handleNavigationKeyDown(event)) {
454
+ return;
455
+ }
456
+ if (event.key === 'Enter' || event.key === ' ') {
457
+ event.preventDefault();
458
+ setIsPanelOpen(false);
459
+ }
460
+ };
461
+ // Actions are rows of the listbox but not results, so they keep the list
462
+ // rendered without hiding the message that nothing matched.
463
+ const hasRows = flatOptions.length > 0;
464
+ const hasOptionRows = flatOptions.length > actionRows.length;
465
+ // The pinned action rows live outside the scrolling listbox, so the listbox
466
+ // claims them through aria-owns to keep them its logical children.
467
+ const actionOwnsIds = actionRows.length > 0
468
+ ? actionRows.map((row) => getOptionId(row)).join(' ')
469
+ : undefined;
470
+ const isDialogPanel = isSearchable;
471
+ // An error replaces the help text, so only one message is ever rendered and
472
+ // only that one is described.
473
+ const displayedErrorText = errorText !== null && errorText !== void 0 ? errorText : nativeValidationMessage;
474
+ const hasError = Boolean(displayedErrorText);
475
+ const hasHelpText = Boolean(helpText) && !hasError;
476
+ const messageId = hasError ? errorTextId : helpTextId;
477
+ const describedById = hasError || hasHelpText ? messageId : undefined;
478
+ const triggerAria = {
479
+ 'aria-activedescendant': isPanelOpen && !isSearchable ? activeOptionId : undefined,
480
+ 'aria-controls': getTriggerControlsId({
481
+ hasRows,
482
+ isDialogPanel,
483
+ isPanelOpen,
484
+ listboxId,
485
+ panelId,
486
+ }),
487
+ 'aria-describedby': describedById,
488
+ 'aria-disabled': isDisabled ? true : undefined,
489
+ 'aria-expanded': isPanelOpen,
490
+ 'aria-haspopup': isDialogPanel ? 'dialog' : 'listbox',
491
+ 'aria-invalid': hasError ? true : undefined,
492
+ 'aria-required': isRequired ? true : undefined,
493
+ // The label points at the trigger through `htmlFor`, so clicking it moves
494
+ // focus to the trigger.
495
+ id: triggerId,
496
+ onKeyDown: handleTriggerKeyDown,
497
+ role: 'combobox',
498
+ };
499
+ // A custom trigger becomes the anchor itself, so it gets the reference props
500
+ // and the reference ref. The built-in trigger leaves both to its wrapper,
501
+ // which is what the panel should size to, and takes only the ARIA.
502
+ //
503
+ // It is also named by the label text itself rather than by a reference to it,
504
+ // since a custom trigger is free to leave the label unrendered.
505
+ const triggerProps = Object.assign(Object.assign(Object.assign({}, getReferenceProps()), triggerAria), { 'aria-label': label, ref: customTriggerRef });
506
+ const builtInTriggerProps = Object.assign(Object.assign({}, triggerAria), {
507
+ // The label row also holds the required indicator, so only the label text
508
+ // is referenced, keeping "Optional" out of the accessible name.
509
+ 'aria-labelledby': labelId, ref: builtInTriggerRef });
510
+ const field = {
511
+ errorText: displayedErrorText,
512
+ errorTextId,
513
+ hasError,
514
+ hasRequiredIndicator,
515
+ hasVisuallyHiddenLabel,
516
+ helpText,
517
+ helpTextId,
518
+ isRequired,
519
+ label,
520
+ labelId,
521
+ triggerId,
522
+ };
523
+ const triggerState = {
524
+ clear: clearSelection,
525
+ field,
526
+ isDisabled,
527
+ isMulti,
528
+ isOpen: isPanelOpen,
529
+ remove: removeOption,
530
+ selectedOptions,
531
+ };
532
+ // While windowed, the DOM no longer holds every option, so each one carries
533
+ // its position in the full list for assistive technology.
534
+ const getSetPositionProps = (row) => {
535
+ var _a;
536
+ return isListVirtualized
537
+ ? {
538
+ posInSet: ((_a = rowIndexes.get(row)) !== null && _a !== void 0 ? _a : 0) + 1,
539
+ setSize: flatOptions.length,
540
+ }
541
+ : {};
542
+ };
543
+ const renderOption = (option) => {
544
+ var _a;
545
+ const sharedProps = Object.assign({ id: getOptionId(option), isFocused: option === activeOption, onClick: () => handleOptionChange(option), onHover: () => handleOptionHover(option) }, getSetPositionProps(option));
546
+ // The create row is never selected and shows the create text rather than
547
+ // the label of the option it would produce.
548
+ if ((0, combobox_utils_1.isCreateOption)(option)) {
549
+ return ((0, jsx_runtime_1.jsx)(Option, Object.assign({}, sharedProps, { isCreate: true, isSelected: false, children: (0, jsx_runtime_1.jsx)(CreateOptionContent, { children: formatCreateLabel(currentSearchValue) }) }), "ndl-combobox-create"));
550
+ }
551
+ const { OptionLabel } = components;
552
+ const isSelected = isOptionSelected(option);
553
+ return ((0, jsx_runtime_1.jsx)(Option, Object.assign({}, sharedProps, { isSelected: isSelected, isDisabled: option.isDisabled, children: (0, jsx_runtime_1.jsx)(OptionContent, { label: option.label, customLabel: OptionLabel === undefined ? undefined : ((0, jsx_runtime_1.jsx)(OptionLabel, { option: option, isSelected: isSelected, isDisabled: (_a = option.isDisabled) !== null && _a !== void 0 ? _a : false, isFocused: option === activeOption, isMulti: isMulti, size: size })), description: option.description, leadingVisual: option.leadingVisual, isDisabled: option.isDisabled, isSelected: isSelected, isMulti: isMulti }) }), option.value));
554
+ };
555
+ // An action row shares the chrome of an option but never the selection, so it
556
+ // is rendered as a single select row: its own leading visual instead of a
557
+ // checkbox, and no selection indicator.
558
+ const renderAction = (row) => {
559
+ const action = (0, combobox_utils_1.getAction)(row);
560
+ return ((0, jsx_runtime_1.jsx)(Option, Object.assign({ id: getOptionId(row), isAction: true, isDisabled: action.isDisabled, isFocused: row === activeOption, onClick: () => handleOptionChange(row), onHover: () => handleOptionHover(row) }, getSetPositionProps(row), { children: (0, jsx_runtime_1.jsx)(OptionContent, { label: action.label, description: action.description, leadingVisual: action.leadingVisual, isDisabled: action.isDisabled }) }), row.value));
561
+ };
562
+ const panel = ((0, jsx_runtime_1.jsxs)(Panel, Object.assign({}, getFloatingProps(), { ref: refs.setFloating, id: panelId, ariaLabel: label, isDialog: isDialogPanel, className: themeClassName, style: floatingStyles, children: [isSearchable && ((0, jsx_runtime_1.jsx)(Input, { ref: searchInputRef, ariaLabel: searchPlaceholder, value: currentSearchValue, onValueChange: setCurrentSearchValue, placeholder: searchPlaceholder, listboxId: hasRows ? listboxId : undefined, activeOptionId: hasRows ? activeOptionId : undefined, onKeyDown: handleSearchKeyDown })), !hasOptionRows &&
563
+ (isLoading ? ((0, jsx_runtime_1.jsx)(LoadingState, { children: loadingMessage })) : ((0, jsx_runtime_1.jsx)(EmptyState, {}))), hasOptionRows &&
564
+ (virtualRows !== undefined ? ((0, jsx_runtime_1.jsx)(VirtualizedListBox, { id: listboxId, ariaLabel: label, isMulti: isMulti, ownsIds: actionOwnsIds, rows: virtualRows.displayRows, rowsKey: optionsKey, flatToDisplayIndex: virtualRows.flatToDisplayIndex, scrollToFlatIndexRef: scrollToActiveRowRef, renderRow: renderOption })) : ((0, jsx_runtime_1.jsx)(ListBox, { id: listboxId, ariaLabel: label, isMulti: isMulti, ownsIds: actionOwnsIds, children: optionItems.map((item) => (0, combobox_utils_1.isOptionGroup)(item) ? ((0, jsx_runtime_1.jsx)(Group, { label: item.label, children: item.options.map(renderOption) }, item.label)) : (renderOption(item))) }))), actionRows.length > 0 && ((0, jsx_runtime_1.jsxs)("div", Object.assign({}, (hasOptionRows
565
+ ? { role: 'presentation' }
566
+ : {
567
+ 'aria-label': label,
568
+ 'aria-multiselectable': isMulti || undefined,
569
+ id: listboxId,
570
+ role: 'listbox',
571
+ }), { className: "ndl-combobox-actions", children: [(0, jsx_runtime_1.jsx)(divider_1.Divider, { className: "ndl-combobox-action-divider", htmlAttributes: { 'aria-hidden': true } }), actionRows.map((row, index) => ((0, jsx_runtime_1.jsxs)(react_2.Fragment, { children: [index > 0 && (0, combobox_utils_1.getAction)(row).hasDivider === true && ((0, jsx_runtime_1.jsx)(divider_1.Divider, { className: "ndl-combobox-action-divider", htmlAttributes: { 'aria-hidden': true } })), renderAction(row)] }, row.value)))] })))] })));
572
+ return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: ref, style: style, className: (0, classnames_1.default)('ndl-combobox', {
573
+ 'ndl-clean': isClean,
574
+ 'ndl-combobox-custom-trigger': hasCustomTrigger,
575
+ 'ndl-combobox-field': !hasCustomTrigger,
576
+ 'ndl-disabled': isDisabled,
577
+ 'ndl-fluid': isFluid,
578
+ 'ndl-has-error': hasError,
579
+ 'ndl-large': size === 'large',
580
+ 'ndl-medium': size === 'medium',
581
+ 'ndl-small': size === 'small',
582
+ }, className) }, htmlAttributes, { children: [name !== undefined && ((0, jsx_runtime_1.jsx)(ComboboxFormControl, { ref: internalTriggerRef, name: name, form: formId, autoComplete: autoComplete, options: formOptions, selectedOptions: selectedOptions, resetSelectedOptions: resetSelectedOptions, isMulti: isMulti, isDisabled: isDisabled, isRequired: isRequired, isSelectionControlled: value !== undefined, onValueChange: setSelectedValue, onInvalid: setNativeValidationMessage, onReset: resetFromForm })), renderTrigger === undefined ? ((0, jsx_runtime_1.jsx)(Control, { triggerProps: builtInTriggerProps, state: triggerState, field: field, placeholder: placeholder, isClearable: isClearable, isClean: isClean, size: size, maxRows: isMulti ? maxRows : undefined, components: components, referenceProps: getReferenceProps(), referenceRef: refs.setReference })) : (renderTrigger(triggerProps, triggerState)), context.open && ((0, jsx_runtime_1.jsx)(conditional_wrap_1.ConditionalWrap, { shouldWrap: isPanelPortaled, wrap: (wrapChildren) => ((0, jsx_runtime_1.jsx)(react_1.FloatingPortal, { root: portalTarget, children: wrapChildren })), children: (0, jsx_runtime_1.jsx)(react_1.FloatingFocusManager, { context: context, modal: false,
583
+ // A select-only combobox keeps DOM focus on the trigger and speaks
584
+ // through aria-activedescendant, so nothing inside is focused.
585
+ initialFocus: isSearchable ? searchInputRef : -1, returnFocus: internalTriggerRef, children: panel }) }))] })));
586
+ };
587
+ exports.Combobox = Combobox;
588
+ const getTriggerControlsId = ({ isPanelOpen, isDialogPanel, hasRows, panelId, listboxId, }) => {
589
+ if (!isPanelOpen) {
590
+ return undefined;
591
+ }
592
+ if (isDialogPanel) {
593
+ return panelId;
594
+ }
595
+ // An expanded combobox must always control something, so fall back to the
596
+ // panel when the list is empty and only the status message is rendered.
597
+ return hasRows ? listboxId : panelId;
598
+ };
599
+ const getSelectedOptions = (value) => {
600
+ if (Array.isArray(value)) {
601
+ return value;
602
+ }
603
+ return value === null ? [] : [value];
604
+ };
605
+ /**
606
+ * A native select mirrors the custom control for submission, autofill,
607
+ * constraint validation, and reset. It is hidden from assistive technology
608
+ * because the combobox trigger exposes the same field semantics.
609
+ *
610
+ * Above `FORM_OPTION_MIRROR_LIMIT` it receives only the selected and default
611
+ * options rather than the full list, so autofill has nothing to offer but
612
+ * everything else keeps working without thousands of mirrored options.
613
+ */
614
+ const ComboboxFormControl = ({ ref: triggerRef, name, form, autoComplete, options, selectedOptions, resetSelectedOptions, isMulti, isDisabled, isRequired, isSelectionControlled, onValueChange, onInvalid, onReset, }) => {
615
+ var _a;
616
+ const selectRef = (0, react_2.useRef)(null);
617
+ const isDispatchingChangeRef = (0, react_2.useRef)(false);
618
+ const isResettingRef = (0, react_2.useRef)(false);
619
+ const skipNextChangeRef = (0, react_2.useRef)(false);
620
+ const selectedValues = selectedOptions.map((option) => option.value);
621
+ const resetSelectedValues = resetSelectedOptions.map((option) => option.value);
622
+ const selectedValuesKey = selectedValues.join('\u0000');
623
+ const previousValuesKeyRef = (0, react_2.useRef)(selectedValuesKey);
624
+ const valuesOnReset = isSelectionControlled
625
+ ? selectedValues
626
+ : resetSelectedValues;
627
+ const valuesOnResetKey = valuesOnReset.join('\u0000');
628
+ (0, react_2.useEffect)(() => {
629
+ const select = selectRef.current;
630
+ if (select === null) {
631
+ return;
632
+ }
633
+ const resetValues = new Set(valuesOnReset);
634
+ Array.from(select.options).forEach((option) => {
635
+ option.defaultSelected = resetValues.has(option.value);
636
+ });
637
+ // valuesOnReset is rebuilt every render, so the effect keys on its content.
638
+ // eslint-disable-next-line react-hooks/exhaustive-deps
639
+ }, [valuesOnResetKey]);
640
+ (0, react_2.useEffect)(() => {
641
+ if (previousValuesKeyRef.current === selectedValuesKey) {
642
+ return;
643
+ }
644
+ previousValuesKeyRef.current = selectedValuesKey;
645
+ if (isResettingRef.current || skipNextChangeRef.current) {
646
+ skipNextChangeRef.current = false;
647
+ return;
648
+ }
649
+ const select = selectRef.current;
650
+ if (select === null) {
651
+ return;
652
+ }
653
+ isDispatchingChangeRef.current = true;
654
+ select.dispatchEvent(new Event('change', { bubbles: true }));
655
+ isDispatchingChangeRef.current = false;
656
+ }, [selectedValuesKey]);
657
+ (0, react_2.useEffect)(() => {
658
+ const select = selectRef.current;
659
+ const formElement = select === null || select === void 0 ? void 0 : select.form;
660
+ if (select === null ||
661
+ select === undefined ||
662
+ formElement === null ||
663
+ formElement === undefined) {
664
+ return;
665
+ }
666
+ const handleReset = () => {
667
+ isResettingRef.current = true;
668
+ onReset();
669
+ window.setTimeout(() => {
670
+ isResettingRef.current = false;
671
+ });
672
+ };
673
+ formElement.addEventListener('reset', handleReset);
674
+ return () => formElement.removeEventListener('reset', handleReset);
675
+ }, [onReset]);
676
+ const handleChange = (event) => {
677
+ var _a;
678
+ if (isDispatchingChangeRef.current) {
679
+ return;
680
+ }
681
+ const optionsByValue = new Map(options.map((option) => [option.value, option]));
682
+ const nextOptions = Array.from(event.currentTarget.selectedOptions)
683
+ .map((selectedOption) => optionsByValue.get(selectedOption.value))
684
+ .filter((option) => option !== undefined);
685
+ skipNextChangeRef.current =
686
+ nextOptions.map((option) => option.value).join('\u0000') !==
687
+ selectedValuesKey;
688
+ onValueChange(isMulti ? nextOptions : ((_a = nextOptions[0]) !== null && _a !== void 0 ? _a : null));
689
+ };
690
+ return ((0, jsx_runtime_1.jsxs)("select", { ref: selectRef, className: "ndl-combobox-form-control", "aria-hidden": "true", tabIndex: -1, name: name, form: form, autoComplete: autoComplete, disabled: isDisabled, required: isRequired, multiple: isMulti, value: isMulti ? selectedValues : ((_a = selectedValues[0]) !== null && _a !== void 0 ? _a : ''), onChange: handleChange, onInvalid: (event) => {
691
+ var _a;
692
+ event.preventDefault();
693
+ onInvalid(event.currentTarget.validationMessage);
694
+ (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
695
+ }, children: [!isMulti && (0, jsx_runtime_1.jsx)("option", { value: "" }), options.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option.value, children: option.label }, option.value)))] }));
696
+ };
697
+ const Control = ({ triggerProps, state, field, placeholder, isClearable, isClean, size, maxRows, components, referenceProps, referenceRef, }) => {
698
+ const { isOpen, isMulti, isDisabled, selectedOptions, clear, remove } = state;
699
+ const { MultiValue, SingleValue } = components;
700
+ const { ref: triggerRef } = triggerProps, triggerAria = __rest(triggerProps, ["ref"]);
701
+ const hasValue = selectedOptions.length > 0;
702
+ const singleValueOption = isMulti ? undefined : selectedOptions[0];
703
+ // Selected values are shown as tags, so the trigger renders nothing and is
704
+ // taken out of the wrapping flow rather than claiming a row of its own.
705
+ const isTriggerCollapsed = isMulti && hasValue;
706
+ const multiValueSize = size === 'large' ? 'small' : 'x-small';
707
+ // `null` opts out of the default row limit, so both it and a missing value
708
+ // resolve to unlimited, which the hook expresses as undefined.
709
+ const rowLimit = isMulti && maxRows !== null && maxRows !== undefined ? maxRows : undefined;
710
+ // Under a row limit only the tags that could possibly be visible are
711
+ // rendered and measured; the rest of a huge selection is overflowed
712
+ // arithmetically without ever touching the DOM. Without a limit every tag
713
+ // renders, so unlimited rows with a huge selection is on the consumer.
714
+ const tagCandidateCap = rowLimit === undefined
715
+ ? selectedOptions.length
716
+ : Math.max(1, Math.floor(rowLimit)) * TAG_CANDIDATES_PER_ROW;
717
+ const renderedOptions = selectedOptions.length > tagCandidateCap
718
+ ? selectedOptions.slice(0, tagCandidateCap)
719
+ : selectedOptions;
720
+ const { containerRef: valueContainerRef, visibleCount } = (0, use_combobox_multi_value_overflow_1.useComboboxMultiValueOverflow)({
721
+ maxRows: rowLimit,
722
+ // Disabling swaps the dismissible tags for read-only ones, which have no
723
+ // remove button and are narrower. The total count is included since it
724
+ // sets the measure tag texts.
725
+ measureKey: [
726
+ size,
727
+ isDisabled,
728
+ selectedOptions.length,
729
+ ...renderedOptions.map((option) => `${option.value}:${option.label}`),
730
+ ].join('\u0000'),
731
+ tagCount: renderedOptions.length,
732
+ });
733
+ const visibleTagCount = visibleCount !== null && visibleCount !== void 0 ? visibleCount : renderedOptions.length;
734
+ const overflowCount = selectedOptions.length - visibleTagCount;
735
+ const overflowedOptions = selectedOptions.slice(visibleTagCount, visibleTagCount + OVERFLOW_LABEL_LIMIT);
736
+ const unnamedOverflowCount = overflowCount - overflowedOptions.length;
737
+ const overflowedLabels = overflowedOptions.map((option) => option.label).join(', ') +
738
+ (unnamedOverflowCount > 0 ? `, and ${unnamedOverflowCount} more` : '');
739
+ // "+999" is wider than "+1K", so each text the counter could show at its
740
+ // widest is measured; the hook reserves room for the widest of them.
741
+ const counterMeasureTexts = (0, use_combobox_multi_value_overflow_1.getOverflowCounterMeasureTexts)(selectedOptions.length);
742
+ const internalTriggerRef = (0, react_2.useRef)(null);
743
+ const mergedTriggerRef = (0, react_1.useMergeRefs)([
744
+ triggerRef,
745
+ internalTriggerRef,
746
+ ]);
747
+ const handleAuxiliaryActionKeyDown = (event) => {
748
+ var _a, _b;
749
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
750
+ event.preventDefault();
751
+ event.stopPropagation();
752
+ (_a = internalTriggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
753
+ (_b = triggerAria.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(triggerAria, event);
754
+ }
755
+ };
756
+ const handleTagRemove = (event, option) => {
757
+ var _a;
758
+ // Capture the following removal targets before React removes the current
759
+ // tag. With `maxRows`, the removal can promote a collapsed tag into view,
760
+ // so which of them is focusable is only known after the update.
761
+ const tag = event.currentTarget.closest('[data-ndl-combobox-multi-value]');
762
+ const followingRemoveButtons = [];
763
+ let sibling = (_a = tag === null || tag === void 0 ? void 0 : tag.nextElementSibling) !== null && _a !== void 0 ? _a : null;
764
+ while (sibling !== null) {
765
+ const removeButton = sibling.querySelector('[data-ndl-dismissible-tag-remove]');
766
+ if (removeButton !== null) {
767
+ followingRemoveButtons.push(removeButton);
768
+ }
769
+ sibling = sibling.nextElementSibling;
770
+ }
771
+ // Otherwise the click bubbles to the control and opens the panel.
772
+ event.stopPropagation();
773
+ remove(option);
774
+ requestAnimationFrame(() => {
775
+ var _a;
776
+ // A tag collapsed into the overflow counter is invisible and cannot take
777
+ // focus, so it is passed over.
778
+ const nextRemoveButton = followingRemoveButtons.find((removeButton) => removeButton.isConnected &&
779
+ removeButton.closest('.ndl-overflowed') === null);
780
+ if (nextRemoveButton !== undefined) {
781
+ nextRemoveButton.focus();
782
+ return;
783
+ }
784
+ (_a = internalTriggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
785
+ });
786
+ };
787
+ const handleClear = (event) => {
788
+ // Otherwise the click bubbles to the control and opens the panel.
789
+ event.stopPropagation();
790
+ clear();
791
+ // Clearing removes the focused clear button from the DOM. Restore focus to
792
+ // the combobox after React has committed that update.
793
+ requestAnimationFrame(() => {
794
+ var _a;
795
+ (_a = internalTriggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
796
+ });
797
+ };
798
+ // Clicking the label moves focus to the trigger without opening the panel,
799
+ // as with a native select. Preventing the default cancels the click the
800
+ // label would otherwise forward to the trigger button, which would toggle
801
+ // the panel.
802
+ const handleLabelClick = (event) => {
803
+ var _a;
804
+ event.preventDefault();
805
+ (_a = internalTriggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
806
+ };
807
+ return (
808
+ // A fragment, so the label, the control, and the message are rows of the
809
+ // combobox itself. A custom trigger replaces all three.
810
+ (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(FieldLabel, { field: field, size: size, onLabelClick: handleLabelClick }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: referenceRef, className: (0, classnames_1.default)('ndl-combobox-control', {
811
+ 'ndl-clean': isClean,
812
+ 'ndl-disabled': isDisabled,
813
+ 'ndl-has-error': field.hasError,
814
+ 'ndl-is-multi': isMulti,
815
+ 'ndl-large': size === 'large',
816
+ 'ndl-medium': size === 'medium',
817
+ 'ndl-small': size === 'small',
818
+ }) }, referenceProps, { children: [(0, jsx_runtime_1.jsxs)("div", { ref: valueContainerRef, className: (0, classnames_1.default)('ndl-combobox-value-container', {
819
+ // While a counter may be needed, every tag reserves room for it,
820
+ // so an extreme-width tag shrinks instead of collapsing entirely.
821
+ 'ndl-counter-reserved': rowLimit !== undefined && selectedOptions.length > 1,
822
+ }), children: [(0, jsx_runtime_1.jsxs)("button", Object.assign({ type: "button", className: (0, classnames_1.default)('ndl-combobox-trigger', {
823
+ 'ndl-collapsed': isTriggerCollapsed,
824
+ 'ndl-disabled': isDisabled,
825
+ }) }, triggerAria, { ref: mergedTriggerRef, children: [singleValueOption !== undefined &&
826
+ (SingleValue === undefined ? ((0, jsx_runtime_1.jsx)("span", { className: "ndl-combobox-single-value", children: singleValueOption.label })) : ((0, jsx_runtime_1.jsx)(SingleValue, { option: singleValueOption, isDisabled: isDisabled, size: size }))), !hasValue && ((0, jsx_runtime_1.jsx)("span", { className: "ndl-combobox-placeholder", children: placeholder }))] })), isMulti &&
827
+ renderedOptions.map((option, index) => {
828
+ // Tags past the overflow cutoff stay rendered so they keep their
829
+ // measurements, but the stylesheet takes them out of the layout,
830
+ // out of the accessibility tree, and out of focus reach.
831
+ const multiValueClasses = (0, classnames_1.default)('ndl-combobox-multi-value', {
832
+ 'ndl-overflowed': index >= visibleTagCount,
833
+ });
834
+ // A custom value owns its own presentation, so it is only given
835
+ // the wrapper the value container lays out, and no remove button.
836
+ if (MultiValue !== undefined) {
837
+ return ((0, jsx_runtime_1.jsx)("span", { className: multiValueClasses, children: (0, jsx_runtime_1.jsx)(MultiValue, { option: option, index: index, isDisabled: isDisabled, size: size, remove: () => remove(option) }) }, option.value));
838
+ }
839
+ return isDisabled ? ((0, jsx_runtime_1.jsx)(read_only_tag_1.ReadOnlyTag, { className: multiValueClasses, size: multiValueSize, children: option.label }, option.value)) : ((0, jsx_runtime_1.jsx)(dismissible_tag_1.DismissibleTag, { className: multiValueClasses, size: multiValueSize, htmlAttributes: {
840
+ 'data-ndl-combobox-multi-value': '',
841
+ onKeyDown: handleAuxiliaryActionKeyDown,
842
+ }, onRemove: (event) => handleTagRemove(event, option), children: option.label }, option.value));
843
+ }), overflowCount > 0 && ((0, jsx_runtime_1.jsxs)(read_only_tag_1.ReadOnlyTag, { className: "ndl-combobox-multi-value-counter", size: multiValueSize, htmlAttributes: {
844
+ title: overflowedLabels,
845
+ }, children: [(0, jsx_runtime_1.jsxs)("span", { "aria-hidden": "true", children: ["+", (0, use_combobox_multi_value_overflow_1.formatOverflowCount)(overflowCount)] }), (0, jsx_runtime_1.jsxs)("span", { className: "ndl-combobox-visually-hidden", children: [overflowCount, " more selected: ", overflowedLabels] })] })), rowLimit !== undefined &&
846
+ hasValue &&
847
+ // Invisible and out of the layout, purely to measure the widest
848
+ // counter the current selection could need, so the fit computation
849
+ // can reserve room for it in a single deterministic pass.
850
+ counterMeasureTexts.map((text) => ((0, jsx_runtime_1.jsxs)(read_only_tag_1.ReadOnlyTag, { className: "ndl-combobox-multi-value-measure", size: multiValueSize, htmlAttributes: { 'aria-hidden': true }, children: ["+", text] }, text)))] }), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-indicators-container", children: [isClearable && hasValue && !isDisabled && (
851
+ // The divider only separates the clear button from the chevron, so
852
+ // it is tied to the button rather than always rendered, as in
853
+ // Select.
854
+ (0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-clear", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "ndl-combobox-clear-button", "aria-label": "Clear selection", onKeyDown: handleAuxiliaryActionKeyDown, onClick: handleClear, children: (0, jsx_runtime_1.jsx)(heroIcons_1.XMarkIconOutline, {}) }), (0, jsx_runtime_1.jsx)("div", { className: "ndl-combobox-divider" })] })), (0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)('ndl-combobox-dropdown-indicator', {
855
+ 'ndl-open': isOpen,
856
+ }), children: (0, jsx_runtime_1.jsx)(heroIcons_1.ChevronDownIconOutline, { "aria-hidden": "true", className: "ndl-combobox-icon" }) })] })] })), (0, jsx_runtime_1.jsx)(FieldMessage, { field: field, size: size })] }));
857
+ };
858
+ /**
859
+ * The trigger is a button with `role="combobox"`, which takes its accessible
860
+ * name from the author rather than from its content, so the label is referenced
861
+ * through `aria-labelledby` instead of wrapping the control in a `<label>`.
862
+ * The label is still a `<label>` associated through `htmlFor`, and clicking it
863
+ * moves focus to the trigger, as with a native select. It deliberately does
864
+ * not open the panel, which is also what a native select label leaves alone.
865
+ */
866
+ const FieldLabel = ({ field, size, onLabelClick }) => {
867
+ const { label, labelId, triggerId, hasVisuallyHiddenLabel, hasRequiredIndicator, isRequired, } = field;
868
+ const variant = size === 'large' ? 'body-large' : 'body-medium';
869
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)('ndl-combobox-label-row', {
870
+ 'ndl-visually-hidden': hasVisuallyHiddenLabel,
871
+ }), children: [(0, jsx_runtime_1.jsx)(typography_1.Typography, { as: "label", variant: variant, className: "ndl-combobox-label", htmlAttributes: {
872
+ htmlFor: triggerId,
873
+ id: labelId,
874
+ onClick: onLabelClick,
875
+ }, children: label }), hasRequiredIndicator && ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: variant, className: "ndl-combobox-label-indicator", children: isRequired ? 'Required' : 'Optional' }))] }));
876
+ };
877
+ /**
878
+ * Rendered whether or not there is a message, so the live region is in the
879
+ * document before the text arrives and a validation error is announced.
880
+ */
881
+ const FieldMessage = ({ field, size }) => {
882
+ const { errorText, hasError, helpText, helpTextId, errorTextId } = field;
883
+ const variant = size === 'large' ? 'body-medium' : 'body-small';
884
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-message", "aria-live": "polite", children: [hasError && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "ndl-error-icon", children: (0, jsx_runtime_1.jsx)(heroIcons_1.ExclamationCircleIconSolid, {}) }), (0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: variant, className: "ndl-error-text", htmlAttributes: { id: errorTextId }, children: errorText })] })), !hasError && Boolean(helpText) && ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: variant, htmlAttributes: { id: helpTextId }, children: helpText }))] }));
885
+ };
886
+ const Panel = (_a) => {
887
+ var { children, ref, id, ariaLabel, isDialog, className, style } = _a, restProps = __rest(_a, ["children", "ref", "id", "ariaLabel", "isDialog", "className", "style"]);
888
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, id: id, role: isDialog === true ? 'dialog' : undefined, "aria-label": isDialog === true ? ariaLabel : undefined, className: (0, classnames_1.default)('ndl-combobox-panel', className), style: style }, restProps, { children: children })));
889
+ };
890
+ const Input = ({ ref, ariaLabel, value, onValueChange, placeholder, listboxId, activeOptionId, onKeyDown, }) => {
891
+ return ((0, jsx_runtime_1.jsxs)("label", { className: "ndl-combobox-input-wrapper", children: [(0, jsx_runtime_1.jsx)("span", { className: "ndl-combobox-input-label", children: ariaLabel }), (0, jsx_runtime_1.jsx)(heroIcons_1.MagnifyingGlassIconOutline, { className: "ndl-combobox-input-leading" }), (0, jsx_runtime_1.jsx)("input", { ref: ref, type: "text", role: "combobox", autoComplete: "off", "aria-autocomplete": "list", "aria-haspopup": "listbox", "aria-expanded": listboxId !== undefined, "aria-controls": listboxId, "aria-activedescendant": activeOptionId, className: "ndl-combobox-input", placeholder: placeholder, value: value, onChange: (event) => onValueChange(event.target.value), onKeyDown: onKeyDown })] }));
892
+ };
893
+ const ListBox = ({ ariaLabel, children, isMulti, id, ownsIds, }) => {
894
+ return ((0, jsx_runtime_1.jsx)("div", { id: id, role: "listbox", "aria-label": ariaLabel, "aria-owns": ownsIds, "aria-multiselectable": isMulti || undefined, className: "ndl-combobox-listbox", children: children }));
895
+ };
896
+ /**
897
+ * The windowed listbox used above the row threshold: only the visible slice of
898
+ * rows is in the DOM, absolutely positioned inside one full-height spacer.
899
+ * Group nesting cannot survive windowing, so headers render as presentational
900
+ * rows of their own and options carry `aria-setsize`/`aria-posinset` instead.
901
+ * Pinned action rows render outside, below the listbox, so they are never
902
+ * windowed.
903
+ */
904
+ const VirtualizedListBox = ({ id, ariaLabel, isMulti, ownsIds, rows, rowsKey, flatToDisplayIndex, scrollToFlatIndexRef, renderRow, }) => {
905
+ const scrollRef = (0, react_2.useRef)(null);
906
+ const virtualizer = (0, react_virtual_1.useVirtualizer)({
907
+ count: rows.length,
908
+ estimateSize: () => VIRTUAL_ROW_ESTIMATED_HEIGHT,
909
+ getScrollElement: () => scrollRef.current,
910
+ overscan: 10,
911
+ });
912
+ const pendingScrollIndexRef = (0, react_2.useRef)(null);
913
+ (0, react_2.useEffect)(() => {
914
+ scrollToFlatIndexRef.current = (flatIndex) => {
915
+ const displayIndex = flatToDisplayIndex[flatIndex];
916
+ if (displayIndex !== undefined) {
917
+ pendingScrollIndexRef.current = displayIndex;
918
+ virtualizer.scrollToIndex(displayIndex);
919
+ }
920
+ };
921
+ return () => {
922
+ scrollToFlatIndexRef.current = null;
923
+ };
924
+ });
925
+ // A long jump scrolls to an offset built from estimated sizes, and the
926
+ // estimates only get corrected once the rows near the target render, so one
927
+ // scroll can land short. Each render after a scroll re-issues it until the
928
+ // target row is actually mounted, which is also what lets
929
+ // aria-activedescendant resolve.
930
+ (0, react_2.useEffect)(() => {
931
+ const target = pendingScrollIndexRef.current;
932
+ if (target === null) {
933
+ return;
934
+ }
935
+ const isRendered = virtualizer
936
+ .getVirtualItems()
937
+ .some((item) => item.index === target);
938
+ if (isRendered) {
939
+ pendingScrollIndexRef.current = null;
940
+ return;
941
+ }
942
+ virtualizer.scrollToIndex(target);
943
+ });
944
+ // A filtered list is a different list, so it starts at the top.
945
+ (0, react_2.useEffect)(() => {
946
+ virtualizer.scrollToOffset(0);
947
+ // The virtualizer is stable and scrolling must not rerun on its rerenders.
948
+ // eslint-disable-next-line react-hooks/exhaustive-deps
949
+ }, [rowsKey]);
950
+ return ((0, jsx_runtime_1.jsx)("div", { ref: scrollRef, id: id, role: "listbox", "aria-label": ariaLabel, "aria-owns": ownsIds, "aria-multiselectable": isMulti === true || undefined, className: "ndl-combobox-listbox", children: (0, jsx_runtime_1.jsx)("div", { role: "presentation", className: "ndl-combobox-virtual-space", style: { height: virtualizer.getTotalSize() }, children: virtualizer.getVirtualItems().map((item) => {
951
+ const row = rows[item.index];
952
+ return ((0, jsx_runtime_1.jsx)("div", { ref: virtualizer.measureElement, "data-index": item.index, role: "presentation", className: "ndl-combobox-virtual-row", style: { transform: `translateY(${item.start}px)` }, children: row.kind === 'group-header' ? ((0, jsx_runtime_1.jsx)(GroupHeader, { isPresentational: true, label: row.label })) : (renderRow(row.row)) }, item.key));
953
+ }) }) }));
954
+ };
955
+ const Option = ({ children, id, isSelected, isDisabled, isFocused, isCreate, isAction, onClick, onHover, posInSet, setSize, }) => {
956
+ const classes = (0, classnames_1.default)('ndl-combobox-option', {
957
+ 'ndl-combobox-option-action': isAction,
958
+ 'ndl-combobox-option-create': isCreate,
959
+ 'ndl-disabled': isDisabled,
960
+ 'ndl-focused': isFocused,
961
+ });
962
+ return (
963
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus -- options are never DOM focused; keyboard is handled on the search input or the listbox and exposed through aria-activedescendant
964
+ (0, jsx_runtime_1.jsx)("div", { id: id, role: "option", "aria-selected": isSelected, "aria-disabled": isDisabled, "aria-posinset": posInSet, "aria-setsize": setSize, className: classes, onClick: onClick,
965
+ // Options are not focusable, so a plain mousedown would blur whatever
966
+ // holds DOM focus - the search input or the trigger - and drop the panel.
967
+ onMouseDown: (event) => event.preventDefault(),
968
+ // mouseover fires when the element under a stationary cursor changes
969
+ // because the list scrolled, which would let a rested mouse steal virtual
970
+ // focus back during keyboard navigation. mousemove needs real movement.
971
+ onMouseMove: onHover, children: children }));
972
+ };
973
+ const OptionContent = ({ label, customLabel, description, leadingVisual, isDisabled, isSelected, isMulti, }) => {
974
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-option-content", children: [isMulti ? ((0, jsx_runtime_1.jsx)(MultiSelectIndicator, { isDisabled: isDisabled, isSelected: isSelected })) : (leadingVisual && ((0, jsx_runtime_1.jsx)(LeadingVisual, { isDisabled: isDisabled, children: leadingVisual }))), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-option-content-main", children: [customLabel !== null && customLabel !== void 0 ? customLabel : (0, jsx_runtime_1.jsx)(Label, { isDisabled: isDisabled, children: label }), description && ((0, jsx_runtime_1.jsx)(Description, { isDisabled: isDisabled, children: description }))] }), !isMulti && isSelected && (0, jsx_runtime_1.jsx)(SelectionIndicator, { isDisabled: isDisabled })] }));
975
+ };
976
+ const CreateOptionContent = ({ children }) => {
977
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "ndl-combobox-option-content", children: [(0, jsx_runtime_1.jsx)(LeadingVisual, { children: (0, jsx_runtime_1.jsx)(heroIcons_1.PlusIconOutline, {}) }), (0, jsx_runtime_1.jsx)("div", { className: "ndl-combobox-option-content-main", children: (0, jsx_runtime_1.jsx)(Label, { children: children }) })] }));
978
+ };
979
+ const LeadingVisual = ({ children, isDisabled }) => {
980
+ const classes = (0, classnames_1.default)('ndl-combobox-option-content-leading-visual', {
981
+ 'ndl-disabled': isDisabled,
982
+ });
983
+ return (0, jsx_runtime_1.jsx)("div", { className: classes, children: children });
984
+ };
985
+ const Label = ({ children, isDisabled }) => {
986
+ const classes = (0, classnames_1.default)('ndl-combobox-option-content-label', {
987
+ 'ndl-disabled': isDisabled,
988
+ });
989
+ return ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: "body-medium", className: classes, children: children }));
990
+ };
991
+ const Description = ({ children, isDisabled }) => {
992
+ const classes = (0, classnames_1.default)('ndl-combobox-option-content-description', {
993
+ 'ndl-disabled': isDisabled,
994
+ });
995
+ return ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: "body-small", className: classes, children: children }));
996
+ };
997
+ const SelectionIndicator = ({ isDisabled, }) => {
998
+ const classes = (0, classnames_1.default)('ndl-combobox-option-content-selection-indicator', {
999
+ 'ndl-disabled': isDisabled,
1000
+ });
1001
+ return (0, jsx_runtime_1.jsx)(heroIcons_1.CheckIconOutline, { className: classes });
1002
+ };
1003
+ /**
1004
+ * The option itself owns selection through `aria-selected` and its click
1005
+ * handler, so the checkbox is decorative. `inert` keeps it out of the
1006
+ * accessibility tree and out of hit testing - without it the wrapping label
1007
+ * forwards a second click to the input, which bubbles back to the option and
1008
+ * undoes the toggle.
1009
+ */
1010
+ const MultiSelectIndicator = ({ isDisabled, isSelected, }) => {
1011
+ return ((0, jsx_runtime_1.jsx)("span", { className: "ndl-combobox-option-content-multi-select-indicator", inert: true, children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { ariaLabel: "Select", isChecked: isSelected, isDisabled: isDisabled }) }));
1012
+ };
1013
+ /**
1014
+ * The visible group header text, shared by both listbox paths: nested inside a
1015
+ * `role="group"` wrapper below the row threshold, or as a presentational row of
1016
+ * its own while windowed.
1017
+ */
1018
+ const GroupHeader = ({ label, id, isPresentational, }) => {
1019
+ return ((0, jsx_runtime_1.jsx)("div", { id: id, role: isPresentational === true ? 'presentation' : undefined, className: "ndl-combobox-group-header", children: label }));
1020
+ };
1021
+ const Group = ({ children, label }) => {
1022
+ const headerId = (0, react_2.useId)();
1023
+ return ((0, jsx_runtime_1.jsxs)("div", { role: "group", "aria-labelledby": headerId, className: "ndl-combobox-group", children: [(0, jsx_runtime_1.jsx)(GroupHeader, { id: headerId, label: label }), children] }));
1024
+ };
1025
+ const EmptyState = ({ children }) => {
1026
+ return ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: "body-medium", className: "ndl-combobox-empty-state", htmlAttributes: { role: 'status' }, children: children !== null && children !== void 0 ? children : 'No results found' }));
1027
+ };
1028
+ const LoadingState = ({ children }) => {
1029
+ return ((0, jsx_runtime_1.jsxs)("div", { role: "status", className: "ndl-combobox-empty-state ndl-combobox-loading-state", children: [(0, jsx_runtime_1.jsx)(loading_spinner_1.LoadingSpinner, { size: "small", htmlAttributes: { 'aria-hidden': true } }), (0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: "body-medium", children: children !== null && children !== void 0 ? children : DEFAULT_LOADING_MESSAGE })] }));
1030
+ };
1031
+ //# sourceMappingURL=Combobox.js.map