141 lines
3.5 KiB
TypeScript
141 lines
3.5 KiB
TypeScript
import {
|
|
type RowData,
|
|
type TableOptions,
|
|
type TableOptionsResolved,
|
|
type TableState,
|
|
createTable,
|
|
} from "@tanstack/table-core";
|
|
|
|
/**
|
|
* Creates a reactive TanStack table object for Svelte.
|
|
* @param options Table options to create the table with.
|
|
* @returns A reactive table object.
|
|
* @example
|
|
* ```svelte
|
|
* <script>
|
|
* const table = createSvelteTable({ ... })
|
|
* </script>
|
|
*
|
|
* <table>
|
|
* <thead>
|
|
* {#each table.getHeaderGroups() as headerGroup}
|
|
* <tr>
|
|
* {#each headerGroup.headers as header}
|
|
* <th colspan={header.colSpan}>
|
|
* <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
|
|
* </th>
|
|
* {/each}
|
|
* </tr>
|
|
* {/each}
|
|
* </thead>
|
|
* <!-- ... -->
|
|
* </table>
|
|
* ```
|
|
*/
|
|
export function createSvelteTable<TData extends RowData>(options: TableOptions<TData>) {
|
|
const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
|
|
{
|
|
state: {},
|
|
onStateChange() {},
|
|
renderFallbackValue: null,
|
|
mergeOptions: (
|
|
defaultOptions: TableOptions<TData>,
|
|
options: Partial<TableOptions<TData>>
|
|
) => {
|
|
return mergeObjects(defaultOptions, options);
|
|
},
|
|
},
|
|
options
|
|
);
|
|
|
|
const table = createTable(resolvedOptions);
|
|
let state = $state<Partial<TableState>>(table.initialState);
|
|
|
|
function updateOptions() {
|
|
table.setOptions((prev) => {
|
|
return mergeObjects(prev, options, {
|
|
state: mergeObjects(state, options.state || {}),
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
onStateChange: (updater: any) => {
|
|
if (updater instanceof Function) state = updater(state);
|
|
else state = mergeObjects(state, updater);
|
|
|
|
options.onStateChange?.(updater);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
updateOptions();
|
|
|
|
$effect.pre(() => {
|
|
updateOptions();
|
|
});
|
|
|
|
return table;
|
|
}
|
|
|
|
type MaybeThunk<T extends object> = T | (() => T | null | undefined);
|
|
type Intersection<T extends readonly unknown[]> = (T extends [infer H, ...infer R]
|
|
? H & Intersection<R>
|
|
: unknown) & {};
|
|
|
|
/**
|
|
* Lazily merges several objects (or thunks) while preserving
|
|
* getter semantics from every source.
|
|
*
|
|
* Proxy-based to avoid known WebKit recursion issue.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export function mergeObjects<Sources extends readonly MaybeThunk<any>[]>(
|
|
...sources: Sources
|
|
): Intersection<{ [K in keyof Sources]: Sources[K] }> {
|
|
const resolve = <T extends object>(src: MaybeThunk<T>): T | undefined =>
|
|
typeof src === "function" ? (src() ?? undefined) : src;
|
|
|
|
const findSourceWithKey = (key: PropertyKey) => {
|
|
for (let i = sources.length - 1; i >= 0; i--) {
|
|
const obj = resolve(sources[i]);
|
|
if (obj && key in obj) return obj;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
return new Proxy(Object.create(null), {
|
|
get(_, key) {
|
|
const src = findSourceWithKey(key);
|
|
|
|
return src?.[key as never];
|
|
},
|
|
|
|
has(_, key) {
|
|
return !!findSourceWithKey(key);
|
|
},
|
|
|
|
ownKeys(): (string | symbol)[] {
|
|
const all = new Set<string | symbol>();
|
|
for (const s of sources) {
|
|
const obj = resolve(s);
|
|
if (obj) {
|
|
for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) {
|
|
all.add(k);
|
|
}
|
|
}
|
|
}
|
|
return [...all];
|
|
},
|
|
|
|
getOwnPropertyDescriptor(_, key) {
|
|
const src = findSourceWithKey(key);
|
|
if (!src) return undefined;
|
|
return {
|
|
configurable: true,
|
|
enumerable: true,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
value: (src as any)[key],
|
|
writable: true,
|
|
};
|
|
},
|
|
}) as Intersection<{ [K in keyof Sources]: Sources[K] }>;
|
|
}
|