@atscript/vue-table 0.1.132 → 0.1.133

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.
Files changed (111) hide show
  1. package/dist/as-action-form-dialog.cjs +42 -30
  2. package/dist/as-action-form-dialog.mjs +42 -30
  3. package/dist/{as-action-menu-content-4UfcabUe.mjs → as-action-menu-content-4D7CJNgr.mjs} +1 -1
  4. package/dist/{as-action-menu-content-C5qx9MPE.cjs → as-action-menu-content-DiS37BYK.cjs} +1 -1
  5. package/dist/as-column-menu.d.cts +1 -1
  6. package/dist/as-column-menu.d.mts +1 -1
  7. package/dist/{as-column-menu.vue-DL5N9mKR.d.mts → as-column-menu.vue-CZ8XqHl0.d.cts} +2 -2
  8. package/dist/{as-column-menu.vue-u_DNy3f-.d.cts → as-column-menu.vue-Cuelpv3p.d.mts} +2 -2
  9. package/dist/{as-config-dialog-BehfqIGd.mjs → as-config-dialog-BKsm8OHR.mjs} +30 -22
  10. package/dist/{as-config-dialog-BOYWPYYH.cjs → as-config-dialog-Bt8oP8qg.cjs} +29 -21
  11. package/dist/as-config-dialog.cjs +1 -1
  12. package/dist/as-config-dialog.mjs +1 -1
  13. package/dist/{as-confirm-dialog-bpN4A-KN.mjs → as-confirm-dialog-BV01t7dn.mjs} +12 -9
  14. package/dist/{as-confirm-dialog-UwhDHysk.cjs → as-confirm-dialog-Cw7KErhz.cjs} +12 -9
  15. package/dist/as-confirm-dialog.cjs +1 -1
  16. package/dist/as-confirm-dialog.mjs +1 -1
  17. package/dist/{as-filter-dialog-Dh2Ylthb.cjs → as-filter-dialog-Bxii-mYH.cjs} +31 -23
  18. package/dist/{as-filter-dialog-D1qzq-8b.mjs → as-filter-dialog-Ckrl79kU.mjs} +32 -24
  19. package/dist/as-filter-dialog.cjs +1 -1
  20. package/dist/as-filter-dialog.mjs +1 -1
  21. package/dist/{as-filter-field-DirBmnbb.mjs → as-filter-field-C-IFnj8K.mjs} +4 -4
  22. package/dist/{as-filter-field-DRsDJdpx.cjs → as-filter-field-xBr6TtKo.cjs} +4 -4
  23. package/dist/as-filter-field.cjs +1 -1
  24. package/dist/as-filter-field.mjs +1 -1
  25. package/dist/as-filters-DJEQj7_p.mjs +125 -0
  26. package/dist/as-filters-DuCk8nkk.cjs +130 -0
  27. package/dist/as-filters.cjs +1 -1
  28. package/dist/as-filters.d.cts +1 -1
  29. package/dist/as-filters.d.mts +1 -1
  30. package/dist/as-filters.mjs +1 -1
  31. package/dist/as-filters.vue-BG_ysCkC.d.mts +65 -0
  32. package/dist/as-filters.vue-DcxDnWAU.d.cts +65 -0
  33. package/dist/{as-preset-dialog-CDqCAR7N.mjs → as-preset-dialog-CCIacZ2C.mjs} +70 -20
  34. package/dist/{as-preset-dialog-DpM06jqR.cjs → as-preset-dialog-jZeyXJeJ.cjs} +74 -24
  35. package/dist/as-preset-dialog.cjs +1 -1
  36. package/dist/as-preset-dialog.mjs +1 -1
  37. package/dist/{as-preset-picker-jUS_YDX8.cjs → as-preset-picker-4xXU1KQz.cjs} +45 -21
  38. package/dist/{as-preset-picker-CISaxEZ0.mjs → as-preset-picker-DGSqQl9A.mjs} +39 -15
  39. package/dist/as-preset-picker.cjs +1 -1
  40. package/dist/as-preset-picker.mjs +1 -1
  41. package/dist/{as-row-actions-Bm6Bzyno.cjs → as-row-actions-B_OHWIAt.cjs} +2 -2
  42. package/dist/{as-row-actions-xqJeAdQl.mjs → as-row-actions-pukI1JT7.mjs} +2 -2
  43. package/dist/as-row-actions.cjs +1 -1
  44. package/dist/as-row-actions.mjs +1 -1
  45. package/dist/{as-table-BWUPzjRn.cjs → as-table-BejpMVid.cjs} +22 -4
  46. package/dist/{as-table-CDSG2yED.mjs → as-table-D5_bQ0p5.mjs} +22 -4
  47. package/dist/{as-table-actions-DMxeaczm.mjs → as-table-actions-DKYK_mTO.mjs} +57 -11
  48. package/dist/{as-table-actions-gV4lqqxc.cjs → as-table-actions-qb0gQz2D.cjs} +56 -10
  49. package/dist/as-table-actions.cjs +1 -1
  50. package/dist/as-table-actions.d.cts +1 -1
  51. package/dist/as-table-actions.d.mts +1 -1
  52. package/dist/as-table-actions.mjs +1 -1
  53. package/dist/{as-table-actions.vue-B-cgiCXP.d.cts → as-table-actions.vue-CckRRsL6.d.cts} +2 -2
  54. package/dist/{as-table-actions.vue-ClMa0OD1.d.mts → as-table-actions.vue-DsqauvXR.d.mts} +2 -2
  55. package/dist/{as-table-base-BaTE3VSN.mjs → as-table-base-B0arhJI8.mjs} +141 -46
  56. package/dist/{as-table-base-RG2TEIgV.cjs → as-table-base-Dfw012G5.cjs} +141 -46
  57. package/dist/{as-table-header-cell-CIqlO6Lw.cjs → as-table-header-cell-Bb4ddLmQ.cjs} +1 -1
  58. package/dist/{as-table-header-cell-Bdhl5Lox.mjs → as-table-header-cell-q-c_QklI.mjs} +1 -1
  59. package/dist/as-table-header-cell.cjs +1 -1
  60. package/dist/as-table-header-cell.d.cts +1 -1
  61. package/dist/as-table-header-cell.d.mts +1 -1
  62. package/dist/as-table-header-cell.mjs +1 -1
  63. package/dist/{as-table-header-cell.vue-BCszzxtU.d.mts → as-table-header-cell.vue-BjVOEFqz.d.mts} +1 -1
  64. package/dist/{as-table-header-cell.vue-BzTzfGRo.d.cts → as-table-header-cell.vue-qS1Xfc-U.d.cts} +1 -1
  65. package/dist/{as-table-root-BLReUbRH.mjs → as-table-root-Bpsrp0Vz.mjs} +5 -5
  66. package/dist/{as-table-root-DLJ_3Jf4.cjs → as-table-root-CUDO9ybm.cjs} +5 -5
  67. package/dist/as-table-root.cjs +1 -1
  68. package/dist/as-table-root.d.cts +1 -1
  69. package/dist/as-table-root.d.mts +1 -1
  70. package/dist/as-table-root.mjs +1 -1
  71. package/dist/{as-table-root.vue-QMT93qPk.d.mts → as-table-root.vue-BPU92_Ns.d.mts} +55 -12
  72. package/dist/{as-table-root.vue-D0T3n-Vm.d.cts → as-table-root.vue-szvjZsCn.d.cts} +55 -12
  73. package/dist/{as-table-status-DGTZlpyq.mjs → as-table-status-B9jlzPPV.mjs} +53 -37
  74. package/dist/{as-table-status-Du4WLF4k.cjs → as-table-status-BgDexT2x.cjs} +53 -37
  75. package/dist/as-table.cjs +1 -1
  76. package/dist/as-table.d.cts +1 -1
  77. package/dist/as-table.d.mts +1 -1
  78. package/dist/as-table.mjs +1 -1
  79. package/dist/{as-table.vue-Ds7MEp37.d.cts → as-table.vue-4WhMA8AM.d.mts} +27 -3
  80. package/dist/{as-table.vue-Ce5606ZO.d.mts → as-table.vue-_urybtQj.d.cts} +27 -3
  81. package/dist/{as-window-table-jD_y9yet.cjs → as-window-table-CXq6UuUx.cjs} +98 -19
  82. package/dist/{as-window-table-DWujiUQy.mjs → as-window-table-S1XWpDHQ.mjs} +98 -19
  83. package/dist/as-window-table.cjs +1 -1
  84. package/dist/as-window-table.d.cts +1 -1
  85. package/dist/as-window-table.d.mts +1 -1
  86. package/dist/as-window-table.mjs +1 -1
  87. package/dist/{as-window-table.vue-DfZF1Mux.d.mts → as-window-table.vue-CWsJvOkL.d.mts} +25 -2
  88. package/dist/{as-window-table.vue-Bhf9BK8E.d.cts → as-window-table.vue-h1bdyMv7.d.cts} +25 -2
  89. package/dist/error-message-CglODWl4.cjs +18 -0
  90. package/dist/error-message-hPSAu8Ug.mjs +13 -0
  91. package/dist/index.cjs +61 -29
  92. package/dist/index.d.cts +9 -9
  93. package/dist/index.d.mts +9 -9
  94. package/dist/index.mjs +62 -30
  95. package/dist/{types-CTMxE_zA.d.cts → types-BZ0HssRY.d.cts} +126 -4
  96. package/dist/{types-ByU4O0Yf.d.mts → types-CXz-4dPz.d.mts} +126 -4
  97. package/dist/{use-table-CyFcVtUN.mjs → use-table-Bk-jSxOM.mjs} +26 -11
  98. package/dist/{use-table-nvBq1dZ4.cjs → use-table-CdFFqaEx.cjs} +24 -9
  99. package/dist/{use-table-component-DO_kvKSf.cjs → use-table-component-DlLganVc.cjs} +1 -1
  100. package/dist/{use-table-component-K6xj1obF.mjs → use-table-component-KuzZxAP8.mjs} +1 -1
  101. package/dist/{use-table-nav-bridge-DeGeaQX-.mjs → use-table-nav-bridge-B9SSEajW.mjs} +1 -1
  102. package/dist/{use-table-nav-bridge-uOQUDooh.cjs → use-table-nav-bridge-CVUmTjDr.cjs} +1 -1
  103. package/dist/{use-table-state-Oi2ds4_a.cjs → use-table-state-D6dHOFrA.cjs} +329 -53
  104. package/dist/{use-table-state-BxX3SpeW.mjs → use-table-state-DLJbW753.mjs} +295 -55
  105. package/package.json +9 -9
  106. package/dist/as-filters-DCZTHjf5.cjs +0 -51
  107. package/dist/as-filters-beIud9sF.mjs +0 -46
  108. package/dist/as-filters.vue-DmECMd1v.d.mts +0 -10
  109. package/dist/as-filters.vue-fv-tRL2H.d.cts +0 -10
  110. package/dist/preset-aspect-display-IICxAlVo.mjs +0 -43
  111. package/dist/preset-aspect-display-TWwf6j1B.cjs +0 -72
