@urbicon-ui/sveltekit-utils 6.50.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/table-query.d.ts +52 -0
- package/dist/table-query.js +59 -0
- package/dist/url.svelte.d.ts +51 -15
- package/dist/url.svelte.js +62 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ updateUrlSearchParams({ page: '1', tag: ['a', 'b'] }, { replaceState: true });
|
|
|
59
59
|
|
|
60
60
|
## Table Query ↔ URL (`table-query` + `url.svelte`)
|
|
61
61
|
|
|
62
|
-
Opt-in URL sync for `@urbicon-ui/table
|
|
62
|
+
Opt-in URL sync for `@urbicon-ui/table`, in client mode as well as `mode="server"`: the `TableQuery` the table emits (search, sort, page, page size, filters, grouping) is mirrored onto query parameters (`?q=…&sort=…&page=…`), so the view state survives reloads, can be shared as a link, and — unlike `localStorage` — is visible to the server.
|
|
63
63
|
|
|
64
64
|
```svelte
|
|
65
65
|
<script lang="ts">
|
|
@@ -74,12 +74,7 @@ Opt-in URL sync for `@urbicon-ui/table` in `mode="server"`: the `TableQuery` the
|
|
|
74
74
|
mode="server"
|
|
75
75
|
{columns}
|
|
76
76
|
itemsPerPage={25}
|
|
77
|
-
|
|
78
|
-
initialGroupBy={sync.initialQuery.groupByKey}
|
|
79
|
-
initialSort={sync.initialQuery.sortColumn
|
|
80
|
-
? { column: sync.initialQuery.sortColumn, direction: sync.initialQuery.sortDirection }
|
|
81
|
-
: undefined}
|
|
82
|
-
initialFilters={sync.initialQuery.activeFilters}
|
|
77
|
+
query={sync.viewState}
|
|
83
78
|
queryFn={async (query, { signal }) => {
|
|
84
79
|
sync.syncQuery(query); // mirror the query onto the URL (replaceState)
|
|
85
80
|
const res = await fetch(`/api/users?${tableQueryToSearchParams(query)}`, { signal });
|
|
@@ -89,6 +84,12 @@ Opt-in URL sync for `@urbicon-ui/table` in `mode="server"`: the `TableQuery` the
|
|
|
89
84
|
/>
|
|
90
85
|
```
|
|
91
86
|
|
|
87
|
+
`viewState` carries **only the axes the URL actually names**, and the table's `query` prop reads it by field presence: a present field controls that axis, an absent one leaves persistence and the `initial*` seeds alone. That is why this is not `initialQuery` — that one describes every axis, including the ones it filled in from the defaults, so a table wired to it ignores `persistenceConfig`, `initialSort`, `initialFilters` and `initialGroupBy` on every URL. `initialQuery` stays what its name says: a complete snapshot, parsed once, to seed a fetch with.
|
|
88
|
+
|
|
89
|
+
Because `viewState` re-reads the URL rather than capturing it, the browser's back button works: navigating back to a URL without `?sort` returns the table to its unsorted view.
|
|
90
|
+
|
|
91
|
+
A controlled axis is not written to `localStorage` by default, so a visit that arrives without query params starts clean. For a business table where "my filters are still there tomorrow" is expected, pair the sync with `persistenceConfig={{ tableId: '…', persistControlled: true }}` — the table then stores what the reader themselves changed (never what a shared link brought), and hands it back on a bare visit. Reading order is unaffected: the URL still wins.
|
|
92
|
+
|
|
92
93
|
With manual control (`onQueryChange` instead of `queryFn`), pass `sync.syncQuery` directly — note that `onQueryChange` does not fire when `queryFn` is set, which is why the managed variant calls it inside `queryFn`.
|
|
93
94
|
|
|
94
95
|
The pure serializers live under `@urbicon-ui/sveltekit-utils/table-query` and work without SvelteKit — e.g. to parse the initial query in a server `load` and fetch the first page during SSR:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './cron';
|
|
2
|
-
export * from './sse';
|
|
3
|
-
export * from './table-query';
|
|
4
|
-
export * from './url.svelte';
|
|
1
|
+
export * from './cron.js';
|
|
2
|
+
export * from './sse.js';
|
|
3
|
+
export * from './table-query.js';
|
|
4
|
+
export * from './url.svelte.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './cron';
|
|
2
|
-
export * from './sse';
|
|
3
|
-
export * from './table-query';
|
|
4
|
-
export * from './url.svelte';
|
|
1
|
+
export * from './cron.js';
|
|
2
|
+
export * from './sse.js';
|
|
3
|
+
export * from './table-query.js';
|
|
4
|
+
export * from './url.svelte.js';
|
package/dist/table-query.d.ts
CHANGED
|
@@ -59,6 +59,31 @@ export interface TableQueryParams {
|
|
|
59
59
|
/** Column ID for grouping, or null if ungrouped. */
|
|
60
60
|
groupByKey: string | null;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The partial half of {@link TableQueryParams} — structural mirror of
|
|
64
|
+
* `TableViewState` from `@urbicon-ui/table`, the type its `query` prop takes.
|
|
65
|
+
*
|
|
66
|
+
* Every field optional, and that is the whole contract: **presence means
|
|
67
|
+
* controlled**. A consumer may hand over the sort and leave paging to the
|
|
68
|
+
* table, so "absent" has to stay distinguishable from "set to its default
|
|
69
|
+
* value" — which is exactly what {@link TableQueryParams} cannot express.
|
|
70
|
+
*/
|
|
71
|
+
export interface TableQueryViewState {
|
|
72
|
+
/** Current page (1-based). */
|
|
73
|
+
page?: number;
|
|
74
|
+
/** Number of items per page. */
|
|
75
|
+
itemsPerPage?: number;
|
|
76
|
+
/** Column ID to sort by, or empty string for no sort. */
|
|
77
|
+
sortColumn?: string;
|
|
78
|
+
/** Sort direction. */
|
|
79
|
+
sortDirection?: TableQuerySortDirection;
|
|
80
|
+
/** Full-text search term. */
|
|
81
|
+
searchTerm?: string;
|
|
82
|
+
/** Active column filters. */
|
|
83
|
+
activeFilters?: TableQueryFilter[];
|
|
84
|
+
/** Column ID for grouping, or null for ungrouped. */
|
|
85
|
+
groupByKey?: string | null;
|
|
86
|
+
}
|
|
62
87
|
/**
|
|
63
88
|
* Baseline used for default elision: query values equal to these defaults are
|
|
64
89
|
* omitted from the URL, and missing params parse back to them.
|
|
@@ -136,6 +161,33 @@ export declare function tableQueryToSearchParams(query: TableQueryParams, option
|
|
|
136
161
|
* @returns Complete query object (assignable to `TableQuery`).
|
|
137
162
|
*/
|
|
138
163
|
export declare function searchParamsToTableQuery(params: URLSearchParams, options?: TableQueryUrlOptions): TableQueryParams;
|
|
164
|
+
/**
|
|
165
|
+
* Parse `URLSearchParams` into the **partial** view state a table's `query`
|
|
166
|
+
* prop takes — a key per axis the URL actually carries, and nothing else.
|
|
167
|
+
*
|
|
168
|
+
* This is the deliberate opposite of {@link searchParamsToTableQuery}, and the
|
|
169
|
+
* distinction is load-bearing rather than cosmetic. The table reads `query` by
|
|
170
|
+
* **field presence**: a present field means "this axis is controlled — outrank
|
|
171
|
+
* persistence and the `initial*` seed"; an absent one means "the URL has no
|
|
172
|
+
* opinion, carry on". A complete object therefore claims every axis, so
|
|
173
|
+
* handing `searchParamsToTableQuery`'s output to `query` silently switches off
|
|
174
|
+
* `persistenceConfig`, `initialSort`, `initialFilters` and `initialGroupBy` —
|
|
175
|
+
* even on a URL with no params at all, where its every field is a default it
|
|
176
|
+
* invented. That is not a hypothetical: it was the shipped wiring, and this
|
|
177
|
+
* function exists because of it.
|
|
178
|
+
*
|
|
179
|
+
* `sortColumn` and `sortDirection` are emitted as a pair or not at all, so the
|
|
180
|
+
* half-controlled sort (a direction with no column) cannot be produced here.
|
|
181
|
+
*
|
|
182
|
+
* Same tolerance as the full parser: an unparsable `page`/`size` falls back to
|
|
183
|
+
* the resolved default *for that key* — the key was present, so the axis stays
|
|
184
|
+
* controlled — and malformed filters are skipped individually.
|
|
185
|
+
*
|
|
186
|
+
* @param params - Search params to read (not mutated).
|
|
187
|
+
* @param options - Fallback defaults + key prefix.
|
|
188
|
+
* @returns Only the axes present in `params`.
|
|
189
|
+
*/
|
|
190
|
+
export declare function searchParamsToTableViewState(params: URLSearchParams, options?: TableQueryUrlOptions): TableQueryViewState;
|
|
139
191
|
/**
|
|
140
192
|
* Merge a table query into existing search params: all managed keys (`q`,
|
|
141
193
|
* `page`, `size`, `sort`, `dir`, `group`, `filter` — with the configured
|
package/dist/table-query.js
CHANGED
|
@@ -206,6 +206,65 @@ export function searchParamsToTableQuery(params, options = {}) {
|
|
|
206
206
|
groupByKey: rawGroup !== null ? (rawGroup === '' ? null : rawGroup) : d.groupByKey
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Parse `URLSearchParams` into the **partial** view state a table's `query`
|
|
211
|
+
* prop takes — a key per axis the URL actually carries, and nothing else.
|
|
212
|
+
*
|
|
213
|
+
* This is the deliberate opposite of {@link searchParamsToTableQuery}, and the
|
|
214
|
+
* distinction is load-bearing rather than cosmetic. The table reads `query` by
|
|
215
|
+
* **field presence**: a present field means "this axis is controlled — outrank
|
|
216
|
+
* persistence and the `initial*` seed"; an absent one means "the URL has no
|
|
217
|
+
* opinion, carry on". A complete object therefore claims every axis, so
|
|
218
|
+
* handing `searchParamsToTableQuery`'s output to `query` silently switches off
|
|
219
|
+
* `persistenceConfig`, `initialSort`, `initialFilters` and `initialGroupBy` —
|
|
220
|
+
* even on a URL with no params at all, where its every field is a default it
|
|
221
|
+
* invented. That is not a hypothetical: it was the shipped wiring, and this
|
|
222
|
+
* function exists because of it.
|
|
223
|
+
*
|
|
224
|
+
* `sortColumn` and `sortDirection` are emitted as a pair or not at all, so the
|
|
225
|
+
* half-controlled sort (a direction with no column) cannot be produced here.
|
|
226
|
+
*
|
|
227
|
+
* Same tolerance as the full parser: an unparsable `page`/`size` falls back to
|
|
228
|
+
* the resolved default *for that key* — the key was present, so the axis stays
|
|
229
|
+
* controlled — and malformed filters are skipped individually.
|
|
230
|
+
*
|
|
231
|
+
* @param params - Search params to read (not mutated).
|
|
232
|
+
* @param options - Fallback defaults + key prefix.
|
|
233
|
+
* @returns Only the axes present in `params`.
|
|
234
|
+
*/
|
|
235
|
+
export function searchParamsToTableViewState(params, options = {}) {
|
|
236
|
+
const d = resolveDefaults(options.defaults);
|
|
237
|
+
const k = paramKeys(options.prefix ?? '');
|
|
238
|
+
const view = {};
|
|
239
|
+
const rawPage = params.get(k.page);
|
|
240
|
+
if (rawPage !== null)
|
|
241
|
+
view.page = parsePositiveInt(rawPage) ?? d.page;
|
|
242
|
+
const rawSize = params.get(k.size);
|
|
243
|
+
if (rawSize !== null)
|
|
244
|
+
view.itemsPerPage = parsePositiveInt(rawSize) ?? d.itemsPerPage;
|
|
245
|
+
const rawSort = params.get(k.sort);
|
|
246
|
+
if (rawSort !== null) {
|
|
247
|
+
view.sortColumn = rawSort;
|
|
248
|
+
view.sortDirection = rawSort !== '' && params.get(k.dir) === 'desc' ? 'desc' : 'asc';
|
|
249
|
+
}
|
|
250
|
+
const rawSearch = params.get(k.q);
|
|
251
|
+
if (rawSearch !== null)
|
|
252
|
+
view.searchTerm = rawSearch;
|
|
253
|
+
const rawFilters = params.getAll(k.filter);
|
|
254
|
+
if (rawFilters.length > 0) {
|
|
255
|
+
const activeFilters = [];
|
|
256
|
+
for (const raw of rawFilters) {
|
|
257
|
+
const filter = parseFilterParam(raw);
|
|
258
|
+
if (filter)
|
|
259
|
+
activeFilters.push(filter);
|
|
260
|
+
}
|
|
261
|
+
view.activeFilters = activeFilters;
|
|
262
|
+
}
|
|
263
|
+
const rawGroup = params.get(k.group);
|
|
264
|
+
if (rawGroup !== null)
|
|
265
|
+
view.groupByKey = rawGroup === '' ? null : rawGroup;
|
|
266
|
+
return view;
|
|
267
|
+
}
|
|
209
268
|
/**
|
|
210
269
|
* Merge a table query into existing search params: all managed keys (`q`,
|
|
211
270
|
* `page`, `size`, `sort`, `dir`, `group`, `filter` — with the configured
|
package/dist/url.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TableQueryParams, type TableQueryUrlOptions } from './table-query';
|
|
1
|
+
import { type TableQueryParams, type TableQueryUrlOptions, type TableQueryViewState } from './table-query.js';
|
|
2
2
|
/**
|
|
3
3
|
* How {@link useUrlArrayParam} maps an array onto the URL:
|
|
4
4
|
* - `repeat` — one entry per key: `?tag=a&tag=b`
|
|
@@ -145,17 +145,20 @@ export interface TableQueryUrlSyncOptions extends TableQueryUrlOptions {
|
|
|
145
145
|
replaceState?: boolean;
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
|
-
* Opt-in URL sync for a table
|
|
149
|
-
*
|
|
150
|
-
*
|
|
148
|
+
* Opt-in URL sync for a table: mirrors the `TableQuery` the table emits onto
|
|
149
|
+
* `?q=…&sort=…&page=…` query params, so the view state survives reloads, can
|
|
150
|
+
* be shared as a link, and — unlike `localStorage` — is visible to the server.
|
|
151
|
+
* Works in client mode as well as server mode; the table emits its query in
|
|
152
|
+
* both.
|
|
151
153
|
*
|
|
152
154
|
* Two directions, both explicit:
|
|
153
|
-
* - **URL → query**: `
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
155
|
+
* - **URL → query**: `viewState` carries the axes the URL actually names, and
|
|
156
|
+
* re-reads the URL on every navigation. Hand it to the table's `query` prop,
|
|
157
|
+
* which controls exactly those axes — per field, and ahead of both
|
|
158
|
+
* `persistenceConfig` and the `initial*` seeds. Because a controlled axis is
|
|
159
|
+
* a derived rather than a write, it resolves during server rendering too,
|
|
160
|
+
* which is the point: the server renders the view the link asked for instead
|
|
161
|
+
* of a default one the client swaps out on hydration.
|
|
159
162
|
* - **query → URL**: pass `syncQuery` the query from `onQueryChange`, or call
|
|
160
163
|
* it inside `queryFn` (when `queryFn` is set, `onQueryChange` does not
|
|
161
164
|
* fire). It rewrites only its own — optionally prefixed — params via
|
|
@@ -167,7 +170,7 @@ export interface TableQueryUrlSyncOptions extends TableQueryUrlOptions {
|
|
|
167
170
|
* `initialPage`, `initialGroupBy`) so the elision baseline matches the state
|
|
168
171
|
* the table actually starts in.
|
|
169
172
|
*
|
|
170
|
-
* @example
|
|
173
|
+
* @example Client mode — the whole wiring is two props
|
|
171
174
|
* ```svelte
|
|
172
175
|
* <script lang="ts">
|
|
173
176
|
* import { Table } from '@urbicon-ui/table';
|
|
@@ -176,11 +179,32 @@ export interface TableQueryUrlSyncOptions extends TableQueryUrlOptions {
|
|
|
176
179
|
* const sync = createTableQueryUrlSync({ defaults: { itemsPerPage: 25 } });
|
|
177
180
|
* </script>
|
|
178
181
|
*
|
|
182
|
+
* <Table {items} {columns} query={sync.viewState} onQueryChange={sync.syncQuery} />
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* `query` is a *controlled* prop, which is what makes this work on the server:
|
|
186
|
+
* the URL is parsed during SSR and the table renders the linked view rather
|
|
187
|
+
* than an unfiltered one the client then replaces. It also outranks
|
|
188
|
+
* `persistenceConfig` per axis, so there is no longer a caveat about a stored
|
|
189
|
+
* value beating the link.
|
|
190
|
+
*
|
|
191
|
+
* Pass `viewState`, never `initialQuery`. `initialQuery` describes **every**
|
|
192
|
+
* axis, so as a `query` value it claims every axis — including the ones the URL
|
|
193
|
+
* says nothing about, whose values it filled in from the defaults. A table
|
|
194
|
+
* wired that way ignores `persistenceConfig`, `initialSort`, `initialFilters`
|
|
195
|
+
* and `initialGroupBy` entirely, on any URL, and says nothing about it.
|
|
196
|
+
*
|
|
197
|
+
* @example Server mode — the same two directions, with the fetch in between
|
|
198
|
+
* ```svelte
|
|
199
|
+
* <script lang="ts">
|
|
200
|
+
* const sync = createTableQueryUrlSync({ defaults: { itemsPerPage: 25 } });
|
|
201
|
+
* </script>
|
|
202
|
+
*
|
|
179
203
|
* <Table
|
|
180
204
|
* mode="server"
|
|
181
|
-
*
|
|
205
|
+
* {columns}
|
|
182
206
|
* itemsPerPage={25}
|
|
183
|
-
*
|
|
207
|
+
* query={sync.viewState}
|
|
184
208
|
* queryFn={async (query, { signal }) => {
|
|
185
209
|
* sync.syncQuery(query);
|
|
186
210
|
* const res = await fetch(`/api/users?${new URLSearchParams(...)}`, { signal });
|
|
@@ -191,10 +215,22 @@ export interface TableQueryUrlSyncOptions extends TableQueryUrlOptions {
|
|
|
191
215
|
* ```
|
|
192
216
|
*
|
|
193
217
|
* @param options - Elision defaults, key prefix, history behaviour.
|
|
194
|
-
* @returns `
|
|
195
|
-
* (
|
|
218
|
+
* @returns `viewState` (the controlled axes, re-read on every navigation),
|
|
219
|
+
* `initialQuery` (a complete snapshot for a fetch) + `syncQuery` (write a
|
|
220
|
+
* query back to the URL).
|
|
196
221
|
*/
|
|
197
222
|
export declare function createTableQueryUrlSync(options?: TableQueryUrlSyncOptions): {
|
|
223
|
+
/**
|
|
224
|
+
* The axes the URL names, re-read on every navigation.
|
|
225
|
+
*
|
|
226
|
+
* A getter, not a captured value: `createTableQueryUrlSync` runs once per
|
|
227
|
+
* page component, and SvelteKit does not remount that component when only
|
|
228
|
+
* the query string changes. A snapshot would therefore never see the back
|
|
229
|
+
* button — the table would stay sorted after the user navigated back to a
|
|
230
|
+
* URL with no sort param. Reading `page.url` here makes every consumer of
|
|
231
|
+
* this getter a reader of SvelteKit's own reactive page state.
|
|
232
|
+
*/
|
|
233
|
+
readonly viewState: TableQueryViewState;
|
|
198
234
|
readonly initialQuery: TableQueryParams;
|
|
199
235
|
readonly syncQuery: (query: TableQueryParams) => void;
|
|
200
236
|
};
|
package/dist/url.svelte.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { building } from '$app/environment';
|
|
2
2
|
import { goto } from '$app/navigation';
|
|
3
3
|
import { page } from '$app/state';
|
|
4
|
-
import { applyTableQueryToSearchParams, searchParamsToTableQuery } from './table-query';
|
|
4
|
+
import { applyTableQueryToSearchParams, searchParamsToTableQuery, searchParamsToTableViewState } from './table-query.js';
|
|
5
5
|
/**
|
|
6
6
|
* Low-level escape hatch to update several params at once via `goto` (without a
|
|
7
7
|
* full navigation). Starts from the current URL, applies `next`, and keeps
|
|
@@ -176,17 +176,20 @@ export function useUrlArrayParam(key, opts) {
|
|
|
176
176
|
return useUrlParam(key, { parse, serialize, initial: opts.initial });
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
179
|
-
* Opt-in URL sync for a table
|
|
180
|
-
*
|
|
181
|
-
*
|
|
179
|
+
* Opt-in URL sync for a table: mirrors the `TableQuery` the table emits onto
|
|
180
|
+
* `?q=…&sort=…&page=…` query params, so the view state survives reloads, can
|
|
181
|
+
* be shared as a link, and — unlike `localStorage` — is visible to the server.
|
|
182
|
+
* Works in client mode as well as server mode; the table emits its query in
|
|
183
|
+
* both.
|
|
182
184
|
*
|
|
183
185
|
* Two directions, both explicit:
|
|
184
|
-
* - **URL → query**: `
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
186
|
+
* - **URL → query**: `viewState` carries the axes the URL actually names, and
|
|
187
|
+
* re-reads the URL on every navigation. Hand it to the table's `query` prop,
|
|
188
|
+
* which controls exactly those axes — per field, and ahead of both
|
|
189
|
+
* `persistenceConfig` and the `initial*` seeds. Because a controlled axis is
|
|
190
|
+
* a derived rather than a write, it resolves during server rendering too,
|
|
191
|
+
* which is the point: the server renders the view the link asked for instead
|
|
192
|
+
* of a default one the client swaps out on hydration.
|
|
190
193
|
* - **query → URL**: pass `syncQuery` the query from `onQueryChange`, or call
|
|
191
194
|
* it inside `queryFn` (when `queryFn` is set, `onQueryChange` does not
|
|
192
195
|
* fire). It rewrites only its own — optionally prefixed — params via
|
|
@@ -198,7 +201,7 @@ export function useUrlArrayParam(key, opts) {
|
|
|
198
201
|
* `initialPage`, `initialGroupBy`) so the elision baseline matches the state
|
|
199
202
|
* the table actually starts in.
|
|
200
203
|
*
|
|
201
|
-
* @example
|
|
204
|
+
* @example Client mode — the whole wiring is two props
|
|
202
205
|
* ```svelte
|
|
203
206
|
* <script lang="ts">
|
|
204
207
|
* import { Table } from '@urbicon-ui/table';
|
|
@@ -207,11 +210,32 @@ export function useUrlArrayParam(key, opts) {
|
|
|
207
210
|
* const sync = createTableQueryUrlSync({ defaults: { itemsPerPage: 25 } });
|
|
208
211
|
* </script>
|
|
209
212
|
*
|
|
213
|
+
* <Table {items} {columns} query={sync.viewState} onQueryChange={sync.syncQuery} />
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* `query` is a *controlled* prop, which is what makes this work on the server:
|
|
217
|
+
* the URL is parsed during SSR and the table renders the linked view rather
|
|
218
|
+
* than an unfiltered one the client then replaces. It also outranks
|
|
219
|
+
* `persistenceConfig` per axis, so there is no longer a caveat about a stored
|
|
220
|
+
* value beating the link.
|
|
221
|
+
*
|
|
222
|
+
* Pass `viewState`, never `initialQuery`. `initialQuery` describes **every**
|
|
223
|
+
* axis, so as a `query` value it claims every axis — including the ones the URL
|
|
224
|
+
* says nothing about, whose values it filled in from the defaults. A table
|
|
225
|
+
* wired that way ignores `persistenceConfig`, `initialSort`, `initialFilters`
|
|
226
|
+
* and `initialGroupBy` entirely, on any URL, and says nothing about it.
|
|
227
|
+
*
|
|
228
|
+
* @example Server mode — the same two directions, with the fetch in between
|
|
229
|
+
* ```svelte
|
|
230
|
+
* <script lang="ts">
|
|
231
|
+
* const sync = createTableQueryUrlSync({ defaults: { itemsPerPage: 25 } });
|
|
232
|
+
* </script>
|
|
233
|
+
*
|
|
210
234
|
* <Table
|
|
211
235
|
* mode="server"
|
|
212
|
-
*
|
|
236
|
+
* {columns}
|
|
213
237
|
* itemsPerPage={25}
|
|
214
|
-
*
|
|
238
|
+
* query={sync.viewState}
|
|
215
239
|
* queryFn={async (query, { signal }) => {
|
|
216
240
|
* sync.syncQuery(query);
|
|
217
241
|
* const res = await fetch(`/api/users?${new URLSearchParams(...)}`, { signal });
|
|
@@ -222,13 +246,17 @@ export function useUrlArrayParam(key, opts) {
|
|
|
222
246
|
* ```
|
|
223
247
|
*
|
|
224
248
|
* @param options - Elision defaults, key prefix, history behaviour.
|
|
225
|
-
* @returns `
|
|
226
|
-
* (
|
|
249
|
+
* @returns `viewState` (the controlled axes, re-read on every navigation),
|
|
250
|
+
* `initialQuery` (a complete snapshot for a fetch) + `syncQuery` (write a
|
|
251
|
+
* query back to the URL).
|
|
227
252
|
*/
|
|
228
253
|
export function createTableQueryUrlSync(options = {}) {
|
|
229
254
|
// Same prerender rule as `useUrlParam`: no query string exists while
|
|
230
|
-
// building, so
|
|
231
|
-
const
|
|
255
|
+
// building, so both readers parse from empty params instead of throwing.
|
|
256
|
+
const currentParams = () => (building ? new URLSearchParams() : page.url.searchParams);
|
|
257
|
+
// Parsed once, on purpose — a snapshot to seed a fetch with, not a source of
|
|
258
|
+
// truth. `viewState` below is the one the table binds to.
|
|
259
|
+
const initialQuery = searchParamsToTableQuery(currentParams(), options);
|
|
232
260
|
function syncQuery(query) {
|
|
233
261
|
const next = applyTableQueryToSearchParams(page.url.searchParams, query, options);
|
|
234
262
|
const qs = next.toString();
|
|
@@ -238,5 +266,21 @@ export function createTableQueryUrlSync(options = {}) {
|
|
|
238
266
|
keepFocus: true
|
|
239
267
|
});
|
|
240
268
|
}
|
|
241
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
/**
|
|
271
|
+
* The axes the URL names, re-read on every navigation.
|
|
272
|
+
*
|
|
273
|
+
* A getter, not a captured value: `createTableQueryUrlSync` runs once per
|
|
274
|
+
* page component, and SvelteKit does not remount that component when only
|
|
275
|
+
* the query string changes. A snapshot would therefore never see the back
|
|
276
|
+
* button — the table would stay sorted after the user navigated back to a
|
|
277
|
+
* URL with no sort param. Reading `page.url` here makes every consumer of
|
|
278
|
+
* this getter a reader of SvelteKit's own reactive page state.
|
|
279
|
+
*/
|
|
280
|
+
get viewState() {
|
|
281
|
+
return searchParamsToTableViewState(currentParams(), options);
|
|
282
|
+
},
|
|
283
|
+
initialQuery,
|
|
284
|
+
syncQuery
|
|
285
|
+
};
|
|
242
286
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/sveltekit-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "SvelteKit helper utilities — createCronRunner, streamSse, and URL-state runes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
],
|
|
62
62
|
"scripts": {
|
|
63
63
|
"dev": "svelte-package --watch",
|
|
64
|
-
"build": "svelte-kit sync && svelte-package",
|
|
64
|
+
"build": "svelte-kit sync && svelte-package && bun ../../scripts/complete-esm-specifiers.ts dist",
|
|
65
65
|
"clean": "rm -rf dist .svelte-kit",
|
|
66
66
|
"clean-all": "bun --bun run clean && rm -rf node_modules",
|
|
67
|
-
"package": "svelte-kit sync && svelte-package",
|
|
67
|
+
"package": "svelte-kit sync && svelte-package && bun ../../scripts/complete-esm-specifiers.ts dist",
|
|
68
68
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
69
69
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
70
70
|
"format": "biome format --write . && prettier --write \"**/*.svelte\"",
|