@central-ui/react 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -2
  3. package/dist/components/accordion/index.d.ts +73 -0
  4. package/dist/components/accordion/index.d.ts.map +1 -0
  5. package/dist/components/accordion/index.js +92 -0
  6. package/dist/components/accordion/index.js.map +1 -0
  7. package/dist/components/autocomplete/index.d.ts +365 -0
  8. package/dist/components/autocomplete/index.d.ts.map +1 -0
  9. package/dist/components/autocomplete/index.js +369 -0
  10. package/dist/components/autocomplete/index.js.map +1 -0
  11. package/dist/components/avatar/index.d.ts +48 -0
  12. package/dist/components/avatar/index.d.ts.map +1 -0
  13. package/dist/components/avatar/index.js +60 -0
  14. package/dist/components/avatar/index.js.map +1 -0
  15. package/dist/components/badge/index.d.ts +29 -0
  16. package/dist/components/badge/index.d.ts.map +1 -0
  17. package/dist/components/badge/index.js +36 -0
  18. package/dist/components/badge/index.js.map +1 -0
  19. package/dist/components/banner/index.d.ts +71 -0
  20. package/dist/components/banner/index.d.ts.map +1 -0
  21. package/dist/components/banner/index.js +119 -0
  22. package/dist/components/banner/index.js.map +1 -0
  23. package/dist/components/breadcrumb/index.d.ts +31 -0
  24. package/dist/components/breadcrumb/index.d.ts.map +1 -0
  25. package/dist/components/breadcrumb/index.js +42 -0
  26. package/dist/components/breadcrumb/index.js.map +1 -0
  27. package/dist/components/button/index.d.ts +48 -0
  28. package/dist/components/button/index.d.ts.map +1 -0
  29. package/dist/components/button/index.js +60 -0
  30. package/dist/components/button/index.js.map +1 -0
  31. package/dist/components/card/index.d.ts +34 -0
  32. package/dist/components/card/index.d.ts.map +1 -0
  33. package/dist/components/card/index.js +45 -0
  34. package/dist/components/card/index.js.map +1 -0
  35. package/dist/components/checkbox/index.d.ts +66 -0
  36. package/dist/components/checkbox/index.d.ts.map +1 -0
  37. package/dist/components/checkbox/index.js +146 -0
  38. package/dist/components/checkbox/index.js.map +1 -0
  39. package/dist/components/chip/index.d.ts +26 -0
  40. package/dist/components/chip/index.d.ts.map +1 -0
  41. package/dist/components/chip/index.js +40 -0
  42. package/dist/components/chip/index.js.map +1 -0
  43. package/dist/components/combobox/index.d.ts +427 -0
  44. package/dist/components/combobox/index.d.ts.map +1 -0
  45. package/dist/components/combobox/index.js +416 -0
  46. package/dist/components/combobox/index.js.map +1 -0
  47. package/dist/components/datepicker/index.d.ts +297 -0
  48. package/dist/components/datepicker/index.d.ts.map +1 -0
  49. package/dist/components/datepicker/index.js +507 -0
  50. package/dist/components/datepicker/index.js.map +1 -0
  51. package/dist/components/dialog/index.d.ts +88 -0
  52. package/dist/components/dialog/index.d.ts.map +1 -0
  53. package/dist/components/dialog/index.js +102 -0
  54. package/dist/components/dialog/index.js.map +1 -0
  55. package/dist/components/divider/index.d.ts +21 -0
  56. package/dist/components/divider/index.d.ts.map +1 -0
  57. package/dist/components/divider/index.js +24 -0
  58. package/dist/components/divider/index.js.map +1 -0
  59. package/dist/components/drawer/index.d.ts +99 -0
  60. package/dist/components/drawer/index.d.ts.map +1 -0
  61. package/dist/components/drawer/index.js +137 -0
  62. package/dist/components/drawer/index.js.map +1 -0
  63. package/dist/components/empty/index.d.ts +40 -0
  64. package/dist/components/empty/index.d.ts.map +1 -0
  65. package/dist/components/empty/index.js +61 -0
  66. package/dist/components/empty/index.js.map +1 -0
  67. package/dist/components/fileupload/index.d.ts +107 -0
  68. package/dist/components/fileupload/index.d.ts.map +1 -0
  69. package/dist/components/fileupload/index.js +185 -0
  70. package/dist/components/fileupload/index.js.map +1 -0
  71. package/dist/components/form/index.d.ts +34 -0
  72. package/dist/components/form/index.d.ts.map +1 -0
  73. package/dist/components/form/index.js +35 -0
  74. package/dist/components/form/index.js.map +1 -0
  75. package/dist/components/input/index.d.ts +85 -0
  76. package/dist/components/input/index.d.ts.map +1 -0
  77. package/dist/components/input/index.js +249 -0
  78. package/dist/components/input/index.js.map +1 -0
  79. package/dist/components/menu/index.d.ts +115 -0
  80. package/dist/components/menu/index.d.ts.map +1 -0
  81. package/dist/components/menu/index.js +188 -0
  82. package/dist/components/menu/index.js.map +1 -0
  83. package/dist/components/metric/index.d.ts +24 -0
  84. package/dist/components/metric/index.d.ts.map +1 -0
  85. package/dist/components/metric/index.js +26 -0
  86. package/dist/components/metric/index.js.map +1 -0
  87. package/dist/components/numberinput/index.d.ts +105 -0
  88. package/dist/components/numberinput/index.d.ts.map +1 -0
  89. package/dist/components/numberinput/index.js +240 -0
  90. package/dist/components/numberinput/index.js.map +1 -0
  91. package/dist/components/otpinput/index.d.ts +63 -0
  92. package/dist/components/otpinput/index.d.ts.map +1 -0
  93. package/dist/components/otpinput/index.js +133 -0
  94. package/dist/components/otpinput/index.js.map +1 -0
  95. package/dist/components/page/index.d.ts +54 -0
  96. package/dist/components/page/index.d.ts.map +1 -0
  97. package/dist/components/page/index.js +100 -0
  98. package/dist/components/page/index.js.map +1 -0
  99. package/dist/components/passwordinput/index.d.ts +88 -0
  100. package/dist/components/passwordinput/index.d.ts.map +1 -0
  101. package/dist/components/passwordinput/index.js +251 -0
  102. package/dist/components/passwordinput/index.js.map +1 -0
  103. package/dist/components/popover/index.d.ts +67 -0
  104. package/dist/components/popover/index.d.ts.map +1 -0
  105. package/dist/components/popover/index.js +76 -0
  106. package/dist/components/popover/index.js.map +1 -0
  107. package/dist/components/radio/index.d.ts +90 -0
  108. package/dist/components/radio/index.d.ts.map +1 -0
  109. package/dist/components/radio/index.js +232 -0
  110. package/dist/components/radio/index.js.map +1 -0
  111. package/dist/components/ratinginput/index.d.ts +59 -0
  112. package/dist/components/ratinginput/index.d.ts.map +1 -0
  113. package/dist/components/ratinginput/index.js +88 -0
  114. package/dist/components/ratinginput/index.js.map +1 -0
  115. package/dist/components/select/index.d.ts +205 -0
  116. package/dist/components/select/index.d.ts.map +1 -0
  117. package/dist/components/select/index.js +234 -0
  118. package/dist/components/select/index.js.map +1 -0
  119. package/dist/components/skeleton/index.d.ts +19 -0
  120. package/dist/components/skeleton/index.d.ts.map +1 -0
  121. package/dist/components/skeleton/index.js +20 -0
  122. package/dist/components/skeleton/index.js.map +1 -0
  123. package/dist/components/slider/index.d.ts +72 -0
  124. package/dist/components/slider/index.d.ts.map +1 -0
  125. package/dist/components/slider/index.js +159 -0
  126. package/dist/components/slider/index.js.map +1 -0
  127. package/dist/components/spinner/index.d.ts +14 -0
  128. package/dist/components/spinner/index.d.ts.map +1 -0
  129. package/dist/components/spinner/index.js +15 -0
  130. package/dist/components/spinner/index.js.map +1 -0
  131. package/dist/components/splitter/index.d.ts +49 -0
  132. package/dist/components/splitter/index.d.ts.map +1 -0
  133. package/dist/components/splitter/index.js +67 -0
  134. package/dist/components/splitter/index.js.map +1 -0
  135. package/dist/components/stepper/index.d.ts +82 -0
  136. package/dist/components/stepper/index.d.ts.map +1 -0
  137. package/dist/components/stepper/index.js +100 -0
  138. package/dist/components/stepper/index.js.map +1 -0
  139. package/dist/components/switch/index.d.ts +57 -0
  140. package/dist/components/switch/index.d.ts.map +1 -0
  141. package/dist/components/switch/index.js +101 -0
  142. package/dist/components/switch/index.js.map +1 -0
  143. package/dist/components/table/index.d.ts +110 -0
  144. package/dist/components/table/index.d.ts.map +1 -0
  145. package/dist/components/table/index.js +211 -0
  146. package/dist/components/table/index.js.map +1 -0
  147. package/dist/components/tabs/index.d.ts +67 -0
  148. package/dist/components/tabs/index.d.ts.map +1 -0
  149. package/dist/components/tabs/index.js +78 -0
  150. package/dist/components/tabs/index.js.map +1 -0
  151. package/dist/components/textarea/index.d.ts +72 -0
  152. package/dist/components/textarea/index.d.ts.map +1 -0
  153. package/dist/components/textarea/index.js +205 -0
  154. package/dist/components/textarea/index.js.map +1 -0
  155. package/dist/components/textlink/index.d.ts +39 -0
  156. package/dist/components/textlink/index.d.ts.map +1 -0
  157. package/dist/components/textlink/index.js +42 -0
  158. package/dist/components/textlink/index.js.map +1 -0
  159. package/dist/components/toast/index.d.ts +111 -0
  160. package/dist/components/toast/index.d.ts.map +1 -0
  161. package/dist/components/toast/index.js +205 -0
  162. package/dist/components/toast/index.js.map +1 -0
  163. package/dist/components/toggle/index.d.ts +41 -0
  164. package/dist/components/toggle/index.d.ts.map +1 -0
  165. package/dist/components/toggle/index.js +78 -0
  166. package/dist/components/toggle/index.js.map +1 -0
  167. package/dist/components/tooltip/index.d.ts +44 -0
  168. package/dist/components/tooltip/index.d.ts.map +1 -0
  169. package/dist/components/tooltip/index.js +51 -0
  170. package/dist/components/tooltip/index.js.map +1 -0
  171. package/dist/index.d.ts +50 -0
  172. package/dist/index.d.ts.map +1 -0
  173. package/dist/index.js +46 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/types/Style.d.ts +14 -0
  176. package/dist/types/Style.d.ts.map +1 -0
  177. package/dist/types/Style.js +2 -0
  178. package/dist/types/Style.js.map +1 -0
  179. package/dist/types/Tone.d.ts +4 -0
  180. package/dist/types/Tone.d.ts.map +1 -0
  181. package/dist/types/Tone.js +2 -0
  182. package/dist/types/Tone.js.map +1 -0
  183. package/dist/utils/ArkStateToDataAttributes.d.ts +4 -0
  184. package/dist/utils/ArkStateToDataAttributes.d.ts.map +1 -0
  185. package/dist/utils/ArkStateToDataAttributes.js +9 -0
  186. package/dist/utils/ArkStateToDataAttributes.js.map +1 -0
  187. package/dist/utils/Tone.d.ts +3 -0
  188. package/dist/utils/Tone.d.ts.map +1 -0
  189. package/dist/utils/Tone.js +7 -0
  190. package/dist/utils/Tone.js.map +1 -0
  191. package/dist/utils/useForwardClickToInput.d.ts +4 -0
  192. package/dist/utils/useForwardClickToInput.d.ts.map +1 -0
  193. package/dist/utils/useForwardClickToInput.js +27 -0
  194. package/dist/utils/useForwardClickToInput.js.map +1 -0
  195. package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts +198 -0
  196. package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts.map +1 -0
  197. package/dist/utils/virtualization/useVirtualizedAutocomplete.js +180 -0
  198. package/dist/utils/virtualization/useVirtualizedAutocomplete.js.map +1 -0
  199. package/dist/utils/virtualization/useVirtualizedCombobox.d.ts +200 -0
  200. package/dist/utils/virtualization/useVirtualizedCombobox.d.ts.map +1 -0
  201. package/dist/utils/virtualization/useVirtualizedCombobox.js +174 -0
  202. package/dist/utils/virtualization/useVirtualizedCombobox.js.map +1 -0
  203. package/dist/utils/virtualization/useVirtualizedTable.d.ts +169 -0
  204. package/dist/utils/virtualization/useVirtualizedTable.d.ts.map +1 -0
  205. package/dist/utils/virtualization/useVirtualizedTable.js +127 -0
  206. package/dist/utils/virtualization/useVirtualizedTable.js.map +1 -0
  207. package/package.json +64 -4
  208. package/src/presets/shopify-polaris/shopify-polaris-accordion.css +28 -0
  209. package/src/presets/shopify-polaris/shopify-polaris-autocomplete.css +155 -0
  210. package/src/presets/shopify-polaris/shopify-polaris-avatar.css +47 -0
  211. package/src/presets/shopify-polaris/shopify-polaris-badge.css +27 -0
  212. package/src/presets/shopify-polaris/shopify-polaris-banner.css +111 -0
  213. package/src/presets/shopify-polaris/shopify-polaris-breadcrumb.css +16 -0
  214. package/src/presets/shopify-polaris/shopify-polaris-button.css +79 -0
  215. package/src/presets/shopify-polaris/shopify-polaris-card.css +28 -0
  216. package/src/presets/shopify-polaris/shopify-polaris-checkbox.css +52 -0
  217. package/src/presets/shopify-polaris/shopify-polaris-chip.css +16 -0
  218. package/src/presets/shopify-polaris/shopify-polaris-combobox.css +179 -0
  219. package/src/presets/shopify-polaris/shopify-polaris-datepicker.css +183 -0
  220. package/src/presets/shopify-polaris/shopify-polaris-dialog.css +40 -0
  221. package/src/presets/shopify-polaris/shopify-polaris-divider.css +12 -0
  222. package/src/presets/shopify-polaris/shopify-polaris-drawer.css +40 -0
  223. package/src/presets/shopify-polaris/shopify-polaris-empty.css +32 -0
  224. package/src/presets/shopify-polaris/shopify-polaris-fileupload.css +78 -0
  225. package/src/presets/shopify-polaris/shopify-polaris-form.css +8 -0
  226. package/src/presets/shopify-polaris/shopify-polaris-input.css +72 -0
  227. package/src/presets/shopify-polaris/shopify-polaris-menu.css +112 -0
  228. package/src/presets/shopify-polaris/shopify-polaris-metric.css +16 -0
  229. package/src/presets/shopify-polaris/shopify-polaris-numberinput.css +88 -0
  230. package/src/presets/shopify-polaris/shopify-polaris-otpinput.css +42 -0
  231. package/src/presets/shopify-polaris/shopify-polaris-page.css +69 -0
  232. package/src/presets/shopify-polaris/shopify-polaris-passwordinput.css +76 -0
  233. package/src/presets/shopify-polaris/shopify-polaris-popover.css +47 -0
  234. package/src/presets/shopify-polaris/shopify-polaris-radio.css +54 -0
  235. package/src/presets/shopify-polaris/shopify-polaris-ratinginput.css +42 -0
  236. package/src/presets/shopify-polaris/shopify-polaris-select.css +119 -0
  237. package/src/presets/shopify-polaris/shopify-polaris-skeleton.css +8 -0
  238. package/src/presets/shopify-polaris/shopify-polaris-slider.css +86 -0
  239. package/src/presets/shopify-polaris/shopify-polaris-spinner.css +16 -0
  240. package/src/presets/shopify-polaris/shopify-polaris-splitter.css +34 -0
  241. package/src/presets/shopify-polaris/shopify-polaris-stepper.css +55 -0
  242. package/src/presets/shopify-polaris/shopify-polaris-switch.css +38 -0
  243. package/src/presets/shopify-polaris/shopify-polaris-table.css +72 -0
  244. package/src/presets/shopify-polaris/shopify-polaris-tabs.css +33 -0
  245. package/src/presets/shopify-polaris/shopify-polaris-textarea.css +61 -0
  246. package/src/presets/shopify-polaris/shopify-polaris-textlink.css +8 -0
  247. package/src/presets/shopify-polaris/shopify-polaris-toggle.css +52 -0
  248. package/src/presets/shopify-polaris/shopify-polaris-tooltip.css +31 -0
  249. package/src/presets/shopify-polaris/shopify-polaris.css +380 -0
  250. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-accordion.css +28 -0
  251. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-autocomplete.css +155 -0
  252. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-avatar.css +47 -0
  253. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-badge.css +27 -0
  254. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-banner.css +96 -0
  255. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-breadcrumb.css +16 -0
  256. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-button.css +68 -0
  257. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-card.css +28 -0
  258. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-checkbox.css +52 -0
  259. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-chip.css +16 -0
  260. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-combobox.css +184 -0
  261. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-datepicker.css +187 -0
  262. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-dialog.css +40 -0
  263. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-divider.css +12 -0
  264. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-drawer.css +40 -0
  265. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-empty.css +32 -0
  266. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-fileupload.css +72 -0
  267. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-form.css +8 -0
  268. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-input.css +72 -0
  269. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-menu.css +112 -0
  270. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-metric.css +16 -0
  271. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-numberinput.css +88 -0
  272. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-otpinput.css +42 -0
  273. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-page.css +69 -0
  274. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-passwordinput.css +76 -0
  275. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-popover.css +47 -0
  276. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-radio.css +54 -0
  277. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-ratinginput.css +42 -0
  278. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-select.css +118 -0
  279. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-skeleton.css +8 -0
  280. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-slider.css +86 -0
  281. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-spinner.css +16 -0
  282. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-splitter.css +34 -0
  283. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-stepper.css +55 -0
  284. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-switch.css +38 -0
  285. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-table.css +72 -0
  286. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tabs.css +33 -0
  287. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textarea.css +61 -0
  288. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textlink.css +8 -0
  289. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toast.css +43 -0
  290. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toggle.css +52 -0
  291. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tooltip.css +31 -0
  292. package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy.css +381 -0