@@ -1,6 +1,6 @@
1
- const require_use_table_state = require("./use-table-state-Oi2ds4_a.cjs");
1
+ const require_use_table_state = require("./use-table-state-D6dHOFrA.cjs");
2
2
  const require_get_cell_value = require("./get-cell-value-4rHKP-TG.cjs");
3
- const require_as_table_status = require("./as-table-status-Du4WLF4k.cjs");
3
+ const require_as_table_status = require("./as-table-status-BgDexT2x.cjs");
4
4
  let vue = require("vue");
5
5
  let _atscript_ui_table = require("@atscript/ui-table");
6
6
  let _vueuse_core = require("@vueuse/core");
@@ -160,6 +160,7 @@ const _hoisted_6 = [
160
160
  "id",
161
161
  "aria-rowindex",
162
162
  "aria-selected",
163
+ "data-selectable",
163
164
  "onClick",
164
165
  "onDblclick"
165
166
  ];
@@ -168,16 +169,24 @@ const _hoisted_7 = {
168
169
  class: "as-td-select",
169
170
  role: "gridcell"
170
171
  };
171
- const _hoisted_8 = {
172
+ const _hoisted_8 = [
173
+ "tabindex",
174
+ "aria-checked",
175
+ "aria-disabled",
176
+ "aria-label",
177
+ "title",
178
+ "onKeydown"
179
+ ];
180
+ const _hoisted_9 = {
172
181
  key: 0,
173
182
  class: "as-table-checkbox-tick",
174
183
  "aria-hidden": "true"
175
184
  };
176
- const _hoisted_9 = {
185
+ const _hoisted_10 = {
177
186
  key: 0,
178
187
  role: "gridcell"
179
188
  };
180
- const _hoisted_10 = {
189
+ const _hoisted_11 = {
181
190
  key: 3,
182
191
  class: "as-td-filler",
183
192
  role: "gridcell"
@@ -289,6 +298,14 @@ var as_table_base_default = /* @__PURE__ */ (0, vue.defineComponent)({
289
298
  type: Boolean,
290
299
  required: false,
291
300
  default: false
301
+ },
302
+ rowClass: {
303
+ type: Function,
304
+ required: false
305
+ },
306
+ rowAttrs: {
307
+ type: Function,
308
+ required: false
292
309
  }
293
310
  },
294
311
  emits: [
@@ -314,14 +331,69 @@ var as_table_base_default = /* @__PURE__ */ (0, vue.defineComponent)({
314
331
  const isListbox = (0, vue.computed)(() => props.renderMode === "listbox");
315
332
  const isRekaWrapped = (0, vue.computed)(() => isCombobox.value || isListbox.value);
316
333
  const hasValue = (0, vue.computed)(() => isRekaWrapped.value || props.select !== "none");
334
+ const SELECTABLE = Object.freeze({ ok: true });
335
+ const DEFAULT_ROW_META = Object.freeze({
336
+ selectable: SELECTABLE,
337
+ cls: void 0,
338
+ attrs: void 0
339
+ });
340
+ const rowHooks = (0, vue.computed)(() => {
341
+ const rows = props.rows;
342
+ if (!isStandalone.value) return {
343
+ metas: [],
344
+ selectable: rows.length,
345
+ selected: 0
346
+ };
347
+ const gated = !!ctx?.state.rowSelectable.value;
348
+ if (!gated && !props.rowClass && !props.rowAttrs) {
349
+ let selected = 0;
350
+ for (const row of rows) if (isPkSelected(row)) selected++;
351
+ return {
352
+ metas: [],
353
+ selectable: rows.length,
354
+ selected
355
+ };
356
+ }
357
+ let selectable = 0;
358
+ let selected = 0;
359
+ return {
360
+ metas: rows.map((row, index) => {
361
+ const hookCtx = {
362
+ index,
363
+ get selected() {
364
+ return isPkSelected(row);
365
+ }
366
+ };
367
+ const verdict = gated ? ctx.state.isRowSelectable(row, index) : SELECTABLE;
368
+ if (verdict.ok) {
369
+ selectable++;
370
+ if (isPkSelected(row)) selected++;
371
+ }
372
+ return {
373
+ selectable: verdict,
374
+ cls: props.rowClass?.(row, hookCtx),
375
+ attrs: props.rowAttrs?.(row, hookCtx)
376
+ };
377
+ }),
378
+ selectable,
379
+ selected
380
+ };
381
+ });
382
+ function metaFor(index) {
383
+ return rowHooks.value.metas[index] ?? DEFAULT_ROW_META;
384
+ }
385
+ /** Accessible name for a row's selection control, carrying the disabled reason. */
386
+ function selectLabel(verdict) {
387
+ if (verdict.ok) return "Select row";
388
+ return verdict.reason ? `Select row, ${verdict.reason}` : "Select row, not selectable";
389
+ }
317
390
  const hasActiveFilters = (0, vue.computed)(() => props.filters ? (0, _atscript_ui_table.filledFilterCount)(props.filters) > 0 : false);
318
391
  const showSelectAllCheckbox = (0, vue.computed)(() => isStandalone.value && props.select === "multi" && !!props.selectedRows);
319
392
  const selectAllState = (0, vue.computed)(() => {
320
393
  if (!showSelectAllCheckbox.value) return void 0;
321
- const sel = props.selectedRows ?? [];
322
- if (sel.length === 0) return "none";
323
- if (sel.length === props.rows.length && props.rows.length > 0) return "all";
324
- return "some";
394
+ if ((props.selectedRows ?? []).length === 0) return "none";
395
+ const { selectable, selected } = rowHooks.value;
396
+ return selectable > 0 && selected >= selectable ? "all" : "some";
325
397
  });
326
398
  const emit = __emit;
327
399
  const slots = (0, vue.useSlots)();
@@ -360,6 +432,11 @@ var as_table_base_default = /* @__PURE__ */ (0, vue.defineComponent)({
360
432
  if (!isStandalone.value || !ctx) return;
361
433
  ctx.state.handleNavKey(event, { mode: props.select });
362
434
  }
435
+ /** Space on a row's selection control toggles just that row. */
436
+ function onSelectCellKeydown(event, index) {
437
+ if (!ctx) return;
438
+ require_use_table_state.onSelectControlKeydown(event, index, props.select, ctx.state);
439
+ }
363
440
  const scrollContainerRef = (0, vue.ref)(null);
364
441
  function alignActiveRow(idx) {
365
442
  if (!ctx) return;
@@ -597,50 +674,68 @@ var as_table_base_default = /* @__PURE__ */ (0, vue.defineComponent)({
597
674
  "aria-activedescendant": ariaActiveDescendant.value,
598
675
  onKeydown: onTbodyKeydown
599
676
  }, {
600
- default: (0, vue.withCtx)(({ item, index, spaceBefore }) => [(0, vue.createElementVNode)("tr", {
601
- id: rowIdFor(index),
602
- role: "row",
603
- "aria-rowindex": index + 2,
604
- "aria-selected": ariaSelectedFor(item),
605
- class: (0, vue.normalizeClass)({ "as-table-row-active": isActiveRow(index) }),
606
- style: (0, vue.normalizeStyle)({
607
- height: __props.virtualRowHeight ? `${__props.virtualRowHeight}px` : void 0,
608
- transform: spaceBefore ? `translateY(${spaceBefore}px)` : void 0
609
- }),
610
- onClick: ($event) => onRowClick(item, $event, index),
611
- onDblclick: ($event) => onRowDblClick(item, $event, index)
612
- }, [
613
- hasValue.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_7, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["as-table-checkbox", { "as-table-checkbox-checked": isPkSelected(item) }]) }, [isPkSelected(item) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8)) : (0, vue.createCommentVNode)("v-if", true)], 2)])) : (0, vue.createCommentVNode)("v-if", true),
614
- (0, vue.unref)(hasAnyCellBindings) ? ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(__props.columns, (col) => {
615
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: col.path }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)([(0, vue.unref)(cellResolver)(col, item, index)], (bindings) => {
616
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [cellSlotFlags.value[col.path] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", (0, vue.mergeProps)({
617
- key: 0,
618
- role: "gridcell"
619
- }, { ref_for: true }, bindings), [(0, vue.renderSlot)(_ctx.$slots, `cell-${col.path}`, {
677
+ default: (0, vue.withCtx)(({ item, index, spaceBefore }) => [(0, vue.createCommentVNode)("\n `meta` is resolved ONCE per row (the row's hook results + its\n selectability verdict); `v-bind=\"meta.attrs\"` comes FIRST so every\n framework binding after it wins — consumer attrs can decorate a\n row but can never rewrite its id / role / aria / data contract.\n "), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)([metaFor(index)], (meta) => {
678
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", (0, vue.mergeProps)({
679
+ key: 0,
680
+ ref_for: true
681
+ }, meta.attrs, {
682
+ id: rowIdFor(index),
683
+ role: "row",
684
+ "aria-rowindex": index + 2,
685
+ "aria-selected": ariaSelectedFor(item),
686
+ "data-selectable": meta.selectable.ok ? void 0 : "false",
687
+ class: [{ "as-table-row-active": isActiveRow(index) }, meta.cls],
688
+ style: {
689
+ height: __props.virtualRowHeight ? `${__props.virtualRowHeight}px` : void 0,
690
+ transform: spaceBefore ? `translateY(${spaceBefore}px)` : void 0
691
+ },
692
+ onClick: ($event) => onRowClick(item, $event, index),
693
+ onDblclick: ($event) => onRowDblClick(item, $event, index)
694
+ }), [
695
+ hasValue.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_7, [(0, vue.createElementVNode)("span", {
696
+ class: (0, vue.normalizeClass)(["as-table-checkbox", {
697
+ "as-table-checkbox-checked": isPkSelected(item),
698
+ "as-table-checkbox-disabled": !meta.selectable.ok
699
+ }]),
700
+ role: "checkbox",
701
+ tabindex: meta.selectable.ok ? 0 : void 0,
702
+ "aria-checked": isPkSelected(item) ? "true" : "false",
703
+ "aria-disabled": meta.selectable.ok ? void 0 : "true",
704
+ "aria-label": selectLabel(meta.selectable),
705
+ title: meta.selectable.reason,
706
+ onKeydown: ($event) => onSelectCellKeydown($event, index)
707
+ }, [isPkSelected(item) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_9)) : (0, vue.createCommentVNode)("v-if", true)], 42, _hoisted_8)])) : (0, vue.createCommentVNode)("v-if", true),
708
+ (0, vue.unref)(hasAnyCellBindings) ? ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(__props.columns, (col) => {
709
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: col.path }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)([(0, vue.unref)(cellResolver)(col, item, index)], (bindings) => {
710
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [cellSlotFlags.value[col.path] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", (0, vue.mergeProps)({
711
+ key: 0,
712
+ role: "gridcell"
713
+ }, { ref_for: true }, bindings), [(0, vue.renderSlot)(_ctx.$slots, `cell-${col.path}`, {
714
+ row: item,
715
+ value: (0, vue.unref)(require_get_cell_value.getCellValue)(item, col.path),
716
+ column: col
717
+ })], 16)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(cellComponents)[col.path]), (0, vue.mergeProps)({
718
+ key: 1,
719
+ row: item,
720
+ column: col,
721
+ role: "gridcell"
722
+ }, { ref_for: true }, bindings), null, 16, ["row", "column"]))], 64);
723
+ }), 128))], 64);
724
+ }), 128)) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(__props.columns, (col) => {
725
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: col.path }, [cellSlotFlags.value[col.path] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_10, [(0, vue.renderSlot)(_ctx.$slots, `cell-${col.path}`, {
620
726
  row: item,
621
727
  value: (0, vue.unref)(require_get_cell_value.getCellValue)(item, col.path),
622
728
  column: col
623
- })], 16)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(cellComponents)[col.path]), (0, vue.mergeProps)({
729
+ })])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(cellComponents)[col.path]), {
624
730
  key: 1,
625
731
  row: item,
626
732
  column: col,
627
733
  role: "gridcell"
628
- }, { ref_for: true }, bindings), null, 16, ["row", "column"]))], 64);
629
- }), 128))], 64);
630
- }), 128)) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(__props.columns, (col) => {
631
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: col.path }, [cellSlotFlags.value[col.path] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_9, [(0, vue.renderSlot)(_ctx.$slots, `cell-${col.path}`, {
632
- row: item,
633
- value: (0, vue.unref)(require_get_cell_value.getCellValue)(item, col.path),
634
- column: col
635
- })])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(cellComponents)[col.path]), {
636
- key: 1,
637
- row: item,
638
- column: col,
639
- role: "gridcell"
640
- }, null, 8, ["row", "column"]))], 64);
641
- }), 128)),
642
- __props.stretch ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_10)) : (0, vue.createCommentVNode)("v-if", true)
643
- ], 46, _hoisted_6)]),
734
+ }, null, 8, ["row", "column"]))], 64);
735
+ }), 128)),
736
+ __props.stretch ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("td", _hoisted_11)) : (0, vue.createCommentVNode)("v-if", true)
737
+ ], 16, _hoisted_6);
738
+ }), 128))]),
644
739
  _: 3
