@central-ui/react 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/components/accordion/index.d.ts +73 -0
- package/dist/components/accordion/index.d.ts.map +1 -0
- package/dist/components/accordion/index.js +92 -0
- package/dist/components/accordion/index.js.map +1 -0
- package/dist/components/autocomplete/index.d.ts +365 -0
- package/dist/components/autocomplete/index.d.ts.map +1 -0
- package/dist/components/autocomplete/index.js +369 -0
- package/dist/components/autocomplete/index.js.map +1 -0
- package/dist/components/avatar/index.d.ts +48 -0
- package/dist/components/avatar/index.d.ts.map +1 -0
- package/dist/components/avatar/index.js +60 -0
- package/dist/components/avatar/index.js.map +1 -0
- package/dist/components/badge/index.d.ts +29 -0
- package/dist/components/badge/index.d.ts.map +1 -0
- package/dist/components/badge/index.js +36 -0
- package/dist/components/badge/index.js.map +1 -0
- package/dist/components/banner/index.d.ts +71 -0
- package/dist/components/banner/index.d.ts.map +1 -0
- package/dist/components/banner/index.js +119 -0
- package/dist/components/banner/index.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +31 -0
- package/dist/components/breadcrumb/index.d.ts.map +1 -0
- package/dist/components/breadcrumb/index.js +42 -0
- package/dist/components/breadcrumb/index.js.map +1 -0
- package/dist/components/button/index.d.ts +48 -0
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +60 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/card/index.d.ts +34 -0
- package/dist/components/card/index.d.ts.map +1 -0
- package/dist/components/card/index.js +45 -0
- package/dist/components/card/index.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +66 -0
- package/dist/components/checkbox/index.d.ts.map +1 -0
- package/dist/components/checkbox/index.js +146 -0
- package/dist/components/checkbox/index.js.map +1 -0
- package/dist/components/chip/index.d.ts +26 -0
- package/dist/components/chip/index.d.ts.map +1 -0
- package/dist/components/chip/index.js +40 -0
- package/dist/components/chip/index.js.map +1 -0
- package/dist/components/combobox/index.d.ts +427 -0
- package/dist/components/combobox/index.d.ts.map +1 -0
- package/dist/components/combobox/index.js +416 -0
- package/dist/components/combobox/index.js.map +1 -0
- package/dist/components/datepicker/index.d.ts +297 -0
- package/dist/components/datepicker/index.d.ts.map +1 -0
- package/dist/components/datepicker/index.js +507 -0
- package/dist/components/datepicker/index.js.map +1 -0
- package/dist/components/dialog/index.d.ts +88 -0
- package/dist/components/dialog/index.d.ts.map +1 -0
- package/dist/components/dialog/index.js +102 -0
- package/dist/components/dialog/index.js.map +1 -0
- package/dist/components/divider/index.d.ts +21 -0
- package/dist/components/divider/index.d.ts.map +1 -0
- package/dist/components/divider/index.js +24 -0
- package/dist/components/divider/index.js.map +1 -0
- package/dist/components/drawer/index.d.ts +99 -0
- package/dist/components/drawer/index.d.ts.map +1 -0
- package/dist/components/drawer/index.js +137 -0
- package/dist/components/drawer/index.js.map +1 -0
- package/dist/components/empty/index.d.ts +40 -0
- package/dist/components/empty/index.d.ts.map +1 -0
- package/dist/components/empty/index.js +61 -0
- package/dist/components/empty/index.js.map +1 -0
- package/dist/components/fileupload/index.d.ts +107 -0
- package/dist/components/fileupload/index.d.ts.map +1 -0
- package/dist/components/fileupload/index.js +185 -0
- package/dist/components/fileupload/index.js.map +1 -0
- package/dist/components/form/index.d.ts +34 -0
- package/dist/components/form/index.d.ts.map +1 -0
- package/dist/components/form/index.js +35 -0
- package/dist/components/form/index.js.map +1 -0
- package/dist/components/input/index.d.ts +85 -0
- package/dist/components/input/index.d.ts.map +1 -0
- package/dist/components/input/index.js +249 -0
- package/dist/components/input/index.js.map +1 -0
- package/dist/components/menu/index.d.ts +115 -0
- package/dist/components/menu/index.d.ts.map +1 -0
- package/dist/components/menu/index.js +188 -0
- package/dist/components/menu/index.js.map +1 -0
- package/dist/components/metric/index.d.ts +24 -0
- package/dist/components/metric/index.d.ts.map +1 -0
- package/dist/components/metric/index.js +26 -0
- package/dist/components/metric/index.js.map +1 -0
- package/dist/components/numberinput/index.d.ts +105 -0
- package/dist/components/numberinput/index.d.ts.map +1 -0
- package/dist/components/numberinput/index.js +240 -0
- package/dist/components/numberinput/index.js.map +1 -0
- package/dist/components/otpinput/index.d.ts +63 -0
- package/dist/components/otpinput/index.d.ts.map +1 -0
- package/dist/components/otpinput/index.js +133 -0
- package/dist/components/otpinput/index.js.map +1 -0
- package/dist/components/page/index.d.ts +54 -0
- package/dist/components/page/index.d.ts.map +1 -0
- package/dist/components/page/index.js +100 -0
- package/dist/components/page/index.js.map +1 -0
- package/dist/components/passwordinput/index.d.ts +88 -0
- package/dist/components/passwordinput/index.d.ts.map +1 -0
- package/dist/components/passwordinput/index.js +251 -0
- package/dist/components/passwordinput/index.js.map +1 -0
- package/dist/components/popover/index.d.ts +67 -0
- package/dist/components/popover/index.d.ts.map +1 -0
- package/dist/components/popover/index.js +76 -0
- package/dist/components/popover/index.js.map +1 -0
- package/dist/components/radio/index.d.ts +90 -0
- package/dist/components/radio/index.d.ts.map +1 -0
- package/dist/components/radio/index.js +232 -0
- package/dist/components/radio/index.js.map +1 -0
- package/dist/components/ratinginput/index.d.ts +59 -0
- package/dist/components/ratinginput/index.d.ts.map +1 -0
- package/dist/components/ratinginput/index.js +88 -0
- package/dist/components/ratinginput/index.js.map +1 -0
- package/dist/components/select/index.d.ts +205 -0
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +234 -0
- package/dist/components/select/index.js.map +1 -0
- package/dist/components/skeleton/index.d.ts +19 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +20 -0
- package/dist/components/skeleton/index.js.map +1 -0
- package/dist/components/slider/index.d.ts +72 -0
- package/dist/components/slider/index.d.ts.map +1 -0
- package/dist/components/slider/index.js +159 -0
- package/dist/components/slider/index.js.map +1 -0
- package/dist/components/spinner/index.d.ts +14 -0
- package/dist/components/spinner/index.d.ts.map +1 -0
- package/dist/components/spinner/index.js +15 -0
- package/dist/components/spinner/index.js.map +1 -0
- package/dist/components/splitter/index.d.ts +49 -0
- package/dist/components/splitter/index.d.ts.map +1 -0
- package/dist/components/splitter/index.js +67 -0
- package/dist/components/splitter/index.js.map +1 -0
- package/dist/components/stepper/index.d.ts +82 -0
- package/dist/components/stepper/index.d.ts.map +1 -0
- package/dist/components/stepper/index.js +100 -0
- package/dist/components/stepper/index.js.map +1 -0
- package/dist/components/switch/index.d.ts +57 -0
- package/dist/components/switch/index.d.ts.map +1 -0
- package/dist/components/switch/index.js +101 -0
- package/dist/components/switch/index.js.map +1 -0
- package/dist/components/table/index.d.ts +110 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +211 -0
- package/dist/components/table/index.js.map +1 -0
- package/dist/components/tabs/index.d.ts +67 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/components/tabs/index.js +78 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/textarea/index.d.ts +72 -0
- package/dist/components/textarea/index.d.ts.map +1 -0
- package/dist/components/textarea/index.js +205 -0
- package/dist/components/textarea/index.js.map +1 -0
- package/dist/components/textlink/index.d.ts +39 -0
- package/dist/components/textlink/index.d.ts.map +1 -0
- package/dist/components/textlink/index.js +42 -0
- package/dist/components/textlink/index.js.map +1 -0
- package/dist/components/toast/index.d.ts +111 -0
- package/dist/components/toast/index.d.ts.map +1 -0
- package/dist/components/toast/index.js +205 -0
- package/dist/components/toast/index.js.map +1 -0
- package/dist/components/toggle/index.d.ts +41 -0
- package/dist/components/toggle/index.d.ts.map +1 -0
- package/dist/components/toggle/index.js +78 -0
- package/dist/components/toggle/index.js.map +1 -0
- package/dist/components/tooltip/index.d.ts +44 -0
- package/dist/components/tooltip/index.d.ts.map +1 -0
- package/dist/components/tooltip/index.js +51 -0
- package/dist/components/tooltip/index.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/types/Style.d.ts +14 -0
- package/dist/types/Style.d.ts.map +1 -0
- package/dist/types/Style.js +2 -0
- package/dist/types/Style.js.map +1 -0
- package/dist/types/Tone.d.ts +4 -0
- package/dist/types/Tone.d.ts.map +1 -0
- package/dist/types/Tone.js +2 -0
- package/dist/types/Tone.js.map +1 -0
- package/dist/utils/ArkStateToDataAttributes.d.ts +4 -0
- package/dist/utils/ArkStateToDataAttributes.d.ts.map +1 -0
- package/dist/utils/ArkStateToDataAttributes.js +9 -0
- package/dist/utils/ArkStateToDataAttributes.js.map +1 -0
- package/dist/utils/Tone.d.ts +3 -0
- package/dist/utils/Tone.d.ts.map +1 -0
- package/dist/utils/Tone.js +7 -0
- package/dist/utils/Tone.js.map +1 -0
- package/dist/utils/useForwardClickToInput.d.ts +4 -0
- package/dist/utils/useForwardClickToInput.d.ts.map +1 -0
- package/dist/utils/useForwardClickToInput.js +27 -0
- package/dist/utils/useForwardClickToInput.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts +198 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.js +180 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.d.ts +200 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.js +174 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedTable.d.ts +169 -0
- package/dist/utils/virtualization/useVirtualizedTable.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedTable.js +127 -0
- package/dist/utils/virtualization/useVirtualizedTable.js.map +1 -0
- package/package.json +64 -4
- package/src/presets/shopify-polaris/shopify-polaris-accordion.css +28 -0
- package/src/presets/shopify-polaris/shopify-polaris-autocomplete.css +155 -0
- package/src/presets/shopify-polaris/shopify-polaris-avatar.css +47 -0
- package/src/presets/shopify-polaris/shopify-polaris-badge.css +27 -0
- package/src/presets/shopify-polaris/shopify-polaris-banner.css +111 -0
- package/src/presets/shopify-polaris/shopify-polaris-breadcrumb.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-button.css +79 -0
- package/src/presets/shopify-polaris/shopify-polaris-card.css +28 -0
- package/src/presets/shopify-polaris/shopify-polaris-checkbox.css +52 -0
- package/src/presets/shopify-polaris/shopify-polaris-chip.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-combobox.css +179 -0
- package/src/presets/shopify-polaris/shopify-polaris-datepicker.css +183 -0
- package/src/presets/shopify-polaris/shopify-polaris-dialog.css +40 -0
- package/src/presets/shopify-polaris/shopify-polaris-divider.css +12 -0
- package/src/presets/shopify-polaris/shopify-polaris-drawer.css +40 -0
- package/src/presets/shopify-polaris/shopify-polaris-empty.css +32 -0
- package/src/presets/shopify-polaris/shopify-polaris-fileupload.css +78 -0
- package/src/presets/shopify-polaris/shopify-polaris-form.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-input.css +72 -0
- package/src/presets/shopify-polaris/shopify-polaris-menu.css +112 -0
- package/src/presets/shopify-polaris/shopify-polaris-metric.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-numberinput.css +88 -0
- package/src/presets/shopify-polaris/shopify-polaris-otpinput.css +42 -0
- package/src/presets/shopify-polaris/shopify-polaris-page.css +69 -0
- package/src/presets/shopify-polaris/shopify-polaris-passwordinput.css +76 -0
- package/src/presets/shopify-polaris/shopify-polaris-popover.css +47 -0
- package/src/presets/shopify-polaris/shopify-polaris-radio.css +54 -0
- package/src/presets/shopify-polaris/shopify-polaris-ratinginput.css +42 -0
- package/src/presets/shopify-polaris/shopify-polaris-select.css +119 -0
- package/src/presets/shopify-polaris/shopify-polaris-skeleton.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-slider.css +86 -0
- package/src/presets/shopify-polaris/shopify-polaris-spinner.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-splitter.css +34 -0
- package/src/presets/shopify-polaris/shopify-polaris-stepper.css +55 -0
- package/src/presets/shopify-polaris/shopify-polaris-switch.css +38 -0
- package/src/presets/shopify-polaris/shopify-polaris-table.css +72 -0
- package/src/presets/shopify-polaris/shopify-polaris-tabs.css +33 -0
- package/src/presets/shopify-polaris/shopify-polaris-textarea.css +61 -0
- package/src/presets/shopify-polaris/shopify-polaris-textlink.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-toggle.css +52 -0
- package/src/presets/shopify-polaris/shopify-polaris-tooltip.css +31 -0
- package/src/presets/shopify-polaris/shopify-polaris.css +380 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-accordion.css +28 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-autocomplete.css +155 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-avatar.css +47 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-badge.css +27 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-banner.css +96 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-breadcrumb.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-button.css +68 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-card.css +28 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-checkbox.css +52 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-chip.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-combobox.css +184 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-datepicker.css +187 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-dialog.css +40 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-divider.css +12 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-drawer.css +40 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-empty.css +32 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-fileupload.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-form.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-input.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-menu.css +112 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-metric.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-numberinput.css +88 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-otpinput.css +42 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-page.css +69 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-passwordinput.css +76 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-popover.css +47 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-radio.css +54 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-ratinginput.css +42 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-select.css +118 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-skeleton.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-slider.css +86 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-spinner.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-splitter.css +34 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-stepper.css +55 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-switch.css +38 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-table.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tabs.css +33 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textarea.css +61 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textlink.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toast.css +43 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toggle.css +52 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tooltip.css +31 -0
- 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"}
|