mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 14:23:36 -06:00
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import {
|
|
type RowData,
|
|
type TableOptions,
|
|
type TableOptionsResolved,
|
|
type TableState,
|
|
createTable,
|
|
} from "@tanstack/table-core";
|
|
|
|
/**
|
|
* Creates a reactive TanStack table for Svelte 5 using runes.
|
|
*
|
|
* Adapted from shadcn-svelte's data-table wrapper — uses `$state` and
|
|
* `$effect.pre` instead of Svelte stores for reactivity.
|
|
*/
|
|
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 = { ...state, ...(updater as Partial<TableState>) };
|
|
|
|
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.
|
|
*/
|
|
// 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] }>;
|
|
}
|