645
740
  }, 8, [
646
741
  "options",
@@ -1,4 +1,4 @@
1
- const require_use_table_component = require("./use-table-component-DO_kvKSf.cjs");
1
+ const require_use_table_component = require("./use-table-component-DlLganVc.cjs");
2
2
  const require_as_column_menu = require("./as-column-menu-fZVbJ9lU.cjs");
3
3
  let vue = require("vue");
4
4
  let _atscript_ui_table = require("@atscript/ui-table");
@@ -1,4 +1,4 @@
1
- import { t as useTableComponent } from "./use-table-component-K6xj1obF.mjs";
1
+ import { t as useTableComponent } from "./use-table-component-KuzZxAP8.mjs";
2
2
  import { t as as_column_menu_default } from "./as-column-menu-kTIQfLTi.mjs";
3
3
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, resolveDynamicComponent, toDisplayString, unref, withCtx, withModifiers } from "vue";
4
4
  import { isFilled } from "@atscript/ui-table";
@@ -1,2 +1,2 @@
1
- const require_as_table_header_cell = require("./as-table-header-cell-CIqlO6Lw.cjs");
1
+ const require_as_table_header_cell = require("./as-table-header-cell-Bb4ddLmQ.cjs");
2
2
  module.exports = require_as_table_header_cell.as_table_header_cell_default;
