@galaxy-io/dls 1.6.1 → 2.0.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 (540) hide show
  1. package/README.md +47 -50
  2. package/biome/index.json +74 -0
  3. package/biome/plugins/no-hex-literal.grit +20 -0
  4. package/biome/plugins/no-inline-style.grit +13 -0
  5. package/biome/plugins/no-margin.grit +9 -0
  6. package/biome/plugins/warn/no-hex-literal.grit +23 -0
  7. package/biome/plugins/warn/no-inline-style.grit +13 -0
  8. package/biome/plugins/warn/no-margin.grit +9 -0
  9. package/dist/a11y/FocusScope.d.ts +44 -0
  10. package/dist/a11y/FocusScope.js +229 -0
  11. package/dist/a11y/VisuallyHidden.d.ts +21 -0
  12. package/dist/a11y/VisuallyHidden.js +30 -0
  13. package/dist/avatar/Avatar.d.ts +56 -14
  14. package/dist/avatar/Avatar.js +250 -96
  15. package/dist/avatar/AvatarGroup.d.ts +43 -0
  16. package/dist/avatar/AvatarGroup.js +153 -0
  17. package/dist/backgrounds/GridBackground.d.ts +48 -0
  18. package/dist/backgrounds/GridBackground.js +146 -0
  19. package/dist/beacons/Beacon.d.ts +86 -22
  20. package/dist/beacons/Beacon.js +170 -47
  21. package/dist/brand/GalaxyFilamentWordmark.d.ts +30 -0
  22. package/dist/brand/GalaxyFilamentWordmark.js +39 -0
  23. package/dist/brand/GalaxyLogomark.d.ts +26 -0
  24. package/dist/brand/GalaxyLogomark.js +41 -0
  25. package/dist/brand/GalaxyLogomarkAnimation.d.ts +36 -0
  26. package/dist/brand/GalaxyLogomarkAnimation.js +107 -0
  27. package/dist/brand/GalaxyWordmark.d.ts +30 -0
  28. package/dist/brand/GalaxyWordmark.js +63 -0
  29. package/dist/brand/assets/galaxy-logomark-animation.js +136678 -0
  30. package/dist/brand/brandMark.d.ts +26 -0
  31. package/dist/brand/brandMark.js +35 -0
  32. package/dist/buttons/Button.d.ts +148 -59
  33. package/dist/buttons/Button.js +437 -227
  34. package/dist/buttons/ButtonGroup.d.ts +38 -0
  35. package/dist/buttons/ButtonGroup.js +68 -0
  36. package/dist/buttons/CopyButton.d.ts +77 -0
  37. package/dist/buttons/CopyButton.js +65 -0
  38. package/dist/charts/AreaChart.d.ts +49 -0
  39. package/dist/charts/AreaChart.js +31 -0
  40. package/dist/charts/BarChart.d.ts +54 -67
  41. package/dist/charts/BarChart.js +160 -124
  42. package/dist/charts/ChartAxis.d.ts +35 -40
  43. package/dist/charts/ChartAxis.js +114 -90
  44. package/dist/charts/ChartCategoryTargets.d.ts +12 -26
  45. package/dist/charts/ChartCategoryTargets.js +33 -20
  46. package/dist/charts/ChartFrame.d.ts +71 -116
  47. package/dist/charts/ChartFrame.js +306 -97
  48. package/dist/charts/ChartGroupProvider.d.ts +30 -41
  49. package/dist/charts/ChartGroupProvider.js +20 -7
  50. package/dist/charts/ChartLegend.d.ts +37 -48
  51. package/dist/charts/ChartLegend.js +243 -125
  52. package/dist/charts/ChartMarkTargets.d.ts +12 -28
  53. package/dist/charts/ChartMarkTargets.js +34 -20
  54. package/dist/charts/ChartPrimitives.d.ts +50 -305
  55. package/dist/charts/ChartPrimitives.js +65 -413
  56. package/dist/charts/ChartTooltip.d.ts +20 -26
  57. package/dist/charts/ChartTooltip.js +77 -40
  58. package/dist/charts/Heatmap.d.ts +35 -0
  59. package/dist/charts/Heatmap.js +307 -0
  60. package/dist/charts/LineChart.d.ts +54 -47
  61. package/dist/charts/LineChart.js +174 -121
  62. package/dist/charts/PieChart.d.ts +32 -53
  63. package/dist/charts/PieChart.js +159 -136
  64. package/dist/charts/Sparkline.d.ts +107 -0
  65. package/dist/charts/Sparkline.js +244 -0
  66. package/dist/charts/StatChart.d.ts +37 -13
  67. package/dist/charts/StatChart.js +82 -35
  68. package/dist/charts/barChartGeometry.d.ts +138 -86
  69. package/dist/charts/barChartGeometry.js +122 -101
  70. package/dist/charts/barChartLegend.d.ts +14 -18
  71. package/dist/charts/barChartLegend.js +18 -13
  72. package/dist/charts/chartFormat.d.ts +12 -18
  73. package/dist/charts/chartFormat.js +20 -52
  74. package/dist/charts/chartPalette.d.ts +33 -0
  75. package/dist/charts/chartPalette.js +68 -0
  76. package/dist/charts/chartScales.d.ts +41 -108
  77. package/dist/charts/chartScales.js +62 -128
  78. package/dist/charts/chartSelection.d.ts +15 -0
  79. package/dist/charts/chartSelection.js +22 -0
  80. package/dist/charts/constants.d.ts +100 -256
  81. package/dist/charts/constants.js +109 -275
  82. package/dist/charts/heatmapGeometry.d.ts +95 -0
  83. package/dist/charts/heatmapGeometry.js +155 -0
  84. package/dist/charts/lineChartGeometry.d.ts +108 -58
  85. package/dist/charts/lineChartGeometry.js +120 -78
  86. package/dist/charts/pieChartGeometry.d.ts +66 -40
  87. package/dist/charts/pieChartGeometry.js +68 -38
  88. package/dist/charts/types.d.ts +206 -466
  89. package/dist/charts/types.js +55 -144
  90. package/dist/charts/useCartesianChartLayout.d.ts +28 -59
  91. package/dist/charts/useCartesianChartLayout.js +47 -32
  92. package/dist/charts/useChartDimensions.d.ts +19 -40
  93. package/dist/charts/useChartDimensions.js +40 -56
  94. package/dist/charts/useChartFormatters.d.ts +6 -15
  95. package/dist/charts/useChartFormatters.js +4 -9
  96. package/dist/charts/useChartInteraction.d.ts +69 -116
  97. package/dist/charts/useChartInteraction.js +148 -78
  98. package/dist/charts/useChartTooltipPosition.d.ts +29 -39
  99. package/dist/charts/useChartTooltipPosition.js +14 -5
  100. package/dist/charts/useSeriesColorResolver.d.ts +11 -33
  101. package/dist/charts/useSeriesColorResolver.js +19 -12
  102. package/dist/chips/Chip.d.ts +155 -44
  103. package/dist/chips/Chip.js +329 -155
  104. package/dist/chips/ChipGroup.d.ts +53 -0
  105. package/dist/chips/ChipGroup.js +98 -0
  106. package/dist/drawer/Drawer.d.ts +83 -17
  107. package/dist/drawer/Drawer.js +399 -65
  108. package/dist/dropdown/Dropdown.d.ts +80 -63
  109. package/dist/dropdown/Dropdown.js +127 -129
  110. package/dist/editor/CodeEditor.d.ts +119 -40
  111. package/dist/editor/CodeEditor.js +490 -400
  112. package/dist/editor/CodeEditorReactWidget.d.ts +20 -17
  113. package/dist/editor/CodeEditorReactWidget.js +28 -21
  114. package/dist/feedback/Alert.d.ts +49 -0
  115. package/dist/feedback/Alert.js +172 -0
  116. package/dist/feedback/EmptyState.d.ts +39 -0
  117. package/dist/feedback/EmptyState.js +109 -0
  118. package/dist/feedback/ProgressBar.d.ts +125 -0
  119. package/dist/feedback/ProgressBar.js +183 -0
  120. package/dist/feedback/ProgressCircle.d.ts +99 -0
  121. package/dist/feedback/ProgressCircle.js +187 -0
  122. package/dist/feedback/Skeleton.d.ts +69 -0
  123. package/dist/feedback/Skeleton.js +130 -0
  124. package/dist/feedback/Spinner.d.ts +63 -0
  125. package/dist/feedback/Spinner.js +141 -0
  126. package/dist/fonts.css +70 -0
  127. package/dist/hooks/internal/useIsClient.d.ts +2 -0
  128. package/dist/hooks/internal/useIsClient.js +14 -0
  129. package/dist/hooks/useClipboard.d.ts +19 -0
  130. package/dist/hooks/useClipboard.js +45 -0
  131. package/dist/hooks/useControlledOpenState.d.ts +21 -0
  132. package/dist/hooks/useControlledOpenState.js +19 -0
  133. package/dist/hooks/useControlledState.d.ts +19 -0
  134. package/dist/hooks/useControlledState.js +22 -0
  135. package/dist/hooks/useDebouncedValue.d.ts +14 -0
  136. package/dist/hooks/useDebouncedValue.js +33 -0
  137. package/dist/hooks/useDisclosure.d.ts +26 -0
  138. package/dist/hooks/useDisclosure.js +36 -0
  139. package/dist/hooks/useDismiss.d.ts +23 -27
  140. package/dist/hooks/useDismiss.js +39 -27
  141. package/dist/hooks/useElementClientSize.d.ts +10 -10
  142. package/dist/hooks/useElementClientSize.js +8 -11
  143. package/dist/hooks/useElementWidth.d.ts +6 -13
  144. package/dist/hooks/useElementWidth.js +6 -14
  145. package/dist/hooks/useField.d.ts +47 -0
  146. package/dist/hooks/useField.js +40 -0
  147. package/dist/hooks/useHotkey.d.ts +77 -0
  148. package/dist/hooks/useHotkey.js +246 -0
  149. package/dist/hooks/useIsMac.d.ts +12 -0
  150. package/dist/hooks/useIsMac.js +27 -0
  151. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +17 -0
  152. package/dist/hooks/useIsomorphicLayoutEffect.js +20 -0
  153. package/dist/hooks/useLocalStorage.d.ts +28 -0
  154. package/dist/hooks/useLocalStorage.js +145 -0
  155. package/dist/hooks/useMediaQuery.d.ts +11 -0
  156. package/dist/hooks/useMediaQuery.js +23 -0
  157. package/dist/hooks/usePrefersReducedMotion.d.ts +12 -0
  158. package/dist/hooks/usePrefersReducedMotion.js +25 -0
  159. package/dist/hooks/useResize.d.ts +42 -62
  160. package/dist/hooks/useResize.js +43 -31
  161. package/dist/icons/Icon.d.ts +64 -37
  162. package/dist/icons/Icon.js +96 -37
  163. package/dist/icons/internal/IconSlot.d.ts +10 -0
  164. package/dist/icons/internal/IconSlot.js +32 -0
  165. package/dist/inputs/CheckboxGroup.d.ts +115 -0
  166. package/dist/inputs/CheckboxGroup.js +183 -0
  167. package/dist/inputs/CheckboxInput.d.ts +98 -28
  168. package/dist/inputs/CheckboxInput.js +218 -88
  169. package/dist/inputs/ColorInput.d.ts +115 -29
  170. package/dist/inputs/ColorInput.js +748 -326
  171. package/dist/inputs/CopyInput.d.ts +49 -20
  172. package/dist/inputs/CopyInput.js +57 -49
  173. package/dist/inputs/CronInput.d.ts +94 -0
  174. package/dist/inputs/CronInput.js +618 -0
  175. package/dist/inputs/DateInput.d.ts +175 -28
  176. package/dist/inputs/DateInput.js +1109 -451
  177. package/dist/inputs/DurationInput.d.ts +101 -0
  178. package/dist/inputs/DurationInput.js +441 -0
  179. package/dist/inputs/Field.d.ts +75 -0
  180. package/dist/inputs/Field.js +247 -0
  181. package/dist/inputs/Fieldset.d.ts +53 -0
  182. package/dist/inputs/Fieldset.js +149 -0
  183. package/dist/inputs/FileInput.d.ts +110 -0
  184. package/dist/inputs/FileInput.js +476 -0
  185. package/dist/inputs/Input.d.ts +148 -187
  186. package/dist/inputs/Input.js +339 -259
  187. package/dist/inputs/KeyValueInput.d.ts +120 -0
  188. package/dist/inputs/KeyValueInput.js +383 -0
  189. package/dist/inputs/MultiSelectInput.d.ts +165 -54
  190. package/dist/inputs/MultiSelectInput.js +175 -559
  191. package/dist/inputs/NumberInput.d.ts +65 -9
  192. package/dist/inputs/NumberInput.js +197 -40
  193. package/dist/inputs/PasswordInput.d.ts +17 -12
  194. package/dist/inputs/PasswordInput.js +33 -33
  195. package/dist/inputs/PinInput.d.ts +117 -0
  196. package/dist/inputs/PinInput.js +364 -0
  197. package/dist/inputs/RadioGroup.d.ts +115 -0
  198. package/dist/inputs/RadioGroup.js +179 -0
  199. package/dist/inputs/RadioInput.d.ts +87 -24
  200. package/dist/inputs/RadioInput.js +182 -95
  201. package/dist/inputs/SearchInput.d.ts +66 -0
  202. package/dist/inputs/SearchInput.js +156 -0
  203. package/dist/inputs/SelectInput.d.ts +195 -55
  204. package/dist/inputs/SelectInput.js +1308 -446
  205. package/dist/inputs/SliderInput.d.ts +150 -30
  206. package/dist/inputs/SliderInput.js +441 -186
  207. package/dist/inputs/SwitchInput.d.ts +109 -0
  208. package/dist/inputs/SwitchInput.js +248 -0
  209. package/dist/inputs/TagInput.d.ts +117 -0
  210. package/dist/inputs/TagInput.js +408 -0
  211. package/dist/inputs/TextAreaInput.d.ts +146 -44
  212. package/dist/inputs/TextAreaInput.js +298 -198
  213. package/dist/inputs/TextInput.d.ts +4 -13
  214. package/dist/inputs/TextInput.js +13 -48
  215. package/dist/inputs/ToggleInput.d.ts +120 -22
  216. package/dist/inputs/ToggleInput.js +262 -116
  217. package/dist/inputs/internal/bareField.d.ts +18 -0
  218. package/dist/inputs/internal/bareField.js +38 -0
  219. package/dist/inputs/internal/devWarn.d.ts +7 -0
  220. package/dist/inputs/internal/devWarn.js +30 -0
  221. package/dist/inputs/internal/fieldFrame.d.ts +34 -0
  222. package/dist/inputs/internal/fieldFrame.js +64 -0
  223. package/dist/inputs/internal/fieldWiring.d.ts +50 -0
  224. package/dist/inputs/internal/fieldWiring.js +38 -0
  225. package/dist/inputs/internal/innerButton.d.ts +2 -0
  226. package/dist/inputs/internal/innerButton.js +24 -0
  227. package/dist/inputs/internal/ownField.d.ts +45 -0
  228. package/dist/inputs/internal/ownField.js +58 -0
  229. package/dist/inputs/internal/predicates.d.ts +9 -0
  230. package/dist/inputs/internal/predicates.js +11 -0
  231. package/dist/inputs/internal/useComboboxPopover.d.ts +46 -0
  232. package/dist/inputs/internal/useComboboxPopover.js +92 -0
  233. package/dist/json/JsonViewer.d.ts +62 -0
  234. package/dist/json/JsonViewer.js +483 -0
  235. package/dist/layout/Box.d.ts +87 -0
  236. package/dist/layout/Box.js +109 -0
  237. package/dist/layout/Divider.d.ts +25 -0
  238. package/dist/layout/Divider.js +70 -0
  239. package/dist/layout/Flex.d.ts +139 -0
  240. package/dist/layout/Flex.js +165 -0
  241. package/dist/layout/FlexItem.d.ts +72 -0
  242. package/dist/layout/FlexItem.js +84 -0
  243. package/dist/layout/Grid.d.ts +140 -0
  244. package/dist/layout/Grid.js +178 -0
  245. package/dist/layout/Portal.d.ts +29 -0
  246. package/dist/layout/Portal.js +27 -0
  247. package/dist/layout/ResizablePanels.d.ts +88 -0
  248. package/dist/layout/ResizablePanels.js +198 -0
  249. package/dist/layout/ResizeHandle.d.ts +48 -0
  250. package/dist/layout/ResizeHandle.js +143 -0
  251. package/dist/layout/ScrollArea.d.ts +30 -0
  252. package/dist/layout/ScrollArea.js +138 -0
  253. package/dist/links/Link.d.ts +101 -1
  254. package/dist/links/Link.js +166 -4
  255. package/dist/lists/BulletedList.d.ts +25 -7
  256. package/dist/lists/BulletedList.js +72 -27
  257. package/dist/lists/DescriptionList.d.ts +91 -0
  258. package/dist/lists/DescriptionList.js +147 -0
  259. package/dist/menu/ContextMenu.d.ts +64 -0
  260. package/dist/menu/ContextMenu.js +34 -0
  261. package/dist/menu/Menu.d.ts +280 -0
  262. package/dist/menu/Menu.js +1003 -0
  263. package/dist/modal/ConfirmDialog.d.ts +55 -0
  264. package/dist/modal/ConfirmDialog.js +151 -0
  265. package/dist/modal/Modal.d.ts +64 -14
  266. package/dist/modal/Modal.js +189 -48
  267. package/dist/navigation/Breadcrumbs.d.ts +65 -0
  268. package/dist/navigation/Breadcrumbs.js +243 -0
  269. package/dist/navigation/CommandPalette.d.ts +83 -0
  270. package/dist/navigation/CommandPalette.js +509 -0
  271. package/dist/navigation/Pagination.d.ts +87 -0
  272. package/dist/navigation/Pagination.js +300 -0
  273. package/dist/navigation/SidebarNav.d.ts +107 -0
  274. package/dist/navigation/SidebarNav.js +341 -0
  275. package/dist/navigation/Stepper.d.ts +52 -0
  276. package/dist/navigation/Stepper.js +207 -0
  277. package/dist/navigation/Tabs.d.ts +113 -0
  278. package/dist/navigation/Tabs.js +543 -0
  279. package/dist/navigation/Topbar.d.ts +20 -0
  280. package/dist/navigation/Topbar.js +62 -0
  281. package/dist/overlay/OverlayProvider.d.ts +28 -32
  282. package/dist/overlay/OverlayProvider.js +42 -93
  283. package/dist/overlay/useOverlay.d.ts +88 -12
  284. package/dist/overlay/useOverlay.js +220 -43
  285. package/dist/overlays/Popover.d.ts +114 -0
  286. package/dist/overlays/Popover.js +389 -0
  287. package/dist/shapes/Circle.d.ts +84 -8
  288. package/dist/shapes/Circle.js +122 -20
  289. package/dist/shapes/Square.d.ts +84 -8
  290. package/dist/shapes/Square.js +123 -20
  291. package/dist/styles/mixins.d.ts +35 -0
  292. package/dist/styles/mixins.js +60 -0
  293. package/dist/styles.css +1 -202
  294. package/dist/table/InfiniteSpreadsheet.d.ts +140 -10
  295. package/dist/table/InfiniteSpreadsheet.js +762 -225
  296. package/dist/table/InfiniteTable.d.ts +116 -14
  297. package/dist/table/InfiniteTable.js +287 -429
  298. package/dist/table/SpreadsheetCellEditor.d.ts +54 -6
  299. package/dist/table/SpreadsheetCellEditor.js +163 -84
  300. package/dist/table/TableCore.d.ts +252 -0
  301. package/dist/table/TableCore.js +1082 -0
  302. package/dist/table/VirtualizedInfiniteTable.d.ts +104 -14
  303. package/dist/table/VirtualizedInfiniteTable.js +280 -219
  304. package/dist/table/types.d.ts +66 -244
  305. package/dist/table/types.js +0 -51
  306. package/dist/table/useInfiniteScrollSentinel.d.ts +25 -21
  307. package/dist/table/useInfiniteScrollSentinel.js +38 -30
  308. package/dist/table/useSpreadsheetCellNavigation.d.ts +44 -0
  309. package/dist/table/useSpreadsheetCellNavigation.js +141 -0
  310. package/dist/table/useTableColumnLayout.d.ts +90 -25
  311. package/dist/table/useTableColumnLayout.js +255 -50
  312. package/dist/table/useTableKeyboardNavigation.d.ts +32 -21
  313. package/dist/table/useTableKeyboardNavigation.js +95 -71
  314. package/dist/table/useTableRowSelection.d.ts +24 -25
  315. package/dist/table/useTableRowSelection.js +57 -84
  316. package/dist/table/useTableVirtualizer.d.ts +20 -0
  317. package/dist/table/useTableVirtualizer.js +48 -0
  318. package/dist/text/Bold.d.ts +5 -6
  319. package/dist/text/Bold.js +16 -15
  320. package/dist/text/Code.d.ts +5 -6
  321. package/dist/text/Code.js +16 -25
  322. package/dist/text/CodeBlock.d.ts +77 -0
  323. package/dist/text/CodeBlock.js +373 -0
  324. package/dist/text/EditableText.d.ts +55 -15
  325. package/dist/text/EditableText.js +153 -135
  326. package/dist/text/Ellipsis.d.ts +34 -8
  327. package/dist/text/Ellipsis.js +55 -23
  328. package/dist/text/Kbd.d.ts +72 -0
  329. package/dist/text/Kbd.js +284 -0
  330. package/dist/text/MarkdownText.d.ts +27 -10
  331. package/dist/text/MarkdownText.js +317 -300
  332. package/dist/text/RequiredMarker.d.ts +17 -5
  333. package/dist/text/RequiredMarker.js +34 -36
  334. package/dist/text/Selectable.d.ts +5 -6
  335. package/dist/text/Selectable.js +16 -13
  336. package/dist/text/Span.d.ts +82 -0
  337. package/dist/text/Span.js +115 -0
  338. package/dist/text/Text.d.ts +128 -41
  339. package/dist/text/Text.js +209 -76
  340. package/dist/theme/GalaxyProvider.d.ts +75 -0
  341. package/dist/theme/GalaxyProvider.js +223 -0
  342. package/dist/theme/ThemeSwitcher.d.ts +52 -0
  343. package/dist/theme/ThemeSwitcher.js +94 -0
  344. package/dist/theme/enums.d.ts +118 -0
  345. package/dist/theme/enums.js +144 -0
  346. package/dist/theme/tokens/dark.d.ts +2 -0
  347. package/dist/theme/tokens/dark.js +275 -0
  348. package/dist/theme/tokens/generate.d.ts +15 -0
  349. package/dist/theme/tokens/generate.js +47 -0
  350. package/dist/theme/tokens/light.d.ts +2 -0
  351. package/dist/theme/tokens/light.js +274 -0
  352. package/dist/theme/tokens/palette.d.ts +14 -0
  353. package/dist/theme/tokens/palette.js +153 -0
  354. package/dist/theme/tokens/scales.d.ts +26 -0
  355. package/dist/theme/tokens/scales.js +154 -0
  356. package/dist/theme/tokens/t.d.ts +3 -0
  357. package/dist/theme/tokens/t.js +25 -0
  358. package/dist/theme/tokens/types.d.ts +189 -0
  359. package/dist/theme/tokens/types.js +0 -0
  360. package/dist/theme/useGalaxyTheme.d.ts +46 -0
  361. package/dist/theme/useGalaxyTheme.js +36 -0
  362. package/dist/theme/useInverseScope.d.ts +18 -0
  363. package/dist/theme/useInverseScope.js +33 -0
  364. package/dist/three/GalaxyLogomark3D.d.ts +46 -0
  365. package/dist/three/GalaxyLogomark3D.js +134 -0
  366. package/dist/three/assets/galaxy-logomark.js +4 -0
  367. package/dist/three/galaxyLogomark3DScene.d.ts +27 -0
  368. package/dist/three/galaxyLogomark3DScene.js +165 -0
  369. package/dist/timeline/Timeline.d.ts +120 -0
  370. package/dist/timeline/Timeline.js +301 -0
  371. package/dist/toast/Toast.d.ts +43 -19
  372. package/dist/toast/Toast.js +130 -89
  373. package/dist/toast/ToastProvider.d.ts +23 -23
  374. package/dist/toast/ToastProvider.js +298 -101
  375. package/dist/toast/ToastViewport.d.ts +12 -0
  376. package/dist/toast/ToastViewport.js +210 -0
  377. package/dist/toast/useToast.d.ts +139 -5
  378. package/dist/toast/useToast.js +15 -13
  379. package/dist/tokens.css +654 -0
  380. package/dist/tooltip/Tooltip.d.ts +64 -44
  381. package/dist/tooltip/Tooltip.js +280 -73
  382. package/dist/tooltip/internal/LazyTooltip.d.ts +7 -0
  383. package/dist/tooltip/internal/LazyTooltip.js +53 -0
  384. package/dist/transform/Fade.d.ts +33 -11
  385. package/dist/transform/Fade.js +71 -19
  386. package/dist/transform/Flasher.d.ts +25 -0
  387. package/dist/transform/Flasher.js +39 -0
  388. package/dist/transform/Rotate.d.ts +24 -12
  389. package/dist/transform/Rotate.js +36 -22
  390. package/dist/transform/ScaleFade.d.ts +36 -11
  391. package/dist/transform/ScaleFade.js +86 -28
  392. package/dist/transform/internal/CollapseMotion.d.ts +10 -0
  393. package/dist/transform/internal/CollapseMotion.js +65 -0
  394. package/dist/tree/Tree.d.ts +85 -0
  395. package/dist/tree/Tree.js +387 -0
  396. package/dist/tree/treeRow.d.ts +39 -0
  397. package/dist/tree/treeRow.js +75 -0
  398. package/dist/utils/colors.d.ts +46 -13
  399. package/dist/utils/colors.js +92 -34
  400. package/dist/utils/css.d.ts +23 -0
  401. package/dist/utils/css.js +30 -0
  402. package/dist/utils/format.d.ts +58 -0
  403. package/dist/utils/format.js +220 -0
  404. package/dist/vite/index.d.ts +16 -0
  405. package/dist/vite/index.js +52 -0
  406. package/dist/widget/Widget.d.ts +164 -48
  407. package/dist/widget/Widget.js +335 -96
  408. package/dist/widget/WidgetGroup.d.ts +36 -0
  409. package/dist/widget/WidgetGroup.js +103 -0
  410. package/package.json +855 -157
  411. package/dist/accordion/Accordion.d.ts +0 -54
  412. package/dist/accordion/Accordion.js +0 -183
  413. package/dist/assets/components/GalaxyFilamentWordmarkDark.d.ts +0 -3
  414. package/dist/assets/components/GalaxyFilamentWordmarkDark.js +0 -14
  415. package/dist/assets/components/GalaxyFilamentWordmarkLight.d.ts +0 -3
  416. package/dist/assets/components/GalaxyFilamentWordmarkLight.js +0 -14
  417. package/dist/assets/components/GalaxyLogomarkDark.d.ts +0 -3
  418. package/dist/assets/components/GalaxyLogomarkDark.js +0 -11
  419. package/dist/assets/components/GalaxyLogomarkLight.d.ts +0 -3
  420. package/dist/assets/components/GalaxyLogomarkLight.js +0 -17
  421. package/dist/assets/components/GalaxyWordmarkDark.d.ts +0 -3
  422. package/dist/assets/components/GalaxyWordmarkDark.js +0 -15
  423. package/dist/assets/components/GalaxyWordmarkLight.d.ts +0 -3
  424. package/dist/assets/components/GalaxyWordmarkLight.js +0 -28
  425. package/dist/assets/models/galaxy-animation.js +0 -136666
  426. package/dist/assets/models/galaxy-logomark.js +0 -4
  427. package/dist/avatar/AvatarShimmer.d.ts +0 -30
  428. package/dist/avatar/AvatarShimmer.js +0 -22
  429. package/dist/backgrounds/CellGridBackground.d.ts +0 -24
  430. package/dist/backgrounds/CellGridBackground.js +0 -44
  431. package/dist/backgrounds/CrossGridBackground.d.ts +0 -26
  432. package/dist/backgrounds/CrossGridBackground.js +0 -49
  433. package/dist/backgrounds/DotGridBackground.d.ts +0 -24
  434. package/dist/backgrounds/DotGridBackground.js +0 -47
  435. package/dist/badge/Badge.d.ts +0 -42
  436. package/dist/badge/Badge.js +0 -99
  437. package/dist/charts/ChartEmptyState.d.ts +0 -17
  438. package/dist/charts/ChartEmptyState.js +0 -29
  439. package/dist/charts/ProgressBar.d.ts +0 -29
  440. package/dist/charts/ProgressBar.js +0 -65
  441. package/dist/charts/chartTheme.d.ts +0 -18
  442. package/dist/charts/chartTheme.js +0 -26
  443. package/dist/containers/FlexItem.d.ts +0 -50
  444. package/dist/containers/FlexItem.js +0 -91
  445. package/dist/containers/FlexWrapper.d.ts +0 -96
  446. package/dist/containers/FlexWrapper.js +0 -143
  447. package/dist/containers/GridWrapper.d.ts +0 -79
  448. package/dist/containers/GridWrapper.js +0 -125
  449. package/dist/containers/Spacing.d.ts +0 -23
  450. package/dist/containers/Spacing.js +0 -59
  451. package/dist/containers/Wrapper.d.ts +0 -26
  452. package/dist/containers/Wrapper.js +0 -54
  453. package/dist/dividers/HorizontalDivider.d.ts +0 -7
  454. package/dist/dividers/HorizontalDivider.js +0 -39
  455. package/dist/dividers/VerticalDivider.d.ts +0 -6
  456. package/dist/dividers/VerticalDivider.js +0 -21
  457. package/dist/icons/GalaxyFilamentWordmark.d.ts +0 -11
  458. package/dist/icons/GalaxyFilamentWordmark.js +0 -25
  459. package/dist/icons/GalaxyLogomark.d.ts +0 -13
  460. package/dist/icons/GalaxyLogomark.js +0 -27
  461. package/dist/icons/GalaxyLogomark3D.d.ts +0 -56
  462. package/dist/icons/GalaxyLogomark3D.js +0 -319
  463. package/dist/icons/GalaxyLogomarkAnimation.d.ts +0 -18
  464. package/dist/icons/GalaxyLogomarkAnimation.js +0 -48
  465. package/dist/icons/GalaxyWordmark.d.ts +0 -11
  466. package/dist/icons/GalaxyWordmark.js +0 -25
  467. package/dist/inputs/MultiTextInput.d.ts +0 -32
  468. package/dist/inputs/MultiTextInput.js +0 -180
  469. package/dist/inputs/SwitcherInput.d.ts +0 -30
  470. package/dist/inputs/SwitcherInput.js +0 -81
  471. package/dist/inputs/hooks.d.ts +0 -10
  472. package/dist/inputs/hooks.js +0 -22
  473. package/dist/overlay/constants.d.ts +0 -28
  474. package/dist/overlay/constants.js +0 -37
  475. package/dist/resizable/HorizontalResizeHandle.d.ts +0 -17
  476. package/dist/resizable/HorizontalResizeHandle.js +0 -38
  477. package/dist/resizable/VerticalResizeHandle.d.ts +0 -18
  478. package/dist/resizable/VerticalResizeHandle.js +0 -39
  479. package/dist/table/SpreadsheetCell.d.ts +0 -21
  480. package/dist/table/SpreadsheetCell.js +0 -71
  481. package/dist/table/SpreadsheetHeaderCell.d.ts +0 -23
  482. package/dist/table/SpreadsheetHeaderCell.js +0 -91
  483. package/dist/table/SpreadsheetMenuItem.d.ts +0 -14
  484. package/dist/table/SpreadsheetMenuItem.js +0 -33
  485. package/dist/table/SpreadsheetPrimitives.d.ts +0 -172
  486. package/dist/table/SpreadsheetPrimitives.js +0 -105
  487. package/dist/table/SpreadsheetRow.d.ts +0 -33
  488. package/dist/table/SpreadsheetRow.js +0 -73
  489. package/dist/table/SpreadsheetRowActionCell.d.ts +0 -14
  490. package/dist/table/SpreadsheetRowActionCell.js +0 -51
  491. package/dist/table/TableExpandCell.d.ts +0 -11
  492. package/dist/table/TableExpandCell.js +0 -24
  493. package/dist/table/TableExpandedRow.d.ts +0 -16
  494. package/dist/table/TableExpandedRow.js +0 -77
  495. package/dist/table/TableHeaderCellContent.d.ts +0 -30
  496. package/dist/table/TableHeaderCellContent.js +0 -48
  497. package/dist/table/TablePrimitives.d.ts +0 -313
  498. package/dist/table/TablePrimitives.js +0 -302
  499. package/dist/table/TableSelectCell.d.ts +0 -18
  500. package/dist/table/TableSelectCell.js +0 -27
  501. package/dist/table/TableStatusRow.d.ts +0 -18
  502. package/dist/table/TableStatusRow.js +0 -50
  503. package/dist/table/useSpreadsheetEditing.d.ts +0 -13
  504. package/dist/table/useSpreadsheetEditing.js +0 -41
  505. package/dist/table/useTableColumnResize.d.ts +0 -38
  506. package/dist/table/useTableColumnResize.js +0 -155
  507. package/dist/table/useTableRowExpansion.d.ts +0 -24
  508. package/dist/table/useTableRowExpansion.js +0 -62
  509. package/dist/table/utils.d.ts +0 -50
  510. package/dist/table/utils.js +0 -78
  511. package/dist/text/HotKeys.d.ts +0 -30
  512. package/dist/text/HotKeys.js +0 -74
  513. package/dist/text/Paragraph.d.ts +0 -33
  514. package/dist/text/Paragraph.js +0 -83
  515. package/dist/text/TextShimmer.d.ts +0 -14
  516. package/dist/text/TextShimmer.js +0 -34
  517. package/dist/theme/GalaxyTheme.d.ts +0 -67
  518. package/dist/theme/GalaxyTheme.js +0 -119
  519. package/dist/theme/constants.d.ts +0 -8
  520. package/dist/theme/constants.js +0 -38
  521. package/dist/theme/index.d.ts +0 -6
  522. package/dist/theme/index.js +0 -7
  523. package/dist/theme/types.d.ts +0 -195
  524. package/dist/theme/types.js +0 -10
  525. package/dist/theme/typography.d.ts +0 -2
  526. package/dist/theme/typography.js +0 -73
  527. package/dist/theme/variants/dark.d.ts +0 -2
  528. package/dist/theme/variants/dark.js +0 -125
  529. package/dist/theme/variants/light.d.ts +0 -2
  530. package/dist/theme/variants/light.js +0 -125
  531. package/dist/toast/ToastContainer.d.ts +0 -10
  532. package/dist/toast/ToastContainer.js +0 -31
  533. package/dist/toast/ToastWrapper.d.ts +0 -6
  534. package/dist/toast/ToastWrapper.js +0 -27
  535. package/dist/transform/Flash.d.ts +0 -7
  536. package/dist/transform/Flash.js +0 -18
  537. package/dist/transform/Spin.d.ts +0 -14
  538. package/dist/transform/Spin.js +0 -25
  539. package/dist/utils/linaria.d.ts +0 -1
  540. package/dist/utils/linaria.js +0 -8
