@asteby/metacore-runtime-react 23.12.1 → 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 CHANGED
@@ -1,5 +1,18 @@
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
+
3
16
  ## 23.12.1
4
17
 
5
18
  ### Patch 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,+BAmlCnB"}
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"}
@@ -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,18 +121,26 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
114
121
  const alias = operatorToUrlAlias[prefix];
115
122
  return alias ? `${alias}:${rest}` : value;
116
123
  };
117
- // Order- and encoding-independent fingerprint of a query string. The host
118
- // router (TanStack) re-serializes the same params in its own canonical form
119
- // — different key order and percent-encoded operator colons
120
- // (`f_status=eq%3Areception`) — than the table writes (`f_status=eq:reception`).
121
- // Comparing raw strings then NEVER matches, so the table's write effect and
122
- // the router's re-serialize fight forever ("Throttling navigation…" +
123
- // React #185). URLSearchParams decodes values, so a sorted `key=value` list
124
- // is stable across both spellings and lets us detect real no-ops.
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).
125
137
  const canonicalSearch = (searchStr) => {
126
138
  const p = new URLSearchParams(searchStr);
127
139
  const entries = [];
128
- p.forEach((value, key) => entries.push(`${key}=${value}`));
140
+ p.forEach((value, key) => {
141
+ const norm = key.startsWith('f_') ? urlValueToInternal(value) : value;
142
+ entries.push(`${key}=${norm}`);
143
+ });
129
144
  entries.sort();
130
145
  return entries.join('&');
131
146
  };
@@ -191,7 +206,10 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
191
206
  }
192
207
  if (pagination.pageIndex > 0)
193
208
  params.set('page', String(pagination.pageIndex + 1));
194
- if (pagination.pageSize !== 10)
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)
195
213
  params.set('per_page', String(pagination.pageSize));
196
214
  if (sorting.length > 0) {
197
215
  params.set('sortBy', sorting[0].id);
@@ -296,6 +314,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
296
314
  // localStorage.
297
315
  if (cached) {
298
316
  setMetadata(cached);
317
+ defaultPerPage.current = cached.defaultPerPage || 10;
299
318
  if (!urlHadPerPage.current)
300
319
  setPagination((prev) => ({ ...prev, pageSize: cached.defaultPerPage || 10 }));
301
320
  setLoading(false);
@@ -311,6 +330,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
311
330
  meta = fresh;
312
331
  setMetadata(fresh);
313
332
  cacheMetadata(model, fresh);
333
+ defaultPerPage.current = fresh.defaultPerPage || 10;
314
334
  if (!urlHadPerPage.current)
315
335
  setPagination((prev) => ({ ...prev, pageSize: fresh.defaultPerPage || 10 }));
316
336
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "23.12.1",
3
+ "version": "23.12.2",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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,18 +236,26 @@ export function DynamicTable({
229
236
  return alias ? `${alias}:${rest}` : value
230
237
  }
231
238
 
232
- // Order- and encoding-independent fingerprint of a query string. The host
233
- // router (TanStack) re-serializes the same params in its own canonical form
234
- // — different key order and percent-encoded operator colons
235
- // (`f_status=eq%3Areception`) — than the table writes (`f_status=eq:reception`).
236
- // Comparing raw strings then NEVER matches, so the table's write effect and
237
- // the router's re-serialize fight forever ("Throttling navigation…" +
238
- // React #185). URLSearchParams decodes values, so a sorted `key=value` list
239
- // is stable across both spellings and lets us detect real no-ops.
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).
240
252
  const canonicalSearch = (searchStr: string): string => {
241
253
  const p = new URLSearchParams(searchStr)
242
254
  const entries: string[] = []
243
- p.forEach((value, key) => entries.push(`${key}=${value}`))
255
+ p.forEach((value, key) => {
256
+ const norm = key.startsWith('f_') ? urlValueToInternal(value) : value
257
+ entries.push(`${key}=${norm}`)
258
+ })
244
259
  entries.sort()
245
260
  return entries.join('&')
246
261
  }
@@ -298,7 +313,11 @@ export function DynamicTable({
298
313
  if (v) params.set(key, v)
299
314
  }
300
315
  if (pagination.pageIndex > 0) params.set('page', String(pagination.pageIndex + 1))
301
- if (pagination.pageSize !== 10) params.set('per_page', String(pagination.pageSize))
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))
302
321
  if (sorting.length > 0) {
303
322
  params.set('sortBy', sorting[0].id)
304
323
  params.set('order', sorting[0].desc ? 'desc' : 'asc')
@@ -389,6 +408,7 @@ export function DynamicTable({
389
408
  // localStorage.
390
409
  if (cached) {
391
410
  setMetadata(cached)
411
+ defaultPerPage.current = cached.defaultPerPage || 10
392
412
  if (!urlHadPerPage.current) setPagination((prev: PaginationState) => ({ ...prev, pageSize: cached.defaultPerPage || 10 }))
393
413
  setLoading(false)
394
414
  } else {
@@ -402,6 +422,7 @@ export function DynamicTable({
402
422
  meta = fresh
403
423
  setMetadata(fresh)
404
424
  cacheMetadata(model, fresh)
425
+ defaultPerPage.current = fresh.defaultPerPage || 10
405
426
  if (!urlHadPerPage.current) setPagination((prev: PaginationState) => ({ ...prev, pageSize: fresh.defaultPerPage || 10 }))
406
427
  }
407
428
  } catch (error) {