@@ -1,2 +1,2 @@
1
- import { t as _default } from "./as-table-header-cell.vue-BzTzfGRo.cjs";
1
+ import { t as _default } from "./as-table-header-cell.vue-qS1Xfc-U.cjs";
2
2
  export = _default;
@@ -1,2 +1,2 @@
1
- import { t as _default } from "./as-table-header-cell.vue-BCszzxtU.mjs";
1
+ import { t as _default } from "./as-table-header-cell.vue-BjVOEFqz.mjs";
2
2
  export { _default as default };
@@ -1,2 +1,2 @@
1
- import { t as as_table_header_cell_default } from "./as-table-header-cell-Bdhl5Lox.mjs";
1
+ import { t as as_table_header_cell_default } from "./as-table-header-cell-q-c_QklI.mjs";
2
2
  export { as_table_header_cell_default as default };
@@ -1,4 +1,4 @@
1
- import { n as ColumnMenuConfig } from "./types-ByU4O0Yf.mjs";
1
+ import { n as ColumnMenuConfig } from "./types-CXz-4dPz.mjs";
2
2
  import { ColumnDef } from "@atscript/ui";
3
3
  import * as vue from "vue";
4
4
  import { ColumnWidthEntry, FilterCondition } from "@atscript/ui-table";
@@ -1,4 +1,4 @@
1
- import { n as ColumnMenuConfig } from "./types-CTMxE_zA.cjs";
1
+ import { n as ColumnMenuConfig } from "./types-BZ0HssRY.cjs";
2
2
  import * as vue from "vue";
