@asteby/metacore-runtime-react 23.12.0 → 23.12.2
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/CHANGELOG.md +27 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +49 -2
- package/package.json +1 -1
- package/src/dynamic-table.tsx +51 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 23.12.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5333315: fix(dynamic-table): don't rewrite a clean filtered deep-link (no URL flicker)
|
|
8
|
+
|
|
9
|
+
Normalise the `eq:` operator when fingerprinting the query string and stop
|
|
10
|
+
stamping `per_page` for the plain server default, so opening/clicking a
|
|
11
|
+
sidebar deep-link (`f_status=eq:in_progress&view=list`) is a true no-op: the
|
|
12
|
+
table adopts the filter without rewriting the URL to its own spelling
|
|
13
|
+
(`f_status=in_progress&per_page=15`). This removes the visible URL flicker and
|
|
14
|
+
keeps the sidebar active-state exact-href match working.
|
|
15
|
+
|
|
16
|
+
## 23.12.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 8788161: fix(dynamic-table): stop infinite URL replaceState loop on filtered deep-links
|
|
21
|
+
|
|
22
|
+
The host router re-serializes the query string in its own canonical form
|
|
23
|
+
(different key order, percent-encoded operator colons) than the table writes,
|
|
24
|
+
so the raw-string comparison never matched and the table's write effect fought
|
|
25
|
+
the router's re-serialize forever ("Throttling navigation…" + React #185),
|
|
26
|
+
hanging any filtered sidebar view (e.g. `f_status=eq:reception`). Both URL-sync
|
|
27
|
+
effects now compare an order/encoding-independent fingerprint and the write is
|
|
28
|
+
skipped when semantically a no-op.
|
|
29
|
+
|
|
3
30
|
## 23.12.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAanF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAanF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+BAwmCnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -78,6 +78,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
78
78
|
const [filterOptionsMap, setFilterOptionsMap] = useState(new Map());
|
|
79
79
|
const initializedFromUrl = useRef(false);
|
|
80
80
|
const urlHadPerPage = useRef(false);
|
|
81
|
+
// The model's server-declared default page size. When the current page size
|
|
82
|
+
// is just this default (the user hasn't paged/resized and the URL didn't
|
|
83
|
+
// pin one), we deliberately DON'T stamp `per_page` into the URL — otherwise
|
|
84
|
+
// the table appends `per_page=15` to the sidebar's clean deep-link on mount,
|
|
85
|
+
// which reads as a URL flicker and fights the router's spelling. Starts at 10
|
|
86
|
+
// to match the initial pagination state until metadata arrives.
|
|
87
|
+
const defaultPerPage = useRef(10);
|
|
81
88
|
useEffect(() => {
|
|
82
89
|
if (prevBranchId.current !== currentBranch?.id) {
|
|
83
90
|
prevBranchId.current = currentBranch?.id;
|
|
@@ -114,6 +121,29 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
114
121
|
const alias = operatorToUrlAlias[prefix];
|
|
115
122
|
return alias ? `${alias}:${rest}` : value;
|
|
116
123
|
};
|
|
124
|
+
// Order-, encoding- and operator-independent fingerprint of a query string.
|
|
125
|
+
// The host router (TanStack) and the sidebar deep-link the filter in THEIR
|
|
126
|
+
// canonical form — different key order, percent-encoded operator colons, and
|
|
127
|
+
// the explicit `eq:` operator (`f_status=eq%3Ain_progress`) — while the table
|
|
128
|
+
// internally unwraps `eq:` for selects and would write the bare value
|
|
129
|
+
// (`f_status=in_progress`). Comparing raw (or even just decoded) strings then
|
|
130
|
+
// NEVER matches, so the table rewrites the URL on every navigation and the
|
|
131
|
+
// two spellings visibly ping-pong ("parpadeo", "Throttling navigation…" +
|
|
132
|
+
// React #185). Normalising each `f_` value through `urlValueToInternal`
|
|
133
|
+
// collapses `eq:X` and `X` to the same token, so a sorted `key=value` list is
|
|
134
|
+
// a true semantic fingerprint and the write effect can detect real no-ops —
|
|
135
|
+
// leaving the router's own spelling untouched (so the sidebar active-state
|
|
136
|
+
// match on the exact href keeps working too).
|
|
137
|
+
const canonicalSearch = (searchStr) => {
|
|
138
|
+
const p = new URLSearchParams(searchStr);
|
|
139
|
+
const entries = [];
|
|
140
|
+
p.forEach((value, key) => {
|
|
141
|
+
const norm = key.startsWith('f_') ? urlValueToInternal(value) : value;
|
|
142
|
+
entries.push(`${key}=${norm}`);
|
|
143
|
+
});
|
|
144
|
+
entries.sort();
|
|
145
|
+
return entries.join('&');
|
|
146
|
+
};
|
|
117
147
|
useEffect(() => {
|
|
118
148
|
if (!enableUrlSync || initializedFromUrl.current)
|
|
119
149
|
return;
|
|
@@ -176,7 +206,10 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
176
206
|
}
|
|
177
207
|
if (pagination.pageIndex > 0)
|
|
178
208
|
params.set('page', String(pagination.pageIndex + 1));
|
|
179
|
-
|
|
209
|
+
// Only pin per_page when it deviates from the model's default (an explicit
|
|
210
|
+
// user page-size change) or the URL already carried one — not for the
|
|
211
|
+
// plain server default, which would needlessly mutate a clean deep-link.
|
|
212
|
+
if (pagination.pageSize !== defaultPerPage.current || urlHadPerPage.current)
|
|
180
213
|
params.set('per_page', String(pagination.pageSize));
|
|
181
214
|
if (sorting.length > 0) {
|
|
182
215
|
params.set('sortBy', sorting[0].id);
|
|
@@ -195,6 +228,16 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
195
228
|
params.set(`f_${key}`, `in:${values.join(',')}`);
|
|
196
229
|
});
|
|
197
230
|
const search = params.toString();
|
|
231
|
+
// If what we'd write is semantically identical to what's already in the
|
|
232
|
+
// bar (only key order / colon-encoding differ from the router's form),
|
|
233
|
+
// skip the write entirely. Rewriting would flip the raw string, the
|
|
234
|
+
// router would re-serialize back to its form, and the two would ping-pong
|
|
235
|
+
// forever. We still adopt the current location as "ours" so the resync
|
|
236
|
+
// effect below doesn't treat the router's spelling as an external change.
|
|
237
|
+
if (canonicalSearch(search) === canonicalSearch(window.location.search)) {
|
|
238
|
+
lastSelfSearch.current = window.location.search;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
198
241
|
const newUrl = search ? `${window.location.pathname}?${search}` : window.location.pathname;
|
|
199
242
|
lastSelfSearch.current = search ? `?${search}` : '';
|
|
200
243
|
window.history.replaceState(null, '', newUrl);
|
|
@@ -209,7 +252,9 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
209
252
|
useEffect(() => {
|
|
210
253
|
if (!enableUrlSync || !initializedFromUrl.current)
|
|
211
254
|
return;
|
|
212
|
-
|
|
255
|
+
// Semantic compare: the router's spelling of the query string we just
|
|
256
|
+
// wrote (different order/encoding) is NOT an external change.
|
|
257
|
+
if (canonicalSearch(locationSearch) === canonicalSearch(lastSelfSearch.current ?? ''))
|
|
213
258
|
return;
|
|
214
259
|
lastSelfSearch.current = locationSearch;
|
|
215
260
|
const params = new URLSearchParams(locationSearch);
|
|
@@ -269,6 +314,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
269
314
|
// localStorage.
|
|
270
315
|
if (cached) {
|
|
271
316
|
setMetadata(cached);
|
|
317
|
+
defaultPerPage.current = cached.defaultPerPage || 10;
|
|
272
318
|
if (!urlHadPerPage.current)
|
|
273
319
|
setPagination((prev) => ({ ...prev, pageSize: cached.defaultPerPage || 10 }));
|
|
274
320
|
setLoading(false);
|
|
@@ -284,6 +330,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
284
330
|
meta = fresh;
|
|
285
331
|
setMetadata(fresh);
|
|
286
332
|
cacheMetadata(model, fresh);
|
|
333
|
+
defaultPerPage.current = fresh.defaultPerPage || 10;
|
|
287
334
|
if (!urlHadPerPage.current)
|
|
288
335
|
setPagination((prev) => ({ ...prev, pageSize: fresh.defaultPerPage || 10 }));
|
|
289
336
|
}
|
package/package.json
CHANGED
package/src/dynamic-table.tsx
CHANGED
|
@@ -190,6 +190,13 @@ export function DynamicTable({
|
|
|
190
190
|
|
|
191
191
|
const initializedFromUrl = useRef(false)
|
|
192
192
|
const urlHadPerPage = useRef(false)
|
|
193
|
+
// The model's server-declared default page size. When the current page size
|
|
194
|
+
// is just this default (the user hasn't paged/resized and the URL didn't
|
|
195
|
+
// pin one), we deliberately DON'T stamp `per_page` into the URL — otherwise
|
|
196
|
+
// the table appends `per_page=15` to the sidebar's clean deep-link on mount,
|
|
197
|
+
// which reads as a URL flicker and fights the router's spelling. Starts at 10
|
|
198
|
+
// to match the initial pagination state until metadata arrives.
|
|
199
|
+
const defaultPerPage = useRef(10)
|
|
193
200
|
|
|
194
201
|
useEffect(() => {
|
|
195
202
|
if (prevBranchId.current !== currentBranch?.id) {
|
|
@@ -229,6 +236,30 @@ export function DynamicTable({
|
|
|
229
236
|
return alias ? `${alias}:${rest}` : value
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
// Order-, encoding- and operator-independent fingerprint of a query string.
|
|
240
|
+
// The host router (TanStack) and the sidebar deep-link the filter in THEIR
|
|
241
|
+
// canonical form — different key order, percent-encoded operator colons, and
|
|
242
|
+
// the explicit `eq:` operator (`f_status=eq%3Ain_progress`) — while the table
|
|
243
|
+
// internally unwraps `eq:` for selects and would write the bare value
|
|
244
|
+
// (`f_status=in_progress`). Comparing raw (or even just decoded) strings then
|
|
245
|
+
// NEVER matches, so the table rewrites the URL on every navigation and the
|
|
246
|
+
// two spellings visibly ping-pong ("parpadeo", "Throttling navigation…" +
|
|
247
|
+
// React #185). Normalising each `f_` value through `urlValueToInternal`
|
|
248
|
+
// collapses `eq:X` and `X` to the same token, so a sorted `key=value` list is
|
|
249
|
+
// a true semantic fingerprint and the write effect can detect real no-ops —
|
|
250
|
+
// leaving the router's own spelling untouched (so the sidebar active-state
|
|
251
|
+
// match on the exact href keeps working too).
|
|
252
|
+
const canonicalSearch = (searchStr: string): string => {
|
|
253
|
+
const p = new URLSearchParams(searchStr)
|
|
254
|
+
const entries: string[] = []
|
|
255
|
+
p.forEach((value, key) => {
|
|
256
|
+
const norm = key.startsWith('f_') ? urlValueToInternal(value) : value
|
|
257
|
+
entries.push(`${key}=${norm}`)
|
|
258
|
+
})
|
|
259
|
+
entries.sort()
|
|
260
|
+
return entries.join('&')
|
|
261
|
+
}
|
|
262
|
+
|
|
232
263
|
useEffect(() => {
|
|
233
264
|
if (!enableUrlSync || initializedFromUrl.current) return
|
|
234
265
|
initializedFromUrl.current = true
|
|
@@ -282,7 +313,11 @@ export function DynamicTable({
|
|
|
282
313
|
if (v) params.set(key, v)
|
|
283
314
|
}
|
|
284
315
|
if (pagination.pageIndex > 0) params.set('page', String(pagination.pageIndex + 1))
|
|
285
|
-
|
|
316
|
+
// Only pin per_page when it deviates from the model's default (an explicit
|
|
317
|
+
// user page-size change) or the URL already carried one — not for the
|
|
318
|
+
// plain server default, which would needlessly mutate a clean deep-link.
|
|
319
|
+
if (pagination.pageSize !== defaultPerPage.current || urlHadPerPage.current)
|
|
320
|
+
params.set('per_page', String(pagination.pageSize))
|
|
286
321
|
if (sorting.length > 0) {
|
|
287
322
|
params.set('sortBy', sorting[0].id)
|
|
288
323
|
params.set('order', sorting[0].desc ? 'desc' : 'asc')
|
|
@@ -295,6 +330,16 @@ export function DynamicTable({
|
|
|
295
330
|
else params.set(`f_${key}`, `in:${values.join(',')}`)
|
|
296
331
|
})
|
|
297
332
|
const search = params.toString()
|
|
333
|
+
// If what we'd write is semantically identical to what's already in the
|
|
334
|
+
// bar (only key order / colon-encoding differ from the router's form),
|
|
335
|
+
// skip the write entirely. Rewriting would flip the raw string, the
|
|
336
|
+
// router would re-serialize back to its form, and the two would ping-pong
|
|
337
|
+
// forever. We still adopt the current location as "ours" so the resync
|
|
338
|
+
// effect below doesn't treat the router's spelling as an external change.
|
|
339
|
+
if (canonicalSearch(search) === canonicalSearch(window.location.search)) {
|
|
340
|
+
lastSelfSearch.current = window.location.search
|
|
341
|
+
return
|
|
342
|
+
}
|
|
298
343
|
const newUrl = search ? `${window.location.pathname}?${search}` : window.location.pathname
|
|
299
344
|
lastSelfSearch.current = search ? `?${search}` : ''
|
|
300
345
|
window.history.replaceState(null, '', newUrl)
|
|
@@ -309,7 +354,9 @@ export function DynamicTable({
|
|
|
309
354
|
const locationSearch = typeof window !== 'undefined' ? window.location.search : ''
|
|
310
355
|
useEffect(() => {
|
|
311
356
|
if (!enableUrlSync || !initializedFromUrl.current) return
|
|
312
|
-
|
|
357
|
+
// Semantic compare: the router's spelling of the query string we just
|
|
358
|
+
// wrote (different order/encoding) is NOT an external change.
|
|
359
|
+
if (canonicalSearch(locationSearch) === canonicalSearch(lastSelfSearch.current ?? '')) return
|
|
313
360
|
lastSelfSearch.current = locationSearch
|
|
314
361
|
const params = new URLSearchParams(locationSearch)
|
|
315
362
|
const filters: Record<string, string[]> = {}
|
|
@@ -361,6 +408,7 @@ export function DynamicTable({
|
|
|
361
408
|
// localStorage.
|
|
362
409
|
if (cached) {
|
|
363
410
|
setMetadata(cached)
|
|
411
|
+
defaultPerPage.current = cached.defaultPerPage || 10
|
|
364
412
|
if (!urlHadPerPage.current) setPagination((prev: PaginationState) => ({ ...prev, pageSize: cached.defaultPerPage || 10 }))
|
|
365
413
|
setLoading(false)
|
|
366
414
|
} else {
|
|
@@ -374,6 +422,7 @@ export function DynamicTable({
|
|
|
374
422
|
meta = fresh
|
|
375
423
|
setMetadata(fresh)
|
|
376
424
|
cacheMetadata(model, fresh)
|
|
425
|
+
defaultPerPage.current = fresh.defaultPerPage || 10
|
|
377
426
|
if (!urlHadPerPage.current) setPagination((prev: PaginationState) => ({ ...prev, pageSize: fresh.defaultPerPage || 10 }))
|
|
378
427
|
}
|
|
379
428
|
} catch (error) {
|