virtua 0.35.1 → 0.36.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.
@@ -1,20 +1,37 @@
1
- <script lang="ts">
2
- import { afterUpdate, onDestroy } from "svelte";
1
+ <script lang="ts" generics="T">
2
+ import { type Snippet, onDestroy } from "svelte";
3
3
  import { isRTLDocument, type ItemResizeObserver } from "./core";
4
4
  import { styleToString } from "./utils";
5
+ import type { SvelteHTMLElements } from "svelte/elements";
5
6
 
6
- export let index: number;
7
- export let offset: number;
8
- export let hide: boolean;
9
- export let horizontal: boolean;
10
- export let resizer: ItemResizeObserver;
7
+ interface Props {
8
+ children: Snippet<[item: T, index: number]>;
9
+ item: T;
10
+ as: keyof SvelteHTMLElements | undefined;
11
+ index: number;
12
+ offset: number;
13
+ hide: boolean;
14
+ horizontal: boolean;
15
+ resizer: ItemResizeObserver;
16
+ }
17
+
18
+ let {
19
+ children,
20
+ item,
21
+ as = "div",
22
+ index,
23
+ offset,
24
+ hide,
25
+ horizontal,
26
+ resizer,
27
+ }: Props = $props();
11
28
 
12
29
  let elementRef: HTMLDivElement;
13
30
 
14
31
  let cleanupResizer: (() => void) | undefined;
15
32
  // The index may be changed if elements are inserted to or removed from the start of props.children
16
33
  let prevIndex: number | undefined;