@@ -1,495 +1,1357 @@
1
- import { withTheme } from "../theme/GalaxyTheme.js";
2
- import { getCssSizeValue } from "../utils/linaria.js";
1
+ import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
2
+ import { Placement } from "../theme/enums.js";
3
+ import Text, { TextSize, TextVariant, TextWeight } from "../text/Text.js";
4
+ import { CONTROL_SIZE, SPACE, TYPE_SCALE } from "../theme/tokens/scales.js";
5
+ import { useControlledState } from "../hooks/useControlledState.js";
6
+ import { useControlledOpenState } from "../hooks/useControlledOpenState.js";
7
+ import Tooltip from "../tooltip/Tooltip.js";
8
+ import Divider from "../layout/Divider.js";
9
+ import ScrollArea from "../layout/ScrollArea.js";
10
+ import Popover, { PopoverRole, PopoverTrigger, PopoverVariant } from "../overlays/Popover.js";
11
+ import Icon, { IconVariant } from "../icons/Icon.js";
3
12
  import Rotate from "../transform/Rotate.js";
4
- import Text, { TextSize, TextVariant } from "../text/Text.js";
5
- import FlexItem from "../containers/FlexItem.js";
6
- import Dropdown, { DropdownVariant } from "../dropdown/Dropdown.js";
7
- import Icon, { IconVariant, IconWeight } from "../icons/Icon.js";
8
- import FlexWrapper, { AlignItems, FlexDirection } from "../containers/FlexWrapper.js";
9
- import { InputLabel, InputSize, InputVariant, getInputBorderColor, getInputFontSize, getInputHeight, getInputIconSize, getInputLineHeight, getInputPadding, getInputSurfaceColor, getInputTextSize } from "./Input.js";
10
- import HorizontalDivider from "../dividers/HorizontalDivider.js";
11
- import { useElementWidth } from "../hooks/useElementWidth.js";
12
- import { useControlledOpenState, useDebouncedValue } from "./hooks.js";
13
+ import Button, { ButtonSize, ButtonVariant } from "../buttons/Button.js";
14
+ import { isDevelopment, useDevWarning } from "./internal/devWarn.js";
15
+ import Spinner, { SpinnerSize } from "../feedback/Spinner.js";
16
+ import { useDebouncedValue } from "../hooks/useDebouncedValue.js";
17
+ import Span from "../text/Span.js";
18
+ import { hasContent, isFilled } from "./internal/predicates.js";
19
+ import { useFieldWiring } from "./internal/fieldWiring.js";
20
+ import Field, { FieldSize } from "./Field.js";
21
+ import { rootStyle, useOwnField } from "./internal/ownField.js";
22
+ import { BareFieldContext, useIsBareField } from "./internal/bareField.js";
23
+ import "./internal/innerButton.js";
24
+ import { useDedupedOpen, useRestoreFocusAfterOutsidePress } from "./internal/useComboboxPopover.js";
13
25
  /* empty css */
