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} + + {/each} + + + i} + {scrollRef} + as="tbody" + item="tr" + startMargin={headerHeight} + > + {#snippet children({ item })} + {#each COLUMN_WIDTHS as width, index} + + {/each} + {/snippet} + +
Header{index}
{item} {index}
+
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, + }), +};