17
- afterUpdate(() => {
34
+ $effect(() => {
18
35
  if (prevIndex === index) return;
19
36
  if (cleanupResizer) cleanupResizer();
20
37
  cleanupResizer = resizer(elementRef, (prevIndex = index));
@@ -23,8 +40,7 @@
23
40
  if (cleanupResizer) cleanupResizer();
24
41
  });
25
42
 
26
- let style: string;
27
- $: {
43
+ let style: string = $derived.by(() => {
28
44
  const _style: Record<string, string> = {
29
45
  position: "absolute",
30
46
  [horizontal ? "height" : "width"]: "100%",
@@ -37,10 +53,10 @@
37
53
  _style["display"] = "flex";
38
54
  }
39
55
 
40
- style = styleToString(_style);
41
- }
56
+ return styleToString(_style);
57
+ });
42
58
  </script>
43
59
 
44
- <div bind:this={elementRef} {style}>
45
- <slot />
46
- </div>
60
+ <svelte:element this={as} bind:this={elementRef} {style}>
61
+ {@render children(item, index)}
62
+ </svelte:element>
@@ -1,25 +1,29 @@
1
- import { SvelteComponent } from "svelte";
1
+ import { type Snippet } from "svelte";
2
2
  import { type ItemResizeObserver } from "./core";
3
- declare const __propDef: {
4
- props: {
3
+ import type { SvelteHTMLElements } from "svelte/elements";
4
+ declare class __sveltets_Render<T> {
5
+ props(): {
6
+ children: Snippet<[item: T, index: number]>;
7
+ item: T;
8
+ as: keyof SvelteHTMLElements | undefined;
5
9
  index: number;
6
10
  offset: number;
7
11
  hide: boolean;
8
12
  horizontal: boolean;
9
13
  resizer: ItemResizeObserver;
10
14
  };
11
- events: {
12
- [evt: string]: CustomEvent<any>;
13
- };
14
- slots: {
15
- default: {};
16
- };
17
- exports?: {} | undefined;
18
- bindings?: string | undefined;
19
- };
20
- export type ListItemProps = typeof __propDef.props;
21
- export type ListItemEvents = typeof __propDef.events;
22
- export type ListItemSlots = typeof __propDef.slots;
23
- export default class ListItem extends SvelteComponent<ListItemProps, ListItemEvents, ListItemSlots> {
15
+ events(): {};
16
+ slots(): {};
17
+ bindings(): "";
18
+ exports(): {};
19
+ }
20
+ interface $$IsomorphicComponent {
21
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
22
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
23
+ } & ReturnType<__sveltets_Render<T>['exports']>;
24
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
25
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
24
26
  }
25
- export {};
27
+ declare const ListItem: $$IsomorphicComponent;
28
+ type ListItem<T> = InstanceType<typeof ListItem<T>>;
29
+ export default ListItem;
@@ -1,197 +1,44 @@
1
1
  <script lang="ts" generics="T">
2
- import { onMount, onDestroy, createEventDispatcher } from "svelte";
3
- import type { HTMLAttributes } from "svelte/elements";
4
- import {
5
- SCROLL_IDLE,
6
- type StateVersion,
7
- getOverscanedRange,
8
- CHANGE_ITEM_LENGTH,
9
- createVirtualizer,
10
- FIX_SCROLL_JUMP,
11
- GET_ITEM_OFFSET,
12
- GET_JUMP_COUNT,
13
- GET_RANGE,
14
- GET_SCROLL_DIRECTION,
15
- GET_SCROLL_OFFSET,
16
- GET_SCROLL_SIZE,
17
- GET_TOTAL_SIZE,
18
- GET_VIEWPORT_SIZE,
19
- IS_ITEM_HIDDEN,
20
- OBSERVE_ITEM_RESIZE,
21
- ON_MOUNT,
22
- ON_UN_MOUNT,
23
- SCROLL_BY,
24
- SCROLL_TO,
25
- SCROLL_TO_INDEX,
26
- } from "./core";
27
- import {
28
- onUpdate,
29
- type UnwrapCustomEvents,
30
- defaultGetKey,
31
- styleToString,
32
- } from "./utils";
33
- import ListItem from "./ListItem.svelte";
2
+ import { styleToString } from "./utils";
3
+ import Virtualizer from "./Virtualizer.svelte";
4
+ import type { VListProps, VListHandle } from "./VList.type";
34
5
 
35
- // https://github.com/sveltejs/rfcs/pull/38
36
- interface $$Slots {
37
- default: { item: T; index: number };
38
- }
39
- interface $$Props extends HTMLAttributes<HTMLDivElement> {
40
- /**
41
- * The data items rendered by this component.
42
- */
43
- data: T[];
44
- /**
45
- * Function that returns the key of an item in the list. It's recommended to specify whenever possible for performance.
46
- * @default defaultGetKey (returns index of item)
47
- */
48
- getKey?: (data: T, index: number) => string | number;
49
- /**
50
- * Number of items to render above/below the visible bounds of the list. You can increase to avoid showing blank items in fast scrolling.
51
- * @defaultValue 4
52
- */
53
- overscan?: number;
54
- /**
55
- * Item size hint for unmeasured items. It will help to reduce scroll jump when items are measured if used properly.
56
- *
57
- * - If not set, initial item sizes will be automatically estimated from measured sizes. This is recommended for most cases.
58
- * - If set, you can opt out estimation and use the value as initial item size.
59
- */
60
- itemSize?: number;
61
- /**
62
- * While true is set, scroll position will be maintained from the end not usual start when items are added to/removed from start. It's recommended to set false if you add to/remove from mid/end of the list because it can cause unexpected behavior. This prop is useful for reverse infinite scrolling.
63
- */
64
- shift?: boolean;
65
- /**
66
- * If true, rendered as a horizontally scrollable list. Otherwise rendered as a vertically scrollable list.
67
- */
68
- horizontal?: boolean;
69
- }
6
+ interface Props extends VListProps<T> {}
70
7
 
71
- // https://github.com/kitschpatrol/svelte-tweakpane-ui/blob/248fd7c24926af1e485a0676b6a8c053177e92db/src/lib/control/Button.svelte#L50-L51
72
- interface $$Events {
73
- /**
74
- * Callback invoked whenever scroll offset changes.
75
- * @param offset Current scrollTop or scrollLeft.
76
- */
77
- scroll: CustomEvent<number>;
78
- /**
79
- * Callback invoked when scrolling stops.
80
- */
81
- scrollEnd: CustomEvent<void>;
82
- /**
83
- * Callback invoked when visible items range changes.
84
- */
85
- rangeChange: CustomEvent<[start: number, end: number]>;
86
- }
87
-
88
- export let data: $$Props["data"];
89
- export let getKey: NonNullable<$$Props["getKey"]> = defaultGetKey;
90
- export let overscan: NonNullable<$$Props["overscan"]> = 4;
91
- export let itemSize: $$Props["itemSize"] = undefined;
92
- export let shift: NonNullable<$$Props["shift"]> = false;
93
- export let horizontal: NonNullable<$$Props["horizontal"]> = false;
94
-
95
- let containerRef: HTMLDivElement;
96
-
97
- let rerender: StateVersion = [];
98
-
99
- const dispatchEvent = createEventDispatcher<UnwrapCustomEvents<$$Events>>();
100
-
101
- const virtualizer = createVirtualizer(
102
- data.length,
8
+ let {
9
+ data,
10
+ getKey,
11
+ overscan,
103
12
  itemSize,
13
+ shift,
104
14
  horizontal,
105
- (v) => {
106
- rerender = v;
107
- },
108
- (offset) => {
109
- dispatchEvent("scroll", offset);
110
- },
111
- () => {
112
- dispatchEvent("scrollEnd");
113
- }
114
- );
115
-
116
- $: range = rerender && virtualizer[GET_RANGE]();
117
- $: scrollDirection = rerender && virtualizer[GET_SCROLL_DIRECTION]();
118
- $: totalSize = rerender && virtualizer[GET_TOTAL_SIZE]();
119
- $: jumpCount = rerender && virtualizer[GET_JUMP_COUNT]();
120
- $: extendedRange = getOverscanedRange(
121
- range[0],
122
- range[1],
123
- overscan,
124
- scrollDirection,
125
- data.length
126
- );
127
-
128
- onMount(() => {
129
- const root = containerRef.parentElement!;
130
- virtualizer[ON_MOUNT](root);
131
- });
132
- onDestroy(() => {
133
- virtualizer[ON_UN_MOUNT]();
134
- });
135
-
136
- let prevLength = data.length;
137
- onUpdate(() => {
138
- if (prevLength === data.length) return;
139
- virtualizer[CHANGE_ITEM_LENGTH]((prevLength = data.length), shift);
140
- }, true);
141
-
142
- let prevJumpCount: number | undefined;
143
- onUpdate(() => {
144
- if (prevJumpCount === jumpCount) return;
145
- prevJumpCount = jumpCount;
146
- virtualizer[FIX_SCROLL_JUMP]();
147
- });
148
-
149
- let prevRange: typeof range | undefined;
150
- onUpdate(() => {
151
- if (prevRange && prevRange[0] === range[0] && prevRange[1] === range[1])
152
- return;
153
- prevRange = range;
154
- dispatchEvent("rangeChange", [range[0], range[1]]);
155
- });
156
-
157
- /**
158
- * Get current scrollTop or scrollLeft.
159
- */
160
- export const getScrollOffset = virtualizer[GET_SCROLL_OFFSET];
161
- /**
162
- * Get current scrollHeight or scrollWidth.
163
- */
164
- export const getScrollSize = virtualizer[GET_SCROLL_SIZE];
165
- /**
166
- * Get current offsetHeight or offsetWidth.
167
- */
168
- export const getViewportSize = virtualizer[GET_VIEWPORT_SIZE];
169
- /**
170
- * Scroll to the item specified by index.
171
- * @param index index of item
172
- * @param opts options
173
- */
174
- export const scrollToIndex = virtualizer[SCROLL_TO_INDEX];
175
- /**
176
- * Scroll to the given offset.
177
- * @param offset offset from start
178
- */
179
- export const scrollTo = virtualizer[SCROLL_TO];
180
- /**
181
- * Scroll by the given offset.
182
- * @param offset offset from current position
183
- */
184
- export const scrollBy = virtualizer[SCROLL_BY];
185
-
186
- let items: T[];
187
- $: {
188
- const [startIndex, endIndex] = extendedRange;
189
- const newItems: T[] = [];
190
- for (let i = startIndex, j = endIndex; i <= j; i++) {
191
- newItems.push(data[i]!);
192
- }
193
- items = newItems;
194
- }
15
+ children,
16
+ onscroll,
17
+ onscrollend,
18
+ onrangechange,
19
+ ...rest
20
+ }: Props = $props();
21
+
22
+ let ref: Virtualizer<T> = $state()!;
23
+
24
+ export const getScrollOffset = (() =>
25
+ ref.getScrollOffset()) satisfies VListHandle["getScrollOffset"] as VListHandle["getScrollOffset"];
26
+ export const getScrollSize = (() =>
27
+ ref.getScrollSize()) satisfies VListHandle["getScrollSize"] as VListHandle["getScrollSize"];
28
+ export const getViewportSize = (() =>
29
+ ref.getViewportSize()) satisfies VListHandle["getViewportSize"] as VListHandle["getViewportSize"];
30
+ export const scrollToIndex = ((...args) =>
31
+ ref.scrollToIndex(
32
+ ...args
33
+ )) satisfies VListHandle["scrollToIndex"] as VListHandle["scrollToIndex"];
34
+ export const scrollTo = ((...args) =>
35
+ ref.scrollTo(
36
+ ...args
37
+ )) satisfies VListHandle["scrollTo"] as VListHandle["scrollTo"];
38
+ export const scrollBy = ((...args) =>
39
+ ref.scrollBy(
40
+ ...args
41
+ )) satisfies VListHandle["scrollBy"] as VListHandle["scrollBy"];
195
42
 
196
43
  const viewportStyle = styleToString({
197
44
  display: horizontal ? "inline-block" : "block",
@@ -200,42 +47,24 @@
200
47
  width: "100%",
201
48
  height: "100%",
202
49
  });
203
-
204
- const containerStyle = styleToString({
205
- // contain: "content",
206
- "overflow-anchor": "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
207
- flex: "none", // flex style can break layout
208
- position: "relative",
209
- visibility: "hidden", // TODO replace with other optimization methods
210
- });
211
-
212
- $: dynamicContainerStyle = styleToString({
213
- width: horizontal ? totalSize + "px" : "100%",
214
- height: horizontal ? "100%" : totalSize + "px",
215
- "pointer-events": scrollDirection !== SCROLL_IDLE ? "none" : undefined,
216
- });
217
50
  </script>
218
51
 
219
52
  <!--
220
53
  @component
221
- Virtualized list component.
54
+ Virtualized list component. See {@link VListProps} and {@link VListHandle}.
222
55
  -->
223
- <div {...$$restProps} style={`${viewportStyle} ${$$restProps["style"] || ""}`}>
224
- <div
225
- bind:this={containerRef}
226
- style={`${containerStyle} ${dynamicContainerStyle}`}
227
- >
228
- {#each items as item, i (getKey(item, i + extendedRange[0]))}
229
- {@const index = i + extendedRange[0]}
230
- <ListItem
231
- {index}
232
- offset={rerender && virtualizer[GET_ITEM_OFFSET](index)}
233
- hide={rerender && virtualizer[IS_ITEM_HIDDEN](index)}
234
- {horizontal}
235
- resizer={virtualizer[OBSERVE_ITEM_RESIZE]}
236
- >
237
- <slot {item} {index} />
238
- </ListItem>
239
- {/each}
240
- </div>
56
+ <div {...rest} style={`${viewportStyle} ${rest["style"] || ""}`}>
57
+ <Virtualizer
58
+ bind:this={ref}
59
+ {data}
60
+ {children}
61
+ {getKey}
62
+ {overscan}
63
+ {itemSize}
64
+ {shift}
65
+ {horizontal}
66
+ {onscroll}
67
+ {onscrollend}
68
+ {onrangechange}
69
+ />
241
70
  </div>
@@ -1,92 +1,26 @@
1
- import { SvelteComponent } from "svelte";
2
- import type { HTMLAttributes } from "svelte/elements";
1
+ import type { VListProps } from "./VList.type";
3
2
  declare class __sveltets_Render<T> {
4
- props(): {
5
- /**
6
- * Get current scrollTop or scrollLeft.
7
- */ getScrollOffset?: () => number;
8
- /**
9
- * Get current scrollHeight or scrollWidth.
10
- */ getScrollSize?: () => number;
11
- /**
12
- * Get current offsetHeight or offsetWidth.
13
- */ getViewportSize?: () => number;
14
- /**
15
- * Scroll to the item specified by index.
16
- * @param index index of item
17
- * @param opts options
18
- */ scrollToIndex?: (index: number, opts?: import("../core/types").ScrollToIndexOpts) => void;
19
- /**
20
- * Scroll to the given offset.
21
- * @param offset offset from start
22
- */ scrollTo?: (offset: number) => void;
23
- /**
24
- * Scroll by the given offset.
25
- * @param offset offset from current position
26
- */ scrollBy?: (offset: number) => void;
27
- } & HTMLAttributes<HTMLDivElement> & {
28
- /**
29
- * The data items rendered by this component.
30
- */
31
- data: T[];
32
- /**
33
- * Function that returns the key of an item in the list. It's recommended to specify whenever possible for performance.
34
- * @default defaultGetKey (returns index of item)
35
- */
36
- getKey?: ((data: T, index: number) => string | number) | undefined;
37
- /**
38
- * Number of items to render above/below the visible bounds of the list. You can increase to avoid showing blank items in fast scrolling.
39
- * @defaultValue 4
40
- */
41
- overscan?: number;
42
- /**
43
- * Item size hint for unmeasured items. It will help to reduce scroll jump when items are measured if used properly.
44
- *
45
- * - If not set, initial item sizes will be automatically estimated from measured sizes. This is recommended for most cases.
46
- * - If set, you can opt out estimation and use the value as initial item size.
47
- */
48
- itemSize?: number;
49
- /**
50
- * While true is set, scroll position will be maintained from the end not usual start when items are added to/removed from start. It's recommended to set false if you add to/remove from mid/end of the list because it can cause unexpected behavior. This prop is useful for reverse infinite scrolling.
51
- */
52
- shift?: boolean;
53
- /**
54
- * If true, rendered as a horizontally scrollable list. Otherwise rendered as a vertically scrollable list.
55
- */
56
- horizontal?: boolean;
57
- };
58
- events(): {
59
- /**
60
- * Callback invoked whenever scroll offset changes.
61
- * @param offset Current scrollTop or scrollLeft.
62
- */
63
- scroll: CustomEvent<number>;
64
- /**
65
- * Callback invoked when scrolling stops.
66
- */
67
- scrollEnd: CustomEvent<void>;
68
- /**
69
- * Callback invoked when visible items range changes.
70
- */
71
- rangeChange: CustomEvent<[start: number, end: number]>;
72
- };
73
- slots(): {
74
- default: {
75
- item: T;
76
- index: number;
77
- };
3
+ props(): VListProps<T>;
4
+ events(): {};
5
+ slots(): {};
6
+ bindings(): "";
7
+ exports(): {
8
+ getScrollOffset: () => number;
9
+ getScrollSize: () => number;
10
+ getViewportSize: () => number;
11
+ scrollToIndex: (index: number, opts?: import("../core/types").ScrollToIndexOpts) => void;
12
+ scrollTo: (offset: number) => void;
13
+ scrollBy: (offset: number) => void;
78
14
  };
79
15
  }
80
- export type VListProps<T> = ReturnType<__sveltets_Render<T>['props']>;
81
- export type VListEvents<T> = ReturnType<__sveltets_Render<T>['events']>;
82
- export type VListSlots<T> = ReturnType<__sveltets_Render<T>['slots']>;
83
- /** Virtualized list component. */
84
- export default class VList<T> extends SvelteComponent<VListProps<T>, VListEvents<T>, VListSlots<T>> {
85
- get getScrollOffset(): () => number;
86
- get getScrollSize(): () => number;
87
- get getViewportSize(): () => number;
88
- get scrollToIndex(): (index: number, opts?: import("../core/types").ScrollToIndexOpts) => void;
89
- get scrollTo(): (offset: number) => void;
90
- get scrollBy(): (offset: number) => void;
16
+ interface $$IsomorphicComponent {
17
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
18
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
19
+ } & ReturnType<__sveltets_Render<T>['exports']>;
20
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
21
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
91
22
  }
92
- export {};
23
+ /** Virtualized list component. See {@link VListProps} and {@link VListHandle}. */
24
+ declare const VList: $$IsomorphicComponent;
25
+ type VList<T> = InstanceType<typeof VList<T>>;
26
+ export default VList;
@@ -0,0 +1,13 @@
1
+ // @ts-nocheck
2
+ import type { ViewportComponentAttributes } from "./types";
3
+ import type { VirtualizerHandle, VirtualizerProps } from "./Virtualizer.type";
4
+ /**
5
+ * Props of {@link VList}.
6
+ */
7
+ export interface VListProps<T> extends Pick<VirtualizerProps<T>, "data" | "getKey" | "overscan" | "itemSize" | "shift" | "horizontal" | "children" | "onscroll" | "onscrollend" | "onrangechange">, ViewportComponentAttributes {
8
+ }
9
+ /**
10
+ * Methods of {@link VList}.
11
+ */
12
+ export interface VListHandle extends VirtualizerHandle {
13
+ }
@@ -0,0 +1 @@
1
+ export {};