Keep track of ICE and police locations in your city. Very much a work-in-progress and not ready yet, stay tuned I guess?
1import {
2 type RowData,
3 type TableOptions,
4 type TableOptionsResolved,
5 type TableState,
6 createTable,
7} from "@tanstack/table-core";
8
9/**
10 * Creates a reactive TanStack table object for Svelte.
11 * @param options Table options to create the table with.
12 * @returns A reactive table object.
13 * @example
14 * ```svelte
15 * <script>
16 * const table = createSvelteTable({ ... })
17 * </script>
18 *
19 * <table>
20 * <thead>
21 * {#each table.getHeaderGroups() as headerGroup}
22 * <tr>
23 * {#each headerGroup.headers as header}
24 * <th colspan={header.colSpan}>
25 * <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
26 * </th>
27 * {/each}
28 * </tr>
29 * {/each}
30 * </thead>
31 * <!-- ... -->
32 * </table>
33 * ```
34 */
35export function createSvelteTable<TData extends RowData>(options: TableOptions<TData>) {
36 const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
37 {
38 state: {},
39 onStateChange() {},
40 renderFallbackValue: null,
41 mergeOptions: (
42 defaultOptions: TableOptions<TData>,
43 options: Partial<TableOptions<TData>>
44 ) => {
45 return mergeObjects(defaultOptions, options);
46 },
47 },
48 options
49 );
50
51 const table = createTable(resolvedOptions);
52 let state = $state<Partial<TableState>>(table.initialState);
53
54 function updateOptions() {
55 table.setOptions((prev) => {
56 return mergeObjects(prev, options, {
57 state: mergeObjects(state, options.state || {}),
58
59 // eslint-disable-next-line @typescript-eslint/no-explicit-any
60 onStateChange: (updater: any) => {
61 if (updater instanceof Function) state = updater(state);
62 else state = mergeObjects(state, updater);
63
64 options.onStateChange?.(updater);
65 },
66 });
67 });
68 }
69
70 updateOptions();
71
72 $effect.pre(() => {
73 updateOptions();
74 });
75
76 return table;
77}
78
79type MaybeThunk<T extends object> = T | (() => T | null | undefined);
80type Intersection<T extends readonly unknown[]> = (T extends [infer H, ...infer R]
81 ? H & Intersection<R>
82 : unknown) & {};
83
84/**
85 * Lazily merges several objects (or thunks) while preserving
86 * getter semantics from every source.
87 *
88 * Proxy-based to avoid known WebKit recursion issue.
89 */
90// eslint-disable-next-line @typescript-eslint/no-explicit-any
91export function mergeObjects<Sources extends readonly MaybeThunk<any>[]>(
92 ...sources: Sources
93): Intersection<{ [K in keyof Sources]: Sources[K] }> {
94 const resolve = <T extends object>(src: MaybeThunk<T>): T | undefined =>
95 typeof src === "function" ? (src() ?? undefined) : src;
96
97 const findSourceWithKey = (key: PropertyKey) => {
98 for (let i = sources.length - 1; i >= 0; i--) {
99 const obj = resolve(sources[i]);
100 if (obj && key in obj) return obj;
101 }
102 return undefined;
103 };
104
105 return new Proxy(Object.create(null), {
106 get(_, key) {
107 const src = findSourceWithKey(key);
108
109 return src?.[key as never];
110 },
111
112 has(_, key) {
113 return !!findSourceWithKey(key);
114 },
115
116 ownKeys(): (string | symbol)[] {
117 // eslint-disable-next-line svelte/prefer-svelte-reactivity
118 const all = new Set<string | symbol>();
119 for (const s of sources) {
120 const obj = resolve(s);
121 if (obj) {
122 for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) {
123 all.add(k);
124 }
125 }
126 }
127 return [...all];
128 },
129
130 getOwnPropertyDescriptor(_, key) {
131 const src = findSourceWithKey(key);
132 if (!src) return undefined;
133 return {
134 configurable: true,
135 enumerable: true,
136 // eslint-disable-next-line @typescript-eslint/no-explicit-any
137 value: (src as any)[key],
138 writable: true,
139 };
140 },
141 }) as Intersection<{ [K in keyof Sources]: Sources[K] }>;
142}