3
3
  import { ColumnDef } from "@atscript/ui";
4
4
  import { ColumnWidthEntry, FilterCondition } from "@atscript/ui-table";
@@ -1,8 +1,8 @@
1
- import { i as useRegisterMainActionListener } from "./use-table-state-BxX3SpeW.mjs";
2
- import { n as useTable } from "./use-table-CyFcVtUN.mjs";
3
- import { t as useTableNavBridge } from "./use-table-nav-bridge-DeGeaQX-.mjs";
1
+ import { i as useRegisterMainActionListener } from "./use-table-state-DLJbW753.mjs";
2
+ import { n as useTable } from "./use-table-Bk-jSxOM.mjs";
3
+ import { t as useTableNavBridge } from "./use-table-nav-bridge-B9SSEajW.mjs";
4
4
  import { t as useHasEmitListener } from "./use-has-emit-listener-BTMDIv2e.mjs";
5
- import { t as as_confirm_dialog_default } from "./as-confirm-dialog-bpN4A-KN.mjs";
5
+ import { t as as_confirm_dialog_default } from "./as-confirm-dialog-BV01t7dn.mjs";
6
6
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineAsyncComponent, defineComponent, mergeModels, openBlock, ref, renderSlot, resolveDynamicComponent, unref, useModel, watch } from "vue";
7
7
  //#endregion
8
8
  //#region src/components/as-table-root.vue
