ropav 0.4.0 → 0.5.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.
- package/dist/components/autocomplete/autocomplete-root.js +3 -1
- package/dist/components/autocomplete/index.d.ts +3 -0
- package/dist/components/breadcrumbs/breadcrumbs-item.js +1 -0
- package/dist/components/breadcrumbs/index.d.ts +1 -0
- package/dist/components/calendar/index.d.ts +5 -3
- package/dist/components/calendar-year-picker/index.d.ts +4 -2
- package/dist/components/checkbox/checkbox-content.js +1 -1
- package/dist/components/color-field/index.d.ts +1 -0
- package/dist/components/color-input-group/color-input-group-root.js +2 -0
- package/dist/components/color-input-group/color-input-group.types.d.ts +2 -0
- package/dist/components/color-input-group/index.d.ts +2 -0
- package/dist/components/combo-box/combo-box-root.js +9 -2
- package/dist/components/combo-box/combo-box.types.d.ts +3 -0
- package/dist/components/combo-box/index.d.ts +3 -0
- package/dist/components/date-field/index.d.ts +1 -0
- package/dist/components/date-input-group/date-input-group-root.js +2 -0
- package/dist/components/date-input-group/date-input-group-segment.js +1 -1
- package/dist/components/date-input-group/date-input-group.types.d.ts +2 -0
- package/dist/{composables → components/date-input-group}/use-date-segment.d.ts +2 -2
- package/dist/{composables → components/date-input-group}/use-date-segment.js +9 -9
- package/dist/components/date-range-picker/date-range-picker-root.js +1 -1
- package/dist/components/date-range-picker/index.d.ts +15 -14
- package/dist/{composables → components/date-range-picker}/use-date-range-picker.d.ts +7 -7
- package/dist/{composables → components/date-range-picker}/use-date-range-picker.js +10 -10
- package/dist/components/drawer/drawer-dialog.js +1 -1
- package/dist/{composables → components/drawer}/use-drawer-drag.d.ts +1 -1
- package/dist/{composables → components/drawer}/use-drawer-drag.js +1 -1
- package/dist/components/dropdown/dropdown-menu.js +1 -0
- package/dist/components/dropdown/dropdown-submenu-trigger.js +1 -1
- package/dist/components/dropdown/index.d.ts +4 -2
- package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.js +1 -1
- package/dist/components/icons/icon-overlay-arrow.js +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/input-root.js +3 -0
- package/dist/components/input/input.types.d.ts +5 -0
- package/dist/components/input-group/index.d.ts +2 -0
- package/dist/components/input-group/input-group-root.js +3 -0
- package/dist/components/input-group/input-group.types.d.ts +5 -0
- package/dist/components/input-otp/index.d.ts +6 -4
- package/dist/components/link/index.d.ts +2 -0
- package/dist/components/link/link-root.js +22 -5
- package/dist/components/link/link.types.d.ts +14 -3
- package/dist/{composables → components/list-box}/drag-collection.d.ts +2 -2
- package/dist/{composables → components/list-box}/drag-collection.js +1 -1
- package/dist/components/list-box/index.d.ts +4 -0
- package/dist/components/list-box/list-box-root.js +53 -32
- package/dist/components/list-box/list-box.types.d.ts +2 -0
- package/dist/components/menu/index.d.ts +2 -0
- package/dist/components/menu/menu-root.js +5 -1
- package/dist/components/menu/menu.types.d.ts +3 -0
- package/dist/components/number-field/index.d.ts +3 -0
- package/dist/components/number-field/number-field-root.js +2 -0
- package/dist/components/number-field/number-field.types.d.ts +2 -0
- package/dist/components/overlay/overlay-arrow.js +64 -11
- package/dist/components/radio/radio-content.js +1 -1
- package/dist/components/range-calendar/index.d.ts +18 -15
- package/dist/components/router-provider/index.d.ts +5 -0
- package/dist/components/router-provider/index.js +3 -0
- package/dist/components/router-provider/router-provider.context.d.ts +40 -0
- package/dist/components/router-provider/router-provider.context.js +16 -0
- package/dist/components/router-provider/router-provider.d.ts +21 -0
- package/dist/components/router-provider/router-provider.js +22 -0
- package/dist/components/router-provider/router-provider.types.d.ts +23 -0
- package/dist/components/scroll-shadow/use-scroll-shadow.js +25 -1
- package/dist/components/search-field/index.d.ts +2 -0
- package/dist/components/search-field/search-field-root.js +3 -1
- package/dist/components/search-field/search-field.types.d.ts +2 -0
- package/dist/{composables → components/search-field}/use-search-field.d.ts +2 -2
- package/dist/{composables → components/search-field}/use-search-field.js +3 -3
- package/dist/components/segmented-control/index.d.ts +134 -0
- package/dist/components/segmented-control/index.js +15 -0
- package/dist/components/segmented-control/segmented-control-indicator.d.ts +19 -0
- package/dist/components/segmented-control/segmented-control-indicator.js +36 -0
- package/dist/components/segmented-control/segmented-control-item.d.ts +21 -0
- package/dist/components/segmented-control/segmented-control-item.js +96 -0
- package/dist/components/segmented-control/segmented-control-root.d.ts +25 -0
- package/dist/components/segmented-control/segmented-control-root.js +112 -0
- package/dist/components/segmented-control/segmented-control-separator.d.ts +19 -0
- package/dist/components/segmented-control/segmented-control-separator.js +18 -0
- package/dist/components/segmented-control/segmented-control.context.d.ts +17 -0
- package/dist/components/segmented-control/segmented-control.context.js +12 -0
- package/dist/components/segmented-control/segmented-control.types.d.ts +67 -0
- package/dist/components/select/index.d.ts +3 -0
- package/dist/components/select/select-hidden-select.js +1 -1
- package/dist/components/select/select-root.js +2 -0
- package/dist/components/select/select.types.d.ts +2 -0
- package/dist/components/sidebar/index.d.ts +544 -0
- package/dist/components/sidebar/index.js +47 -0
- package/dist/components/sidebar/sidebar-collapsible-trigger.d.ts +21 -0
- package/dist/components/sidebar/sidebar-collapsible-trigger.js +70 -0
- package/dist/components/sidebar/sidebar-collapsible.d.ts +24 -0
- package/dist/components/sidebar/sidebar-collapsible.js +78 -0
- package/dist/components/sidebar/sidebar-content.d.ts +21 -0
- package/dist/components/sidebar/sidebar-content.js +37 -0
- package/dist/components/sidebar/sidebar-footer.d.ts +21 -0
- package/dist/components/sidebar/sidebar-footer.js +24 -0
- package/dist/components/sidebar/sidebar-group-label.d.ts +21 -0
- package/dist/components/sidebar/sidebar-group-label.js +32 -0
- package/dist/components/sidebar/sidebar-group.d.ts +21 -0
- package/dist/components/sidebar/sidebar-group.js +48 -0
- package/dist/components/sidebar/sidebar-header.d.ts +21 -0
- package/dist/components/sidebar/sidebar-header.js +24 -0
- package/dist/components/sidebar/sidebar-inset.d.ts +21 -0
- package/dist/components/sidebar/sidebar-inset.js +24 -0
- package/dist/components/sidebar/sidebar-item-icon.d.ts +21 -0
- package/dist/components/sidebar/sidebar-item-icon.js +20 -0
- package/dist/components/sidebar/sidebar-item-indicator.d.ts +21 -0
- package/dist/components/sidebar/sidebar-item-indicator.js +48 -0
- package/dist/components/sidebar/sidebar-item-label.d.ts +21 -0
- package/dist/components/sidebar/sidebar-item-label.js +28 -0
- package/dist/components/sidebar/sidebar-item-tooltip.d.ts +21 -0
- package/dist/components/sidebar/sidebar-item-tooltip.js +38 -0
- package/dist/components/sidebar/sidebar-item-trailing.d.ts +21 -0
- package/dist/components/sidebar/sidebar-item-trailing.js +28 -0
- package/dist/components/sidebar/sidebar-item.d.ts +23 -0
- package/dist/components/sidebar/sidebar-item.js +160 -0
- package/dist/components/sidebar/sidebar-panel.d.ts +21 -0
- package/dist/components/sidebar/sidebar-panel.js +88 -0
- package/dist/components/sidebar/sidebar-rail.d.ts +21 -0
- package/dist/components/sidebar/sidebar-rail.js +210 -0
- package/dist/components/sidebar/sidebar-root.d.ts +31 -0
- package/dist/components/sidebar/sidebar-root.js +138 -0
- package/dist/components/sidebar/sidebar-separator.d.ts +19 -0
- package/dist/components/sidebar/sidebar-separator.js +16 -0
- package/dist/components/sidebar/sidebar-sub-menu.d.ts +23 -0
- package/dist/components/sidebar/sidebar-sub-menu.js +40 -0
- package/dist/components/sidebar/sidebar-trigger.d.ts +21 -0
- package/dist/components/sidebar/sidebar-trigger.js +69 -0
- package/dist/components/sidebar/sidebar.context.d.ts +75 -0
- package/dist/components/sidebar/sidebar.context.js +30 -0
- package/dist/components/sidebar/sidebar.state.d.ts +73 -0
- package/dist/components/sidebar/sidebar.state.js +76 -0
- package/dist/components/sidebar/sidebar.storage.d.ts +8 -0
- package/dist/components/sidebar/sidebar.storage.js +45 -0
- package/dist/components/sidebar/sidebar.types.d.ts +259 -0
- package/dist/components/splitter/index.d.ts +143 -0
- package/dist/components/splitter/index.js +13 -0
- package/dist/components/splitter/splitter-handle.d.ts +21 -0
- package/dist/components/splitter/splitter-handle.js +209 -0
- package/dist/components/splitter/splitter-panel.d.ts +21 -0
- package/dist/components/splitter/splitter-panel.js +70 -0
- package/dist/components/splitter/splitter-root.d.ts +31 -0
- package/dist/components/splitter/splitter-root.js +152 -0
- package/dist/components/splitter/splitter.context.d.ts +15 -0
- package/dist/components/splitter/splitter.context.js +5 -0
- package/dist/components/splitter/splitter.state.d.ts +87 -0
- package/dist/components/splitter/splitter.state.js +322 -0
- package/dist/components/splitter/splitter.storage.d.ts +15 -0
- package/dist/components/splitter/splitter.storage.js +56 -0
- package/dist/components/splitter/splitter.types.d.ts +98 -0
- package/dist/components/switch/switch-content.js +1 -1
- package/dist/components/table/index.d.ts +10 -9
- package/dist/components/table/table-body.js +13 -13
- package/dist/components/table/table-cell.js +49 -46
- package/dist/components/table/table-column-resizer.js +1 -1
- package/dist/components/table/table-column.js +1 -1
- package/dist/components/table/table-content.js +37 -18
- package/dist/{composables → components/table}/table-drag-collection.d.ts +1 -1
- package/dist/{composables → components/table}/table-drag-collection.js +1 -1
- package/dist/components/table/table-drop-indicator.js +3 -1
- package/dist/components/table/table-resizable-container.d.ts +4 -5
- package/dist/components/table/table-row.js +45 -14
- package/dist/components/table/table-virtualizer-item.d.ts +2 -7
- package/dist/components/table/table-virtualizer-item.js +20 -9
- package/dist/components/table/table.context.d.ts +11 -3
- package/dist/components/table/table.types.d.ts +1 -1
- package/dist/{composables → components/table}/use-grid-keyboard.d.ts +3 -3
- package/dist/{composables → components/table}/use-grid-keyboard.js +57 -55
- package/dist/{composables → components/table}/use-grid-selection-announcement.d.ts +2 -2
- package/dist/{composables → components/table}/use-grid-selection-announcement.js +2 -2
- package/dist/{composables → components/table}/use-table-collection.d.ts +2 -2
- package/dist/{composables → components/table}/use-table-collection.js +4 -4
- package/dist/{composables → components/table}/use-table-column-layout.d.ts +13 -20
- package/dist/components/table/use-table-column-layout.js +115 -0
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/textarea-root.js +3 -0
- package/dist/components/textarea/textarea.types.d.ts +5 -0
- package/dist/components/textfield/index.d.ts +2 -0
- package/dist/components/textfield/textfield-root.js +5 -1
- package/dist/components/textfield/textfield.context.d.ts +2 -0
- package/dist/components/textfield/textfield.types.d.ts +5 -0
- package/dist/components/time-field/index.d.ts +1 -0
- package/dist/components/toast/index.d.ts +1 -0
- package/dist/components/toast/toast-default-content.js +1 -1
- package/dist/components/toolbar/index.d.ts +3 -2
- package/dist/components/tooltip/index.js +1 -1
- package/dist/components/tooltip/tooltip-root.js +1 -1
- package/dist/components/virtualizer/index.d.ts +3 -1
- package/dist/components/virtualizer/index.js +2 -1
- package/dist/components/virtualizer/use-virtualizer-item.d.ts +23 -0
- package/dist/components/virtualizer/use-virtualizer-item.js +45 -0
- package/dist/components/virtualizer/virtualizer-item.js +7 -41
- package/dist/components/virtualizer/virtualizer-scrollbar-track.d.ts +20 -0
- package/dist/components/virtualizer/virtualizer-scrollbar-track.js +141 -0
- package/dist/components/virtualizer/virtualizer-scrollbar.d.ts +18 -0
- package/dist/components/virtualizer/virtualizer-scrollbar.js +55 -0
- package/dist/components/virtualizer/virtualizer.context.d.ts +17 -1
- package/dist/composables/drag-manager.js +1 -13
- package/dist/composables/index.d.ts +3 -71
- package/dist/composables/use-collection.d.ts +22 -3
- package/dist/composables/use-collection.js +67 -14
- package/dist/composables/use-color-area.js +2 -2
- package/dist/composables/use-color-field.js +1 -1
- package/dist/composables/use-combo-box.js +1 -1
- package/dist/composables/use-date-field.js +1 -1
- package/dist/composables/use-drag-and-drop.js +1 -1
- package/dist/composables/use-drag.d.ts +6 -5
- package/dist/composables/use-draggable-item.d.ts +2 -2
- package/dist/composables/use-droppable-collection.js +2 -2
- package/dist/composables/use-droppable-item.js +1 -1
- package/dist/composables/use-form-reset.d.ts +6 -2
- package/dist/composables/use-form-reset.js +9 -5
- package/dist/composables/use-list-keyboard.js +64 -62
- package/dist/composables/use-number-field.js +1 -1
- package/dist/composables/use-select.js +2 -2
- package/dist/composables/use-selection-manager.js +4 -4
- package/dist/composables/use-single-select-list-state.d.ts +37 -0
- package/dist/composables/use-single-select-list-state.js +110 -0
- package/dist/composables/use-slider-thumb.js +2 -2
- package/dist/composables/use-slider.js +1 -1
- package/dist/composables/use-tab-list-state.d.ts +10 -29
- package/dist/composables/use-tab-list-state.js +10 -99
- package/dist/composables/use-text-field.js +1 -1
- package/dist/composables/use-virtualizer-scroll.d.ts +52 -2
- package/dist/composables/use-virtualizer-scroll.js +79 -13
- package/dist/composables/use-virtualizer.d.ts +24 -4
- package/dist/composables/use-virtualizer.js +40 -6
- package/dist/index.js +52 -80
- package/dist/utils/document-order.d.ts +14 -0
- package/dist/utils/document-order.js +17 -0
- package/dist/utils/flex-sizing.d.ts +41 -0
- package/dist/utils/flex-sizing.js +140 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/open-link.d.ts +31 -0
- package/dist/utils/open-link.js +32 -0
- package/dist/utils/story-truncation.d.ts +20 -0
- package/dist/utils/story-users.d.ts +11 -0
- package/dist/utils/virtualizer-collection.d.ts +14 -4
- package/dist/utils/virtualizer-collection.js +9 -14
- package/dist/utils/virtualizer-layout-info.js +3 -2
- package/dist/utils/virtualizer-list-layout.d.ts +75 -29
- package/dist/utils/virtualizer-list-layout.js +182 -99
- package/dist/utils/virtualizer-offsets.d.ts +111 -0
- package/dist/utils/virtualizer-offsets.js +159 -0
- package/dist/utils/virtualizer-overscan.d.ts +9 -15
- package/dist/utils/virtualizer-overscan.js +14 -33
- package/dist/utils/virtualizer-table-layout.d.ts +50 -21
- package/dist/utils/virtualizer-table-layout.js +130 -89
- package/dist/version.js +1 -1
- package/package.json +19 -3
- package/dist/composables/use-table-column-layout.js +0 -214
- /package/dist/{composables → components/dropdown}/use-safely-mouse-to-submenu.d.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { useMove } from "./use-move.js";
|
|
1
2
|
import { useFormReset } from "./use-form-reset.js";
|
|
2
3
|
import { setFormValue } from "../utils/form-value.js";
|
|
3
4
|
import { clamp } from "../utils/number.js";
|
|
4
|
-
import { useMove } from "./use-move.js";
|
|
5
5
|
import { computed, nextTick, onScopeDispose, toValue, watch, watchEffect } from "vue";
|
|
6
6
|
//#region src/composables/use-slider-thumb.ts
|
|
7
7
|
/**
|
|
@@ -110,7 +110,7 @@ var useSliderThumb = (options) => {
|
|
|
110
110
|
nextTick(() => {
|
|
111
111
|
setFormValue(inputEl.value, String(state.getThumbValue(index.value)));
|
|
112
112
|
});
|
|
113
|
-
});
|
|
113
|
+
}, options.form);
|
|
114
114
|
watch([inputEl, () => state.getThumbValue(index.value)], ([input, value]) => setFormValue(input, String(value)), {
|
|
115
115
|
flush: "post",
|
|
116
116
|
immediate: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setInteractionModality } from "./use-interaction-states.js";
|
|
2
|
-
import { clamp } from "../utils/number.js";
|
|
3
2
|
import { useMove } from "./use-move.js";
|
|
3
|
+
import { clamp } from "../utils/number.js";
|
|
4
4
|
import { computed, onScopeDispose, toValue } from "vue";
|
|
5
5
|
//#region src/composables/use-slider.ts
|
|
6
6
|
/**
|
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
import type { CollectionKey
|
|
2
|
-
import type {
|
|
1
|
+
import type { CollectionKey } from "./use-collection.js";
|
|
2
|
+
import type { UseSingleSelectListStateOptions, UseSingleSelectListStateReturn } from "./use-single-select-list-state.js";
|
|
3
3
|
import type { ComputedRef, MaybeRefOrGetter } from "vue";
|
|
4
|
-
export interface UseTabListStateOptions {
|
|
5
|
-
/** The selected tab, when the caller drives it. */
|
|
6
|
-
selectedKey?: MaybeRefOrGetter<CollectionKey | undefined>;
|
|
7
|
-
/** The initially selected tab. Falls back to the first tab that is not disabled. */
|
|
8
|
-
defaultSelectedKey?: CollectionKey;
|
|
9
|
-
onSelectionChange?: (key: CollectionKey) => void;
|
|
10
|
-
disabledKeys?: MaybeRefOrGetter<Iterable<CollectionKey> | undefined>;
|
|
11
|
-
/** Whether the whole tab list is disabled: a selection that exists but cannot be changed. */
|
|
12
|
-
isDisabled?: MaybeRefOrGetter<boolean | undefined>;
|
|
4
|
+
export interface UseTabListStateOptions extends UseSingleSelectListStateOptions {
|
|
13
5
|
/** A base the tab and panel ids are derived from. Generated when absent. */
|
|
14
6
|
id?: MaybeRefOrGetter<string | undefined>;
|
|
15
7
|
}
|
|
16
|
-
export interface UseTabListStateReturn {
|
|
17
|
-
collection: UseCollectionReturn;
|
|
18
|
-
selection: UseSelectionManagerReturn;
|
|
19
|
-
/** The selected tab, with the default resolved against the collection. */
|
|
20
|
-
selectedKey: ComputedRef<CollectionKey | null>;
|
|
21
|
-
setSelectedKey: (key: CollectionKey) => void;
|
|
22
|
-
isDisabled: ComputedRef<boolean>;
|
|
8
|
+
export interface UseTabListStateReturn extends UseSingleSelectListStateReturn {
|
|
23
9
|
/** The tab list's own id, which the tab and panel ids hang off. */
|
|
24
10
|
tabsId: ComputedRef<string>;
|
|
25
11
|
/** `undefined` for a key that does not exist, so no attribute points at nothing. */
|
|
@@ -39,17 +25,12 @@ export interface UseTabListStateReturn {
|
|
|
39
25
|
/**
|
|
40
26
|
* Which tab is selected, and the ids that tie each tab to its panel.
|
|
41
27
|
*
|
|
42
|
-
* Ported from React Stately's `useTabListState`
|
|
43
|
-
*
|
|
44
|
-
*
|
|
28
|
+
* Ported from React Stately's `useTabListState` (`react-stately/src/tabs/useTabListState.ts`,
|
|
29
|
+
* react-stately 3.49.0), together with the id scheme from `react-aria/src/tabs/utils.ts`
|
|
30
|
+
* (react-aria 3.51.0).
|
|
45
31
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* caller through `selectOnFocus`, not a question about what a press means.
|
|
50
|
-
*
|
|
51
|
-
* The three pieces have to be built in one order — the collection, then the fallback that reads
|
|
52
|
-
* it, then the selection manager the fallback feeds — which is the reason this is a composable
|
|
53
|
-
* rather than a few lines in the root component.
|
|
32
|
+
* The selection itself is {@link useSingleSelectListState}, the layer React Stately builds this
|
|
33
|
+
* one on. What is left here is what makes a tab list a tab list rather than any other list with
|
|
34
|
+
* one selection: the ids, and the panels they point at.
|
|
54
35
|
*/
|
|
55
36
|
export declare const useTabListState: (options?: UseTabListStateOptions) => UseTabListStateReturn;
|
|
@@ -1,113 +1,27 @@
|
|
|
1
1
|
import { useId as useId$1 } from "./use-id.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useSelectionManager } from "./use-selection-manager.js";
|
|
5
|
-
import { computed, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
import { useSingleSelectListState } from "./use-single-select-list-state.js";
|
|
3
|
+
import { shallowRef, toValue } from "vue";
|
|
6
4
|
//#region src/composables/use-tab-list-state.ts
|
|
7
5
|
/**
|
|
8
6
|
* Which tab is selected, and the ids that tie each tab to its panel.
|
|
9
7
|
*
|
|
10
|
-
* Ported from React Stately's `useTabListState`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Ported from React Stately's `useTabListState` (`react-stately/src/tabs/useTabListState.ts`,
|
|
9
|
+
* react-stately 3.49.0), together with the id scheme from `react-aria/src/tabs/utils.ts`
|
|
10
|
+
* (react-aria 3.51.0).
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* caller through `selectOnFocus`, not a question about what a press means.
|
|
18
|
-
*
|
|
19
|
-
* The three pieces have to be built in one order — the collection, then the fallback that reads
|
|
20
|
-
* it, then the selection manager the fallback feeds — which is the reason this is a composable
|
|
21
|
-
* rather than a few lines in the root component.
|
|
12
|
+
* The selection itself is {@link useSingleSelectListState}, the layer React Stately builds this
|
|
13
|
+
* one on. What is left here is what makes a tab list a tab list rather than any other list with
|
|
14
|
+
* one selection: the ids, and the panels they point at.
|
|
22
15
|
*/
|
|
23
16
|
var useTabListState = (options = {}) => {
|
|
24
|
-
const
|
|
25
|
-
const disabledKeySet = computed(() => new Set(toValue(options.disabledKeys) ?? []));
|
|
26
|
-
/**
|
|
27
|
-
* Whether a tab can be selected, asked of the collection rather than of the selection manager.
|
|
28
|
-
*
|
|
29
|
-
* Two reasons, and either alone would be enough: the fallback below runs before the manager
|
|
30
|
-
* exists, and React Stately asks the same question the same way — an item's own disabled state
|
|
31
|
-
* or the group's disabled keys, with no `disabledBehavior` in it.
|
|
32
|
-
*/
|
|
33
|
-
const isKeyDisabled = (key) => disabledKeySet.value.has(key) || Boolean(collection.getItem(key)?.isDisabled());
|
|
34
|
-
/** The first tab that can be selected, or the first tab when none of them can. */
|
|
35
|
-
const findDefaultSelectedKey = () => {
|
|
36
|
-
let key = collection.getFirstKey();
|
|
37
|
-
while (key != null && isKeyDisabled(key) && key !== collection.getLastKey()) key = collection.getKeyAfter(key);
|
|
38
|
-
if (key != null && isKeyDisabled(key) && key === collection.getLastKey()) key = collection.getFirstKey();
|
|
39
|
-
return key;
|
|
40
|
-
};
|
|
41
|
-
const stored = useControllableState({
|
|
42
|
-
defaultValue: options.defaultSelectedKey ?? null,
|
|
43
|
-
onValueChange: (key) => {
|
|
44
|
-
if (key != null) options.onSelectionChange?.(key);
|
|
45
|
-
},
|
|
46
|
-
value: () => toValue(options.selectedKey)
|
|
47
|
-
});
|
|
48
|
-
/**
|
|
49
|
-
* The selected tab, with the fallback resolved as a read rather than written back.
|
|
50
|
-
*
|
|
51
|
-
* React Stately can resolve it before the first render, because the collection is built by a
|
|
52
|
-
* pass that runs before it. Rendering is what creates the collection here, so writing the
|
|
53
|
-
* fallback into the selection would report a selection change nobody made, on every mount of
|
|
54
|
-
* every uncontrolled tab list.
|
|
55
|
-
*
|
|
56
|
-
* `collection.size` is read for its own sake: the fallback walks the collection through
|
|
57
|
-
* calls that stay outside reactivity on purpose, so without it this would resolve against the
|
|
58
|
-
* empty collection of the first render and keep that answer.
|
|
59
|
-
*/
|
|
60
|
-
const selectedKey = computed(() => {
|
|
61
|
-
const key = stored.state.value;
|
|
62
|
-
if (stored.isControlled.value) return key ?? null;
|
|
63
|
-
if (key != null && collection.getItem(key)) return key;
|
|
64
|
-
return findDefaultSelectedKey();
|
|
65
|
-
});
|
|
66
|
-
const selection = useSelectionManager({
|
|
67
|
-
collection,
|
|
68
|
-
disabledKeys: () => toValue(options.disabledKeys),
|
|
69
|
-
disallowEmptySelection: true,
|
|
70
|
-
onSelectionChange: (keys) => {
|
|
71
|
-
if (keys === "all") return;
|
|
72
|
-
const [key] = [...keys];
|
|
73
|
-
if (key != null) stored.setState(key);
|
|
74
|
-
},
|
|
75
|
-
selectedKeys: () => selectedKey.value != null ? [selectedKey.value] : [],
|
|
76
|
-
selectionMode: "single"
|
|
77
|
-
});
|
|
78
|
-
let lastSelectedKey = selectedKey.value;
|
|
79
|
-
/**
|
|
80
|
-
* The selection places focus, because on a tab list it is the only thing that can.
|
|
81
|
-
*
|
|
82
|
-
* React Aria's tab list element ends up with no tab index of its own, so the roving index on
|
|
83
|
-
* the selected tab is the only way in — and with no focused key nothing inside is tabbable
|
|
84
|
-
* either, which would put the whole tab list out of the tab order.
|
|
85
|
-
*
|
|
86
|
-
* Post-flush and keyed on the collection's size, because setting a focused key the collection
|
|
87
|
-
* does not hold is refused: the first attempt runs before the tabs have registered, and the
|
|
88
|
-
* registration is what makes the retry land.
|
|
89
|
-
*/
|
|
90
|
-
watch([
|
|
91
|
-
selectedKey,
|
|
92
|
-
collection.size,
|
|
93
|
-
() => selection.focusedKey.value == null
|
|
94
|
-
], () => {
|
|
95
|
-
const key = selectedKey.value;
|
|
96
|
-
if (key == null) return;
|
|
97
|
-
if (selection.focusedKey.value == null || !selection.isFocused.value && key !== lastSelectedKey) selection.setFocusedKey(key);
|
|
98
|
-
lastSelectedKey = key;
|
|
99
|
-
}, {
|
|
100
|
-
flush: "post",
|
|
101
|
-
immediate: true
|
|
102
|
-
});
|
|
17
|
+
const list = useSingleSelectListState(options);
|
|
103
18
|
const tabsId = useId$1(() => toValue(options.id));
|
|
104
19
|
/** React Aria strips whitespace out of a string key so the id stays a single token. */
|
|
105
20
|
const idFor = (key, role) => key == null ? void 0 : `${tabsId.value}-${role}-${String(key).replace(/\s+/g, "")}`;
|
|
106
21
|
const panelKeys = shallowRef(/* @__PURE__ */ new Set());
|
|
107
22
|
return {
|
|
108
|
-
|
|
23
|
+
...list,
|
|
109
24
|
hasPanel: (key) => key != null && panelKeys.value.has(key),
|
|
110
|
-
isDisabled: computed(() => Boolean(toValue(options.isDisabled))),
|
|
111
25
|
registerPanel: (key) => {
|
|
112
26
|
panelKeys.value = new Set(panelKeys.value).add(key);
|
|
113
27
|
return () => {
|
|
@@ -116,9 +30,6 @@ var useTabListState = (options = {}) => {
|
|
|
116
30
|
panelKeys.value = next;
|
|
117
31
|
};
|
|
118
32
|
},
|
|
119
|
-
selectedKey,
|
|
120
|
-
selection,
|
|
121
|
-
setSelectedKey: (key) => stored.setState(key),
|
|
122
33
|
tabId: (key) => idFor(key, "tab"),
|
|
123
34
|
tabPanelId: (key) => idFor(key, "tabpanel"),
|
|
124
35
|
tabsId
|
|
@@ -91,7 +91,7 @@ var useTextField = (options = {}) => {
|
|
|
91
91
|
reassert();
|
|
92
92
|
};
|
|
93
93
|
const initialValue = value.value;
|
|
94
|
-
if (!options.skipFormReset) useFormReset(element, () => toValue(options.defaultValue) ?? initialValue, setState);
|
|
94
|
+
if (!options.skipFormReset) useFormReset(element, () => toValue(options.defaultValue) ?? initialValue, setState, options.form);
|
|
95
95
|
useFormValidation(element, validation, { commitOnBlur: () => toValue(options.commitOnBlur) });
|
|
96
96
|
watch(element, (control, previous) => {
|
|
97
97
|
if (control && !previous && toValue(options.autoFocus)) control.focus();
|
|
@@ -1,22 +1,72 @@
|
|
|
1
1
|
import type { Size } from "../utils/virtualizer-geometry.js";
|
|
2
2
|
import type { ComputedRef, MaybeRefOrGetter } from "vue";
|
|
3
|
-
import { Rect } from "../utils/virtualizer-geometry.js";
|
|
3
|
+
import { Point, Rect } from "../utils/virtualizer-geometry.js";
|
|
4
4
|
export interface UseVirtualizerScrollOptions {
|
|
5
5
|
/** The element that scrolls — the collection itself. */
|
|
6
6
|
element: MaybeRefOrGetter<HTMLElement | null | undefined>;
|
|
7
|
-
/** The size of everything the layout produced,
|
|
7
|
+
/** The size of everything the layout produced, which is what gives the container its scroll. */
|
|
8
8
|
contentSize: () => Size;
|
|
9
9
|
/** Whether the collection is mid-scroll, which the content wrapper turns off pointers for. */
|
|
10
10
|
isScrolling: () => boolean;
|
|
11
11
|
onVisibleRectChange: (rect: Rect) => void;
|
|
12
12
|
onSizeChange: (size: Size) => void;
|
|
13
|
+
/**
|
|
14
|
+
* How far rows measured above the window have pushed the content under it, and a way to say the
|
|
15
|
+
* shift has been applied.
|
|
16
|
+
*
|
|
17
|
+
* Rows of a height nobody declared are placed at an estimate and corrected once they are in the
|
|
18
|
+
* DOM. A correction above the window moves everything below it — so without putting the shift
|
|
19
|
+
* back into the scroll offset, the collection slides under the pointer while it is read.
|
|
20
|
+
*/
|
|
21
|
+
scrollAdjustment?: () => number;
|
|
22
|
+
takeScrollAdjustment?: () => number;
|
|
13
23
|
onScrollStart?: () => void;
|
|
14
24
|
onScrollEnd?: () => void;
|
|
15
25
|
}
|
|
26
|
+
/** Where to scroll the container to. An axis left out stays where it is. */
|
|
27
|
+
export interface ScrollToOffset {
|
|
28
|
+
left?: number;
|
|
29
|
+
top?: number;
|
|
30
|
+
}
|
|
31
|
+
/** What the container's own style says about it, as far as a scrollbar drawn for it has to know. */
|
|
32
|
+
export interface ScrollBoxInfo {
|
|
33
|
+
/** Which way the inline axis runs, which decides which edge the content starts at. */
|
|
34
|
+
direction: "ltr" | "rtl";
|
|
35
|
+
/** How far the content sits in from the container's top edge. */
|
|
36
|
+
paddingTop: number;
|
|
37
|
+
/** How far the content sits in from the container's inline start edge. */
|
|
38
|
+
paddingInlineStart: number;
|
|
39
|
+
/** Whether the container scrolls along the inline axis, rather than clipping or letting content out. */
|
|
40
|
+
scrollsX: boolean;
|
|
41
|
+
/** Whether the container scrolls along the block axis. */
|
|
42
|
+
scrollsY: boolean;
|
|
43
|
+
}
|
|
16
44
|
export interface UseVirtualizerScrollReturn {
|
|
17
45
|
/** The style for the wrapper that gives the container something to scroll. */
|
|
18
46
|
contentStyle: ComputedRef<Record<string, string | undefined>>;
|
|
19
47
|
/** Re-measures the container. Called on mount, on resize, and by tests. */
|
|
20
48
|
measure: () => void;
|
|
49
|
+
/** The container's padding and which of its axes scroll, read whenever it is measured. */
|
|
50
|
+
scrollBox: ComputedRef<ScrollBoxInfo>;
|
|
51
|
+
/**
|
|
52
|
+
* How far the container is scrolled, as the element reports it.
|
|
53
|
+
*
|
|
54
|
+
* Along the block axis the offset is clamped into the content, as the visible rectangle is.
|
|
55
|
+
* Along the inline axis it keeps its sign: a right-to-left box scrolls into negative offsets,
|
|
56
|
+
* and a scrollbar drawn for it has to know which way the content ran.
|
|
57
|
+
*/
|
|
58
|
+
scrollOffset: ComputedRef<Point>;
|
|
59
|
+
/** The container's scrollable overflow, per axis — what the offset runs up to, less the box. */
|
|
60
|
+
scrollSize: ComputedRef<Size>;
|
|
61
|
+
/**
|
|
62
|
+
* Scrolls the container from here, and moves the window in the same task.
|
|
63
|
+
*
|
|
64
|
+
* A scroll the browser makes on its own reaches this composable a frame after the offset moved,
|
|
65
|
+
* and the compositor has already drawn that frame with the rows built for the old offset. A
|
|
66
|
+
* scroll made here is read back at once, so the offset and the rows it calls for are committed
|
|
67
|
+
* by the same frame — which is what lets a scrollbar the collection draws itself never show an
|
|
68
|
+
* empty window.
|
|
69
|
+
*/
|
|
70
|
+
scrollTo: (offset: ScrollToOffset) => void;
|
|
21
71
|
}
|
|
22
72
|
export declare const useVirtualizerScroll: (options: UseVirtualizerScrollOptions) => UseVirtualizerScrollReturn;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Point, Rect, Size } from "../utils/virtualizer-geometry.js";
|
|
2
|
-
import { computed, onScopeDispose, toValue, watch } from "vue";
|
|
2
|
+
import { computed, onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
3
3
|
//#region src/composables/use-virtualizer-scroll.ts
|
|
4
4
|
/**
|
|
5
5
|
* Measuring the scroll container a virtualizer lives in, ported from React Aria's `useScrollView`.
|
|
@@ -30,8 +30,44 @@ var useVirtualizerScroll = (options) => {
|
|
|
30
30
|
viewportOffset: new Point(),
|
|
31
31
|
viewportSize: new Size()
|
|
32
32
|
};
|
|
33
|
+
const scrollOffset = shallowRef(new Point());
|
|
34
|
+
const scrollSize = shallowRef(new Size());
|
|
35
|
+
const scrollBox = shallowRef({
|
|
36
|
+
direction: "ltr",
|
|
37
|
+
paddingInlineStart: 0,
|
|
38
|
+
paddingTop: 0,
|
|
39
|
+
scrollsX: false,
|
|
40
|
+
scrollsY: false
|
|
41
|
+
});
|
|
33
42
|
let isMeasuring = false;
|
|
34
43
|
const getElement = () => toValue(options.element) ?? null;
|
|
44
|
+
/** Whether an overflow value is one the browser scrolls, rather than clips or lets out. */
|
|
45
|
+
const scrolls = (overflow) => overflow === "auto" || overflow === "scroll" || overflow === "overlay";
|
|
46
|
+
/** Reads what the container's style says about it, for the scrollbar drawn against it. */
|
|
47
|
+
const readBox = (element) => {
|
|
48
|
+
const style = getComputedStyle(element);
|
|
49
|
+
const next = {
|
|
50
|
+
direction: style.direction === "rtl" ? "rtl" : "ltr",
|
|
51
|
+
paddingInlineStart: Number.parseFloat(style.paddingInlineStart) || 0,
|
|
52
|
+
paddingTop: Number.parseFloat(style.paddingTop) || 0,
|
|
53
|
+
scrollsX: scrolls(style.overflowX),
|
|
54
|
+
scrollsY: scrolls(style.overflowY)
|
|
55
|
+
};
|
|
56
|
+
const current = scrollBox.value;
|
|
57
|
+
if (current.direction !== next.direction || current.paddingInlineStart !== next.paddingInlineStart || current.paddingTop !== next.paddingTop || current.scrollsX !== next.scrollsX || current.scrollsY !== next.scrollsY) scrollBox.value = next;
|
|
58
|
+
};
|
|
59
|
+
/** Reads where the element is scrolled to, and how far it could go. */
|
|
60
|
+
const readScroll = (element) => {
|
|
61
|
+
const scrollWidth = element.scrollWidth;
|
|
62
|
+
const scrollHeight = element.scrollHeight;
|
|
63
|
+
const left = element.scrollLeft;
|
|
64
|
+
const top = element.scrollTop;
|
|
65
|
+
state.scrollPosition = new Point(Math.max(0, Math.min(left, scrollWidth - state.size.width)), Math.max(0, Math.min(top, scrollHeight - state.size.height)));
|
|
66
|
+
if (scrollSize.value.width !== scrollWidth || scrollSize.value.height !== scrollHeight) scrollSize.value = new Size(scrollWidth, scrollHeight);
|
|
67
|
+
const inlineRange = Math.max(0, scrollWidth - state.size.width);
|
|
68
|
+
const offset = new Point(Math.sign(left) * Math.min(Math.abs(left), inlineRange), state.scrollPosition.y);
|
|
69
|
+
if (!scrollOffset.value.equals(offset)) scrollOffset.value = offset;
|
|
70
|
+
};
|
|
35
71
|
const updateVisibleRect = () => {
|
|
36
72
|
const visibleRect = new Rect(state.viewportOffset.x + state.scrollPosition.x, state.viewportOffset.y + state.scrollPosition.y, Math.max(0, Math.min(state.size.width - state.viewportOffset.x, state.viewportSize.width)), Math.max(0, Math.min(state.size.height - state.viewportOffset.y, state.viewportSize.height)));
|
|
37
73
|
if (visibleRect.area > 0 || state.lastVisibleRect.area > 0) {
|
|
@@ -51,14 +87,19 @@ var useVirtualizerScroll = (options) => {
|
|
|
51
87
|
if (state.scrollTimeout != null) clearTimeout(state.scrollTimeout);
|
|
52
88
|
state.scrollTimeout = setTimeout(endScrolling, SCROLL_END_DELAY);
|
|
53
89
|
};
|
|
90
|
+
const markScrolling = () => {
|
|
91
|
+
if (!state.isScrolling) {
|
|
92
|
+
state.isScrolling = true;
|
|
93
|
+
options.onScrollStart?.();
|
|
94
|
+
}
|
|
95
|
+
scheduleScrollEnd();
|
|
96
|
+
};
|
|
54
97
|
const onScroll = (event) => {
|
|
55
98
|
const element = getElement();
|
|
56
99
|
const target = event.target;
|
|
57
100
|
if (!element || !(target instanceof Node) || !target.contains(element)) return;
|
|
58
|
-
if (target === element)
|
|
59
|
-
|
|
60
|
-
state.scrollPosition = new Point(Math.max(0, Math.min(element.scrollLeft, contentSize.width - state.size.width)), Math.max(0, Math.min(element.scrollTop, contentSize.height - state.size.height)));
|
|
61
|
-
} else {
|
|
101
|
+
if (target === element) readScroll(element);
|
|
102
|
+
else {
|
|
62
103
|
const bounds = element.getBoundingClientRect();
|
|
63
104
|
const x = bounds.x < 0 ? -bounds.x : 0;
|
|
64
105
|
const y = bounds.y < 0 ? -bounds.y : 0;
|
|
@@ -66,11 +107,16 @@ var useVirtualizerScroll = (options) => {
|
|
|
66
107
|
state.viewportOffset = new Point(x, y);
|
|
67
108
|
}
|
|
68
109
|
updateVisibleRect();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
110
|
+
markScrolling();
|
|
111
|
+
};
|
|
112
|
+
const scrollTo = (offset) => {
|
|
113
|
+
const element = getElement();
|
|
114
|
+
if (!element) return;
|
|
115
|
+
if (offset.left != null) element.scrollLeft = offset.left;
|
|
116
|
+
if (offset.top != null) element.scrollTop = offset.top;
|
|
117
|
+
readScroll(element);
|
|
118
|
+
updateVisibleRect();
|
|
119
|
+
markScrolling();
|
|
74
120
|
};
|
|
75
121
|
/**
|
|
76
122
|
* Re-measures the container.
|
|
@@ -87,14 +133,17 @@ var useVirtualizerScroll = (options) => {
|
|
|
87
133
|
const viewportHeight = window.innerHeight;
|
|
88
134
|
const viewportChanged = state.viewportSize.width !== viewportWidth || state.viewportSize.height !== viewportHeight;
|
|
89
135
|
if (viewportChanged) state.viewportSize = new Size(viewportWidth, viewportHeight);
|
|
136
|
+
readBox(element);
|
|
90
137
|
const clientWidth = element.clientWidth;
|
|
91
138
|
const clientHeight = element.clientHeight;
|
|
92
139
|
if (state.size.width !== clientWidth || state.size.height !== clientHeight || viewportChanged) {
|
|
93
140
|
state.size = new Size(clientWidth, clientHeight);
|
|
141
|
+
readScroll(element);
|
|
94
142
|
updateVisibleRect();
|
|
95
143
|
options.onSizeChange(state.size);
|
|
96
144
|
if (element.clientWidth !== clientWidth || element.clientHeight !== clientHeight) {
|
|
97
145
|
state.size = new Size(element.clientWidth, element.clientHeight);
|
|
146
|
+
readScroll(element);
|
|
98
147
|
updateVisibleRect();
|
|
99
148
|
options.onSizeChange(state.size);
|
|
100
149
|
}
|
|
@@ -104,13 +153,16 @@ var useVirtualizerScroll = (options) => {
|
|
|
104
153
|
watch(() => getElement(), (element, _previous, onCleanup) => {
|
|
105
154
|
if (!element) return;
|
|
106
155
|
const document = element.ownerDocument;
|
|
107
|
-
document.addEventListener("scroll", onScroll,
|
|
156
|
+
document.addEventListener("scroll", onScroll, {
|
|
157
|
+
capture: true,
|
|
158
|
+
passive: true
|
|
159
|
+
});
|
|
108
160
|
window.addEventListener("resize", measure);
|
|
109
161
|
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => measure());
|
|
110
162
|
observer?.observe(element, { box: "border-box" });
|
|
111
163
|
measure();
|
|
112
164
|
onCleanup(() => {
|
|
113
|
-
document.removeEventListener("scroll", onScroll, true);
|
|
165
|
+
document.removeEventListener("scroll", onScroll, { capture: true });
|
|
114
166
|
window.removeEventListener("resize", measure);
|
|
115
167
|
observer?.disconnect();
|
|
116
168
|
});
|
|
@@ -118,6 +170,16 @@ var useVirtualizerScroll = (options) => {
|
|
|
118
170
|
flush: "post",
|
|
119
171
|
immediate: true
|
|
120
172
|
});
|
|
173
|
+
watch(() => options.contentSize(), () => {
|
|
174
|
+
const element = getElement();
|
|
175
|
+
if (element) readScroll(element);
|
|
176
|
+
}, { flush: "post" });
|
|
177
|
+
watch(() => options.scrollAdjustment?.() ?? 0, (adjustment) => {
|
|
178
|
+
const element = getElement();
|
|
179
|
+
if (!element || adjustment === 0) return;
|
|
180
|
+
options.takeScrollAdjustment?.();
|
|
181
|
+
element.scrollTop += adjustment;
|
|
182
|
+
}, { flush: "post" });
|
|
121
183
|
onScopeDispose(() => {
|
|
122
184
|
if (state.scrollTimeout != null) clearTimeout(state.scrollTimeout);
|
|
123
185
|
});
|
|
@@ -131,7 +193,11 @@ var useVirtualizerScroll = (options) => {
|
|
|
131
193
|
width: px(contentSize.width)
|
|
132
194
|
};
|
|
133
195
|
}),
|
|
134
|
-
measure
|
|
196
|
+
measure,
|
|
197
|
+
scrollBox: computed(() => scrollBox.value),
|
|
198
|
+
scrollOffset: computed(() => scrollOffset.value),
|
|
199
|
+
scrollSize: computed(() => scrollSize.value),
|
|
200
|
+
scrollTo
|
|
135
201
|
};
|
|
136
202
|
};
|
|
137
203
|
//#endregion
|
|
@@ -11,10 +11,12 @@ import { Rect, Size } from "../utils/virtualizer-geometry.js";
|
|
|
11
11
|
*
|
|
12
12
|
* Two pieces of upstream are deliberately gone:
|
|
13
13
|
*
|
|
14
|
-
* `ReusableView`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* `ReusableView` as a class. Its idea is kept — see {@link VirtualizerView.slot} — because a
|
|
15
|
+
* keyed `v-for` on its own cannot spare a window that moved wholesale: every key in it is new, so
|
|
16
|
+
* every row mounts anew, and a scrollbar dragged across a long collection lands on nothing for as
|
|
17
|
+
* long as the mounting takes. Reordering is not deferred as React defers it: the rendered set is
|
|
18
|
+
* kept in collection order at every moment, which is what a screen reader walks, and moving an
|
|
19
|
+
* element is cheap next to mounting one.
|
|
18
20
|
*
|
|
19
21
|
* The scroll anchor. Upstream calls `UNSTABLE_getScrollAnchorInfo` on the layout, but no layout
|
|
20
22
|
* implements it, so the whole tracker resolves to nothing for every collection in this build.
|
|
@@ -22,6 +24,15 @@ import { Rect, Size } from "../utils/virtualizer-geometry.js";
|
|
|
22
24
|
/** One rendered element: a layout info, what it was built from, and anything nested inside it. */
|
|
23
25
|
export interface VirtualizerView {
|
|
24
26
|
key: VirtualizerKey;
|
|
27
|
+
/**
|
|
28
|
+
* The element this view is rendered into, as a number that outlives the key.
|
|
29
|
+
*
|
|
30
|
+
* A key that leaves the window frees its slot, and the next key to arrive takes it. A collection
|
|
31
|
+
* that keys its rendered items by this rather than by `key` keeps the element and its component
|
|
32
|
+
* when the window moves and changes what they show — which is what makes a window that moved
|
|
33
|
+
* wholesale cost a screenful of updates rather than a screenful of mounts.
|
|
34
|
+
*/
|
|
35
|
+
slot: number;
|
|
25
36
|
layoutInfo: LayoutInfo;
|
|
26
37
|
parentKey: VirtualizerKey | null;
|
|
27
38
|
node?: VirtualizerNode;
|
|
@@ -48,6 +59,15 @@ export interface UseVirtualizerReturn {
|
|
|
48
59
|
endScrolling: () => void;
|
|
49
60
|
/** Records a measured size, and lays out again when it moved anything. */
|
|
50
61
|
updateItemSize: (key: VirtualizerKey, size: Size) => void;
|
|
62
|
+
/**
|
|
63
|
+
* How far the content under the window has been pushed by rows measured above it.
|
|
64
|
+
*
|
|
65
|
+
* Adding this to the scroll offset is what holds the collection still while the rows above it
|
|
66
|
+
* settle. Nothing here applies it — only whoever owns the scrolling element can.
|
|
67
|
+
*/
|
|
68
|
+
scrollAdjustment: ComputedRef<number>;
|
|
69
|
+
/** Reads the accumulated shift and clears it, so it is applied once. */
|
|
70
|
+
takeScrollAdjustment: () => number;
|
|
51
71
|
getLayoutInfo: (key: VirtualizerKey) => LayoutInfo | null;
|
|
52
72
|
isPersistedKey: (key: VirtualizerKey) => boolean;
|
|
53
73
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Rect, Size } from "../utils/virtualizer-geometry.js";
|
|
2
|
-
import {
|
|
2
|
+
import { overscannedRect } from "../utils/virtualizer-overscan.js";
|
|
3
3
|
import { computed, shallowRef } from "vue";
|
|
4
4
|
//#region src/composables/use-virtualizer.ts
|
|
5
5
|
var useVirtualizer = (options) => {
|
|
@@ -8,7 +8,7 @@ var useVirtualizer = (options) => {
|
|
|
8
8
|
const isScrolling = shallowRef(false);
|
|
9
9
|
/** Bumped when something outside the reactive inputs invalidates the layout. */
|
|
10
10
|
const revision = shallowRef(0);
|
|
11
|
-
const
|
|
11
|
+
const scrollAdjustment = shallowRef(0);
|
|
12
12
|
const persistedKeys = () => options.persistedKeys?.() ?? /* @__PURE__ */ new Set();
|
|
13
13
|
/**
|
|
14
14
|
* Whether a key, or anything containing it, must stay rendered.
|
|
@@ -91,9 +91,33 @@ var useVirtualizer = (options) => {
|
|
|
91
91
|
return {
|
|
92
92
|
contentSize: layout.getContentSize(),
|
|
93
93
|
layout,
|
|
94
|
-
layoutInfos: layout.getVisibleLayoutInfos(
|
|
94
|
+
layoutInfos: layout.getVisibleLayoutInfos(overscannedRect(rect))
|
|
95
95
|
};
|
|
96
96
|
});
|
|
97
|
+
/** Which slot each key of the last pass held. Plain state: it is history, not something derived. */
|
|
98
|
+
let slotByKey = /* @__PURE__ */ new Map();
|
|
99
|
+
let nextSlot = 0;
|
|
100
|
+
/**
|
|
101
|
+
* Hands every key in the pass a slot, keeping the one it had.
|
|
102
|
+
*
|
|
103
|
+
* The slots of the keys that left are handed out in the order their keys were rendered, to the
|
|
104
|
+
* arriving keys in the order they will be: a window that moved wholesale therefore keeps every
|
|
105
|
+
* slot in place, and a window that moved by a row moves one element.
|
|
106
|
+
*/
|
|
107
|
+
const assignSlots = (layoutInfos) => {
|
|
108
|
+
const next = /* @__PURE__ */ new Map();
|
|
109
|
+
const arriving = [];
|
|
110
|
+
for (const { key } of layoutInfos) {
|
|
111
|
+
const slot = slotByKey.get(key);
|
|
112
|
+
if (slot == null) arriving.push(key);
|
|
113
|
+
else next.set(key, slot);
|
|
114
|
+
}
|
|
115
|
+
const freed = [];
|
|
116
|
+
for (const [key, slot] of slotByKey) if (!next.has(key)) freed.push(slot);
|
|
117
|
+
for (const key of arriving) next.set(key, freed.length > 0 ? freed.shift() : nextSlot++);
|
|
118
|
+
slotByKey = next;
|
|
119
|
+
return next;
|
|
120
|
+
};
|
|
97
121
|
/**
|
|
98
122
|
* The layout infos arrive parents first, so a child always finds its parent already built. One
|
|
99
123
|
* whose parent is not in the window is treated as a root rather than dropped — losing it would
|
|
@@ -104,13 +128,15 @@ var useVirtualizer = (options) => {
|
|
|
104
128
|
const collection = options.collection();
|
|
105
129
|
const views = /* @__PURE__ */ new Map();
|
|
106
130
|
const roots = [];
|
|
131
|
+
const slots = assignSlots(layoutInfos);
|
|
107
132
|
for (const layoutInfo of layoutInfos) {
|
|
108
133
|
const view = {
|
|
109
134
|
children: [],
|
|
110
135
|
key: layoutInfo.key,
|
|
111
136
|
layoutInfo,
|
|
112
137
|
node: collection.getNode(layoutInfo.key),
|
|
113
|
-
parentKey: layoutInfo.parentKey
|
|
138
|
+
parentKey: layoutInfo.parentKey,
|
|
139
|
+
slot: slots.get(layoutInfo.key)
|
|
114
140
|
};
|
|
115
141
|
views.set(view.key, view);
|
|
116
142
|
const parent = layoutInfo.parentKey != null ? views.get(layoutInfo.parentKey) : void 0;
|
|
@@ -127,20 +153,28 @@ var useVirtualizer = (options) => {
|
|
|
127
153
|
getLayoutInfo: (key) => pass.value.layout.getLayoutInfo(key),
|
|
128
154
|
isPersistedKey,
|
|
129
155
|
isScrolling: computed(() => isScrolling.value),
|
|
156
|
+
scrollAdjustment: computed(() => scrollAdjustment.value),
|
|
130
157
|
setSize: (next) => {
|
|
131
158
|
if (!size.value.equals(next)) size.value = next;
|
|
132
159
|
},
|
|
133
160
|
setVisibleRect: (rect) => {
|
|
134
161
|
if (visibleRect.value.equals(rect)) return;
|
|
135
|
-
overscan.setVisibleRect(rect);
|
|
136
162
|
visibleRect.value = rect;
|
|
137
163
|
},
|
|
138
164
|
size: computed(() => size.value),
|
|
139
165
|
startScrolling: () => {
|
|
140
166
|
isScrolling.value = true;
|
|
141
167
|
},
|
|
168
|
+
takeScrollAdjustment: () => {
|
|
169
|
+
const pending = scrollAdjustment.value;
|
|
170
|
+
scrollAdjustment.value = 0;
|
|
171
|
+
return pending;
|
|
172
|
+
},
|
|
142
173
|
updateItemSize: (key, measured) => {
|
|
143
|
-
|
|
174
|
+
const layout = attachedLayout ?? pass.value.layout;
|
|
175
|
+
const placed = layout.getLayoutInfo(key)?.rect;
|
|
176
|
+
if (!layout.updateItemSize?.(key, measured)) return;
|
|
177
|
+
if (placed != null && placed.y < visibleRect.value.y) scrollAdjustment.value += measured.height - placed.height;
|
|
144
178
|
pendingItemSizeChange = true;
|
|
145
179
|
revision.value += 1;
|
|
146
180
|
},
|