chore: import upstream snapshot with attribution
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
export { createSvelteTable } from "./table.svelte.js";
|
||||
|
||||
export { createSvelteVirtualizer, type VirtualItem } from "./virtual.svelte.js";
|
||||
|
||||
export {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
getFilteredRowModel,
|
||||
type ColumnDef,
|
||||
type TableOptions,
|
||||
type Table,
|
||||
type Header,
|
||||
type Cell,
|
||||
type Row,
|
||||
type SortingState,
|
||||
type ColumnFiltersState,
|
||||
type ColumnPinningState,
|
||||
type FilterFn,
|
||||
type SortingFn,
|
||||
type CellContext,
|
||||
type HeaderContext
|
||||
} from "@tanstack/table-core";
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
createTable,
|
||||
type RowData,
|
||||
type TableOptions,
|
||||
type TableOptionsResolved,
|
||||
type TableState
|
||||
} from "@tanstack/table-core";
|
||||
|
||||
/**
|
||||
* Merges objects while preserving property getters for lazy evaluation.
|
||||
* Properties are defined as getters that look up values from sources in
|
||||
* reverse order at access time. This is critical: it means reading a
|
||||
* property from the merged result doesn't happen until the property is
|
||||
* actually accessed, not when mergeObjects is called.
|
||||
*/
|
||||
export function mergeObjects(...sources: any): any {
|
||||
const target: Record<string, any> = {};
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
let source = sources[i];
|
||||
if (typeof source === "function") source = source();
|
||||
if (source) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(source);
|
||||
for (const key in descriptors) {
|
||||
if (key in target) continue;
|
||||
Object.defineProperty(target, key, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
for (let j = sources.length - 1; j >= 0; j--) {
|
||||
let s = sources[j];
|
||||
if (typeof s === "function") s = s();
|
||||
const v = (s || {})[key];
|
||||
if (v !== undefined) return v;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reactive TanStack Table for Svelte 5.
|
||||
*
|
||||
* The reactivity works through mergeObjects' lazy getters:
|
||||
* - $effect.pre calls table.setOptions() with a merged object
|
||||
* - The merged object has lazy getters for `data`, `columns`, `state`, etc.
|
||||
* - TanStack stores this object but doesn't deep-read all properties immediately
|
||||
* - When getRowModel() is called (in $derived), TanStack reads `data` and `columns`
|
||||
* through the lazy getters, which read the reactive $state/$derived values
|
||||
* - onStateChange fires when TanStack mutates its own state → bumps version
|
||||
* - version is read by getRowModel/getHeaderGroups → $derived re-evaluates
|
||||
*/
|
||||
export function createSvelteTable<TData extends RowData>(
|
||||
options: TableOptions<TData>
|
||||
) {
|
||||
const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
|
||||
{
|
||||
state: {},
|
||||
onStateChange() {},
|
||||
renderFallbackValue: null,
|
||||
mergeOptions: (
|
||||
defaultOptions: TableOptions<TData>,
|
||||
opts: Partial<TableOptions<TData>>
|
||||
) => {
|
||||
return mergeObjects(defaultOptions, opts);
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
const table = createTable(resolvedOptions);
|
||||
let state = $state<Partial<TableState>>(table.initialState);
|
||||
let version = $state(0);
|
||||
|
||||
function updateOptions(): void {
|
||||
table.setOptions(() => {
|
||||
// Always merge from resolvedOptions instead of prev to prevent
|
||||
// unbounded getter chains. Using prev would add a new mergeObjects
|
||||
// layer on every call; properties not in the overrides would have
|
||||
// to traverse every previous layer, causing stack overflow.
|
||||
// TanStack's setOptions already re-applies feature defaults via
|
||||
// mergeOptions, so we don't lose any internal defaults.
|
||||
return mergeObjects(resolvedOptions, options, {
|
||||
state: mergeObjects(state, options.state || {}),
|
||||
onStateChange: (updater: any) => {
|
||||
if (updater instanceof Function) state = updater(state);
|
||||
else state = mergeObjects(state, updater);
|
||||
version += 1;
|
||||
options.onStateChange?.(updater);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initial sync
|
||||
updateOptions();
|
||||
|
||||
// Re-sync when options change. Because mergeObjects uses lazy getters,
|
||||
// this effect's tracked dependencies are only the properties that
|
||||
// table.setOptions() eagerly reads (which is minimal — mostly just
|
||||
// checking if the options object reference changed).
|
||||
$effect.pre(() => {
|
||||
updateOptions();
|
||||
});
|
||||
|
||||
return {
|
||||
getRowModel: () => {
|
||||
void version;
|
||||
return table.getRowModel();
|
||||
},
|
||||
getHeaderGroups: () => {
|
||||
void version;
|
||||
return table.getHeaderGroups();
|
||||
},
|
||||
getColumn: (id: string) => {
|
||||
void version;
|
||||
return table.getColumn(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
Virtualizer,
|
||||
elementScroll,
|
||||
observeElementOffset,
|
||||
observeElementRect,
|
||||
type PartialKeys,
|
||||
type VirtualizerOptions
|
||||
} from "@tanstack/virtual-core";
|
||||
import { untrack } from "svelte";
|
||||
|
||||
export type { VirtualItem } from "@tanstack/virtual-core";
|
||||
|
||||
/**
|
||||
* Creates a reactive TanStack Virtualizer for Svelte 5.
|
||||
*
|
||||
* Returns a getter function that returns the virtualizer instance.
|
||||
* Call the getter inside $derived or template expressions to create
|
||||
* reactive dependencies on the virtualizer state.
|
||||
*/
|
||||
export function createSvelteVirtualizer<
|
||||
TScrollElement extends Element,
|
||||
TItemElement extends Element
|
||||
>(
|
||||
options: PartialKeys<
|
||||
VirtualizerOptions<TScrollElement, TItemElement>,
|
||||
"observeElementRect" | "observeElementOffset" | "scrollToFn"
|
||||
>
|
||||
): {
|
||||
instance: Virtualizer<TScrollElement, TItemElement>;
|
||||
virtualItems: () => ReturnType<
|
||||
Virtualizer<TScrollElement, TItemElement>["getVirtualItems"]
|
||||
>;
|
||||
totalSize: () => number;
|
||||
} {
|
||||
let version = $state(0);
|
||||
|
||||
const virtualizer = new Virtualizer<TScrollElement, TItemElement>({
|
||||
observeElementRect: observeElementRect,
|
||||
observeElementOffset: observeElementOffset,
|
||||
scrollToFn: elementScroll,
|
||||
...options,
|
||||
onChange: (instance, sync) => {
|
||||
if (sync) {
|
||||
version += 1;
|
||||
} else {
|
||||
queueMicrotask(() => {
|
||||
version += 1;
|
||||
});
|
||||
}
|
||||
options.onChange?.(instance, sync);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const cleanup = virtualizer._didMount();
|
||||
untrack(() => {
|
||||
version += 1;
|
||||
});
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
let prev_count = 0;
|
||||
|
||||
$effect(() => {
|
||||
const current_count = options.count;
|
||||
virtualizer.setOptions({
|
||||
observeElementRect: observeElementRect,
|
||||
observeElementOffset: observeElementOffset,
|
||||
scrollToFn: elementScroll,
|
||||
...options,
|
||||
onChange: (instance, sync) => {
|
||||
if (sync) {
|
||||
version += 1;
|
||||
} else {
|
||||
queueMicrotask(() => {
|
||||
version += 1;
|
||||
});
|
||||
}
|
||||
options.onChange?.(instance, sync);
|
||||
}
|
||||
});
|
||||
|
||||
if (prev_count === 0 && current_count > 0) {
|
||||
virtualizer.measure();
|
||||
}
|
||||
prev_count = current_count;
|
||||
});
|
||||
|
||||
$effect.pre(() => {
|
||||
void version;
|
||||
virtualizer._willUpdate();
|
||||
});
|
||||
|
||||
return {
|
||||
instance: virtualizer,
|
||||
virtualItems: () => {
|
||||
void version;
|
||||
return virtualizer.getVirtualItems();
|
||||
},
|
||||
totalSize: () => {
|
||||
void version;
|
||||
return virtualizer.getTotalSize();
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user