diff --git a/src/svelte/ListItem.svelte b/src/svelte/ListItem.svelte
index 666933a7..487a1716 100644
--- a/src/svelte/ListItem.svelte
+++ b/src/svelte/ListItem.svelte
@@ -2,10 +2,12 @@
import { type Snippet, onDestroy } from "svelte";
import { isRTLDocument, type ItemResizeObserver } from "./core";
import { styleToString } from "./utils";
+ import type { SvelteHTMLElements } from "svelte/elements";
interface Props {
children: Snippet<[{ item: T; index: number }]>;
item: T;
+ as: keyof SvelteHTMLElements | undefined;
index: number;
offset: number;
hide: boolean;
@@ -13,8 +15,16 @@
resizer: ItemResizeObserver;
}
- let { children, item, index, offset, hide, horizontal, resizer }: Props =
- $props();
+ let {
+ children,
+ item,
+ as = "div",
+ index,
+ offset,
+ hide,
+ horizontal,
+ resizer,
+ }: Props = $props();
let elementRef: HTMLDivElement;
@@ -47,6 +57,6 @@
});
-
+
{@render children({ item, index })}
-
+
diff --git a/src/svelte/VList.svelte b/src/svelte/VList.svelte
index 2e9aab22..5227040f 100644
--- a/src/svelte/VList.svelte
+++ b/src/svelte/VList.svelte
@@ -1,97 +1,32 @@
-
- {#each items as item, i (getKey(item, i + extendedRange[0]))}
- {@const index = i + extendedRange[0]}
-
- {/each}
-
+
diff --git a/src/svelte/Virtualizer.svelte b/src/svelte/Virtualizer.svelte
new file mode 100644
index 00000000..9a8cc41b
--- /dev/null
+++ b/src/svelte/Virtualizer.svelte
@@ -0,0 +1,276 @@
+
+
+
+
+ {#each items as item, i (getKey(item, i + extendedRange[0]))}
+ {@const index = i + extendedRange[0]}
+
+ {/each}
+
diff --git a/src/svelte/core.ts b/src/svelte/core.ts
index 0ddfbdd4..99b9a45c 100644
--- a/src/svelte/core.ts
+++ b/src/svelte/core.ts
@@ -7,6 +7,7 @@ import {
getScrollSize as _getScrollSize,
type StateVersion,
getScrollSize,
+ ACTION_START_OFFSET_CHANGE,
} from "../core/store";
import { createResizer } from "../core/resizer";
import { createScroller } from "../core/scroller";
@@ -29,13 +30,16 @@ export const GET_SCROLL_DIRECTION = 6;
export const GET_JUMP_COUNT = 7;
export const GET_ITEM_OFFSET = 8;
export const IS_ITEM_HIDDEN = 9;
-export const OBSERVE_ITEM_RESIZE = 10;
-export const FIX_SCROLL_JUMP = 11;
-export const CHANGE_ITEM_LENGTH = 12;
-export const GET_SCROLL_SIZE = 13;
-export const SCROLL_TO = 14;
-export const SCROLL_BY = 15;
-export const SCROLL_TO_INDEX = 16;
+export const GET_ITEMS_LENGTH = 10;
+export const GET_START_SPACER_SIZE = 11;
+export const OBSERVE_ITEM_RESIZE = 12;
+export const FIX_SCROLL_JUMP = 13;
+export const CHANGE_ITEM_LENGTH = 14;
+export const CHANGE_START_MARGIN = 15;
+export const GET_SCROLL_SIZE = 16;
+export const SCROLL_TO = 17;
+export const SCROLL_BY = 18;
+export const SCROLL_TO_INDEX = 19;
/**
* This function is workaround for terser minification.
@@ -92,11 +96,16 @@ export const createVirtualizer = (
[GET_JUMP_COUNT]: store._getJumpCount,
[GET_ITEM_OFFSET]: store._getItemOffset,
[IS_ITEM_HIDDEN]: store._isUnmeasuredItem,
+ [GET_ITEMS_LENGTH]: store._getItemsLength,
+ [GET_START_SPACER_SIZE]: store._getStartSpacerSize,
[OBSERVE_ITEM_RESIZE]: resizer._observeItem,
[FIX_SCROLL_JUMP]: scroller._fixScrollJump,
[CHANGE_ITEM_LENGTH]: (len: number, shift?: boolean) => {
store._update(ACTION_ITEMS_LENGTH_CHANGE, [len, shift]);
},
+ [CHANGE_START_MARGIN]: (value: number) => {
+ store._update(ACTION_START_OFFSET_CHANGE, value);
+ },
[GET_SCROLL_SIZE]: () => getScrollSize(store),
[SCROLL_TO]: scroller._scrollTo,
[SCROLL_BY]: scroller._scrollBy,
diff --git a/src/svelte/index.ts b/src/svelte/index.ts
index 8ca4aa37..142c8f8e 100644
--- a/src/svelte/index.ts
+++ b/src/svelte/index.ts
@@ -2,3 +2,4 @@
* @module svelte
*/
export { default as VList } from "./VList.svelte";
+export { default as Virtualizer } from "./Virtualizer.svelte";
diff --git a/stories/svelte/HeaderAndFooter.svelte b/stories/svelte/HeaderAndFooter.svelte
new file mode 100644
index 00000000..087b2ea7
--- /dev/null
+++ b/stories/svelte/HeaderAndFooter.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+ header
+
+
i} startMargin={headerHeight}>
+ {#snippet children({ item, index })}
+
+ {index}
+
+ {/snippet}
+
+
footer
+
diff --git a/stories/svelte/Nested.svelte b/stories/svelte/Nested.svelte
new file mode 100644
index 00000000..4848b247
--- /dev/null
+++ b/stories/svelte/Nested.svelte
@@ -0,0 +1,45 @@
+
+
+
+
+
+
i}
+ {scrollRef}
+ startMargin={outerPadding + innerPadding}
+ >
+ {#snippet children({ item, index })}
+
+ {index}
+
+ {/snippet}
+
+
+
+
diff --git a/stories/svelte/TableElement.svelte b/stories/svelte/TableElement.svelte
new file mode 100644
index 00000000..254bc5e6
--- /dev/null
+++ b/stories/svelte/TableElement.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ {#each COLUMN_WIDTHS as width, index}
+ Header{index} |
+ {/each}
+
+
+ i}
+ {scrollRef}
+ as="tbody"
+ item="tr"
+ startMargin={headerHeight}
+ >
+ {#snippet children({ item })}
+ {#each COLUMN_WIDTHS as width, index}
+ {item} {index} |
+ {/each}
+ {/snippet}
+
+
+
diff --git a/stories/svelte/Virtualizer.stories.tsx b/stories/svelte/Virtualizer.stories.tsx
new file mode 100644
index 00000000..74980652
--- /dev/null
+++ b/stories/svelte/Virtualizer.stories.tsx
@@ -0,0 +1,27 @@
+import type { Meta, StoryObj } from "@storybook/svelte";
+import { Virtualizer } from "../../src/svelte";
+import HeaderAndFooterComponent from "./HeaderAndFooter.svelte";
+import NestedComponent from "./Nested.svelte";
+import TableComponent from "./TableElement.svelte";
+
+export default {
+ component: Virtualizer,
+} satisfies Meta;
+
+export const HeaderAndFooter: StoryObj = {
+ render: () => ({
+ Component: HeaderAndFooterComponent,
+ }),
+};
+
+export const Nested: StoryObj = {
+ render: () => ({
+ Component: NestedComponent,
+ }),
+};
+
+export const TableElement: StoryObj = {
+ render: () => ({
+ Component: TableComponent,
+ }),
+};