@@ -0,0 +1,198 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { GroupShape, ItemShape, ItemType } from '../../components/autocomplete/index.tsx';
3
+ export type UseVirtualizedAutocompleteOptions<T extends readonly GroupShape[]> = {
4
+ /**
5
+ * Supply the complete grouped data; rootProps passes this same data to Autocomplete.Root.
6
+ * Let native search filter it through AutocompleteFilteredItemsSync; do not pass only
7
+ * the visible rows back to Root. For a flat source, use one group with label: ''.
8
+ * @example items: cityGroups
9
+ */
10
+ items: T;
11
+ /**
12
+ * Match the rendered row height in pixels, including any spacing between rows.
13
+ * Use a callback when rows have known different heights. This hook does not
14
+ * automatically measure rendered rows; inaccurate sizes can leave gaps or overlaps.
15
+ * @example estimateSize: 34
16
+ * @example estimateSize: (city) => city.label.length > 30 ? 56 : 34
17
+ * @default 34
18
+ */
19
+ estimateSize?: number | ((item: ItemType<T>, index: number) => number);
20
+ /**
21
+ * Mount extra rows above and below the visible area to reduce blanks during fast
22
+ * scrolling. Increase this if needed, at the cost of rendering more rows.
23
+ * @example overscan: 8
24
+ * @default 5
25
+ */
26
+ overscan?: number;
27
+ /**
28
+ * Account for space before ItemsList inside ScrollArea, such as a group heading
29
+ * and leading padding. Match your actual layout; this does not render a heading
30
+ * or add CSS padding for you. Omit when there is no leading space.
31
+ * @example paddingStart: 32
32
+ * @default 0
33
+ */
34
+ paddingStart?: number;
35
+ /**
36
+ * Account for space after ItemsList inside ScrollArea, such as group bottom
37
+ * padding. Match the space your markup already supplies; this adds no CSS padding.
38
+ * @example paddingEnd: 4
39
+ * @default 0
40
+ */
41
+ paddingEnd?: number;
42
+ /**
43
+ * Cap the scrolling viewport height while allowing short lists to shrink.
44
+ * Choose this independently of the space available around the popup.
45
+ * @example maxHeight: 480
46
+ * @default 320
47
+ */
48
+ maxHeight?: number;
49
+ /**
50
+ * Apply an additional CSS max-height to the scrolling viewport.
51
+ * The default uses the popup's available space so it can fit near a screen edge.
52
+ * For a custom popup layout without that CSS variable, supply your own constraint.
53
+ * @example availableHeight: 'calc(100dvh - 160px)'
54
+ * @default 'var(--available-height)'
55
+ */
56
+ availableHeight?: CSSProperties['maxHeight'];
57
+ };
58
+ /**
59
+ * Virtualize a large Autocomplete while keeping native search and keyboard behavior.
60
+ * Call this hook in your component, outside Root. Spread each returned props object
61
+ * on its matching element, and keep AutocompleteFilteredItemsSync mounted inside Popup
62
+ * so native filtering supplies the complete results. Keep rootProps.onItemHighlighted
63
+ * connected; it scrolls keyboard highlights into view. Merge it if adding your own callback.
64
+ *
65
+ * Filtered groups are flattened into one list: this hook does not virtualize separate
66
+ * group headings. Use one shared heading, as below, or omit GroupLabel for a flat list
67
+ * and adjust paddingStart. Preserve the returned refs and layout styles when customizing.
68
+ * Render only rows, but give Group all filteredItems and each Item its returned index.
69
+ * The mapped rows already preserve your custom item fields; no manual annotation is needed.
70
+ *
71
+ * @example Complete virtualized Autocomplete
72
+ * import {
73
+ * Autocomplete,
74
+ * AutocompleteFilteredItemsSync,
75
+ * useVirtualizedAutocomplete,
76
+ * } from '@central-ui/react'
77
+ *
78
+ * // Replace this sample with your larger dataset; keep the source stable between renders.
79
+ * const cityGroups = [{
80
+ * label: 'Cities',
81
+ * items: [
82
+ * { value: 'ma-rabat', label: 'Rabat', country: 'Morocco', population: 580000 },
83
+ * { value: 'ma-casablanca', label: 'Casablanca', country: 'Morocco', population: 3359818 },
84
+ * { value: 'fr-paris', label: 'Paris', country: 'France', population: 2102650 },
85
+ * ],
86
+ * }]
87
+ *
88
+ * function CityAutocomplete() {
89
+ * const virtual = useVirtualizedAutocomplete({
90
+ * items: cityGroups, // rootProps supplies these complete groups to Root.
91
+ * estimateSize: 34, // Match the row height plus spacing in your layout.
92
+ * paddingStart: 32, // Match the heading and leading space; adjust if styled differently.
93
+ * paddingEnd: 4, // Match the trailing group padding.
94
+ * })
95
+ *
96
+ * return (
97
+ * <Autocomplete.Root {...virtual.rootProps} openOnInputClick>
98
+ * <Autocomplete.Container>
99
+ * <Autocomplete.Label>City</Autocomplete.Label>
100
+ * <Autocomplete.InputGroup>
101
+ * <Autocomplete.Input placeholder="Search cities" />
102
+ * <Autocomplete.Addon>
103
+ * <Autocomplete.AddonOpenButton aria-label="Show cities">
104
+ * <Autocomplete.AddonOpenButtonIcon />
105
+ * </Autocomplete.AddonOpenButton>
106
+ * <Autocomplete.AddonClearButton aria-label="Clear city">
107
+ * <Autocomplete.AddonClearButtonIcon />
108
+ * </Autocomplete.AddonClearButton>
109
+ * </Autocomplete.Addon>
110
+ * </Autocomplete.InputGroup>
111
+ * <Autocomplete.Popup>
112
+ * <AutocompleteFilteredItemsSync {...virtual.filteredItemsSyncProps} />
113
+ * <Autocomplete.Empty // Keep mounted; native filtering controls visibility.
114
+ * >
115
+ * <Autocomplete.EmptyText>No cities found.</Autocomplete.EmptyText>
116
+ * </Autocomplete.Empty>
117
+ * <Autocomplete.ScrollArea {...virtual.scrollAreaProps}>
118
+ * <Autocomplete.GroupsList>
119
+ * {virtual.filteredItems.length > 0 && (
120
+ * <Autocomplete.Group items={virtual.filteredItems}>
121
+ * <Autocomplete.GroupLabel>Cities</Autocomplete.GroupLabel>
122
+ * <Autocomplete.ItemsList {...virtual.itemsListProps}>
123
+ * {virtual.rows.map((row) => (
124
+ * <Autocomplete.Item
125
+ * key={row.key}
126
+ * value={row.item}
127
+ * index={row.index} // Preserve the index in the full filtered list.
128
+ * {...row.itemProps}
129
+ * >
130
+ * <Autocomplete.ItemText>{row.item.label}</Autocomplete.ItemText>
131
+ * </Autocomplete.Item>
132
+ * ))}
133
+ * </Autocomplete.ItemsList>
134
+ * </Autocomplete.Group>
135
+ * )}
136
+ * </Autocomplete.GroupsList>
137
+ * </Autocomplete.ScrollArea>
138
+ * </Autocomplete.Popup>
139
+ * </Autocomplete.Container>
140
+ * </Autocomplete.Root>
141
+ * )
142
+ * }
143
+ */
144
+ export declare function useVirtualizedAutocomplete<T extends readonly GroupShape[]>({ items: source, estimateSize, overscan, paddingStart, paddingEnd, maxHeight, availableHeight, }: UseVirtualizedAutocompleteOptions<T>): {
145
+ rootProps: {
146
+ items: T;
147
+ virtualized: true;
148
+ onItemHighlighted: (highlightedValue: ItemType<T> | undefined, eventDetails: import("@base-ui/react").AutocompleteRootHighlightEventDetails) => void;
149
+ };
150
+ filteredItemsSyncProps: {
151
+ onChange: (next: ItemType<T>[]) => void;
152
+ };
153
+ filteredItems: ItemType<T>[];
154
+ scrollAreaProps: {
155
+ ref: import("react").RefObject<HTMLDivElement | null>;
156
+ style: {
157
+ height: number;
158
+ maxHeight: import("csstype").Property.MaxHeight<string | number>;
159
+ };
160
+ };
161
+ itemsListProps: {
162
+ style: {
163
+ position: "relative";
164
+ height: number;
165
+ flexShrink: number;
166
+ };
167
+ };
168
+ rows: {
169
+ key: string | number | bigint;
170
+ index: number;
171
+ item: ItemType<T>;
172
+ start: number;
173
+ size: number;
174
+ itemProps: {
175
+ style: {
176
+ position: "absolute";
177
+ top: number;
178
+ left: number;
179
+ width: string;
180
+ transform: `translateY(${number}px)`;
181
+ };
182
+ 'aria-setsize': number;
183
+ 'aria-posinset': number;
184
+ };
185
+ }[];
186
+ scrollToIndex: (index: number, options?: Parameters<(index: number, { align: initialAlign, behavior, }?: import("@tanstack/virtual-core").ScrollToOptions) => void>[1]) => void;
187
+ virtualizer: import("@tanstack/react-virtual").ReactVirtualizer<HTMLDivElement, HTMLDivElement>;
188
+ };
189
+ /**
190
+ * Renders nothing. Mount inside Root (normally inside Popup) to mirror the
191
+ * complete native search results into the external virtualization hook.
192
+ * The callback infers the item type, including extra fields, without a cast.
193
+ */
194
+ export declare function FilteredItemsSync<Item extends ItemShape>({ onChange, }: {
195
+ /** Spread the hook's filteredItemsSyncProps here to sync all native search results, not just mounted rows. */
196
+ onChange: (items: Item[]) => void;
197
+ }): null;
198
+ //# sourceMappingURL=useVirtualizedAutocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVirtualizedAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedAutocomplete.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EACT,QAAQ,EACT,MAAM,yCAAyC,CAAA;AAEhD,MAAM,MAAM,iCAAiC,CAAC,CAAC,SAAS,SAAS,UAAU,EAAE,IAC3E;IACE;;;;;OAKG;IACH,KAAK,EAAE,CAAC,CAAA;IACR;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAA;IACtE;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CAC7C,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,SAAS,UAAU,EAAE,EAAE,EAC1E,KAAK,EAAE,MAAM,EACb,YAAiB,EACjB,QAAY,EACZ,YAAgB,EAChB,UAAc,EACd,SAAe,EACf,eAA2C,GAC5C,EAAE,iCAAiC,CAAC,CAAC,CAAC;;;;;;;yBA4F5B,QAAQ,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA5BX,MAAM,YACH,UAAU,gHAAkC,CAAC,CAAC,CAAC;;EAyC9D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,SAAS,EAAE,EACxD,QAAQ,GACT,EAAE;IACD,8GAA8G;IAC9G,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAA;CAClC,QAUA"}
@@ -0,0 +1,180 @@
1
+ import { Autocomplete as BaseAutocomplete } from '@base-ui/react/autocomplete';
2
+ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
3
+ import { useVirtualizer } from '@tanstack/react-virtual';
4
+ /**
5
+ * Virtualize a large Autocomplete while keeping native search and keyboard behavior.
6
+ * Call this hook in your component, outside Root. Spread each returned props object
7
+ * on its matching element, and keep AutocompleteFilteredItemsSync mounted inside Popup
8
+ * so native filtering supplies the complete results. Keep rootProps.onItemHighlighted
9
+ * connected; it scrolls keyboard highlights into view. Merge it if adding your own callback.
10
+ *
11
+ * Filtered groups are flattened into one list: this hook does not virtualize separate
12
+ * group headings. Use one shared heading, as below, or omit GroupLabel for a flat list
13
+ * and adjust paddingStart. Preserve the returned refs and layout styles when customizing.
14
+ * Render only rows, but give Group all filteredItems and each Item its returned index.
15
+ * The mapped rows already preserve your custom item fields; no manual annotation is needed.
16
+ *
17
+ * @example Complete virtualized Autocomplete
18
+ * import {
19
+ * Autocomplete,
20
+ * AutocompleteFilteredItemsSync,
21
+ * useVirtualizedAutocomplete,
22
+ * } from '@central-ui/react'
23
+ *
24
+ * // Replace this sample with your larger dataset; keep the source stable between renders.
25
+ * const cityGroups = [{
26
+ * label: 'Cities',
27
+ * items: [
28
+ * { value: 'ma-rabat', label: 'Rabat', country: 'Morocco', population: 580000 },
29
+ * { value: 'ma-casablanca', label: 'Casablanca', country: 'Morocco', population: 3359818 },
30
+ * { value: 'fr-paris', label: 'Paris', country: 'France', population: 2102650 },
31
+ * ],
32
+ * }]
33
+ *
34
+ * function CityAutocomplete() {
35
+ * const virtual = useVirtualizedAutocomplete({
36
+ * items: cityGroups, // rootProps supplies these complete groups to Root.
37
+ * estimateSize: 34, // Match the row height plus spacing in your layout.
38
+ * paddingStart: 32, // Match the heading and leading space; adjust if styled differently.
39
+ * paddingEnd: 4, // Match the trailing group padding.
40
+ * })
41
+ *
42
+ * return (
43
+ * <Autocomplete.Root {...virtual.rootProps} openOnInputClick>
44
+ * <Autocomplete.Container>
45
+ * <Autocomplete.Label>City</Autocomplete.Label>
46
+ * <Autocomplete.InputGroup>
47
+ * <Autocomplete.Input placeholder="Search cities" />
48
+ * <Autocomplete.Addon>
49
+ * <Autocomplete.AddonOpenButton aria-label="Show cities">
50
+ * <Autocomplete.AddonOpenButtonIcon />
51
+ * </Autocomplete.AddonOpenButton>
52
+ * <Autocomplete.AddonClearButton aria-label="Clear city">
53
+ * <Autocomplete.AddonClearButtonIcon />
54
+ * </Autocomplete.AddonClearButton>
55
+ * </Autocomplete.Addon>
56
+ * </Autocomplete.InputGroup>
57
+ * <Autocomplete.Popup>
58
+ * <AutocompleteFilteredItemsSync {...virtual.filteredItemsSyncProps} />
59
+ * <Autocomplete.Empty // Keep mounted; native filtering controls visibility.
60
+ * >
61
+ * <Autocomplete.EmptyText>No cities found.</Autocomplete.EmptyText>
62
+ * </Autocomplete.Empty>
63
+ * <Autocomplete.ScrollArea {...virtual.scrollAreaProps}>
64
+ * <Autocomplete.GroupsList>
65
+ * {virtual.filteredItems.length > 0 && (
66
+ * <Autocomplete.Group items={virtual.filteredItems}>
67
+ * <Autocomplete.GroupLabel>Cities</Autocomplete.GroupLabel>
68
+ * <Autocomplete.ItemsList {...virtual.itemsListProps}>
69
+ * {virtual.rows.map((row) => (
70
+ * <Autocomplete.Item
71
+ * key={row.key}
72
+ * value={row.item}
73
+ * index={row.index} // Preserve the index in the full filtered list.
74
+ * {...row.itemProps}
75
+ * >
76
+ * <Autocomplete.ItemText>{row.item.label}</Autocomplete.ItemText>
77
+ * </Autocomplete.Item>
78
+ * ))}
79
+ * </Autocomplete.ItemsList>
80
+ * </Autocomplete.Group>
81
+ * )}
82
+ * </Autocomplete.GroupsList>
83
+ * </Autocomplete.ScrollArea>
84
+ * </Autocomplete.Popup>
85
+ * </Autocomplete.Container>
86
+ * </Autocomplete.Root>
87
+ * )
88
+ * }
89
+ */
90
+ export function useVirtualizedAutocomplete({ items: source, estimateSize = 34, overscan = 5, paddingStart = 0, paddingEnd = 0, maxHeight = 320, availableHeight = 'var(--available-height)', }) {
91
+ const [filteredItems, setFilteredItems] = useState([]);
92
+ const scrollRef = useRef(null);
93
+ const virtualizer = useVirtualizer({
94
+ count: filteredItems.length,
95
+ getScrollElement: () => scrollRef.current,
96
+ estimateSize: (index) => typeof estimateSize === 'function'
97
+ ? estimateSize(filteredItems[index], index)
98
+ : estimateSize,
99
+ getItemKey: (index) => filteredItems[index].value,
100
+ overscan,
101
+ paddingStart,
102
+ paddingEnd,
103
+ });
104
+ // Filtering or sorting supplies a new array; scrolling alone must not reset it.
105
+ useLayoutEffect(() => {
106
+ virtualizer.scrollToOffset(0);
107
+ }, [filteredItems, virtualizer]);
108
+ const totalSize = virtualizer.getTotalSize();
109
+ const virtualRows = virtualizer.getVirtualItems();
110
+ const scrollAreaProps = {
111
+ ref: scrollRef,
112
+ style: {
113
+ height: Math.min(totalSize, maxHeight),
114
+ maxHeight: availableHeight,
115
+ },
116
+ };
117
+ // Exclude the space already occupied by the caller's heading and padding.
118
+ const itemsListProps = {
119
+ style: {
120
+ position: 'relative',
121
+ height: totalSize - paddingStart - paddingEnd,
122
+ flexShrink: 0,
123
+ },
124
+ };
125
+ const rows = virtualRows.map((row) => ({
126
+ key: row.key,
127
+ index: row.index,
128
+ item: filteredItems[row.index],
129
+ start: row.start,
130
+ size: row.size,
131
+ itemProps: {
132
+ style: {
133
+ position: 'absolute',
134
+ top: 0,
135
+ left: 0,
136
+ width: '100%',
137
+ transform: `translateY(${row.start - paddingStart}px)`,
138
+ },
139
+ 'aria-setsize': filteredItems.length,
140
+ 'aria-posinset': row.index + 1,
141
+ },
142
+ }));
143
+ const scrollToIndex = useCallback((index, options) => virtualizer.scrollToIndex(index, { align: 'auto', ...options }), [virtualizer]);
144
+ const onItemHighlighted = useCallback((item, details) => {
145
+ if (!item || details.index < 0)
146
+ return;
147
+ const isStart = details.index === 0;
148
+ const isEnd = details.index === filteredItems.length - 1;
149
+ // Match Base UI's virtualized example: it handles ordinary keyboard
150
+ // movement; the virtualizer handles initial highlights and either end.
151
+ if (details.reason === 'none' ||
152
+ (details.reason === 'keyboard' && (isStart || isEnd))) {
153
+ queueMicrotask(() => scrollToIndex(details.index, { align: isEnd ? 'start' : 'end' }));
154
+ }
155
+ }, [scrollToIndex, filteredItems.length]);
156
+ const onFilteredItemsChange = useCallback((next) => setFilteredItems(next), []);
157
+ return {
158
+ rootProps: { items: source, virtualized: true, onItemHighlighted },
159
+ filteredItemsSyncProps: { onChange: onFilteredItemsChange },
160
+ filteredItems,
161
+ scrollAreaProps,
162
+ itemsListProps,
163
+ rows,
164
+ scrollToIndex,
165
+ virtualizer,
166
+ };
167
+ }
168
+ /**
169
+ * Renders nothing. Mount inside Root (normally inside Popup) to mirror the
170
+ * complete native search results into the external virtualization hook.
171
+ * The callback infers the item type, including extra fields, without a cast.
172
+ */
173
+ export function FilteredItemsSync({ onChange, }) {
174
+ const groups = BaseAutocomplete.useFilteredItems();
175
+ useLayoutEffect(() => {
176
+ onChange(groups.flatMap((group) => group.items));
177
+ }, [groups, onChange]);
178
+ return null;
179
+ }
180
+ //# sourceMappingURL=useVirtualizedAutocomplete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVirtualizedAutocomplete.js","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedAutocomplete.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,200 @@
1
+ import { Combobox as BaseCombobox } from '@base-ui/react/combobox';
2
+ import type { CSSProperties } from 'react';
3
+ import type { GroupShape, ItemShape, ItemType } from '../../components/combobox/index.tsx';
4
+ export type UseVirtualizedComboboxOptions<T extends readonly GroupShape[]> = {
5
+ /**
6
+ * Supply the complete grouped data; rootProps passes this same data to Combobox.Root.
7
+ * Let native search filter it through ComboboxFilteredItemsSync; do not pass only
8
+ * the visible rows back to Root. For a flat source, use one group with label: ''.
9
+ * @example items: cityGroups
10
+ */
11
+ items: T;
12
+ /**
13
+ * Match the rendered row height in pixels, including any spacing between rows.
14
+ * Use a callback when rows have known different heights. This hook does not
15
+ * automatically measure rendered rows; inaccurate sizes can leave gaps or overlaps.
16
+ * @example estimateSize: 34
17
+ * @example estimateSize: (city) => city.label.length > 30 ? 56 : 34
18
+ * @default 34
19
+ */
20
+ estimateSize?: number | ((item: ItemType<T>, index: number) => number);
21
+ /**
22
+ * Mount extra rows above and below the visible area to reduce blanks during fast
23
+ * scrolling. Increase this if needed, at the cost of rendering more rows.
24
+ * @example overscan: 8
25
+ * @default 5
26
+ */
27
+ overscan?: number;
28
+ /**
29
+ * Account for space before ItemsList inside ScrollArea, such as a group heading
30
+ * and leading padding. Match your actual layout; this does not render a heading
31
+ * or add CSS padding for you. Omit when there is no leading space.
32
+ * @example paddingStart: 32
33
+ * @default 0
34
+ */
35
+ paddingStart?: number;
36
+ /**
37
+ * Account for space after ItemsList inside ScrollArea, such as group bottom
38
+ * padding. Match the space your markup already supplies; this adds no CSS padding.
39
+ * @example paddingEnd: 4
40
+ * @default 0
41
+ */
42
+ paddingEnd?: number;
43
+ /**
44
+ * Cap the scrolling viewport height while allowing short lists to shrink.
45
+ * Choose this independently of the space available around the popup.
46
+ * @example maxHeight: 480
47
+ * @default 320
48
+ */
49
+ maxHeight?: number;
50
+ /**
51
+ * Apply an additional CSS max-height to the scrolling viewport.
52
+ * The default uses the popup's available space so it can fit near a screen edge.
53
+ * For a custom popup layout without that CSS variable, supply your own constraint.
54
+ * @example availableHeight: 'calc(100dvh - 160px)'
55
+ * @default 'var(--available-height)'
56
+ */
57
+ availableHeight?: CSSProperties['maxHeight'];
58
+ };
59
+ /**
60
+ * Virtualize a large Combobox while keeping native search and keyboard behavior.
61
+ * Call this hook in your component, outside Root. Spread each returned props object
62
+ * on its matching element, and keep ComboboxFilteredItemsSync mounted inside Popup
63
+ * so native filtering supplies the complete results. Keep rootProps.onItemHighlighted
64
+ * connected; it scrolls keyboard highlights into view. Merge it if adding your own callback.
65
+ *
66
+ * Filtered groups are flattened into one list: this hook does not virtualize separate
67
+ * group headings. Use one shared heading, as below, or omit GroupLabel for a flat list
68
+ * and adjust paddingStart. Preserve the returned refs and layout styles when customizing.
69
+ * Render only rows, but give Group all filteredItems and each Item its returned index.
70
+ * The mapped rows already preserve your custom item fields; no manual annotation is needed.
71
+ *
72
+ * @example Complete virtualized Combobox
73
+ * import {
74
+ * Combobox,
75
+ * ComboboxFilteredItemsSync,
76
+ * useVirtualizedCombobox,
77
+ * } from '@central-ui/react'
78
+ *
79
+ * // Replace this sample with your larger dataset; keep the source stable between renders.
80
+ * const cityGroups = [{
81
+ * label: 'Cities',
82
+ * items: [
83
+ * { value: 'ma-rabat', label: 'Rabat', country: 'Morocco', population: 580000 },
84
+ * { value: 'ma-casablanca', label: 'Casablanca', country: 'Morocco', population: 3359818 },
85
+ * { value: 'fr-paris', label: 'Paris', country: 'France', population: 2102650 },
86
+ * ],
87
+ * }]
88
+ *
89
+ * function CityCombobox() {
90
+ * const virtual = useVirtualizedCombobox({
91
+ * items: cityGroups, // rootProps supplies these complete groups to Root.
92
+ * estimateSize: 34, // Match the row height plus spacing in your layout.
93
+ * paddingStart: 32, // Match the heading and leading space; adjust if styled differently.
94
+ * paddingEnd: 4, // Match the trailing group padding.
95
+ * })
96
+ *
97
+ * return (
98
+ * <Combobox.Root {...virtual.rootProps}>
99
+ * <Combobox.Container>
100
+ * <Combobox.Label>City</Combobox.Label>
101
+ * <Combobox.InputGroup>
102
+ * <Combobox.Input placeholder="Search cities" />
103
+ * <Combobox.Addon>
104
+ * <Combobox.AddonOpenButton aria-label="Show cities">
105
+ * <Combobox.AddonOpenButtonIcon />
106
+ * </Combobox.AddonOpenButton>
107
+ * <Combobox.AddonClearButton aria-label="Clear city">
108
+ * <Combobox.AddonClearButtonIcon />
109
+ * </Combobox.AddonClearButton>
110
+ * </Combobox.Addon>
111
+ * </Combobox.InputGroup>
112
+ * <Combobox.Popup>
113
+ * <ComboboxFilteredItemsSync {...virtual.filteredItemsSyncProps} />
114
+ * <Combobox.Empty // Keep mounted; native filtering controls visibility.
115
+ * >
116
+ * <Combobox.EmptyText>No cities found.</Combobox.EmptyText>
117
+ * </Combobox.Empty>
118
+ * <Combobox.ScrollArea {...virtual.scrollAreaProps}>
119
+ * <Combobox.GroupsList>
120
+ * {virtual.filteredItems.length > 0 && (
121
+ * <Combobox.Group items={virtual.filteredItems}>
122
+ * <Combobox.GroupLabel>Cities</Combobox.GroupLabel>
123
+ * <Combobox.ItemsList {...virtual.itemsListProps}>
124
+ * {virtual.rows.map((row) => (
125
+ * <Combobox.Item
126
+ * key={row.key}
127
+ * value={row.item}
128
+ * index={row.index} // Preserve the index in the full filtered list.
129
+ * {...row.itemProps}
130
+ * >
131
+ * <Combobox.ItemText>{row.item.label}</Combobox.ItemText>
132
+ * <Combobox.ItemIndicator />
133
+ * </Combobox.Item>
134
+ * ))}
135
+ * </Combobox.ItemsList>
136
+ * </Combobox.Group>
137
+ * )}
138
+ * </Combobox.GroupsList>
139
+ * </Combobox.ScrollArea>
140
+ * </Combobox.Popup>
141
+ * </Combobox.Container>
142
+ * </Combobox.Root>
143
+ * )
144
+ * }
145
+ */
146
+ export declare function useVirtualizedCombobox<T extends readonly GroupShape[]>({ items: source, estimateSize, overscan, paddingStart, paddingEnd, maxHeight, availableHeight, }: UseVirtualizedComboboxOptions<T>): {
147
+ rootProps: {
148
+ items: T;
149
+ virtualized: true;
150
+ onItemHighlighted: (highlightedValue: ItemType<T> | undefined, eventDetails: BaseCombobox.Root.HighlightEventDetails) => void;
151
+ };
152
+ filteredItemsSyncProps: {
153
+ onChange: (next: ItemType<T>[]) => void;
154
+ };
155
+ filteredItems: ItemType<T>[];
156
+ scrollAreaProps: {
157
+ ref: import("react").RefObject<HTMLDivElement | null>;
158
+ style: {
159
+ height: number;
160
+ maxHeight: import("csstype").Property.MaxHeight<string | number>;
161
+ };
162
+ };
163
+ itemsListProps: {
164
+ style: {
165
+ position: "relative";
166
+ height: number;
167
+ flexShrink: number;
168
+ };
169
+ };
170
+ rows: {
171
+ key: string | number | bigint;
172
+ index: number;
173
+ item: ItemType<T>;
174
+ start: number;
175
+ size: number;
176
+ itemProps: {
177
+ style: {
178
+ position: "absolute";
179
+ top: number;
180
+ left: number;
181
+ width: string;
182
+ transform: `translateY(${number}px)`;
183
+ };
184
+ 'aria-setsize': number;
185
+ 'aria-posinset': number;
186
+ };
187
+ }[];
188
+ scrollToIndex: (index: number, options?: Parameters<(index: number, { align: initialAlign, behavior, }?: import("@tanstack/virtual-core").ScrollToOptions) => void>[1]) => void;
189
+ virtualizer: import("@tanstack/react-virtual").ReactVirtualizer<HTMLDivElement, HTMLDivElement>;
190
+ };
191
+ /**
192
+ * Renders nothing. Mount inside Root (normally inside Popup) to mirror the
193
+ * complete native search results into the external virtualization hook.
194
+ * The callback infers the item type, including extra fields, without a cast.
195
+ */
196
+ export declare function FilteredItemsSync<Item extends ItemShape>({ onChange, }: {
197
+ /** Spread the hook's filteredItemsSyncProps here to sync all native search results, not just mounted rows. */
198
+ onChange: (items: Item[]) => void;
199
+ }): null;
200
+ //# sourceMappingURL=useVirtualizedCombobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVirtualizedCombobox.d.ts","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedCombobox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EACT,QAAQ,EACT,MAAM,qCAAqC,CAAA;AAE5C,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,SAAS,UAAU,EAAE,IAAI;IAC3E;;;;;OAKG;IACH,KAAK,EAAE,CAAC,CAAA;IACR;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAA;IACtE;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CAC7C,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,SAAS,UAAU,EAAE,EAAE,EACtE,KAAK,EAAE,MAAM,EACb,YAAiB,EACjB,QAAY,EACZ,YAAgB,EAChB,UAAc,EACd,SAAe,EACf,eAA2C,GAC5C,EAAE,6BAA6B,CAAC,CAAC,CAAC;;;;;;;yBAiFxB,QAAQ,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAjBX,MAAM,YACH,UAAU,gHAAkC,CAAC,CAAC,CAAC;;EA8B9D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,SAAS,EAAE,EACxD,QAAQ,GACT,EAAE;IACD,8GAA8G;IAC9G,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAA;CAClC,QAUA"}