14
26
  import { styled } from "@linaria/react";
15
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
27
+ import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
16
28
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
- import { ArrowCounterClockwiseIcon, CaretDownIcon, CheckIcon } from "@phosphor-icons/react";
29
+ import { cx } from "@linaria/core";
30
+ import { useMergeRefs } from "@floating-ui/react";
31
+ import { CaretDownIcon, CheckIcon, PlusIcon, XIcon } from "@phosphor-icons/react";
32
+ import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
18
33
  //#region src/inputs/SelectInput.tsx
19
- var INPUT_VARIANT_TO_DROPDOWN_VARIANT = {
20
- [InputVariant.TRANSPARENT]: DropdownVariant.TRANSPARENT,
21
- [InputVariant.BASE]: DropdownVariant.PRIMARY,
22
- [InputVariant.PRIMARY]: DropdownVariant.PRIMARY,
23
- [InputVariant.SECONDARY]: DropdownVariant.SECONDARY,
24
- [InputVariant.TERTIARY]: DropdownVariant.TERTIARY
34
+ /**
35
+ * The form control for choosing one value from a list of options. A field shows the chosen option
36
+ * (or a placeholder) and opens a listbox panel below it, where the user moves through the options
37
+ * with the arrow keys or the pointer, optionally narrows them by typing a search term, and picks
38
+ * one. The value is the option's `id`, a string, so it serializes into forms and URLs.
39
+ *
40
+ * The panel is a `Popover` (`role` `LISTBOX`, `MANUAL`: opened only by the field). The field is a
41
+ * WAI-ARIA combobox with `aria-activedescendant`: DOM focus stays on it the whole time. Rows follow
42
+ * Menu's anatomy (rungs, highlight, check mark, group headings, filter and typeahead).
43
+ * Options can carry a description, an icon or leading node, a group heading and a disabled
44
+ * reason; some can be pinned to the top. Lists over 100 rows are virtualized.
45
+ *
46
+ * `SelectInputRoot` is the `@internal` engine that `MultiSelectInput` shares. Neither validates
47
+ * (the app passes `error`) nor fetches (the app answers `onSearch` and `onEndReached`).
48
+ *
49
+ * SSR: closed by default, so the server renders the field only. Nothing here touches `window` or
50
+ * `document` at module scope; the virtualizer, measuring and timers run in effects.
51
+ */
52
+ /** Canonical rungs; SelectInput supports all five (the Input rung table). */
53
+ var SelectInputSize = /* @__PURE__ */ function(SelectInputSize) {
54
+ /** 20px field, caption text, 12px icons, 10px caret; 24px rows, 12px row icons. */
55
+ SelectInputSize["X_SMALL"] = "X_SMALL";
56
+ /** 24px field, body_sm text, 12px icons, 10px caret; 24px rows, 12px row icons. */
57
+ SelectInputSize["SMALL"] = "SMALL";
58
+ /** 32px field, body_md text, 14px icons, 12px caret; 32px rows, 14px row icons. The default (1.x `MEDIUM` was 28px). */
59
+ SelectInputSize["MEDIUM"] = "MEDIUM";
60
+ /** 36px field, body_lg text, 14px icons, 12px caret; 32px rows, 14px row icons. */
61
+ SelectInputSize["LARGE"] = "LARGE";
62
+ /** 40px field, body_lg text, 16px icons, 12px caret; 32px rows, 14px row icons. */
63
+ SelectInputSize["X_LARGE"] = "X_LARGE";
64
+ return SelectInputSize;
65
+ }({});
66
+ /** Resting fill of the field (Constitution V-A surface set; 1.x `InputVariant` unchanged). */
67
+ var SelectInputVariant = /* @__PURE__ */ function(SelectInputVariant) {
68
+ /** `background.base` fill. */
69
+ SelectInputVariant["BASE"] = "BASE";
70
+ /** `background.primary` fill. The default. */
71
+ SelectInputVariant["PRIMARY"] = "PRIMARY";
72
+ /** `background.secondary` fill. */
73
+ SelectInputVariant["SECONDARY"] = "SECONDARY";
74
+ /** `background.tertiary` fill. */
75
+ SelectInputVariant["TERTIARY"] = "TERTIARY";
76
+ return SelectInputVariant;
77
+ }({});
78
+ /** The field's width without `fillWidth` (no width token exists; the `MENU_MAX_WIDTH` precedent). */
79
+ /** The panel's widest list (Menu's maximum) and tallest list. */
80
+ /** The panel is `border-box` with a 0.5px hairline top and bottom (Menu's compensation). */
81
+ /** Pause after typing before `onSearch` receives the term. */
82
+ var SELECT_SEARCH_DEBOUNCE_MS = 150;
83
+ /** Pause after which the typeahead prefix starts over. */
84
+ var SELECT_TYPEAHEAD_RESET_MS = 500;
85
+ /** Above this many rows the list renders through the virtualizer. */
86
+ var SELECT_VIRTUALIZE_THRESHOLD = 100;
87
+ /** Rows moved by PageDown / PageUp. */
88
+ var SELECT_PAGE_STEP = 10;
89
+ /** Rows rendered beyond the visible ones when virtualized. */
90
+ var SELECT_VIRTUAL_OVERSCAN = 8;
91
+ /** The list's block inset (4px less the panel hairline), for the virtualizer's start and end padding. */
92
+ var SELECT_LIST_INSET_PX = Number.parseFloat(SPACE[4]) - .5;
93
+ /** The field caret: 12px, 10px on the 20px and 24px rungs (the collapsible Widget's caret). */
94
+ var SELECT_INPUT_SIZE_TO_CARET_PX_MAP = {
95
+ ["X_SMALL"]: 10,
96
+ ["SMALL"]: 10,
97
+ ["MEDIUM"]: 12,
98
+ ["LARGE"]: 12,
99
+ ["X_LARGE"]: 12
25
100
  };
26
- var _exp = () => ({ $size }) => `0 ${getInputPadding($size)}`;
27
- var _exp2 = () => ({ $size }) => getInputHeight($size);
28
- var _exp3 = () => ({ $fillWidth, $width }) => $fillWidth ? "100%" : $width ? getCssSizeValue($width) : "auto";
29
- var _exp4 = () => ({ $isDisabled }) => $isDisabled ? "not-allowed" : "pointer";
30
- var _exp5 = () => ({ $isDisabled }) => $isDisabled ? .5 : 1;
31
- var _exp6 = () => ({ theme, $variant }) => getInputSurfaceColor(theme, $variant);
32
- var _exp7 = () => ({ theme, $variant, $isActive, $isError }) => {
33
- if ($isActive) return theme.color.border.selected;
34
- if ($isError) return theme.color.border.error;
35
- return getInputBorderColor(theme, $variant);
101
+ /** Field icon px per rung: one step below the rung's III-A.4 icon (Mitch, 2026-10-04). */
102
+ var SELECT_INPUT_SIZE_TO_ICON_PX_MAP = {
103
+ ["X_SMALL"]: 12,
104
+ ["SMALL"]: 12,
105
+ ["MEDIUM"]: 14,
106
+ ["LARGE"]: 14,
107
+ ["X_LARGE"]: 16
36
108
  };
37
- var _exp8 = () => ({ theme, $variant, $isActive }) => $isActive ? theme.color.border.selected : getInputBorderColor(theme, $variant);
38
- var StyledSelectInput = withTheme(/*#__PURE__*/ styled("div")({
39
- name: "StyledSelectInput",
40
- class: "s1ecxi56",
41
- propsAsIs: false,
42
- vars: {
43
- "s1ecxi56-0": [_exp()],
44
- "s1ecxi56-1": [_exp2()],
45
- "s1ecxi56-2": [_exp3()],
46
- "s1ecxi56-3": [_exp4()],
47
- "s1ecxi56-4": [_exp5()],
48
- "s1ecxi56-5": [_exp6()],
49
- "s1ecxi56-6": [_exp7()],
50
- "s1ecxi56-7": [_exp8()]
51
- }
52
- }));
53
- var _exp9 = () => ({ $size }) => getInputHeight($size);
54
- var _exp0 = () => ({ theme }) => theme.color.text.primary;
55
- var _exp1 = () => ({ theme, $size }) => getInputFontSize(theme, $size);
56
- var _exp10 = () => ({ theme, $size }) => getInputLineHeight(theme, $size);
57
- var _exp11 = () => ({ theme }) => theme.font.sans.weight.medium;
58
- var _exp12 = () => ({ theme }) => theme.font.sans.family;
59
- var _exp13 = () => ({ theme }) => theme.color.text.tertiary;
60
- var StyledSearchInput = withTheme(/*#__PURE__*/ styled("input")({
61
- name: "StyledSearchInput",
62
- class: "s14m69un",
63
- propsAsIs: false,
64
- vars: {
65
- "s14m69un-0": [_exp9()],
66
- "s14m69un-1": [_exp0()],
67
- "s14m69un-2": [_exp1()],
68
- "s14m69un-3": [_exp10()],
69
- "s14m69un-4": [_exp11()],
70
- "s14m69un-5": [_exp12()],
71
- "s14m69un-6": [_exp13()]
72
- }
73
- }));
74
- var _exp14 = () => ({ $minWidth }) => `min(${getCssSizeValue($minWidth)}, calc(100vw - 16px))`;
75
- var _exp15 = () => ({ $width }) => getCssSizeValue($width, "max-content");
76
- var SelectInputContent = /*#__PURE__*/ styled("div")({
77
- name: "SelectInputContent",
78
- class: "sbfl8n6",
79
- propsAsIs: false,
80
- vars: {
81
- "sbfl8n6-0": [_exp14()],
82
- "sbfl8n6-1": [_exp15()]
83
- }
109
+ /** The clear button's rung: 20px up to MEDIUM, 24px for LARGE and X_LARGE (Input's). */
110
+ var SELECT_INPUT_SIZE_TO_BUTTON_SIZE_MAP = {
111
+ ["X_SMALL"]: ButtonSize.X_SMALL,
112
+ ["SMALL"]: ButtonSize.X_SMALL,
113
+ ["MEDIUM"]: ButtonSize.X_SMALL,
114
+ ["LARGE"]: ButtonSize.SMALL,
115
+ ["X_LARGE"]: ButtonSize.SMALL
116
+ };
117
+ /** The clear button's px per rung, for the reserved padding (16 on X_SMALL: `X_SMALL_INNER_BUTTON_CSS`). */
118
+ /** Field text step per rung (the canonical control text). */
119
+ var SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP = {
120
+ ["X_SMALL"]: TextSize.CAPTION,
121
+ ["SMALL"]: TextSize.BODY_SM,
122
+ ["MEDIUM"]: TextSize.BODY_MD,
123
+ ["LARGE"]: TextSize.BODY_LG,
124
+ ["X_LARGE"]: TextSize.BODY_LG
125
+ };
126
+ /** The `Field` a SelectInput renders for its own `label` / `error` uses the same rung. */
127
+ var SELECT_INPUT_SIZE_TO_FIELD_SIZE_MAP = {
128
+ ["X_SMALL"]: FieldSize.X_SMALL,
129
+ ["SMALL"]: FieldSize.SMALL,
130
+ ["MEDIUM"]: FieldSize.MEDIUM,
131
+ ["LARGE"]: FieldSize.LARGE,
132
+ ["X_LARGE"]: FieldSize.X_LARGE
133
+ };
134
+ /** Row label step per rung (Menu's two rungs). */
135
+ var SELECT_INPUT_SIZE_TO_ROW_TEXT_SIZE_MAP = {
136
+ ["X_SMALL"]: TextSize.BODY_SM,
137
+ ["SMALL"]: TextSize.BODY_SM,
138
+ ["MEDIUM"]: TextSize.BODY_MD,
139
+ ["LARGE"]: TextSize.BODY_MD,
140
+ ["X_LARGE"]: TextSize.BODY_MD
141
+ };
142
+ /** Row description and group heading step per rung. */
143
+ var SELECT_INPUT_SIZE_TO_ROW_SECONDARY_TEXT_SIZE_MAP = {
144
+ ["X_SMALL"]: TextSize.CAPTION,
145
+ ["SMALL"]: TextSize.CAPTION,
146
+ ["MEDIUM"]: TextSize.BODY_SM,
147
+ ["LARGE"]: TextSize.BODY_SM,
148
+ ["X_LARGE"]: TextSize.BODY_SM
149
+ };
150
+ /** Row icon (and check mark) px per row rung (24px → 12, 32px → 14; Mitch, 2026-10-04). */
151
+ var SELECT_INPUT_SIZE_TO_ROW_ICON_PX_MAP = {
152
+ ["X_SMALL"]: 12,
153
+ ["SMALL"]: 12,
154
+ ["MEDIUM"]: 14,
155
+ ["LARGE"]: 14,
156
+ ["X_LARGE"]: 14
157
+ };
158
+ /** The loading row's spinner names the row rung (24px or 32px). */
159
+ var SELECT_INPUT_SIZE_TO_SPINNER_SIZE_MAP = {
160
+ ["X_SMALL"]: SpinnerSize.SMALL,
161
+ ["SMALL"]: SpinnerSize.SMALL,
162
+ ["MEDIUM"]: SpinnerSize.MEDIUM,
163
+ ["LARGE"]: SpinnerSize.MEDIUM,
164
+ ["X_LARGE"]: SpinnerSize.MEDIUM
165
+ };
166
+ /** Row height (px) per rung, the virtualizer's estimate. */
167
+ var SELECT_INPUT_SIZE_TO_ROW_HEIGHT_PX_MAP = {
168
+ ["X_SMALL"]: Number.parseFloat(CONTROL_SIZE.small),
169
+ ["SMALL"]: Number.parseFloat(CONTROL_SIZE.small),
170
+ ["MEDIUM"]: Number.parseFloat(CONTROL_SIZE.medium),
171
+ ["LARGE"]: Number.parseFloat(CONTROL_SIZE.medium),
172
+ ["X_LARGE"]: Number.parseFloat(CONTROL_SIZE.medium)
173
+ };
174
+ /** A description's line height (px) per rung, added to the estimate of rows that have one. */
175
+ var SELECT_INPUT_SIZE_TO_SECONDARY_LINE_PX_MAP = {
176
+ ["X_SMALL"]: Number.parseFloat(TYPE_SCALE.sans.height.caption),
177
+ ["SMALL"]: Number.parseFloat(TYPE_SCALE.sans.height.caption),
178
+ ["MEDIUM"]: Number.parseFloat(TYPE_SCALE.sans.height.body_sm),
179
+ ["LARGE"]: Number.parseFloat(TYPE_SCALE.sans.height.body_sm),
180
+ ["X_LARGE"]: Number.parseFloat(TYPE_SCALE.sans.height.body_sm)
181
+ };
182
+ /** Panel fill: 1.x's map. A ghost field (`isGhost`) gets a readable `BASE` panel instead. */
183
+ var SELECT_INPUT_VARIANT_TO_POPOVER_VARIANT_MAP = {
184
+ ["BASE"]: PopoverVariant.PRIMARY,
185
+ ["PRIMARY"]: PopoverVariant.PRIMARY,
186
+ ["SECONDARY"]: PopoverVariant.SECONDARY,
187
+ ["TERTIARY"]: PopoverVariant.TERTIARY
188
+ };
189
+ /** The polite live region that speaks for the status rows (VisuallyHidden takes no `role`). */
190
+ var StatusRegion = /*#__PURE__*/ styled("span")({
191
+ name: "StatusRegion",
192
+ class: "gx-SelectInput-StatusRegion-s1ecxi56",
193
+ propsAsIs: false
194
+ });
195
+ /** Holds the field box, the trailing parts and the status region; 200px unless `fillWidth`. */
196
+ var Wrapper = /*#__PURE__*/ styled("div")({
197
+ name: "Wrapper",
198
+ class: "gx-SelectInput-Wrapper-w14m69un",
199
+ propsAsIs: false
200
+ });
201
+ /**
202
+ * The combobox (the focused element and Popover's reference): the field box. No outline; focus is
203
+ * `FOCUS_CSS`. Declared before the frame maps so they win at equal specificity.
204
+ */
205
+ var Control = /*#__PURE__*/ styled("div")({
206
+ name: "Control",
207
+ class: "gx-SelectInput-Control-cbfl8n6",
208
+ propsAsIs: false
84
209
  });
85
- var SelectInputOptionsContainer = /*#__PURE__*/ styled("div")({
86
- name: "SelectInputOptionsContainer",
87
- class: "sk260f0",
210
+ /** The searchable combobox: the same box as an `<input>`. Declared before the frame maps. */
211
+ var ControlInput = /*#__PURE__*/ styled("input")({
212
+ name: "ControlInput",
213
+ class: "gx-SelectInput-ControlInput-ck260f0",
88
214
  propsAsIs: false
89
215
  });
90
- var _exp16 = () => ({ theme, $isSelected, $isFocused }) => {
91
- if ($isSelected || $isFocused) return theme.color.background.secondary;
92
- return "transparent";
216
+ /** Rest look per variant: fill and hairline color. */
217
+ var SELECT_INPUT_VARIANT_TO_CSS_MAP = {
218
+ ["BASE"]: "gx-SelectInput-BASE-bo10ras",
219
+ ["PRIMARY"]: "gx-SelectInput-PRIMARY-poe2ind",
220
+ ["SECONDARY"]: "gx-SelectInput-SECONDARY-s9236d8",
221
+ ["TERTIARY"]: "gx-SelectInput-TERTIARY-t5jqpvy"
93
222
  };
94
- var _exp18 = () => ({ theme }) => theme.color.background.secondary;
95
- var SelectInputOption = withTheme(/*#__PURE__*/ styled("button")({
96
- name: "SelectInputOption",
97
- class: "so10ras",
98
- propsAsIs: false,
99
- vars: {
100
- "so10ras-0": [_exp16()],
101
- "so10ras-1": [_exp18()]
102
- }
103
- }));
104
- var _exp19 = () => ({ theme, $isSelected, $isFocused }) => {
105
- if ($isSelected || $isFocused) return theme.color.background.tertiary;
106
- return "transparent";
223
+ /** `isGhost`: no fill and no hairline at rest. Declared after the variant map, which it replaces. */
224
+ var GHOST_CSS = "gx-SelectInput-GHOST_CSS-g1yocujd";
225
+ /**
226
+ * Hover on an editable field: `border.hovered` (stronger than `border.primary` in both themes,
227
+ * Constitution III.6).
228
+ */
229
+ var SELECT_INPUT_VARIANT_TO_HOVER_CSS_MAP = {
230
+ ["BASE"]: "gx-SelectInput-BASE-b1uexxly",
231
+ ["PRIMARY"]: "gx-SelectInput-PRIMARY-pazywje",
232
+ ["SECONDARY"]: "gx-SelectInput-SECONDARY-s18jekpe",
233
+ ["TERTIARY"]: "gx-SelectInput-TERTIARY-t1gos90x"
107
234
  };
108
- var _exp21 = () => ({ theme }) => theme.color.background.tertiary;
109
- var PinnedOptionButton = withTheme(/*#__PURE__*/ styled("button")({
110
- name: "PinnedOptionButton",
111
- class: "poe2ind",
112
- propsAsIs: false,
113
- vars: {
114
- "poe2ind-0": [_exp19()],
115
- "poe2ind-1": [_exp21()]
116
- }
117
- }));
118
- var OptionIconSlot = /*#__PURE__*/ styled("span")({
119
- name: "OptionIconSlot",
120
- class: "o9236d8",
235
+ /** Hover on an editable ghost field: the frame appears, `border.hovered` with no fill. */
236
+ var GHOST_HOVER_CSS = "gx-SelectInput-GHOST_HOVER_CSS-g1oazqgn";
237
+ /**
238
+ * Height, start inset, gap and the end inset that reserves the caret zone (rung padding + caret +
239
+ * gap) per rung. Off-scale insets are sums of `space` tokens and the rung's caret px. The start inset
240
+ * subtracts the 0.5px hairline so the value starts on a whole pixel, the rung padding from the edge.
241
+ */
242
+ var SELECT_INPUT_SIZE_TO_CSS_MAP = {
243
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-xhmj664",
244
+ ["SMALL"]: "gx-SelectInput-SMALL-skndlgg",
245
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-m1v59wlg",
246
+ ["LARGE"]: "gx-SelectInput-LARGE-lkrrva3",
247
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-x1js26tz"
248
+ };
249
+ /** With `isClearable`: the end inset also reserves the clear button and a gap. */
250
+ var SELECT_INPUT_SIZE_TO_CLEARABLE_CSS_MAP = {
251
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x1y9j7k3",
252
+ ["SMALL"]: "gx-SelectInput-SMALL-s14dj8fy",
253
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-mxjxwo8",
254
+ ["LARGE"]: "gx-SelectInput-LARGE-lk7nfq8",
255
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-x1vo3rqs"
256
+ };
257
+ /** The typed term's type per rung (searchable field), so it matches the value text. */
258
+ var SELECT_INPUT_SIZE_TO_FONT_CSS_MAP = {
259
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x1jz5yi4",
260
+ ["SMALL"]: "gx-SelectInput-SMALL-s1ysyxtm",
261
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-ms13kdi",
262
+ ["LARGE"]: "gx-SelectInput-LARGE-llyskmg",
263
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-xcjuv87"
264
+ };
265
+ /**
266
+ * Focus, from the mouse or the keyboard: `FIELD_FOCUS`, `border.focused` at 1px inside the frame. `:focus:hover`
267
+ * beats the hover map, so a ghost field keeps it. Not applied while disabled (the disabled
268
+ * combobox is a `tabIndex={-1}` div, which a click can still focus).
269
+ */
270
+ var FOCUS_CSS = "gx-SelectInput-FOCUS_CSS-fqmfw76";
271
+ /** Open: `border.focused` (1.x's "selected" border). */
272
+ var OPEN_CSS = "gx-SelectInput-OPEN_CSS-o15ps1x0";
273
+ /** Error border at rest, on hover, with focus and while open (the 1px inset focus line is red too). */
274
+ var ERROR_CSS = "gx-SelectInput-ERROR_CSS-e1mxvlaa";
275
+ /**
276
+ * Disabled: the variant's resting fill and hairline are kept (`border.disabled` is not used on
277
+ * frames); the field reads as disabled through `text.disabled` / `text.disabled` and the cursor.
278
+ */
279
+ var DISABLED_CSS = "gx-SelectInput-DISABLED_CSS-due2xpd";
280
+ /** Read-only: no pointer affordance. */
281
+ var READ_ONLY_CSS = "gx-SelectInput-READ_ONLY_CSS-rz60qcy";
282
+ /** The searchable input while it is open: a text cursor. */
283
+ var TYPING_CSS = "gx-SelectInput-TYPING_CSS-t1kwlo75";
284
+ /** The searchable input while closed with a selection: the overlay shows the value. */
285
+ var VALUE_OVERLAID_CSS = "gx-SelectInput-VALUE_OVERLAID_CSS-v7qbedr";
286
+ /** Stretch to the parent's width (wrapper, and the `Field` wrapper when there is one). */
287
+ var FILL_WIDTH_CSS = "gx-SelectInput-FILL_WIDTH_CSS-f14e5rra";
288
+ /** The value inside the field: leading, icon and label in a row; only the label shrinks. */
289
+ var ValueDisplay = /*#__PURE__*/ styled("span")({
290
+ name: "ValueDisplay",
291
+ class: "gx-SelectInput-ValueDisplay-vcoak0",
121
292
  propsAsIs: false
122
293
  });
123
- var INPUT_VARIANT_TO_ICON_VARIANT_MAP = {
124
- [InputVariant.TRANSPARENT]: IconVariant.SECONDARY,
125
- [InputVariant.BASE]: IconVariant.SECONDARY,
126
- [InputVariant.PRIMARY]: IconVariant.SECONDARY,
127
- [InputVariant.SECONDARY]: IconVariant.SECONDARY,
128
- [InputVariant.TERTIARY]: IconVariant.PRIMARY
294
+ /** Gap between the value's leading, icon and label per rung. */
295
+ var SELECT_INPUT_SIZE_TO_VALUE_CSS_MAP = {
296
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-xo0k8iv",
297
+ ["SMALL"]: "gx-SelectInput-SMALL-s10dru8j",
298
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-mxd48k3",
299
+ ["LARGE"]: "gx-SelectInput-LARGE-l1uv1y8i",
300
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-x4kepfq"
129
301
  };
302
+ /** Holds `renderSelection`'s output (or the placeholder): fills the field and may shrink. */
303
+ var SelectionSlot = /*#__PURE__*/ styled("span")({
304
+ name: "SelectionSlot",
305
+ class: "gx-SelectInput-SelectionSlot-s19x5eff",
306
+ propsAsIs: false
307
+ });
130
308
  /**
131
- * Single-choice dropdown with keyboard navigation and optional search.
132
- *
133
- * Controlled: `value` is the selected option and `onChange` hands you the whole
134
- * option back. Passing `onSearch` turns the trigger into a search box — you own
135
- * the filtering, so it works equally well against a local list or a server.
309
+ * The searchable field's value while closed: laid over the input with the field's own insets (a
310
+ * transparent hairline included), so it sits exactly where the `div` field draws its value.
311
+ */
312
+ var ValueOverlay = /*#__PURE__*/ styled("span")({
313
+ name: "ValueOverlay",
314
+ class: "gx-SelectInput-ValueOverlay-v1igascl",
315
+ propsAsIs: false
316
+ });
317
+ /** The overlay's insets per rung (the field's). */
318
+ var SELECT_INPUT_SIZE_TO_OVERLAY_CSS_MAP = {
319
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-xj42cy5",
320
+ ["SMALL"]: "gx-SelectInput-SMALL-sjwzds1",
321
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-mzzsir9",
322
+ ["LARGE"]: "gx-SelectInput-LARGE-l15l4j2i",
323
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-x1ohc03t"
324
+ };
325
+ /** The caret and the clear button: laid over the field's end, outside the combobox. */
326
+ var Trailing = /*#__PURE__*/ styled("span")({
327
+ name: "Trailing",
328
+ class: "gx-SelectInput-Trailing-ta9xiv4",
329
+ propsAsIs: false
330
+ });
331
+ /** Trailing inset (the rung padding) and gap per rung. */
332
+ var SELECT_INPUT_SIZE_TO_TRAILING_CSS_MAP = {
333
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x1p8ykbf",
334
+ ["SMALL"]: "gx-SelectInput-SMALL-s1evykgc",
335
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-m15bsqie",
336
+ ["LARGE"]: "gx-SelectInput-LARGE-liqey9w",
337
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-xhe3qvy"
338
+ };
339
+ /** Wraps the clear button: the only part of `Trailing` that takes the pointer. */
340
+ var ClearSlot = /*#__PURE__*/ styled("span")({
341
+ name: "ClearSlot",
342
+ class: "gx-SelectInput-ClearSlot-c18c84l6",
343
+ propsAsIs: false
344
+ });
345
+ /** Hidden clear button: keeps its width, leaves the tab order and the accessibility tree. */
346
+ var CLEAR_HIDDEN_CSS = "gx-SelectInput-CLEAR_HIDDEN_CSS-c1da7pn7";
347
+ /**
348
+ * The panel content: between the field's width and 320px wide, capped to 320px tall. The
349
+ * available height falls back to the viewport until Popover has measured it, so the list is
350
+ * capped from its first frame (the active row is scrolled to on that frame).
136
351
  */
137
- var SelectInput = ({ label, variant = InputVariant.PRIMARY, size = InputSize.MEDIUM, options, value, placeholder = "Select an option...", onChange, width, fillWidth, dropdownWidth, isDisabled = false, isRequired = false, isOpen: controlledIsOpen, onOpenChange, onReset, pinnedOptions = [], error, onSearch, debounceMs = 300 }) => {
138
- const isSearchable = !!onSearch;
139
- const [isHovered, setIsHovered] = useState(false);
140
- const [searchTerm, setSearchTerm] = useState("");
141
- const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
142
- const inputRef = useRef(null);
143
- const triggerRef = useRef(null);
144
- const optionsRefs = useRef([]);
145
- const { isOpen, setOpen } = useControlledOpenState({
146
- isOpen: controlledIsOpen,
352
+ var ListFrame = /*#__PURE__*/ styled("div")({
353
+ name: "ListFrame",
354
+ class: "gx-SelectInput-ListFrame-lzozmwc",
355
+ propsAsIs: false
356
+ });
357
+ /** A virtualized panel is exactly as wide as the field (absolute rows have no content width). */
358
+ var VIRTUALIZED_FRAME_CSS = "gx-SelectInput-VIRTUALIZED_FRAME_CSS-vz9a0uz";
359
+ /** The rows, 4px in from the panel's outer edge (the 0.5px hairline counts toward the inset). */
360
+ var List = /*#__PURE__*/ styled("div")({
361
+ name: "List",
362
+ class: "gx-SelectInput-List-l1pxtftz",
363
+ propsAsIs: false
364
+ });
365
+ /** Virtualized: the inset is the virtualizer's start / end padding, so only the sides keep it. */
366
+ var VIRTUAL_LIST_CSS = "gx-SelectInput-VIRTUAL_LIST_CSS-v1pcykbv";
367
+ /** Virtualized: the full list height; rows are positioned inside it. */
368
+ var VirtualBody = /*#__PURE__*/ styled("div")({
369
+ name: "VirtualBody",
370
+ class: "gx-SelectInput-VirtualBody-v173z9ja",
371
+ propsAsIs: false
372
+ });
373
+ /** Virtualized row: absolutely placed at its measured offset. */
374
+ var VIRTUAL_ROW_CSS = "gx-SelectInput-VIRTUAL_ROW_CSS-vul1txn";
375
+ /** An option row (Menu's row restated). Declared before the row maps. */
376
+ var OptionRow = /*#__PURE__*/ styled("div")({
377
+ name: "OptionRow",
378
+ class: "gx-SelectInput-OptionRow-o1aponwc",
379
+ propsAsIs: false
380
+ });
381
+ /** A status row (empty, loading): a row's box, not interactive. */
382
+ var StatusRow = /*#__PURE__*/ styled("div")({
383
+ name: "StatusRow",
384
+ class: "gx-SelectInput-StatusRow-sxgsv7z",
385
+ propsAsIs: false
386
+ });
387
+ /** Row height, inline and block padding, and gap per rung (Menu's two rungs). */
388
+ var SELECT_INPUT_SIZE_TO_ROW_CSS_MAP = {
389
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x1n6kh9n",
390
+ ["SMALL"]: "gx-SelectInput-SMALL-sxrvm1c",
391
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-mw9xd7i",
392
+ ["LARGE"]: "gx-SelectInput-LARGE-lgrwy9f",
393
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-xlfzaiz"
394
+ };
395
+ /** The active row: the only highlight, so pointer and keyboard never show two. */
396
+ var ACTIVE_ROW_CSS = "gx-SelectInput-ACTIVE_ROW_CSS-a1r6mtzd";
397
+ /** Pressing an enabled active row. */
398
+ var ACTIVE_ROW_PRESS_CSS = "gx-SelectInput-ACTIVE_ROW_PRESS_CSS-a1lc3k5v";
399
+ /** A disabled row. */
400
+ var ROW_DISABLED_CSS = "gx-SelectInput-ROW_DISABLED_CSS-rfsa9g";
401
+ /** `leading`: never squeezed, so only the text shrinks. */
402
+ var Slot = /*#__PURE__*/ styled("span")({
403
+ name: "Slot",
404
+ class: "gx-SelectInput-Slot-s1vsgpgc",
405
+ propsAsIs: false
406
+ });
407
+ /** Label and description, stacked; the only part of a row that shrinks. */
408
+ var Content = /*#__PURE__*/ styled("span")({
409
+ name: "Content",
410
+ class: "gx-SelectInput-Content-c67enmk",
411
+ propsAsIs: false
412
+ });
413
+ /** The check mark's box (the icon's width), so rows align selected or not. */
414
+ var CheckSlot = /*#__PURE__*/ styled("span")({
415
+ name: "CheckSlot",
416
+ class: "gx-SelectInput-CheckSlot-c12tkpdx",
417
+ propsAsIs: false
418
+ });
419
+ /** The check mark's reserved width per rung: the row icon px. */
420
+ var SELECT_INPUT_SIZE_TO_CHECK_SLOT_CSS_MAP = {
421
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x1p1u54p",
422
+ ["SMALL"]: "gx-SelectInput-SMALL-s1xnxona",
423
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-m740si8",
424
+ ["LARGE"]: "gx-SelectInput-LARGE-lo64a8t",
425
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-xk487n1"
426
+ };
427
+ /** Icons never shrink. */
428
+ var NO_SHRINK_CSS = "gx-SelectInput-NO_SHRINK_CSS-nkvnet2";
429
+ /** A labelled section of rows (unvirtualized). */
430
+ var GroupRoot = /*#__PURE__*/ styled("div")({
431
+ name: "GroupRoot",
432
+ class: "gx-SelectInput-GroupRoot-g1qlz5rz",
433
+ propsAsIs: false
434
+ });
435
+ /** A section heading. */
436
+ var GroupLabel = /*#__PURE__*/ styled("div")({
437
+ name: "GroupLabel",
438
+ class: "gx-SelectInput-GroupLabel-g1mhh2uu",
439
+ propsAsIs: false
440
+ });
441
+ /** Group heading height and inset per rung (Menu's). */
442
+ var SELECT_INPUT_SIZE_TO_GROUP_LABEL_CSS_MAP = {
443
+ ["X_SMALL"]: "gx-SelectInput-X_SMALL-x19rqrei",
444
+ ["SMALL"]: "gx-SelectInput-SMALL-sx1x7i6",
445
+ ["MEDIUM"]: "gx-SelectInput-MEDIUM-m1oifut7",
446
+ ["LARGE"]: "gx-SelectInput-LARGE-lt7vyaq",
447
+ ["X_LARGE"]: "gx-SelectInput-X_LARGE-x1firy3m"
448
+ };
449
+ /** Space above and below the pinned section's separator (Menu's). */
450
+ var SEPARATOR_CSS = "gx-SelectInput-SEPARATOR_CSS-s9yly5q";
451
+ /** Lower-cased, trimmed term (the filter's comparison form; Menu's rule). */
452
+ function normalizeTerm(term) {
453
+ return term.trim().toLocaleLowerCase();
454
+ }
455
+ /** An option matches when the term is empty or its label (or description) contains it. */
456
+ function matches(term, option) {
457
+ if (term === "") return true;
458
+ if (option.label.toLocaleLowerCase().includes(term)) return true;
459
+ return option.description?.toLocaleLowerCase().includes(term) ?? false;
460
+ }
461
+ /** A printable character that should drive typeahead or the search term. */
462
+ function isPrintableKey(event) {
463
+ return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
464
+ }
465
+ /** Builds the rows in order: pinned, separator, sections, Create, then the status row. */
466
+ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReason, createLabel, isLoading, listboxId }) {
467
+ const indexById = /* @__PURE__ */ new Map();
468
+ options.forEach((option, index) => {
469
+ if (!indexById.has(option.id)) indexById.set(option.id, index);
470
+ });
471
+ let position = 0;
472
+ const makeItem = (option, index) => {
473
+ const isSelected = selected.has(option.id);
474
+ const isOwnDisabled = option.isDisabled === true;
475
+ const isLimitDisabled = limitReason !== void 0 && !isSelected;
476
+ position += 1;
477
+ return {
478
+ kind: "option",
479
+ key: `option-${index}`,
480
+ option,
481
+ domId: `${listboxId}-${index}`,
482
+ navIndex: -1,
483
+ flatIndex: -1,
484
+ position,
485
+ isSelected,
486
+ isDisabled: isOwnDisabled || isLimitDisabled,
487
+ disabledReason: isOwnDisabled ? option.disabledReason : limitReason
488
+ };
489
+ };
490
+ const pinnedSet = /* @__PURE__ */ new Set();
491
+ const pinned = [];
492
+ for (const id of pinnedIds ?? []) {
493
+ const index = indexById.get(id);
494
+ if (index === void 0 || pinnedSet.has(id)) continue;
495
+ const option = options[index];
496
+ if (option === void 0) continue;
497
+ pinnedSet.add(id);
498
+ pinned.push(makeItem(option, index));
499
+ }
500
+ const sectionMap = /* @__PURE__ */ new Map();
501
+ const sections = [];
502
+ options.forEach((option, index) => {
503
+ if (pinnedSet.has(option.id) || indexById.get(option.id) !== index) return;
504
+ if (isFiltering && !matches(term, option)) return;
505
+ const sectionKey = option.group === void 0 ? "" : `group:${option.group}`;
506
+ let section = sectionMap.get(sectionKey);
507
+ if (section === void 0) {
508
+ const headingId = `${listboxId}-group-${sections.length}`;
509
+ section = {
510
+ key: sectionKey === "" ? "ungrouped" : sectionKey,
511
+ heading: option.group === void 0 ? null : {
512
+ kind: "heading",
513
+ key: `heading-${sections.length}`,
514
+ id: headingId,
515
+ label: option.group
516
+ },
517
+ rows: []
518
+ };
519
+ sectionMap.set(sectionKey, section);
520
+ sections.push(section);
521
+ }
522
+ section.rows.push(makeItem(option, index));
523
+ });
524
+ const restCount = sections.reduce((count, section) => count + section.rows.length, 0);
525
+ const hasSeparator = pinned.length > 0 && restCount > 0;
526
+ const create = createLabel === null ? null : {
527
+ kind: "create",
528
+ key: "create",
529
+ domId: `${listboxId}-create`,
530
+ navIndex: -1,
531
+ flatIndex: -1,
532
+ label: createLabel
533
+ };
534
+ const nav = [];
535
+ const flat = [];
536
+ const pushNav = (item) => {
537
+ item.navIndex = nav.length;
538
+ item.flatIndex = flat.length;
539
+ nav.push(item);
540
+ flat.push(item);
541
+ };
542
+ for (const item of pinned) pushNav(item);
543
+ if (hasSeparator) flat.push({
544
+ kind: "separator",
545
+ key: "separator"
546
+ });
547
+ for (const section of sections) {
548
+ if (section.heading !== null) flat.push(section.heading);
549
+ for (const item of section.rows) pushNav(item);
550
+ }
551
+ if (create !== null) pushNav(create);
552
+ const status = isLoading ? "loading" : nav.length === 0 ? "empty" : null;
553
+ if (status !== null) flat.push({
554
+ kind: status,
555
+ key: status
556
+ });
557
+ return {
558
+ pinned,
559
+ hasSeparator,
560
+ sections,
561
+ create,
562
+ status,
563
+ nav,
564
+ flat,
565
+ optionCount: position
566
+ };
567
+ }
568
+ /** The first enabled row, else the first row (or -1). */
569
+ function firstEnabledIndex(nav) {
570
+ const index = nav.findIndex((item) => item.kind === "create" || !item.isDisabled);
571
+ return index === -1 ? nav.length > 0 ? 0 : -1 : index;
572
+ }
573
+ /** The first selected option row, or -1. */
574
+ function selectedIndex(nav) {
575
+ return nav.findIndex((item) => item.kind === "option" && item.isSelected);
576
+ }
577
+ /** Resolves an open target to a row index. */
578
+ function openIndex(nav, target) {
579
+ if (nav.length === 0) return -1;
580
+ if (target === "first") return 0;
581
+ if (target === "last") return nav.length - 1;
582
+ const selected = selectedIndex(nav);
583
+ if (selected !== -1) return selected;
584
+ return target === "selected-or-first" ? firstEnabledIndex(nav) : nav.length - 1;
585
+ }
586
+ /** The field, the trailing parts, the panel and the engine, wired to the nearest Field. */
587
+ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
588
+ const { options, selectedIds, isMultiple, onOptionSelect, onClear, renderSelection, getSelectionText, limitReason, onCreate, size = "MEDIUM", variant = "PRIMARY", isGhost = false, label: _label, placeholder = "Select an option…", error, isError = false, isDisabled = false, isReadOnly = false, isRequired = false, fillWidth = false, ariaLabel, isOpen, defaultIsOpen = false, onOpenChange, isSearchable = false, onSearch, isClearable = false, isLoading = false, emptyMessage = "No results", onEndReached, renderOption, pinnedIds, placement = Placement.BOTTOM_START, id, "aria-describedby": ariaDescribedBy, className, style, ...rest } = rootProps;
589
+ const isBare = useIsBareField();
590
+ const { field, controlId, describedBy, isInvalid, isRequired: isRequiredState, hasName } = useFieldWiring({
591
+ id,
592
+ ariaLabel,
593
+ ariaLabelAttribute: rest["aria-label"],
594
+ ariaLabelledBy: rest["aria-labelledby"],
595
+ ariaDescribedBy,
596
+ error,
597
+ isError,
598
+ isRequired
599
+ });
600
+ const listboxId = `${controlId}-listbox`;
601
+ const controlRef = useRef(null);
602
+ const mergedRef = useMergeRefs([controlRef, forwardedRef]);
603
+ const panelRef = useRef(null);
604
+ const wrapperRef = useRef(null);
605
+ const [viewportElement, setViewportElement] = useState(null);
606
+ const isEditable = !isDisabled && !isReadOnly;
607
+ const { isOpen: isOpenState, setOpen: setOpenState } = useControlledOpenState({
608
+ isOpen,
609
+ defaultIsOpen,
147
610
  onOpenChange
148
611
  });
149
- const triggerWidth = useElementWidth(triggerRef);
150
- const dropdownMinWidth = dropdownWidth ?? triggerWidth ?? width ?? 200;
151
- const selectedOption = value ?? null;
152
- const effectiveIsDisabled = isDisabled || options.length <= 1;
153
- const visible = isOpen;
154
- const debouncedSearchTerm = useDebouncedValue(searchTerm, debounceMs, isSearchable);
612
+ const isShown = isOpenState && isEditable;
613
+ const { setOpen } = useDedupedOpen(isShown, setOpenState);
614
+ const [rawTerm, setRawTerm] = useState("");
615
+ const [hasSearched, setHasSearched] = useState(false);
616
+ const [isReasonDismissed, setIsReasonDismissed] = useState(false);
617
+ const cacheRef = useRef(/* @__PURE__ */ new Map());
618
+ const selectedSet = new Set(selectedIds);
619
+ for (const option of options) if (selectedSet.has(option.id)) cacheRef.current.set(option.id, option);
620
+ const warnedIdsRef = useRef(/* @__PURE__ */ new Set());
621
+ const unknownIds = [];
622
+ const selectedOptions = selectedIds.map((selectedId) => {
623
+ const known = options.find((option) => option.id === selectedId) ?? cacheRef.current.get(selectedId);
624
+ if (known !== void 0) return known;
625
+ unknownIds.push(selectedId);
626
+ return {
627
+ id: selectedId,
628
+ label: selectedId
629
+ };
630
+ });
631
+ const unknownKey = unknownIds.join("\n");
155
632
  useEffect(() => {
156
- if (visible && isSearchable && inputRef.current) {
157
- inputRef.current.focus();
158
- inputRef.current.select();
633
+ if (unknownKey === "" || !isDevelopment()) return;
634
+ for (const unknownId of unknownKey.split("\n")) {
635
+ if (warnedIdsRef.current.has(unknownId)) continue;
636
+ warnedIdsRef.current.add(unknownId);
637
+ console.warn(`[@galaxy-io/dls] SelectInput value "${unknownId}" is not in \`options\`; the raw id is shown as its label.`);
159
638
  }
160
- }, [visible, isSearchable]);
161
- const [prevVisible, setPrevVisible] = useState(visible);
162
- if (visible !== prevVisible) {
163
- setPrevVisible(visible);
164
- if (!visible && isSearchable) {
165
- setSearchTerm("");
166
- setFocusedOptionIndex(-1);
639
+ }, [unknownKey]);
640
+ const selectionText = selectedOptions.length > 0 ? getSelectionText(selectedOptions) : "";
641
+ const term = isSearchable ? normalizeTerm(rawTerm) : "";
642
+ const trimmedTerm = rawTerm.trim();
643
+ const isFiltering = isSearchable && onSearch === void 0;
644
+ const hasExactMatch = trimmedTerm !== "" && options.some((option) => option.label.toLocaleLowerCase() === trimmedTerm.toLocaleLowerCase());
645
+ const model = buildModel({
646
+ options,
647
+ pinnedIds,
648
+ term,
649
+ isFiltering,
650
+ selected: selectedSet,
651
+ limitReason,
652
+ createLabel: onCreate !== void 0 && isSearchable && trimmedTerm !== "" && !hasExactMatch && limitReason === void 0 ? trimmedTerm : null,
653
+ isLoading,
654
+ listboxId
655
+ });
656
+ const [activeIndexState, setActiveIndexState] = useState(() => isShown ? openIndex(model.nav, "selected-or-first") : -1);
657
+ const activeIndex = model.nav.length === 0 ? -1 : Math.min(activeIndexState, model.nav.length - 1);
658
+ const activeItem = activeIndex >= 0 ? model.nav[activeIndex] : void 0;
659
+ const shouldScrollRef = useRef(false);
660
+ const [lastTerm, setLastTerm] = useState(rawTerm);
661
+ if (lastTerm !== rawTerm) {
662
+ setLastTerm(rawTerm);
663
+ if (isShown) {
664
+ setHasSearched(true);
665
+ setActiveIndexState(firstEnabledIndex(model.nav));
666
+ shouldScrollRef.current = true;
167
667
  }
168
668
  }
169
- const filteredOptions = useMemo(() => {
170
- if (!isSearchable || !debouncedSearchTerm.trim()) return options;
171
- return onSearch(debouncedSearchTerm, options);
172
- }, [
173
- isSearchable,
174
- onSearch,
175
- debouncedSearchTerm,
176
- options
177
- ]);
178
- const filteredPinnedOptions = useMemo(() => {
179
- if (!isSearchable || !debouncedSearchTerm.trim()) return pinnedOptions;
180
- return [];
181
- }, [
182
- isSearchable,
183
- debouncedSearchTerm,
184
- pinnedOptions
185
- ]);
186
- if (focusedOptionIndex >= filteredPinnedOptions.length + filteredOptions.length && focusedOptionIndex !== -1) setFocusedOptionIndex(-1);
187
- useEffect(() => {
188
- optionsRefs.current = [];
189
- }, [filteredPinnedOptions, filteredOptions]);
190
- const handleToggle = useCallback(() => {
191
- if (effectiveIsDisabled) return;
192
- setOpen(!visible);
193
- }, [
194
- effectiveIsDisabled,
195
- setOpen,
196
- visible
197
- ]);
198
- const handleClose = useCallback(() => {
199
- setOpen(false);
200
- }, [setOpen]);
201
- const handleOptionSelect = (option) => {
202
- if (onChange) onChange(option);
203
- handleClose();
204
- };
205
- const handlePinnedOptionSelect = (pinnedOption) => {
206
- const option = options.find((opt) => opt.id === pinnedOption.optionId);
207
- if (option && onChange) onChange(option);
208
- handleClose();
669
+ const openedInternallyRef = useRef(false);
670
+ const [lastShown, setLastShown] = useState(isShown);
671
+ if (lastShown !== isShown) {
672
+ setLastShown(isShown);
673
+ if (isShown && !openedInternallyRef.current) setActiveIndexState(openIndex(model.nav, "selected-or-first"));
674
+ if (!isShown) {
675
+ setRawTerm("");
676
+ setHasSearched(false);
677
+ setIsReasonDismissed(false);
678
+ }
679
+ openedInternallyRef.current = false;
680
+ }
681
+ /** Keyboard moves scroll the active row into view; pointer moves do not. */
682
+ const setActive = useCallback((index, shouldScroll) => {
683
+ shouldScrollRef.current = shouldScroll;
684
+ setActiveIndexState(index);
685
+ setIsReasonDismissed(false);
686
+ }, []);
687
+ /** Opens the panel on a target row. */
688
+ const openTo = (index) => {
689
+ openedInternallyRef.current = true;
690
+ setActive(index, true);
691
+ setOpen(true);
209
692
  };
210
- const handleReset = () => {
211
- if (onReset) onReset();
212
- handleClose();
693
+ const close = () => setOpen(false);
694
+ useRestoreFocusAfterOutsidePress({
695
+ isShown,
696
+ focusRef: controlRef,
697
+ insideRefs: [wrapperRef, panelRef],
698
+ focusScopeRefs: [wrapperRef, panelRef]
699
+ });
700
+ const onSearchRef = useRef(onSearch);
701
+ onSearchRef.current = onSearch;
702
+ const debouncedTerm = useDebouncedValue(rawTerm, SELECT_SEARCH_DEBOUNCE_MS, onSearch !== void 0);
703
+ const lastSentTermRef = useRef("");
704
+ useEffect(() => {
705
+ if (!isShown || onSearchRef.current === void 0) return;
706
+ if (debouncedTerm === lastSentTermRef.current) return;
707
+ lastSentTermRef.current = debouncedTerm;
708
+ onSearchRef.current(debouncedTerm);
709
+ }, [debouncedTerm, isShown]);
710
+ const wasShownForSearchRef = useRef(isShown);
711
+ useEffect(() => {
712
+ if (wasShownForSearchRef.current && !isShown && onSearchRef.current !== void 0) {
713
+ lastSentTermRef.current = "";
714
+ onSearchRef.current("");
715
+ }
716
+ wasShownForSearchRef.current = isShown;
717
+ }, [isShown]);
718
+ const onEndReachedRef = useRef(onEndReached);
719
+ onEndReachedRef.current = onEndReached;
720
+ const firedAtLengthRef = useRef(-1);
721
+ const optionsLength = options.length;
722
+ const reachEnd = useCallback(() => {
723
+ if (onEndReachedRef.current === void 0) return;
724
+ if (firedAtLengthRef.current === optionsLength) return;
725
+ firedAtLengthRef.current = optionsLength;
726
+ onEndReachedRef.current();
727
+ }, [optionsLength]);
728
+ const lastOptionNavIndex = (() => {
729
+ for (let index = model.nav.length - 1; index >= 0; index -= 1) if (model.nav[index]?.kind === "option") return index;
730
+ return -1;
731
+ })();
732
+ const typeaheadRef = useRef({
733
+ buffer: "",
734
+ timer: null
735
+ });
736
+ useEffect(() => () => {
737
+ if (typeaheadRef.current.timer !== null) clearTimeout(typeaheadRef.current.timer);
738
+ }, []);
739
+ const typeahead = (key, fromIndex) => {
740
+ const state = typeaheadRef.current;
741
+ if (state.timer !== null) clearTimeout(state.timer);
742
+ state.buffer += key.toLocaleLowerCase();
743
+ state.timer = setTimeout(() => {
744
+ state.buffer = "";
745
+ state.timer = null;
746
+ }, SELECT_TYPEAHEAD_RESET_MS);
747
+ const isRepeat = [...state.buffer].every((char) => char === state.buffer[0]);
748
+ const prefix = isRepeat ? state.buffer.slice(0, 1) : state.buffer;
749
+ const rows = model.nav;
750
+ const start = isRepeat ? fromIndex + 1 : Math.max(fromIndex, 0);
751
+ for (let offset = 0; offset < rows.length; offset += 1) {
752
+ const row = rows[(start + offset + rows.length) % rows.length];
753
+ if (row === void 0 || row.kind !== "option") continue;
754
+ if (row.option.label.toLocaleLowerCase().startsWith(prefix)) return row.navIndex;
755
+ }
756
+ return fromIndex;
213
757
  };
214
- const handleMouseEnter = () => {
215
- setIsHovered(true);
758
+ /** Chooses a row: an enabled option, or the Create row. */
759
+ const choose = (index) => {
760
+ const item = model.nav[index];
761
+ if (item === void 0) return;
762
+ if (item.kind === "create") {
763
+ onCreate?.(item.label);
764
+ setRawTerm("");
765
+ return;
766
+ }
767
+ if (item.isDisabled) return;
768
+ onOptionSelect(item.option.id);
769
+ if (!isMultiple) close();
216
770
  };
217
- const handleMouseLeave = () => {
218
- setIsHovered(false);
771
+ /** Moves the active row by the keyboard. */
772
+ const moveTo = (index) => {
773
+ if (model.nav.length === 0) return;
774
+ const next = Math.max(0, Math.min(model.nav.length - 1, index));
775
+ setActive(next, true);
776
+ if (next === lastOptionNavIndex) reachEnd();
219
777
  };
220
- const handleKeyDown = (e) => {
221
- if (effectiveIsDisabled) return;
222
- if (isSearchable && visible) {
223
- handleSearchKeyDown(e);
778
+ const handleKeyDown = (event) => {
779
+ if (event.defaultPrevented || event.nativeEvent.isComposing) return;
780
+ if (!isEditable) return;
781
+ const { key } = event;
782
+ if (!isShown) {
783
+ if (key === "Enter" || key === "ArrowDown" || key === " " && !isSearchable) {
784
+ event.preventDefault();
785
+ openTo(openIndex(model.nav, "selected-or-first"));
786
+ return;
787
+ }
788
+ if (key === "ArrowUp") {
789
+ event.preventDefault();
790
+ openTo(openIndex(model.nav, "selected-or-last"));
791
+ return;
792
+ }
793
+ if ((key === "Home" || key === "End") && !isSearchable) {
794
+ event.preventDefault();
795
+ openTo(openIndex(model.nav, key === "Home" ? "first" : "last"));
796
+ return;
797
+ }
798
+ if (isSearchable && (key === "Backspace" || key === "Delete")) {
799
+ event.preventDefault();
800
+ return;
801
+ }
802
+ if (isPrintableKey(event)) {
803
+ event.preventDefault();
804
+ if (isSearchable) {
805
+ openedInternallyRef.current = true;
806
+ setRawTerm(key);
807
+ setOpen(true);
808
+ return;
809
+ }
810
+ if (key === " ") return;
811
+ openTo(typeahead(key, openIndex(model.nav, "selected-or-first")));
812
+ }
224
813
  return;
225
814
  }
226
- if (e.key === "Enter" || e.key === " ") {
227
- e.preventDefault();
228
- handleToggle();
229
- }
230
- };
231
- const handleSearchKeyDown = (e) => {
232
- if (!isSearchable) return;
233
- const allOptions = [...filteredPinnedOptions, ...filteredOptions];
234
- const currentIndex = focusedOptionIndex;
235
- const focusOptionAtIndex = (nextIndex) => {
236
- setFocusedOptionIndex(nextIndex);
237
- optionsRefs.current[nextIndex]?.scrollIntoView({ block: "nearest" });
238
- };
239
- switch (e.key) {
815
+ switch (key) {
240
816
  case "ArrowDown":
241
- e.preventDefault();
242
- if (currentIndex < allOptions.length - 1) focusOptionAtIndex(currentIndex + 1);
243
- break;
817
+ event.preventDefault();
818
+ moveTo(activeIndex === -1 ? 0 : activeIndex + 1);
819
+ return;
244
820
  case "ArrowUp":
245
- e.preventDefault();
246
- if (currentIndex > 0) focusOptionAtIndex(currentIndex - 1);
247
- else inputRef.current?.focus();
248
- break;
821
+ event.preventDefault();
822
+ if (event.altKey) {
823
+ if (!isMultiple && activeIndex !== -1) choose(activeIndex);
824
+ close();
825
+ return;
826
+ }
827
+ moveTo(activeIndex === -1 ? model.nav.length - 1 : activeIndex - 1);
828
+ return;
829
+ case "PageDown":
830
+ event.preventDefault();
831
+ moveTo((activeIndex === -1 ? 0 : activeIndex) + SELECT_PAGE_STEP);
832
+ return;
833
+ case "PageUp":
834
+ event.preventDefault();
835
+ moveTo((activeIndex === -1 ? 0 : activeIndex) - SELECT_PAGE_STEP);
836
+ return;
837
+ case "Home":
838
+ case "End":
839
+ if (isSearchable) return;
840
+ event.preventDefault();
841
+ moveTo(key === "Home" ? 0 : model.nav.length - 1);
842
+ return;
249
843
  case "Enter":
250
- e.preventDefault();
251
- if (currentIndex >= 0 && currentIndex < allOptions.length) if (currentIndex < filteredPinnedOptions.length) handlePinnedOptionSelect(filteredPinnedOptions[currentIndex]);
252
- else handleOptionSelect(filteredOptions[currentIndex - filteredPinnedOptions.length]);
253
- else if (filteredOptions.length === 1) handleOptionSelect(filteredOptions[0]);
254
- break;
255
- case "Escape":
256
- e.preventDefault();
257
- handleClose();
258
- break;
844
+ event.preventDefault();
845
+ if (activeIndex !== -1) choose(activeIndex);
846
+ return;
847
+ case "Tab":
848
+ close();
849
+ return;
259
850
  default:
260
- if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) setFocusedOptionIndex(-1);
261
- break;
851
+ if (key === " " && !isSearchable) {
852
+ event.preventDefault();
853
+ if (activeIndex !== -1) choose(activeIndex);
854
+ return;
855
+ }
856
+ if (!isSearchable && isPrintableKey(event)) {
857
+ event.preventDefault();
858
+ setActive(typeahead(key, activeIndex), true);
859
+ }
262
860
  }
263
861
  };
264
- const handleInputChange = (e) => {
265
- setSearchTerm(e.target.value);
266
- setFocusedOptionIndex(-1);
862
+ /** A click on the field toggles the panel; inside an open searchable input it only places the caret. */
863
+ const handleClick = () => {
864
+ if (!isEditable) return;
865
+ if (isShown) {
866
+ if (!isSearchable) close();
867
+ return;
868
+ }
869
+ openTo(openIndex(model.nav, "selected-or-first"));
267
870
  };
268
- const isPinnedOptionSelected = (pinnedOption) => {
269
- return value?.id === pinnedOption.optionId;
871
+ /** Focus leaving the field for anything outside the panel closes it. */
872
+ const handleBlur = (event) => {
873
+ if (!isShown) return;
874
+ const next = event.relatedTarget;
875
+ if (next !== null && panelRef.current?.contains(next)) return;
876
+ close();
270
877
  };
271
- const isPinnedOptionFocused = (index) => {
272
- return isSearchable && focusedOptionIndex === index && index < filteredPinnedOptions.length;
878
+ const handleInputChange = (event) => {
879
+ if (!isEditable) return;
880
+ if (!isShown) {
881
+ openedInternallyRef.current = true;
882
+ setOpen(true);
883
+ }
884
+ setRawTerm(event.target.value);
273
885
  };
274
- const isOptionFocused = (index) => {
275
- return isSearchable && focusedOptionIndex === index + filteredPinnedOptions.length && index >= 0;
886
+ /** Pasting into a closed searchable field starts a term instead of editing the value text. */
887
+ const handlePaste = (event) => {
888
+ if (isShown || !isEditable) return;
889
+ event.preventDefault();
890
+ openedInternallyRef.current = true;
891
+ setRawTerm(event.clipboardData.getData("text"));
892
+ setOpen(true);
276
893
  };
277
- const dropdownBody = /* @__PURE__ */ jsxs(SelectInputContent, {
278
- $minWidth: dropdownMinWidth,
279
- $width: dropdownWidth,
280
- children: [
281
- filteredPinnedOptions.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [filteredPinnedOptions.map((pinnedOption, index) => /* @__PURE__ */ jsxs(PinnedOptionButton, {
282
- ref: (el) => {
283
- optionsRefs.current[index] = el;
284
- },
285
- $isSelected: isPinnedOptionSelected(pinnedOption),
286
- $isFocused: isPinnedOptionFocused(index),
287
- onClick: (e) => {
288
- e.preventDefault();
289
- handlePinnedOptionSelect(pinnedOption);
290
- },
291
- onMouseEnter: () => setFocusedOptionIndex(index),
292
- children: [/* @__PURE__ */ jsx(Text, {
293
- size: getInputTextSize(size),
294
- cursor: !effectiveIsDisabled ? "pointer" : "not-allowed",
295
- isEllipsis: true,
296
- children: pinnedOption.label
297
- }), isPinnedOptionSelected(pinnedOption) && /* @__PURE__ */ jsx(OptionIconSlot, { children: /* @__PURE__ */ jsx(Icon, {
298
- component: CheckIcon,
299
- variant: IconVariant.TERTIARY,
300
- weight: IconWeight.BOLD,
301
- size: 12
302
- }) })]
303
- }, pinnedOption.id)), /* @__PURE__ */ jsx(HorizontalDivider, {})] }),
304
- /* @__PURE__ */ jsx(SelectInputOptionsContainer, { children: filteredOptions.length > 0 ? filteredOptions.map((option, index) => {
305
- const absoluteIndex = filteredPinnedOptions.length + index;
306
- return /* @__PURE__ */ jsxs(SelectInputOption, {
307
- ref: (el) => {
308
- optionsRefs.current[absoluteIndex] = el;
309
- },
310
- $isSelected: option.id === value?.id,
311
- $isFocused: isOptionFocused(index),
312
- onClick: () => handleOptionSelect(option),
313
- onMouseEnter: () => setFocusedOptionIndex(absoluteIndex),
314
- children: [/* @__PURE__ */ jsxs(FlexWrapper, {
315
- alignItems: AlignItems.CENTER,
316
- gap: 8,
317
- minWidth: 0,
318
- grow: 1,
319
- children: [option.icon && /* @__PURE__ */ jsx(OptionIconSlot, { children: option.icon }), /* @__PURE__ */ jsx(FlexItem, {
320
- grow: 1,
321
- shrink: 1,
322
- minWidth: 0,
323
- children: /* @__PURE__ */ jsx(Text, {
324
- size: getInputTextSize(size),
325
- cursor: !effectiveIsDisabled ? "pointer" : "not-allowed",
326
- isEllipsis: true,
327
- variant: option.variant ?? TextVariant.PRIMARY,
328
- children: option.label
329
- })
330
- })]
331
- }), option.id === value?.id && /* @__PURE__ */ jsx(OptionIconSlot, { children: /* @__PURE__ */ jsx(Icon, {
332
- component: CheckIcon,
333
- variant: IconVariant.TERTIARY,
334
- weight: IconWeight.BOLD,
335
- size: 12
336
- }) })]
337
- }, option.id);
338
- }) : /* @__PURE__ */ jsx(FlexWrapper, {
339
- padding: 8,
340
- children: /* @__PURE__ */ jsx(Text, {
341
- variant: TextVariant.TERTIARY,
342
- size: getInputTextSize(size),
343
- children: "No options found"
344
- })
345
- }) }),
346
- onReset && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(HorizontalDivider, {}), /* @__PURE__ */ jsx(PinnedOptionButton, {
347
- onClick: handleReset,
348
- $isSelected: false,
349
- $isFocused: false,
350
- children: /* @__PURE__ */ jsxs(FlexWrapper, {
351
- alignItems: AlignItems.CENTER,
352
- gap: 8,
353
- children: [/* @__PURE__ */ jsx(Icon, {
354
- component: ArrowCounterClockwiseIcon,
355
- variant: IconVariant.TERTIARY,
356
- size: 12
357
- }), /* @__PURE__ */ jsx(Text, {
358
- variant: TextVariant.SECONDARY,
359
- size: getInputTextSize(size),
360
- cursor: !effectiveIsDisabled ? "pointer" : "not-allowed",
361
- children: "Reset"
362
- })]
363
- })
364
- })] })
365
- ]
894
+ const clear = () => {
895
+ onClear();
896
+ if (isShown) close();
897
+ controlRef.current?.focus();
898
+ };
899
+ const activeFlatIndex = activeItem?.flatIndex ?? -1;
900
+ const isVirtualized = model.flat.length > SELECT_VIRTUALIZE_THRESHOLD;
901
+ const rowHeightPx = SELECT_INPUT_SIZE_TO_ROW_HEIGHT_PX_MAP[size];
902
+ const secondaryLinePx = SELECT_INPUT_SIZE_TO_SECONDARY_LINE_PX_MAP[size];
903
+ const flatRef = useRef(model.flat);
904
+ flatRef.current = model.flat;
905
+ const virtualizer = useVirtualizer({
906
+ count: isVirtualized ? model.flat.length : 0,
907
+ getScrollElement: () => viewportElement,
908
+ estimateSize: (index) => {
909
+ const item = flatRef.current[index];
910
+ if (item?.kind === "option" && isFilled(item.option.description)) return rowHeightPx + secondaryLinePx;
911
+ if (item?.kind === "separator") return Number.parseFloat(SPACE[4]) * 2 + 1;
912
+ return rowHeightPx;
913
+ },
914
+ overscan: SELECT_VIRTUAL_OVERSCAN,
915
+ rangeExtractor: (range) => {
916
+ const indexes = defaultRangeExtractor(range);
917
+ if (activeFlatIndex < 0 || activeFlatIndex >= range.count) return indexes;
918
+ if (indexes.includes(activeFlatIndex)) return indexes;
919
+ return [...indexes, activeFlatIndex].sort((a, b) => a - b);
920
+ },
921
+ paddingStart: SELECT_LIST_INSET_PX,
922
+ paddingEnd: SELECT_LIST_INSET_PX
366
923
  });
367
- const textVariant = useMemo(() => {
368
- if (selectedOption) return TextVariant.PRIMARY;
369
- if (!effectiveIsDisabled && (visible || isHovered)) return TextVariant.SECONDARY;
370
- return TextVariant.TERTIARY;
924
+ const virtualItems = isVirtualized ? virtualizer.getVirtualItems() : [];
925
+ const visibleRange = isVirtualized ? virtualizer.range : null;
926
+ const lastVirtualIndex = visibleRange === null ? -1 : Math.min(visibleRange.endIndex + SELECT_VIRTUAL_OVERSCAN, model.flat.length - 1);
927
+ const lastOptionFlatIndex = lastOptionNavIndex === -1 ? -1 : model.nav[lastOptionNavIndex]?.flatIndex ?? -1;
928
+ useEffect(() => {
929
+ if (!isShown || !isVirtualized || lastOptionFlatIndex === -1) return;
930
+ if (lastVirtualIndex >= lastOptionFlatIndex) reachEnd();
371
931
  }, [
372
- selectedOption,
373
- visible,
374
- isHovered,
375
- effectiveIsDisabled
932
+ isShown,
933
+ isVirtualized,
934
+ lastVirtualIndex,
935
+ lastOptionFlatIndex,
936
+ reachEnd
376
937
  ]);
377
- return /* @__PURE__ */ jsxs(FlexWrapper, {
378
- direction: FlexDirection.COLUMN,
379
- gap: 4,
380
- minWidth: 0,
381
- fillWidth,
938
+ const activeDomId = activeItem?.domId;
939
+ useIsomorphicLayoutEffect(() => {
940
+ if (!shouldScrollRef.current || !isShown || viewportElement === null) return;
941
+ shouldScrollRef.current = false;
942
+ if (isVirtualized) {
943
+ if (activeFlatIndex >= 0) virtualizer.scrollToIndex(activeFlatIndex, { align: "auto" });
944
+ return;
945
+ }
946
+ if (activeDomId === void 0) return;
947
+ panelRef.current?.ownerDocument.getElementById(activeDomId)?.scrollIntoView({ block: "nearest" });
948
+ });
949
+ const handleScroll = (event) => {
950
+ if (isVirtualized) return;
951
+ const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
952
+ if (scrollTop + clientHeight >= scrollHeight - rowHeightPx) reachEnd();
953
+ };
954
+ useDevWarning(!hasName, "[@galaxy-io/dls] SelectInput has no accessible name. Pass `label`, wrap it in a labelled `Field`, or set `ariaLabel`. `placeholder` is not a name.");
955
+ let statusText = "";
956
+ if (isShown) {
957
+ if (isLoading) statusText = "Loading";
958
+ else if (model.nav.length === 0) statusText = emptyMessage;
959
+ else if (hasSearched) statusText = `${model.optionCount} ${model.optionCount === 1 ? "result" : "results"}`;
960
+ }
961
+ const textSize = SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP[size];
962
+ const caretPx = SELECT_INPUT_SIZE_TO_CARET_PX_MAP[size];
963
+ const rowTextSize = SELECT_INPUT_SIZE_TO_ROW_TEXT_SIZE_MAP[size];
964
+ const rowSecondarySize = SELECT_INPUT_SIZE_TO_ROW_SECONDARY_TEXT_SIZE_MAP[size];
965
+ const rowIconPx = SELECT_INPUT_SIZE_TO_ROW_ICON_PX_MAP[size];
966
+ const hasSelection = selectedOptions.length > 0;
967
+ const isClearHidden = !hasSelection || !isEditable;
968
+ const controlClassName = cx(isGhost ? GHOST_CSS : SELECT_INPUT_VARIANT_TO_CSS_MAP[variant], SELECT_INPUT_SIZE_TO_CSS_MAP[size], isClearable && SELECT_INPUT_SIZE_TO_CLEARABLE_CSS_MAP[size], isEditable && !isInvalid && (isGhost ? GHOST_HOVER_CSS : SELECT_INPUT_VARIANT_TO_HOVER_CSS_MAP[variant]), !isDisabled && FOCUS_CSS, isShown && OPEN_CSS, isInvalid && !isDisabled && ERROR_CSS, isDisabled && DISABLED_CSS, isReadOnly && READ_ONLY_CSS, isBare && "gx-bareField-BARE_FIELD_CSS-b1memido");
969
+ const sharedControlProps = {
970
+ ref: mergedRef,
971
+ id: controlId,
972
+ role: "combobox",
973
+ "aria-activedescendant": isShown && activeDomId !== void 0 ? activeDomId : void 0,
974
+ "aria-labelledby": field?.labelId,
975
+ "aria-label": ariaLabel,
976
+ "aria-describedby": describedBy,
977
+ "aria-invalid": isInvalid ? true : void 0,
978
+ "aria-required": isRequiredState ? true : void 0,
979
+ "aria-readonly": isReadOnly ? true : void 0,
980
+ onKeyDown: handleKeyDown,
981
+ onClick: handleClick,
982
+ onBlur: handleBlur,
983
+ ...rest
984
+ };
985
+ const placeholderText = /* @__PURE__ */ jsx(Text, {
986
+ as: "span",
987
+ size: textSize,
988
+ variant: isDisabled ? TextVariant.DISABLED : TextVariant.TERTIARY,
989
+ lineClamp: 1,
990
+ children: placeholder
991
+ });
992
+ const control = isSearchable ? /* @__PURE__ */ jsx(ControlInput, {
993
+ ...sharedControlProps,
994
+ type: "text",
995
+ autoComplete: "off",
996
+ autoCorrect: "off",
997
+ spellCheck: false,
998
+ "aria-autocomplete": "list",
999
+ value: isShown ? rawTerm : selectionText,
1000
+ placeholder,
1001
+ disabled: isDisabled,
1002
+ readOnly: isReadOnly,
1003
+ onChange: handleInputChange,
1004
+ onPaste: handlePaste,
1005
+ className: cx(controlClassName, SELECT_INPUT_SIZE_TO_FONT_CSS_MAP[size], isShown && TYPING_CSS, !isShown && hasSelection && VALUE_OVERLAID_CSS)
1006
+ }) : /* @__PURE__ */ jsx(Control, {
1007
+ ...sharedControlProps,
1008
+ tabIndex: isDisabled ? -1 : 0,
1009
+ "aria-disabled": isDisabled ? true : void 0,
1010
+ className: controlClassName,
1011
+ children: hasSelection ? /* @__PURE__ */ jsx(SelectionSlot, { children: renderSelection(selectedOptions) }) : /* @__PURE__ */ jsx(ValueDisplay, {
1012
+ className: SELECT_INPUT_SIZE_TO_VALUE_CSS_MAP[size],
1013
+ children: placeholderText
1014
+ })
1015
+ });
1016
+ /** One option or Create row. */
1017
+ const renderNavRow = (item, virtualStart) => {
1018
+ const isActive = item.navIndex === activeIndex;
1019
+ const isRowDisabled = item.kind === "option" && item.isDisabled;
1020
+ const isSelected = item.kind === "option" && item.isSelected;
1021
+ const virtualProps = virtualStart === void 0 ? {} : {
1022
+ "data-index": item.flatIndex,
1023
+ ref: virtualizer.measureElement,
1024
+ style: { "--gx-select-row-y": `${virtualStart}px` },
1025
+ ...item.kind === "option" ? {
1026
+ "aria-setsize": model.optionCount,
1027
+ "aria-posinset": item.position
1028
+ } : {}
1029
+ };
1030
+ let content;
1031
+ if (item.kind === "create") content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Icon, {
1032
+ component: PlusIcon,
1033
+ size: rowIconPx,
1034
+ variant: IconVariant.SECONDARY,
1035
+ className: NO_SHRINK_CSS
1036
+ }), /* @__PURE__ */ jsx(Content, { children: /* @__PURE__ */ jsxs(Text, {
1037
+ as: "span",
1038
+ size: rowTextSize,
1039
+ lineClamp: 1,
1040
+ children: [
1041
+ "Create \"",
1042
+ /* @__PURE__ */ jsx(Span, {
1043
+ weight: TextWeight.MEDIUM,
1044
+ children: item.label
1045
+ }),
1046
+ "\""
1047
+ ]
1048
+ }) })] });
1049
+ else if (renderOption !== void 0) content = /* @__PURE__ */ jsx(Content, { children: renderOption(item.option, {
1050
+ isSelected,
1051
+ isActive
1052
+ }) });
1053
+ else {
1054
+ const { option } = item;
1055
+ content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1056
+ hasContent(option.leading) && /* @__PURE__ */ jsx(Slot, { children: option.leading }),
1057
+ option.icon !== void 0 && /* @__PURE__ */ jsx(Icon, {
1058
+ component: option.icon,
1059
+ size: rowIconPx,
1060
+ variant: isRowDisabled ? IconVariant.DISABLED : IconVariant.SECONDARY,
1061
+ className: NO_SHRINK_CSS
1062
+ }),
1063
+ /* @__PURE__ */ jsxs(Content, { children: [/* @__PURE__ */ jsx(Text, {
1064
+ as: "span",
1065
+ size: rowTextSize,
1066
+ variant: isRowDisabled ? TextVariant.DISABLED : TextVariant.PRIMARY,
1067
+ lineClamp: 1,
1068
+ children: option.label
1069
+ }), isFilled(option.description) && /* @__PURE__ */ jsx(Text, {
1070
+ as: "span",
1071
+ size: rowSecondarySize,
1072
+ variant: isRowDisabled ? TextVariant.DISABLED : TextVariant.TERTIARY,
1073
+ lineClamp: 2,
1074
+ children: option.description
1075
+ })] })
1076
+ ] });
1077
+ }
1078
+ const row = /* @__PURE__ */ jsxs(OptionRow, {
1079
+ ...virtualProps,
1080
+ id: item.domId,
1081
+ role: "option",
1082
+ "aria-selected": isSelected,
1083
+ ...isRowDisabled ? { "aria-disabled": true } : {},
1084
+ onPointerMove: () => {
1085
+ if (item.navIndex !== activeIndex) setActive(item.navIndex, false);
1086
+ },
1087
+ onClick: () => choose(item.navIndex),
1088
+ className: cx(SELECT_INPUT_SIZE_TO_ROW_CSS_MAP[size], isActive && ACTIVE_ROW_CSS, isActive && !isRowDisabled && ACTIVE_ROW_PRESS_CSS, isRowDisabled && ROW_DISABLED_CSS, virtualStart !== void 0 && VIRTUAL_ROW_CSS),
1089
+ children: [content, /* @__PURE__ */ jsx(CheckSlot, {
1090
+ className: SELECT_INPUT_SIZE_TO_CHECK_SLOT_CSS_MAP[size],
1091
+ children: isSelected && /* @__PURE__ */ jsx(Icon, {
1092
+ component: CheckIcon,
1093
+ size: rowIconPx,
1094
+ variant: isRowDisabled ? IconVariant.DISABLED : IconVariant.PRIMARY,
1095
+ "aria-hidden": true
1096
+ })
1097
+ })]
1098
+ }, item.key);
1099
+ if (item.kind !== "option" || !item.isDisabled || !isFilled(item.disabledReason)) return row;
1100
+ return /* @__PURE__ */ jsx(Tooltip, {
1101
+ body: item.disabledReason,
1102
+ placement: Placement.RIGHT,
1103
+ isOpen: isShown && isActive && !isReasonDismissed,
1104
+ onOpenChange: (next) => {
1105
+ if (!next && isActive) setIsReasonDismissed(true);
1106
+ },
1107
+ children: row
1108
+ }, item.key);
1109
+ };
1110
+ const renderHeading = (heading, virtual) => /* @__PURE__ */ jsx(GroupLabel, {
1111
+ role: "presentation",
1112
+ id: heading.id,
1113
+ className: cx(SELECT_INPUT_SIZE_TO_GROUP_LABEL_CSS_MAP[size], virtual !== void 0 && VIRTUAL_ROW_CSS),
1114
+ ...virtual === void 0 ? {} : {
1115
+ "data-index": virtual.index,
1116
+ ref: virtualizer.measureElement,
1117
+ style: { "--gx-select-row-y": `${virtual.start}px` }
1118
+ },
1119
+ children: /* @__PURE__ */ jsx(Text, {
1120
+ as: "span",
1121
+ size: rowSecondarySize,
1122
+ weight: TextWeight.MEDIUM,
1123
+ variant: TextVariant.TERTIARY,
1124
+ lineClamp: 1,
1125
+ children: heading.label
1126
+ })
1127
+ }, heading.key);
1128
+ const renderSeparator = (key, virtual) => /* @__PURE__ */ jsx("div", {
1129
+ role: "presentation",
1130
+ className: cx(SEPARATOR_CSS, virtual !== void 0 && VIRTUAL_ROW_CSS),
1131
+ ...virtual === void 0 ? {} : {
1132
+ "data-index": virtual.index,
1133
+ ref: virtualizer.measureElement,
1134
+ style: { "--gx-select-row-y": `${virtual.start}px` }
1135
+ },
1136
+ children: /* @__PURE__ */ jsx(Divider, { "aria-hidden": true })
1137
+ }, key);
1138
+ const renderStatus = (kind, virtual) => /* @__PURE__ */ jsx(StatusRow, {
1139
+ "aria-hidden": true,
1140
+ className: cx(SELECT_INPUT_SIZE_TO_ROW_CSS_MAP[size], virtual !== void 0 && VIRTUAL_ROW_CSS),
1141
+ ...virtual === void 0 ? {} : {
1142
+ "data-index": virtual.index,
1143
+ ref: virtualizer.measureElement,
1144
+ style: { "--gx-select-row-y": `${virtual.start}px` }
1145
+ },
1146
+ children: kind === "loading" ? /* @__PURE__ */ jsx(Spinner, {
1147
+ size: SELECT_INPUT_SIZE_TO_SPINNER_SIZE_MAP[size],
1148
+ label: "Loading",
1149
+ "aria-hidden": true
1150
+ }) : /* @__PURE__ */ jsx(Text, {
1151
+ size: rowTextSize,
1152
+ variant: TextVariant.TERTIARY,
1153
+ children: emptyMessage
1154
+ })
1155
+ }, kind);
1156
+ let listContent;
1157
+ if (isVirtualized) listContent = /* @__PURE__ */ jsx(VirtualBody, {
1158
+ style: { "--gx-select-list-height": `${virtualizer.getTotalSize()}px` },
1159
+ children: virtualItems.map((virtualItem) => {
1160
+ const item = model.flat[virtualItem.index];
1161
+ if (item === void 0) return null;
1162
+ const virtual = {
1163
+ index: virtualItem.index,
1164
+ start: virtualItem.start
1165
+ };
1166
+ switch (item.kind) {
1167
+ case "option":
1168
+ case "create": return renderNavRow(item, virtualItem.start);
1169
+ case "heading": return renderHeading(item, virtual);
1170
+ case "separator": return renderSeparator(item.key, virtual);
1171
+ default: return renderStatus(item.kind, virtual);
1172
+ }
1173
+ })
1174
+ });
1175
+ else listContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1176
+ model.pinned.map((item) => renderNavRow(item)),
1177
+ model.hasSeparator && renderSeparator("separator"),
1178
+ model.sections.map((section) => section.heading === null ? section.rows.map((item) => renderNavRow(item)) : /* @__PURE__ */ jsxs(GroupRoot, {
1179
+ role: "group",
1180
+ "aria-labelledby": section.heading.id,
1181
+ children: [renderHeading(section.heading), section.rows.map((item) => renderNavRow(item))]
1182
+ }, section.key)),
1183
+ model.create !== null && renderNavRow(model.create),
1184
+ model.status !== null && renderStatus(model.status)
1185
+ ] });
1186
+ const body = /* @__PURE__ */ jsx(ListFrame, {
1187
+ className: cx(isVirtualized && VIRTUALIZED_FRAME_CSS),
1188
+ onMouseDown: (event) => event.preventDefault(),
1189
+ children: /* @__PURE__ */ jsx(ScrollArea, {
1190
+ ref: setViewportElement,
1191
+ onScroll: handleScroll,
1192
+ children: /* @__PURE__ */ jsx(List, {
1193
+ className: cx(isVirtualized && VIRTUAL_LIST_CSS),
1194
+ children: listContent
1195
+ })
1196
+ })
1197
+ });
1198
+ const labelId = field?.labelId;
1199
+ const explicitLabelledBy = rest["aria-labelledby"];
1200
+ return /* @__PURE__ */ jsxs(Wrapper, {
1201
+ ref: wrapperRef,
1202
+ className: cx(fillWidth && FILL_WIDTH_CSS, isRoot ? className : void 0),
1203
+ ...rootStyle(isRoot, style),
382
1204
  children: [
383
- label && /* @__PURE__ */ jsx(InputLabel, {
384
- label,
385
- isRequired
1205
+ /* @__PURE__ */ jsx(Popover, {
1206
+ ref: panelRef,
1207
+ trigger: PopoverTrigger.MANUAL,
1208
+ role: PopoverRole.LISTBOX,
1209
+ variant: isGhost ? PopoverVariant.BASE : SELECT_INPUT_VARIANT_TO_POPOVER_VARIANT_MAP[variant],
1210
+ placement,
1211
+ isOpen: isShown,
1212
+ onOpenChange: setOpen,
1213
+ isDisabled: !isEditable,
1214
+ id: listboxId,
1215
+ ...isFilled(explicitLabelledBy) ? { "aria-labelledby": explicitLabelledBy } : labelId !== void 0 ? { "aria-labelledby": labelId } : isFilled(ariaLabel) ? { ariaLabel } : {},
1216
+ ...isMultiple ? { "aria-multiselectable": true } : {},
1217
+ ...isLoading ? { "aria-busy": true } : {},
1218
+ body: /* @__PURE__ */ jsx(BareFieldContext.Provider, {
1219
+ value: false,
1220
+ children: body
1221
+ }),
1222
+ children: control
386
1223
  }),
387
- /* @__PURE__ */ jsx(Dropdown, {
388
- variant: INPUT_VARIANT_TO_DROPDOWN_VARIANT[variant],
389
- body: dropdownBody,
390
- isOpen: visible,
391
- onClose: handleClose,
392
- minWidth: 0,
393
- offset: [0, 3],
394
- fillWidth,
395
- noPadding: true,
396
- children: isSearchable && visible ? /* @__PURE__ */ jsxs(StyledSelectInput, {
397
- ref: triggerRef,
398
- $variant: variant,
399
- $size: size,
400
- $isActive: visible ?? false,
401
- $isDisabled: effectiveIsDisabled,
402
- $isError: !!error,
403
- $width: width,
404
- $fillWidth: fillWidth,
405
- onMouseEnter: handleMouseEnter,
406
- onMouseLeave: handleMouseLeave,
407
- onClick: handleToggle,
408
- children: [/* @__PURE__ */ jsx(StyledSearchInput, {
409
- ref: inputRef,
410
- $size: size,
411
- $isActive: visible ?? false,
412
- $isDisabled: effectiveIsDisabled,
413
- $isError: !!error,
414
- $width: width,
415
- $fillWidth: fillWidth,
416
- type: "text",
417
- placeholder,
418
- value: searchTerm,
419
- onChange: handleInputChange,
420
- onKeyDown: handleSearchKeyDown,
421
- autoComplete: "off",
422
- autoCorrect: "off",
423
- disabled: effectiveIsDisabled,
424
- onClick: (e) => e.stopPropagation()
425
- }), /* @__PURE__ */ jsx(FlexWrapper, {
426
- shrink: 0,
427
- children: /* @__PURE__ */ jsx(Rotate, {
428
- isRotated: visible,
429
- deg: -180,
430
- children: /* @__PURE__ */ jsx(Icon, {
431
- component: CaretDownIcon,
432
- size: getInputIconSize(size),
433
- variant: !effectiveIsDisabled && (isHovered || visible) ? IconVariant.SECONDARY : IconVariant.TERTIARY
434
- })
435
- })
436
- })]
437
- }) : /* @__PURE__ */ jsxs(StyledSelectInput, {
438
- ref: triggerRef,
439
- $variant: variant,
440
- $size: size,
441
- $isActive: visible ?? false,
442
- $isDisabled: effectiveIsDisabled,
443
- $isError: !!error,
444
- $width: width,
445
- $fillWidth: fillWidth,
446
- onClick: handleToggle,
447
- onKeyDown: handleKeyDown,
448
- onMouseEnter: handleMouseEnter,
449
- onMouseLeave: handleMouseLeave,
450
- role: "button",
451
- tabIndex: effectiveIsDisabled ? -1 : 0,
452
- "aria-haspopup": "listbox",
453
- "aria-expanded": visible,
454
- "aria-label": selectedOption ? selectedOption.label : placeholder,
455
- children: [/* @__PURE__ */ jsxs(FlexWrapper, {
456
- alignItems: AlignItems.CENTER,
457
- gap: 8,
458
- minWidth: 0,
459
- grow: 1,
460
- children: [selectedOption?.icon && /* @__PURE__ */ jsx(OptionIconSlot, { children: selectedOption.icon }), /* @__PURE__ */ jsx(FlexItem, {
461
- grow: 1,
462
- shrink: 1,
463
- minWidth: 0,
464
- children: /* @__PURE__ */ jsx(Text, {
465
- variant: textVariant,
466
- size: getInputTextSize(size),
467
- cursor: !effectiveIsDisabled ? "pointer" : "not-allowed",
468
- isEllipsis: true,
469
- children: selectedOption ? selectedOption.label : placeholder
470
- })
471
- })]
472
- }), /* @__PURE__ */ jsx(FlexWrapper, {
473
- shrink: 0,
474
- children: /* @__PURE__ */ jsx(Rotate, {
475
- isRotated: visible,
476
- deg: -180,
477
- children: /* @__PURE__ */ jsx(Icon, {
478
- component: CaretDownIcon,
479
- size: getInputIconSize(size),
480
- variant: !effectiveIsDisabled && (isHovered || visible) ? IconVariant.SECONDARY : IconVariant.TERTIARY
481
- })
482
- })
483
- })]
484
- })
1224
+ isSearchable && !isShown && hasSelection && /* @__PURE__ */ jsx(ValueOverlay, {
1225
+ "aria-hidden": true,
1226
+ className: cx(SELECT_INPUT_SIZE_TO_OVERLAY_CSS_MAP[size], isClearable && SELECT_INPUT_SIZE_TO_CLEARABLE_CSS_MAP[size]),
1227
+ children: /* @__PURE__ */ jsx(SelectionSlot, { children: renderSelection(selectedOptions) })
485
1228
  }),
486
- error && /* @__PURE__ */ jsx(Text, {
487
- variant: TextVariant.ERROR,
488
- size: TextSize.BODY_SM,
489
- children: error
1229
+ /* @__PURE__ */ jsxs(Trailing, {
1230
+ className: SELECT_INPUT_SIZE_TO_TRAILING_CSS_MAP[size],
1231
+ children: [isClearable && /* @__PURE__ */ jsx(ClearSlot, {
1232
+ className: cx(isClearHidden && CLEAR_HIDDEN_CSS),
1233
+ children: /* @__PURE__ */ jsx(Button, {
1234
+ variant: ButtonVariant.TERTIARY,
1235
+ size: SELECT_INPUT_SIZE_TO_BUTTON_SIZE_MAP[size],
1236
+ icon: XIcon,
1237
+ ariaLabel: "Clear",
1238
+ onClick: clear,
1239
+ className: cx(size === "X_SMALL" && "gx-innerButton-X_SMALL_INNER_BUTTON_CSS-x1ep0pcd")
1240
+ })
1241
+ }), /* @__PURE__ */ jsx(Rotate, {
1242
+ isRotated: isShown,
1243
+ "aria-hidden": true,
1244
+ children: /* @__PURE__ */ jsx(Icon, {
1245
+ component: CaretDownIcon,
1246
+ size: caretPx,
1247
+ variant: isDisabled ? IconVariant.DISABLED : IconVariant.SECONDARY
1248
+ })
1249
+ })]
1250
+ }),
1251
+ /* @__PURE__ */ jsx(StatusRegion, {
1252
+ role: "status",
1253
+ "aria-live": "polite",
1254
+ children: statusText
490
1255
  })
491
1256
  ]
492
1257
  });
493
- };
1258
+ }
1259
+ /**
1260
+ * @internal Shared by SelectInput and MultiSelectInput; apps use those. The engine: the field
1261
+ * (with its own `Field` for `label` / `error`), the listbox panel, the rows, search, typeahead and
1262
+ * virtualization.
1263
+ */
1264
+ var SelectInputRootInner = forwardRef(function SelectInputRoot(props, ref) {
1265
+ const { label, error, isRequired = false, componentName = "SelectInput", size = "MEDIUM", fillWidth = false } = props;
1266
+ const { id, className, style } = props;
1267
+ const ownField = useOwnField({
1268
+ componentName,
1269
+ label,
1270
+ error,
1271
+ isRequired,
1272
+ size: SELECT_INPUT_SIZE_TO_FIELD_SIZE_MAP[size],
1273
+ id,
1274
+ className,
1275
+ style,
1276
+ rootClassName: fillWidth && FILL_WIDTH_CSS
1277
+ });
1278
+ if (ownField !== null) return /* @__PURE__ */ jsx(Field, {
1279
+ ...ownField,
1280
+ children: /* @__PURE__ */ jsx(SelectInputFrame, {
1281
+ rootProps: props,
1282
+ forwardedRef: ref,
1283
+ isRoot: false
1284
+ })
1285
+ });
1286
+ return /* @__PURE__ */ jsx(SelectInputFrame, {
1287
+ rootProps: props,
1288
+ forwardedRef: ref,
1289
+ isRoot: true
1290
+ });
1291
+ });
1292
+ SelectInputRootInner.displayName = "SelectInputRoot";
1293
+ /** @internal Shared by SelectInput and MultiSelectInput; apps use those. */
1294
+ var SelectInputRoot = SelectInputRootInner;
1295
+ /** The default value display: the option's leading, icon and label. */
1296
+ function OptionValue({ option, size, isDisabled }) {
1297
+ return /* @__PURE__ */ jsxs(ValueDisplay, {
1298
+ className: SELECT_INPUT_SIZE_TO_VALUE_CSS_MAP[size],
1299
+ children: [
1300
+ hasContent(option.leading) && /* @__PURE__ */ jsx(Slot, { children: option.leading }),
1301
+ option.icon !== void 0 && /* @__PURE__ */ jsx(Icon, {
1302
+ component: option.icon,
1303
+ size: SELECT_INPUT_SIZE_TO_ICON_PX_MAP[size],
1304
+ variant: isDisabled ? IconVariant.DISABLED : IconVariant.SECONDARY,
1305
+ className: NO_SHRINK_CSS,
1306
+ "aria-hidden": true
1307
+ }),
1308
+ /* @__PURE__ */ jsx(Text, {
1309
+ as: "span",
1310
+ size: SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP[size],
1311
+ variant: isDisabled ? TextVariant.DISABLED : TextVariant.PRIMARY,
1312
+ lineClamp: 1,
1313
+ children: option.label
1314
+ })
1315
+ ]
1316
+ });
1317
+ }
1318
+ /** Choose one value from a list of options, in a field that opens a listbox. */
1319
+ var SelectInputInner = forwardRef(function SelectInput({ value, defaultValue = null, onChange, renderValue, size: sizeProp, isDisabled: isDisabledProp, className, style, ...rest }, ref) {
1320
+ const size = sizeProp ?? "MEDIUM";
1321
+ const isDisabled = isDisabledProp ?? false;
1322
+ const [currentValue, setValue] = useControlledState({
1323
+ value,
1324
+ defaultValue,
1325
+ onChange
1326
+ });
1327
+ return /* @__PURE__ */ jsx(SelectInputRoot, {
1328
+ ...rest,
1329
+ ref,
1330
+ size,
1331
+ isDisabled,
1332
+ selectedIds: currentValue === null ? [] : [currentValue],
1333
+ isMultiple: false,
1334
+ onOptionSelect: setValue,
1335
+ onClear: () => setValue(null),
1336
+ renderSelection: (selected) => {
1337
+ const option = selected[0];
1338
+ if (option === void 0) return null;
1339
+ if (renderValue !== void 0) return renderValue(option);
1340
+ return /* @__PURE__ */ jsx(OptionValue, {
1341
+ option,
1342
+ size,
1343
+ isDisabled
1344
+ });
1345
+ },
1346
+ getSelectionText: (selected) => selected[0]?.label ?? "",
1347
+ limitReason: void 0,
1348
+ onCreate: void 0,
1349
+ ...className === void 0 ? {} : { className },
1350
+ ...style === void 0 ? {} : { style }
1351
+ });
1352
+ });
1353
+ SelectInputInner.displayName = "SelectInput";
1354
+ /** Choose one value from a list of options, in a field that opens a listbox. */
1355
+ var SelectInput = SelectInputInner;
494
1356
  //#endregion
495
- export { INPUT_VARIANT_TO_ICON_VARIANT_MAP, SelectInput as default };
1357
+ export { SelectInputRoot, SelectInputSize, SelectInputVariant, SelectInput as default };