@@ -183,7 +183,7 @@ var as_table_root_default = /* @__PURE__ */ defineComponent({
183
183
  alwaysSelected: props.alwaysSelected,
184
184
  queryFn: props.queryFn,
185
185
  queryOnMount: props.queryOnMount,
186
- blockQuery: props.blockQuery,
186
+ blockQuery: () => props.blockQuery === true,
187
187
  blockSize: props.blockSize,
188
188
  dragReleaseDebounceMs: props.dragReleaseDebounceMs,
189
189
  clientFactory: props.clientFactory,
@@ -1,8 +1,8 @@
1
- const require_use_table_state = require("./use-table-state-Oi2ds4_a.cjs");
2
- const require_use_table = require("./use-table-nvBq1dZ4.cjs");
3
- const require_use_table_nav_bridge = require("./use-table-nav-bridge-uOQUDooh.cjs");
1
+ const require_use_table_state = require("./use-table-state-D6dHOFrA.cjs");
2
+ const require_use_table = require("./use-table-CdFFqaEx.cjs");
3
+ const require_use_table_nav_bridge = require("./use-table-nav-bridge-CVUmTjDr.cjs");
4
4
  const require_use_has_emit_listener = require("./use-has-emit-listener-hhOH8CBI.cjs");
5
- const require_as_confirm_dialog = require("./as-confirm-dialog-UwhDHysk.cjs");
5
+ const require_as_confirm_dialog = require("./as-confirm-dialog-Cw7KErhz.cjs");
6
6
  let vue = require("vue");
7
7
  //#endregion
8
8
  //#region src/components/as-table-root.vue
@@ -183,7 +183,7 @@ var as_table_root_default = /* @__PURE__ */ (0, vue.defineComponent)({
183
183
  alwaysSelected: props.alwaysSelected,
184
184
  queryFn: props.queryFn,
185
185
  queryOnMount: props.queryOnMount,
186
- blockQuery: props.blockQuery,
186
+ blockQuery: () => props.blockQuery === true,
187
187
  blockSize: props.blockSize,
188
188
  dragReleaseDebounceMs: props.dragReleaseDebounceMs,
189
189
  clientFactory: props.clientFactory,
@@ -1,2 +1,2 @@
1
- const require_as_table_root = require("./as-table-root-DLJ_3Jf4.cjs");
1
+ const require_as_table_root = require("./as-table-root-CUDO9ybm.cjs");
2
2
  module.exports = require_as_table_root.as_table_root_default;
@@ -1,2 +1,2 @@
1
- import { t as _default } from "./as-table-root.vue-D0T3n-Vm.cjs";
1
+ import { t as _default } from "./as-table-root.vue-szvjZsCn.cjs";
2
2
  export = _default;
@@ -1,2 +1,2 @@
1
- import { t as _default } from "./as-table-root.vue-QMT93qPk.mjs";
1
+ import { t as _default } from "./as-table-root.vue-BPU92_Ns.mjs";
2
2
  export { _default as default };
@@ -1,2 +1,2 @@
1
- import { t as as_table_root_default } from "./as-table-root-BLReUbRH.mjs";
1
+ import { t as as_table_root_default } from "./as-table-root-Bpsrp0Vz.mjs";
2
2
  export { as_table_root_default as default };
@@ -1,4 +1,4 @@
1
- import { _ as TableNavBridge, c as NavKeyOptions, d as ReactiveTableState, g as TableActionsState, h as TVueTableActionInfo, i as ConfirmOptions, l as PresetConfig, m as TAsTableControls, p as TAsCellTypeComponents, t as ActionResult } from "./types-ByU4O0Yf.mjs";
1
+ import { S as TableNavBridge, b as TVueTableActionInfo, c as NavKeyOptions, d as ReactiveTableState, i as ConfirmOptions, l as PresetConfig, t as ActionResult, v as TAsCellTypeComponents, x as TableActionsState, y as TAsTableControls } from "./types-CXz-4dPz.mjs";
2
2
  import * as _atscript_ui0 from "@atscript/ui";
3
3
  import { ClientFactory, ClientFactory as ClientFactory$1, ColumnDef, SortControl, TableDef, getDefaultClientFactory, resetDefaultClientFactory, setDefaultClientFactory } from "@atscript/ui";
4
4
  import * as vue from "vue";
@@ -30,6 +30,14 @@ interface UseLocalDraftOptions {
30
30
  enabled: Ref<boolean> | boolean;
31
31
  /** Persisted-aspect gate — drafts only persist aspects in this set. */
32
32
  availableAspects: readonly PresetAspect[];
33
+ /**
34
+ * Identity the draft is keyed under — pass the signed-in user's id so a
35
+ * shared browser never restores the previous user's draft. Reactive: when
36
+ * it changes, reads and writes move to the new key and the old scope's
37
+ * draft is neither read nor overwritten. Omitted (or `undefined`) keeps
38
+ * the pre-0.1.133 unscoped key. Since 0.1.133.
39
+ */
40
+ scope?: MaybeRefOrGetter<string | undefined>;
33
41
  /** Optional debounce override (default 300ms). */
34
42
  debounceMs?: number;
35
43
  /**
@@ -67,8 +75,9 @@ interface UseLocalDraftReturn {
67
75
  }
68
76
  /**
69
77
  * localStorage overlay manager for table presets. One overlay per
70
- * `(app, tableKey)`; switching presets clears it (caller's responsibility
71
- * — this composable only tracks state, not which preset is active).
78
+ * `(app, scope, tableKey)`; switching presets clears it (caller's
79
+ * responsibility — this composable only tracks state, not which preset is
80
+ * active).
72
81
  */
73
82
  declare function useLocalDraft(opts: UseLocalDraftOptions): UseLocalDraftReturn;
74
83
  //#endregion
@@ -104,6 +113,11 @@ interface UsePresetsReturn {
104
113
  /** False on 401/403 from initial load — UI hides itself. */
105
114
  available: ComputedRef<boolean>;
106
115
  loading: Ref<boolean>;
116
+ /**
117
+ * Load failure from `reload()`, or null. Mutator failures are NOT recorded
118
+ * here — they rethrow, and the table-state slice's
119
+ * `state.preset.lastError` is the single channel UI renders.
120
+ */
107
121
  error: Ref<unknown>;
108
122
  /**
109
123
  * Authoritative current-user id. `capabilities.userId` when caps are
@@ -212,8 +226,12 @@ interface TableQueryOptions {
212
226
  * meta), regardless of which columns are visible. Additive only.
213
227
  */
214
228
  alwaysSelected?: string[];
215
- /** When true, all triggers (query/queryNext/loadRange) early-return. */
216
- blockQuery?: boolean;
229
+ /**
230
+ * When true, all triggers (query/queryNext/loadRange) early-return. Pass a
231
+ * getter (or a ref) to keep it reactive: when it flips back to `false` the
232
+ * table runs the query it was holding back, once. Since 0.1.133.
233
+ */
234
+ blockQuery?: boolean | (() => boolean);
217
235
  /**
218
236
  * Configured default for the runtime `ignoreSortersWhenSearched` flag
219
237
  * (default `false`). Opt-in for relevance-ranked backends: while the
@@ -275,6 +293,8 @@ interface TablePresetOptions {
275
293
  draftHandle?: UseLocalDraftReturn | null;
276
294
  /** App-declared aspect set; default `['columns','filters','filterOps','sorters']`. */
277
295
  availableAspects?: PresetAspect[];
296
+ /** Aspects a system preset owns; intersected with `availableAspects`, default all. */
297
+ systemAspects?: PresetAspect[];
278
298
  /** Static fallback for system presets when no `presetsHandle` is wired. */
279
299
  fallbackSystemPresets?: SystemPreset[];
280
300
  /** Whether localStorage drafts should be hydrated + persisted on bootstrap. */
@@ -420,8 +440,12 @@ interface UseTableOptions {
420
440
  queryFn?: QueryFn;
421
441
  /** Auto-query when metadata loads (default: true). */
422
442
  queryOnMount?: boolean;
423
- /** When true, all triggers (query/queryNext/loadRange) early-return. */
424
- blockQuery?: boolean;
443
+ /**
444
+ * When true, all triggers (query/queryNext/loadRange) early-return. Pass a
445
+ * getter to keep it reactive — the held-back query runs once when it flips
446
+ * back to `false`. Since 0.1.133.
447
+ */
448
+ blockQuery?: boolean | (() => boolean);
425
449
  /** Page-alignment unit for `loadRange` and the `queryNext` extension. */
426
450
  blockSize?: number;
427
451
  /** Debounce window for the topIndex/viewportRowCount watcher. */
@@ -1982,6 +2006,14 @@ interface UseTableUrlQueryOptions {
1982
2006
  * navigation-recoverable.
1983
2007
  */
1984
2008
  mode?: "replace" | "push";
2009
+ /**
2010
+ * Namespace for this table's query keys. With `prefix: "t1"` every key the
2011
+ * bridge writes becomes `t1.<key>` (`t1.$skip`, `t1.status`, `t1.total>100`)
2012
+ * and only keys under that prefix are read back or removed. Keys with no
2013
+ * prefix — or another table's prefix — are foreign and preserved untouched,
2014
+ * so two tables can share one route. Since 0.1.133.
2015
+ */
2016
+ prefix?: string;
1985
2017
  }
1986
2018
  /**
1987
2019
  * Bridge `<AsTableRoot v-model:url-query>` to vue-router. Uses **type-only**
@@ -1989,11 +2021,19 @@ interface UseTableUrlQueryOptions {
1989
2021
  * dependency on `vue-router` is added to `@atscript/vue-table`. Consumers
1990
2022
  * pass in their already-resolved `useRoute()` and `useRouter()` instances.
1991
2023
  *
1992
- * Scope: **owns the whole query string**. The getter returns the entire
1993
- * `route.query` serialized; the setter replaces `route.query` wholesale.
1994
- * Apps that need the table to coexist with non-table query params should
1995
- * write their own `computed` instead — that pattern is small and keeps the
1996
- * library's contract crisp.
2024
+ * Scope: the bridge owns **only what the table reads back** (since 0.1.133).
2025
+ * A key is the table's iff `urlQueryStringToState` would consume it — see
2026
+ * `urlQueryConsumesKey` — or the bridge has written it before. Read and
2027
+ * write therefore agree in both directions: a key the parser ignores (a host
2028
+ * flag, an unrecognised `$control`) is never removed, and a key the parser
2029
+ * reads is the bridge's to remove. Every write merges into the current
2030
+ * `route.query`, in place.
2031
+ *
2032
+ * Without a `prefix` the bridge cannot tell a plain `field=value` filter from
2033
+ * a page-owned flag of the same shape, so a plain key that was already in the
2034
+ * URL when the bridge mounted counts as foreign until the bridge writes it
2035
+ * itself. Pass `prefix` when a route carries a host key that could collide
2036
+ * with a column path, or when it carries two tables.
1997
2037
  *
1998
2038
  * @example
1999
2039
  * ```vue
@@ -2001,6 +2041,9 @@ interface UseTableUrlQueryOptions {
2001
2041
  * import { useRoute, useRouter } from 'vue-router';
2002
2042
  * import { useTableUrlQuery } from '@atscript/vue-table';
2003
2043
  * const urlQuery = useTableUrlQuery(useRoute(), useRouter());
2044
+ * // Two tables on one route:
2045
+ * // const left = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'a' });
2046
+ * // const right = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'b' });
2004
2047
  * </script>
2005
2048
  * <template>
2006
2049
  * <AsTableRoot v-model:url-query="urlQuery" url="/db/products" .../>
@@ -1,4 +1,4 @@
1
- import { _ as TableNavBridge, c as NavKeyOptions, d as ReactiveTableState, g as TableActionsState, h as TVueTableActionInfo, i as ConfirmOptions, l as PresetConfig, m as TAsTableControls, p as TAsCellTypeComponents, t as ActionResult } from "./types-CTMxE_zA.cjs";
1
+ import { S as TableNavBridge, b as TVueTableActionInfo, c as NavKeyOptions, d as ReactiveTableState, i as ConfirmOptions, l as PresetConfig, t as ActionResult, v as TAsCellTypeComponents, x as TableActionsState, y as TAsTableControls } from "./types-BZ0HssRY.cjs";
2
2
  import * as vue from "vue";
3
3
  import { AllowedComponentProps, AnchorHTMLAttributes, App, Component as Component$1, ComponentCustomProps, ComponentPublicInstance, ComputedRef, DefineComponent, InjectionKey, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef, UnwrapRef, VNode, VNodeProps, WritableComputedRef } from "vue";
4
4
  import { Client, PageResult, TDbActionInfo } from "@atscript/db-client";
@@ -30,6 +30,14 @@ interface UseLocalDraftOptions {
30
30
  enabled: Ref<boolean> | boolean;
31
31
  /** Persisted-aspect gate — drafts only persist aspects in this set. */
32
32
  availableAspects: readonly PresetAspect[];
33
+ /**
34
+ * Identity the draft is keyed under — pass the signed-in user's id so a
35
+ * shared browser never restores the previous user's draft. Reactive: when
36
+ * it changes, reads and writes move to the new key and the old scope's
37
+ * draft is neither read nor overwritten. Omitted (or `undefined`) keeps
38
+ * the pre-0.1.133 unscoped key. Since 0.1.133.
39
+ */
40
+ scope?: MaybeRefOrGetter<string | undefined>;
33
41
  /** Optional debounce override (default 300ms). */
34
42
  debounceMs?: number;
35
43
  /**
@@ -67,8 +75,9 @@ interface UseLocalDraftReturn {
67
75
  }
68
76
  /**
69
77
  * localStorage overlay manager for table presets. One overlay per
70
- * `(app, tableKey)`; switching presets clears it (caller's responsibility
71
- * — this composable only tracks state, not which preset is active).
78
+ * `(app, scope, tableKey)`; switching presets clears it (caller's
79
+ * responsibility — this composable only tracks state, not which preset is
80
+ * active).
72
81
  */
73
82
  declare function useLocalDraft(opts: UseLocalDraftOptions): UseLocalDraftReturn;
74
83
  //#endregion
@@ -104,6 +113,11 @@ interface UsePresetsReturn {
104
113
  /** False on 401/403 from initial load — UI hides itself. */
105
114
  available: ComputedRef<boolean>;
106
115
  loading: Ref<boolean>;
116
+ /**
117
+ * Load failure from `reload()`, or null. Mutator failures are NOT recorded
118
+ * here — they rethrow, and the table-state slice's
119
+ * `state.preset.lastError` is the single channel UI renders.
120
+ */
107
121
  error: Ref<unknown>;
108
122
  /**
109
123
  * Authoritative current-user id. `capabilities.userId` when caps are
@@ -212,8 +226,12 @@ interface TableQueryOptions {
212
226
  * meta), regardless of which columns are visible. Additive only.
213
227
  */
214
228
  alwaysSelected?: string[];
215
- /** When true, all triggers (query/queryNext/loadRange) early-return. */
216
- blockQuery?: boolean;
229
+ /**
230
+ * When true, all triggers (query/queryNext/loadRange) early-return. Pass a
231
+ * getter (or a ref) to keep it reactive: when it flips back to `false` the
232
+ * table runs the query it was holding back, once. Since 0.1.133.
233
+ */
234
+ blockQuery?: boolean | (() => boolean);
217
235
  /**
218
236
  * Configured default for the runtime `ignoreSortersWhenSearched` flag
219
237
  * (default `false`). Opt-in for relevance-ranked backends: while the
@@ -275,6 +293,8 @@ interface TablePresetOptions {
275
293
  draftHandle?: UseLocalDraftReturn | null;
276
294
  /** App-declared aspect set; default `['columns','filters','filterOps','sorters']`. */
277
295
  availableAspects?: PresetAspect[];
296
+ /** Aspects a system preset owns; intersected with `availableAspects`, default all. */
297
+ systemAspects?: PresetAspect[];
278
298
  /** Static fallback for system presets when no `presetsHandle` is wired. */
279
299
  fallbackSystemPresets?: SystemPreset[];
280
300
  /** Whether localStorage drafts should be hydrated + persisted on bootstrap. */
@@ -420,8 +440,12 @@ interface UseTableOptions {
420
440
  queryFn?: QueryFn;
421
441
  /** Auto-query when metadata loads (default: true). */
422
442
  queryOnMount?: boolean;
423
- /** When true, all triggers (query/queryNext/loadRange) early-return. */
424
- blockQuery?: boolean;
443
+ /**
444
+ * When true, all triggers (query/queryNext/loadRange) early-return. Pass a
445
+ * getter to keep it reactive — the held-back query runs once when it flips
446
+ * back to `false`. Since 0.1.133.
447
+ */
448
+ blockQuery?: boolean | (() => boolean);
425
449
  /** Page-alignment unit for `loadRange` and the `queryNext` extension. */
426
450
  blockSize?: number;
427
451
  /** Debounce window for the topIndex/viewportRowCount watcher. */
@@ -1982,6 +2006,14 @@ interface UseTableUrlQueryOptions {
1982
2006
  * navigation-recoverable.
1983
2007
  */
1984
2008
  mode?: "replace" | "push";
2009
+ /**
2010
+ * Namespace for this table's query keys. With `prefix: "t1"` every key the
2011
+ * bridge writes becomes `t1.<key>` (`t1.$skip`, `t1.status`, `t1.total>100`)
2012
+ * and only keys under that prefix are read back or removed. Keys with no
2013
+ * prefix — or another table's prefix — are foreign and preserved untouched,
2014
+ * so two tables can share one route. Since 0.1.133.
2015
+ */
2016
+ prefix?: string;
1985
2017
  }
1986
2018
  /**
1987
2019
  * Bridge `<AsTableRoot v-model:url-query>` to vue-router. Uses **type-only**
@@ -1989,11 +2021,19 @@ interface UseTableUrlQueryOptions {
1989
2021
  * dependency on `vue-router` is added to `@atscript/vue-table`. Consumers
1990
2022
  * pass in their already-resolved `useRoute()` and `useRouter()` instances.
1991
2023
  *
1992
- * Scope: **owns the whole query string**. The getter returns the entire
1993
- * `route.query` serialized; the setter replaces `route.query` wholesale.
1994
- * Apps that need the table to coexist with non-table query params should
1995
- * write their own `computed` instead — that pattern is small and keeps the
1996
- * library's contract crisp.
2024
+ * Scope: the bridge owns **only what the table reads back** (since 0.1.133).
2025
+ * A key is the table's iff `urlQueryStringToState` would consume it — see
2026
+ * `urlQueryConsumesKey` — or the bridge has written it before. Read and
2027
+ * write therefore agree in both directions: a key the parser ignores (a host
2028
+ * flag, an unrecognised `$control`) is never removed, and a key the parser
2029
+ * reads is the bridge's to remove. Every write merges into the current
2030
+ * `route.query`, in place.
2031
+ *
2032
+ * Without a `prefix` the bridge cannot tell a plain `field=value` filter from
2033
+ * a page-owned flag of the same shape, so a plain key that was already in the
2034
+ * URL when the bridge mounted counts as foreign until the bridge writes it
2035
+ * itself. Pass `prefix` when a route carries a host key that could collide
2036
+ * with a column path, or when it carries two tables.
1997
2037
  *
1998
2038
  * @example
1999
2039
  * ```vue
@@ -2001,6 +2041,9 @@ interface UseTableUrlQueryOptions {
2001
2041
  * import { useRoute, useRouter } from 'vue-router';
2002
2042
  * import { useTableUrlQuery } from '@atscript/vue-table';
2003
2043
  * const urlQuery = useTableUrlQuery(useRoute(), useRouter());
2044
+ * // Two tables on one route:
2045
+ * // const left = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'a' });
2046
+ * // const right = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'b' });
2004
2047
  * </script>
2005
2048
  * <template>
2006
2049
  * <AsTableRoot v-model:url-query="urlQuery" url="/db/products" .../>