@atscript/vue-table 0.1.140 → 0.1.142

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 (113) hide show
  1. package/dist/as-action-form-dialog.cjs +1 -1
  2. package/dist/as-action-form-dialog.mjs +1 -1
  3. package/dist/{as-action-menu-content-B5RFt7Hc.cjs → as-action-menu-content-BaNFBYn3.cjs} +1 -1
  4. package/dist/{as-action-menu-content-KNJr1EkO.mjs → as-action-menu-content-miQYVP7x.mjs} +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-DKQNY5gh.d.cts → as-column-menu.vue-Ca3wSDxw.d.mts} +2 -2
  8. package/dist/{as-column-menu.vue-hOmuVcKf.d.mts → as-column-menu.vue-Co7FtiEM.d.cts} +2 -2
  9. package/dist/{as-config-dialog-B1KJJSHE.cjs → as-config-dialog-CVSOrS9O.cjs} +2 -2
  10. package/dist/{as-config-dialog-JXNkCCrh.mjs → as-config-dialog-Di02V3bs.mjs} +2 -2
  11. package/dist/as-config-dialog.cjs +1 -1
  12. package/dist/as-config-dialog.mjs +1 -1
  13. package/dist/{as-confirm-dialog-CC6U8nLZ.mjs → as-confirm-dialog-C9Qat2rG.mjs} +1 -1
  14. package/dist/{as-confirm-dialog-a736BLOm.cjs → as-confirm-dialog-fCwo1ty9.cjs} +1 -1
  15. package/dist/as-confirm-dialog.cjs +1 -1
  16. package/dist/as-confirm-dialog.mjs +1 -1
  17. package/dist/{as-filter-dialog-IflKkOd8.cjs → as-filter-dialog-DYpbsNDx.cjs} +7 -7
  18. package/dist/{as-filter-dialog-C3wltqrN.mjs → as-filter-dialog-DdtrTT5w.mjs} +7 -7
  19. package/dist/as-filter-dialog.cjs +1 -1
  20. package/dist/as-filter-dialog.mjs +1 -1
  21. package/dist/{as-filter-field-pygBjA-K.cjs → as-filter-field-4-qFRsJx.cjs} +8 -10
  22. package/dist/{as-filter-field-D_6DlFcp.mjs → as-filter-field-DEQVodKG.mjs} +8 -10
  23. package/dist/as-filter-field.cjs +1 -1
  24. package/dist/as-filter-field.mjs +1 -1
  25. package/dist/{as-filters-BIoEsPD0.cjs → as-filters-C6ZkLrf4.cjs} +68 -25
  26. package/dist/{as-filters-BDzQ2SM_.mjs → as-filters-Dd_j6_6G.mjs} +69 -26
  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-BSgUEh4D.d.mts → as-filters.vue-dHbGmcbr.d.mts} +56 -4
  32. package/dist/{as-filters.vue-DMXZhQW3.d.cts → as-filters.vue-vH1FHn9L.d.cts} +56 -4
  33. package/dist/{as-preset-dialog-D_w4cxKS.mjs → as-preset-dialog-Bap48oUv.mjs} +1 -1
  34. package/dist/{as-preset-dialog-BgZB8xyk.cjs → as-preset-dialog-SYA-6lGR.cjs} +1 -1
  35. package/dist/as-preset-dialog.cjs +1 -1
  36. package/dist/as-preset-dialog.mjs +1 -1
  37. package/dist/{as-preset-picker-XnGrqHyF.cjs → as-preset-picker-BdKTZa95.cjs} +27 -21
  38. package/dist/{as-preset-picker-B8d6l3U1.mjs → as-preset-picker-NBrvsE6s.mjs} +27 -21
  39. package/dist/as-preset-picker.cjs +1 -1
  40. package/dist/as-preset-picker.mjs +1 -1
  41. package/dist/{as-residual-filter-C65QsWxr.mjs → as-residual-filter-BFnbI3h3.mjs} +1 -1
  42. package/dist/{as-residual-filter-ihvXCtpH.cjs → as-residual-filter-CZ-Bytdj.cjs} +1 -1
  43. package/dist/as-residual-filter.cjs +1 -1
  44. package/dist/as-residual-filter.mjs +1 -1
  45. package/dist/{as-row-actions-C6N0lcZb.cjs → as-row-actions-C3xHkX53.cjs} +2 -2
  46. package/dist/{as-row-actions-xQ9M1PrO.mjs → as-row-actions-KeoHo9Ex.mjs} +2 -2
  47. package/dist/as-row-actions.cjs +1 -1
  48. package/dist/as-row-actions.d.cts +1 -1
  49. package/dist/as-row-actions.d.mts +1 -1
  50. package/dist/as-row-actions.mjs +1 -1
  51. package/dist/{as-row-actions.vue-C8vslg0n.d.mts → as-row-actions.vue-D3_z3ytk.d.cts} +1 -1
  52. package/dist/{as-row-actions.vue-B3XAxDAL.d.cts → as-row-actions.vue-nmaDuxBT.d.mts} +1 -1
  53. package/dist/{as-table-status-2rA91OFT.mjs → as-select-cell-KD_ns8DS.mjs} +149 -36
  54. package/dist/{as-table-status-UWTPJJUL.cjs → as-select-cell-XnzNuW2v.cjs} +154 -35
  55. package/dist/{as-table-B49x0hDM.mjs → as-table-NHqyekNd.mjs} +10 -11
  56. package/dist/{as-table-actions-B2Hy3h7v.mjs → as-table-actions-BpEFcsgh.mjs} +2 -2
  57. package/dist/{as-table-actions-B-jY3s6f.cjs → as-table-actions-UQmFtRdJ.cjs} +2 -2
  58. package/dist/as-table-actions.cjs +1 -1
  59. package/dist/as-table-actions.d.cts +1 -1
  60. package/dist/as-table-actions.d.mts +1 -1
  61. package/dist/as-table-actions.mjs +1 -1
  62. package/dist/{as-table-actions.vue-BgTJuXEH.d.cts → as-table-actions.vue-BG3JVaBQ.d.mts} +1 -1
  63. package/dist/{as-table-actions.vue-BnLH2NqO.d.mts → as-table-actions.vue-DcuzNZoC.d.cts} +1 -1
  64. package/dist/{as-table-ouPxssqv.cjs → as-table-ay1yel3u.cjs} +10 -11
  65. package/dist/{as-table-base-DErJfFYz.mjs → as-table-base-89DkDZ-1.mjs} +46 -60
  66. package/dist/{as-table-base-ceywcVRq.cjs → as-table-base-umzg4PBR.cjs} +51 -65
  67. package/dist/{as-table-header-cell-Ccf06b2_.mjs → as-table-header-cell-DU3ws2x9.mjs} +1 -1
  68. package/dist/{as-table-header-cell-ZCk3XD_d.cjs → as-table-header-cell-f3YpgJX1.cjs} +1 -1
  69. package/dist/as-table-header-cell.cjs +1 -1
  70. package/dist/as-table-header-cell.d.cts +1 -1
  71. package/dist/as-table-header-cell.d.mts +1 -1
  72. package/dist/as-table-header-cell.mjs +1 -1
  73. package/dist/{as-table-header-cell.vue-C7jNmdDh.d.mts → as-table-header-cell.vue-5VGRc23o.d.mts} +1 -1
  74. package/dist/{as-table-header-cell.vue-DkClJBAT.d.cts → as-table-header-cell.vue-XR90UlHO.d.cts} +1 -1
  75. package/dist/{as-table-root-CG7bLzcQ.mjs → as-table-root-47hiBEnq.mjs} +8 -5
  76. package/dist/{as-table-root-_58hdHAi.cjs → as-table-root-DDeGktcl.cjs} +8 -5
  77. package/dist/as-table-root.cjs +1 -1
  78. package/dist/as-table-root.d.cts +1 -1
  79. package/dist/as-table-root.d.mts +1 -1
  80. package/dist/as-table-root.mjs +1 -1
  81. package/dist/{as-table-root.vue-DpPRMy3v.d.mts → as-table-root.vue-BGx453_Q.d.mts} +30 -4
  82. package/dist/{as-table-root.vue-D8hH1xGK.d.cts → as-table-root.vue-yZ7ZcwdT.d.cts} +32 -6
  83. package/dist/as-table.cjs +1 -1
  84. package/dist/as-table.d.cts +1 -1
  85. package/dist/as-table.d.mts +1 -1
  86. package/dist/as-table.mjs +1 -1
  87. package/dist/{as-table.vue-CudFoZTF.d.mts → as-table.vue-CwI5Z0y5.d.cts} +13 -11
  88. package/dist/{as-table.vue-Bv6sKnL5.d.cts → as-table.vue-wdFf4hMW.d.mts} +13 -11
  89. package/dist/{as-window-table-KyS6YUpa.mjs → as-window-table-C312T_bc.mjs} +83 -62
  90. package/dist/{as-window-table-C9YtzepV.cjs → as-window-table-hOYLNhX3.cjs} +89 -68
  91. package/dist/as-window-table.cjs +1 -1
  92. package/dist/as-window-table.d.cts +1 -1
  93. package/dist/as-window-table.d.mts +1 -1
  94. package/dist/as-window-table.mjs +1 -1
  95. package/dist/{as-window-table.vue-COVl3die.d.mts → as-window-table.vue-D6UDA6td.d.cts} +9 -7
  96. package/dist/{as-window-table.vue-CRWdhOgC.d.cts → as-window-table.vue-Deg1mj3t.d.mts} +9 -7
  97. package/dist/index.cjs +18 -18
  98. package/dist/index.d.cts +10 -10
  99. package/dist/index.d.mts +10 -10
  100. package/dist/index.mjs +18 -18
  101. package/dist/{types-BCixOa8x.d.cts → types-BU7J-a1r.d.mts} +79 -10
  102. package/dist/{types-D7qVxU_y.d.mts → types-D4dK49sA.d.cts} +79 -10
  103. package/dist/{use-table-2xPxoNRX.mjs → use-table-DlqGHjPh.mjs} +3 -2
  104. package/dist/{use-table-column-handlers-p_xK-l0e.mjs → use-table-column-handlers-0myVzrZR.mjs} +1 -1
  105. package/dist/{use-table-column-handlers-rqFo9HLz.cjs → use-table-column-handlers-BHMCo_HR.cjs} +1 -1
  106. package/dist/{use-table-component-XQ-Qt_sp.cjs → use-table-component-IziAm0vk.cjs} +1 -1
  107. package/dist/{use-table-component-BYO0zMQj.mjs → use-table-component-TLwFq4rx.mjs} +1 -1
  108. package/dist/{use-table-nav-bridge-BDBnRc4C.mjs → use-table-nav-bridge-CkQqimdS.mjs} +1 -1
  109. package/dist/{use-table-nav-bridge-DvrsPanF.cjs → use-table-nav-bridge-QU4jKhqx.cjs} +1 -1
  110. package/dist/{use-table-state-pF_9gnTG.cjs → use-table-state-BiklfjP0.cjs} +243 -45
  111. package/dist/{use-table-state-BzjbnpBA.mjs → use-table-state-Cq-3tbVr.mjs} +227 -47
  112. package/dist/{use-table-B45WZLwg.cjs → use-table-yZNJTfRq.cjs} +3 -2
  113. package/package.json +9 -9
@@ -1,7 +1,7 @@
1
1
  import { t as getCellValue } from "./get-cell-value-Cb3X8nUn.mjs";
2
2
  import { navigateHrefFor } from "@atscript/ui";
3
3
  import { computed, inject, nextTick, onBeforeUnmount, onScopeDispose, provide, ref, shallowRef, toValue, watch } from "vue";
4
- import { DEV, STANDARD_PRESET_ID, arraysEqual, blockStartFor, buildTableQuery, cellAsString, debounce, derivePresetAspects, filterExprFields, filterExprKey, fromWireSnapshot, gateOwns, isDirtyAgainst, isFilled, isSystemPresetId, mergeDisplayColumns, mergeSorters, normalizeResidualFilters, pageAlignedBlocksFor, planFetch, reconcileColumnWidthDefaults, residualGateOwns, resolveAspectGate, resolveSystemPresets, rowsToPks, sameColumnSet, sortRowsLocally, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
4
+ import { DEV, STANDARD_PRESET_ID, arraysEqual, blockStartFor, buildTableQuery, canonicalPresetSnapshot, cellAsString, compactFieldFilters, debounce, derivePresetAspects, filterExprFields, filterExprKey, fromWireSnapshot, gateOwns, isDirtyAgainst, isFilled, isSystemPresetId, mergeDisplayColumns, mergeSorters, normalizeResidualFilters, pageAlignedBlocksFor, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, residualGateOwns, resolveAspectGate, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, sortRowsLocally, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
5
5
  import { formatIdentifier } from "@atscript/db-client";
6
6
  //#region src/types.ts
7
7
  /** UI-side sentinel for the synthesised row-delete processor. */
@@ -565,6 +565,14 @@ function buildRemoveAction(opts) {
565
565
  /** Shared verdicts — frozen, so the common case allocates nothing per row. */
566
566
  const SELECTABLE = Object.freeze({ ok: true });
567
567
  const NOT_SELECTABLE = Object.freeze({ ok: false });
568
+ /**
569
+ * The header tri-state from how many of the rows in scope are eligible and
570
+ * how many of those are selected.
571
+ */
572
+ function toSelectAllState(selected, selectable) {
573
+ if (selected === 0) return "none";
574
+ return selected >= selectable ? "all" : "some";
575
+ }
568
576
  /** Normalise a {@link RowSelectableHook} return value. `""` carries no reason. */
569
577
  function verdictOf(value) {
570
578
  if (value === void 0 || value === true) return SELECTABLE;
@@ -602,22 +610,33 @@ function createSelectionApi(opts, getActiveRow, activeIndex) {
602
610
  for (let i = 0; i < rows.length; i++) if (isRowSelectable(rows[i], i).ok) n++;
603
611
  return n;
604
612
  }
613
+ function eligiblePks(rows) {
614
+ return rowsToPks(selectableRows(rows), rowValueFn);
615
+ }
616
+ function addPks(pks) {
617
+ const current = selectedSet.value;
618
+ const added = [...new Set(pks)].filter((pk) => !current.has(pk));
619
+ if (added.length > 0) selectedRows.value = [...selectedRows.value, ...added];
620
+ }
621
+ function removePks(pks) {
622
+ const drop = new Set(pks);
623
+ const next = selectedRows.value.filter((pk) => !drop.has(pk));
624
+ if (next.length !== selectedRows.value.length) selectedRows.value = next;
625
+ }
605
626
  function selectAll(rows, indexOf) {
606
- if (!rowSelectable.value) {
607
- selectedRows.value = rowsToPks(rows, rowValueFn);
608
- return;
609
- }
610
- const out = [];
611
- const seen = /* @__PURE__ */ new Set();
612
- for (let i = 0; i < rows.length; i++) {
613
- const row = rows[i];
614
- const pk = rowValueFn(row);
615
- if (seen.has(pk)) continue;
616
- if (!isRowSelectable(row, indexOf ? indexOf(i) : i).ok && !isPkSelected(pk)) continue;
617
- seen.add(pk);
618
- out.push(pk);
619
- }
620
- selectedRows.value = out;
627
+ addPks(rowsToPks(indexOf ? rows.filter((row, i) => isRowSelectable(row, indexOf(i)).ok) : selectableRows(rows), rowValueFn));
628
+ }
629
+ function deselectAll(rows) {
630
+ removePks(eligiblePks(rows));
631
+ }
632
+ function toggleAll(rows) {
633
+ const pks = eligiblePks(rows);
634
+ const selected = pks.filter((pk) => selectedSet.value.has(pk)).length;
635
+ if (toSelectAllState(selected, pks.length) === "all") removePks(pks);
636
+ else addPks(pks);
637
+ }
638
+ function clearSelection() {
639
+ if (selectedRows.value.length > 0) selectedRows.value = [];
621
640
  }
622
641
  function toggleActiveSelection(mode) {
623
642
  if (mode === "none") return;
@@ -637,6 +656,9 @@ function createSelectionApi(opts, getActiveRow, activeIndex) {
637
656
  selectableRows,
638
657
  selectableCount,
639
658
  selectAll,
659
+ deselectAll,
660
+ toggleAll,
661
+ clearSelection,
640
662
  toggleActiveSelection
641
663
  };
642
664
  }
@@ -854,6 +876,15 @@ function createNavController(inputs) {
854
876
  };
855
877
  }
856
878
  /**
879
+ * Toggle the row at `index`: it becomes the active row, then the active row
880
+ * toggles. Selectability is NOT re-checked here: `toggleActiveSelection`
881
+ * gates on `rowSelectable` itself, which is the single place that rule lives.
882
+ */
883
+ function toggleRowAt(index, mode, state) {
884
+ state.setActive(index);
885
+ state.toggleActiveSelection(mode);
886
+ }
887
+ /**
857
888
  * `@keydown` handler for a row's SELECTION CONTROL (the leading checkbox),
858
889
  * shared by `<AsTableBase>` and `<AsWindowTableBase>`. The control carries
859
890
  * `role="checkbox"`, so the tbody-level nav handler deliberately leaves
@@ -862,16 +893,13 @@ function createNavController(inputs) {
862
893
  * Keys other than Space, and `select="none"`, fall through untouched; only
863
894
  * once the press is ours do we consume it, so the Reka-wrapped rendering
864
895
  * modes (which never bind this handler) keep Space for their own item.
865
- * Selectability is NOT re-checked here: `toggleActiveSelection` gates on
866
- * `rowSelectable` itself, which is the single place that rule lives.
867
896
  */
868
897
  function onSelectControlKeydown(event, index, mode, state) {
869
898
  if (event.key !== " ") return;
870
899
  if (mode === "none") return;
871
900
  event.preventDefault();
872
901
  event.stopPropagation();
873
- state.setActive(index);
874
- state.toggleActiveSelection(mode);
902
+ toggleRowAt(index, mode, state);
875
903
  }
876
904
  //#endregion
877
905
  //#region src/composables/preset-aspect-display.ts
@@ -1022,7 +1050,7 @@ function createPresetState(opts) {
1022
1050
  },
1023
1051
  apply(o, value, isSystem) {
1024
1052
  if (!value && !isSystem) return;
1025
- o.filters.value = value ? { ...value } : {};
1053
+ o.filters.value = value ? { ...compactFieldFilters(value) } : {};
1026
1054
  if (o.residualFilters?.value.length) o.residualFilters.value = [];
1027
1055
  },
1028
1056
  expandDefault: () => ({})
@@ -1101,8 +1129,25 @@ function createPresetState(opts) {
1101
1129
  nextActiveId: id
1102
1130
  };
1103
1131
  }
1132
+ /**
1133
+ * The one write-side prune: drop what names a field this table cannot use,
1134
+ * and report it. A no-op before the table definition loads — bootstrap
1135
+ * waits for it, and the query gate covers any other early writer.
1136
+ */
1137
+ function pruneForApply(snapshot, presetId) {
1138
+ const known = opts.knownFields?.value;
1139
+ if (!known) return snapshot;
1140
+ const { snapshot: pruned, dropped } = prunePresetSnapshot(snapshot, known);
1141
+ if (dropped) opts.onFieldsDropped?.({
1142
+ ...dropped,
1143
+ source: "preset",
1144
+ presetId: presetId ?? void 0
1145
+ });
1146
+ return pruned;
1147
+ }
1104
1148
  function apply(idOrSnapshot) {
1105
- const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
1149
+ const { snapshot: resolved, nextActiveId } = resolveSnapshot(idOrSnapshot);
1150
+ const snapshot = pruneForApply(resolved, nextActiveId);
1106
1151
  const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && isSystemPresetId(nextActiveId);
1107
1152
  for (const aspect of ownedAspects(nextActiveId)) {
1108
1153
  const value = snapshot[aspect];
@@ -1143,7 +1188,8 @@ function createPresetState(opts) {
1143
1188
  }
1144
1189
  return out;
1145
1190
  }
1146
- const activeSnapshot = computed(() => {
1191
+ /** The active preset's snapshot as stored — the base `saveActive` merges back into. */
1192
+ const storedActiveSnapshot = computed(() => {
1147
1193
  const id = activeId.value;
1148
1194
  if (!id) return {};
1149
1195
  if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {}, id);
@@ -1152,6 +1198,29 @@ function createPresetState(opts) {
1152
1198
  const wire = row.data?.content;
1153
1199
  return wire ? fromWireSnapshot(wire) : {};
1154
1200
  });
1201
+ const defaultWidths = computed((prev) => {
1202
+ const next = {};
1203
+ for (const [path, entry] of Object.entries(opts.columnWidths.value)) next[path] = entry.d;
1204
+ return prev && shallowEqual(prev, next) ? prev : next;
1205
+ });
1206
+ const prunedActive = computed(() => {
1207
+ const known = opts.knownFields?.value;
1208
+ const stored = storedActiveSnapshot.value;
1209
+ return known ? prunePresetSnapshot(stored, known) : {
1210
+ snapshot: stored,
1211
+ dropped: null
1212
+ };
1213
+ });
1214
+ const activeSnapshot = computed(() => canonicalPresetSnapshot(prunedActive.value.snapshot, defaultWidths.value));
1215
+ const droppedFields = computed(() => {
1216
+ const dropped = prunedActive.value.dropped;
1217
+ const presetId = activeId.value;
1218
+ return dropped && presetId ? {
1219
+ ...dropped,
1220
+ source: "preset",
1221
+ presetId
1222
+ } : null;
1223
+ });
1155
1224
  const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1156
1225
  function requirePresets() {
1157
1226
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
@@ -1163,10 +1232,12 @@ function createPresetState(opts) {
1163
1232
  const id = activeId.value;
1164
1233
  if (!id) throw new Error("[vue-table] saveActive: no active preset");
1165
1234
  if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
1166
- const existing = activeSnapshot.value;
1235
+ const existing = storedActiveSnapshot.value;
1167
1236
  const mask = {};
1168
1237
  for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
1169
- await handle.savePreset(captureSnapshot(mask));
1238
+ const captured = captureSnapshot(mask);
1239
+ const dropped = prunedActive.value.dropped;
1240
+ await handle.savePreset(dropped ? restoreDroppedEntries(captured, dropped) : captured);
1170
1241
  clearLocalDraft();
1171
1242
  });
1172
1243
  }
@@ -1225,20 +1296,25 @@ function createPresetState(opts) {
1225
1296
  function bootstrap() {
1226
1297
  if (!opts.presetsHandle) return;
1227
1298
  const handle = opts.presetsHandle;
1228
- const stop = watch(() => handle.loading.value, (loading) => {
1229
- if (loading) return;
1230
- stop();
1299
+ const knownReady = () => !opts.knownFields || opts.knownFields.value !== null;
1300
+ let done = false;
1301
+ let stop;
1302
+ stop = watch(() => !handle.loading.value && knownReady(), (ready) => {
1303
+ if (!ready || done) return;
1304
+ done = true;
1305
+ stop?.();
1231
1306
  const id = resolveDefaultId();
1232
1307
  const { snapshot, nextActiveId } = resolveSnapshot(id);
1233
1308
  const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot, id) : snapshot;
1234
1309
  const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
1235
- apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
1310
+ apply(pruneForApply(draftEnabled ? opts.draftHandle.hydrate(base) : base, nextActiveId));
1236
1311
  activeId.value = nextActiveId;
1237
1312
  if (draftEnabled) opts.draftHandle.watchAndPersist(() => captureSnapshot(), () => activeSnapshot.value);
1238
1313
  nextTick(() => {
1239
1314
  gate.value = true;
1240
1315
  });
1241
1316
  }, { immediate: true });
1317
+ if (done) stop();
1242
1318
  }
1243
1319
  return {
1244
1320
  slice: {
@@ -1254,6 +1330,7 @@ function createPresetState(opts) {
1254
1330
  available,
1255
1331
  activeId,
1256
1332
  activeSnapshot,
1333
+ droppedFields,
1257
1334
  isDirty,
1258
1335
  canSaveActive,
1259
1336
  currentUser,
@@ -1282,6 +1359,12 @@ function createPresetState(opts) {
1282
1359
  }
1283
1360
  };
1284
1361
  }
1362
+ function shallowEqual(a, b) {
1363
+ const keys = Object.keys(a);
1364
+ if (keys.length !== Object.keys(b).length) return false;
1365
+ for (const k of keys) if (a[k] !== b[k]) return false;
1366
+ return true;
1367
+ }
1285
1368
  function inertMutator() {
1286
1369
  throw new Error("[vue-table] presets feature not configured (preset.url missing)");
1287
1370
  }
@@ -1607,6 +1690,15 @@ function warnUnsupportedFilter(issue) {
1607
1690
  if (!DEV) return;
1608
1691
  console.warn(`[vue-table] URL filter left out (${issue.reason}): ${JSON.stringify(issue.expr)}. Field filters cannot express it, so the table shows more rows than the URL described.`);
1609
1692
  }
1693
+ /**
1694
+ * The report for fields the table left out when nobody handles it
1695
+ * (`onFieldsDropped` / `@fields-dropped`): a dev-mode warning.
1696
+ */
1697
+ function warnFieldsDropped(report) {
1698
+ if (!DEV) return;
1699
+ const from = report.presetId ? `${report.source} "${report.presetId}"` : report.source;
1700
+ console.warn(`[vue-table] Left out fields this table cannot use (${from}): ${report.fields.join(", ")}.`);
1701
+ }
1610
1702
  function createTableState(opts) {
1611
1703
  const client = opts.client;
1612
1704
  const modelOpts = opts.model;
@@ -1681,6 +1773,28 @@ function createTableState(opts) {
1681
1773
  const configDialogOpen = ref(false);
1682
1774
  const configTab = ref("columns");
1683
1775
  const filterDialogColumn = ref(null);
1776
+ /**
1777
+ * The paths the table can use, from its column list: every column, and the
1778
+ * server-backed ones. `null` until the table definition loads. Presets,
1779
+ * drafts, URLs and the query itself are all pruned against it.
1780
+ */
1781
+ const knownFields = computed(() => {
1782
+ const all = allColumns.value;
1783
+ if (all.length === 0) return null;
1784
+ const columns = /* @__PURE__ */ new Set();
1785
+ const server = /* @__PURE__ */ new Set();
1786
+ for (const c of all) {
1787
+ columns.add(c.path);
1788
+ if (!c.local) server.add(c.path);
1789
+ }
1790
+ return {
1791
+ columns,
1792
+ server
1793
+ };
1794
+ });
1795
+ function reportFieldsDropped(report) {
1796
+ (queryOpts?.onFieldsDropped ?? warnFieldsDropped)(report);
1797
+ }
1684
1798
  const { slice: presetSlice, internals: presetInternals } = createPresetState({
1685
1799
  columnNames,
1686
1800
  columnWidths,
@@ -1695,7 +1809,9 @@ function createTableState(opts) {
1695
1809
  availableAspects: opts.preset?.availableAspects,
1696
1810
  systemAspects: opts.preset?.systemAspects,
1697
1811
  persistDrafts: opts.preset?.persistDrafts,
1698
- fallbackSystemPresets: opts.preset?.fallbackSystemPresets
1812
+ fallbackSystemPresets: opts.preset?.fallbackSystemPresets,
1813
+ knownFields,
1814
+ onFieldsDropped: reportFieldsDropped
1699
1815
  });
1700
1816
  presetInternals.bootstrap();
1701
1817
  const promptSlot = createRequestSlot(false);
@@ -1737,7 +1853,10 @@ function createTableState(opts) {
1737
1853
  */
1738
1854
  const localColumnPaths = computed(() => {
1739
1855
  const out = /* @__PURE__ */ new Set();
1740
- for (const c of allColumns.value) if (c.local) out.add(c.path);
1856
+ const known = knownFields.value;
1857
+ if (known) {
1858
+ for (const p of known.columns) if (!known.server.has(p)) out.add(p);
1859
+ }
1741
1860
  return out;
1742
1861
  });
1743
1862
  /**
@@ -1770,22 +1889,67 @@ function createTableState(opts) {
1770
1889
  if (splitSorters.value.local.length === 0 || sortersIgnored()) return rows;
1771
1890
  return sortRowsLocally(rows, queryOpts?.forceSorters?.length ? mergeSorters(queryOpts.forceSorters, sorters.value) : sorters.value, sortValueOf);
1772
1891
  }
1892
+ /**
1893
+ * The query's view of the model, gated to the fields this caller can use.
1894
+ * This is the guarantee that no table state sends a field the server does
1895
+ * not expose: presets and URLs are pruned on apply, but `setFieldFilter`,
1896
+ * `addFilterField`, `v-model` writes, `setResidualFilters` and the like
1897
+ * write straight to the model. `$select` is gated by the column list
1898
+ * (`knownFields.columns`, then client-owned columns are stripped); filters
1899
+ * and residual conditions by the server-backed columns. `forceFilters` /
1900
+ * `forceSorters` are app-authored and never gated. Memoized: pages,
1901
+ * window blocks and exports reuse it.
1902
+ */
1903
+ const queryModel = computed(() => {
1904
+ const model = {
1905
+ columnNames: columnNames.value,
1906
+ filters: filters.value,
1907
+ sorters: splitSorters.value.server,
1908
+ residual: residualFilters.value,
1909
+ stripped: []
1910
+ };
1911
+ const known = knownFields.value;
1912
+ if (!known) return model;
1913
+ const { snapshot, dropped } = prunePresetSnapshot({
1914
+ columns: { columnNames: model.columnNames },
1915
+ filterOps: model.filters,
1916
+ sorters: model.sorters
1917
+ }, known);
1918
+ const residual = pruneResidualFilters(model.residual, known.server);
1919
+ return {
1920
+ columnNames: model.columnNames,
1921
+ filters: snapshot.filterOps,
1922
+ sorters: snapshot.sorters,
1923
+ residual: residual.kept,
1924
+ stripped: [...new Set([...dropped?.fields ?? [], ...residual.dropped?.fields ?? []])]
1925
+ };
1926
+ });
1927
+ /** Stripped path sets already warned about — one dev warning per set. */
1928
+ const warnedStripped = /* @__PURE__ */ new Set();
1773
1929
  function buildCurrentQuery(buildOpts) {
1774
- const available = tableDef.value?.fetchableFields ?? new Set(allColumns.value.map((c) => c.path));
1930
+ const available = tableDef.value?.fetchableFields ?? knownFields.value?.columns ?? /* @__PURE__ */ new Set();
1775
1931
  const extra = /* @__PURE__ */ new Set();
1776
1932
  for (const c of columns.value) for (const p of c.selectWith ?? []) if (available.has(p)) extra.add(p);
1777
1933
  if (queryOpts?.alwaysSelected) {
1778
1934
  for (const p of queryOpts.alwaysSelected) if (available.has(p)) extra.add(p);
1779
1935
  }
1936
+ const model = queryModel.value;
1937
+ if (DEV && model.stripped.length > 0) {
1938
+ const key = model.stripped.join(",");
1939
+ if (!warnedStripped.has(key)) {
1940
+ warnedStripped.add(key);
1941
+ console.warn(`[vue-table] Query leaves out fields this table cannot use: ${model.stripped.join(", ")}.`);
1942
+ }
1943
+ }
1944
+ const known = knownFields.value;
1780
1945
  const localPaths = localColumnPaths.value;
1781
- const requested = buildOpts?.columnPaths ?? columnNames.value;
1782
1946
  return buildTableQuery({
1783
- visibleColumnPaths: localPaths.size ? requested.filter((p) => !localPaths.has(p)) : requested,
1947
+ visibleColumnPaths: (buildOpts?.columnPaths ?? model.columnNames).filter((p) => !localPaths.has(p) && (!known || known.columns.has(p))),
1784
1948
  extraSelect: extra.size ? [...extra] : void 0,
1785
- sorters: splitSorters.value.server,
1949
+ sorters: model.sorters,
1786
1950
  forceSorters: queryOpts?.forceSorters,
1787
- filters: filters.value,
1788
- residualFilters: residualFilters.value,
1951
+ filters: model.filters,
1952
+ residualFilters: model.residual,
1789
1953
  forceFilters: queryOpts?.forceFilters,
1790
1954
  search: searchTerm.value || void 0,
1791
1955
  ignoreSorters: sortersIgnored(),
@@ -1843,7 +2007,7 @@ function createTableState(opts) {
1843
2007
  if (idx < 0) return void 0;
1844
2008
  return navMode.value === "window" ? dataAt(idx) : results.value[idx];
1845
2009
  }
1846
- const { selectedRows, selectedCount, rowValueFn, isPkSelected, rowSelectable, isRowSelectable, selectableRows, selectableCount, selectAll, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow, activeIndex);
2010
+ const { selectedRows, selectedCount, rowValueFn, isPkSelected, rowSelectable, isRowSelectable, selectableRows, selectableCount, selectAll, deselectAll, toggleAll, clearSelection, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow, activeIndex);
1847
2011
  const { actions } = createActions({
1848
2012
  tableDef,
1849
2013
  client,
@@ -2018,6 +2182,9 @@ function createTableState(opts) {
2018
2182
  selectableRows,
2019
2183
  selectableCount,
2020
2184
  selectAll,
2185
+ deselectAll,
2186
+ toggleAll,
2187
+ clearSelection,
2021
2188
  rowDelete,
2022
2189
  rowActions,
2023
2190
  includeActions,
@@ -2081,14 +2248,11 @@ function createTableState(opts) {
2081
2248
  filterFields.value = filterFields.value.filter((f) => f !== path);
2082
2249
  },
2083
2250
  setFieldFilter(path, conditions) {
2084
- if (!conditions.some(isFilled)) {
2085
- if (!(path in filters.value)) return;
2086
- const { [path]: _, ...rest } = filters.value;
2087
- filters.value = rest;
2088
- } else filters.value = {
2251
+ if (!conditions.some(isFilled) && !(path in filters.value)) return;
2252
+ filters.value = compactFieldFilters({
2089
2253
  ...filters.value,
2090
2254
  [path]: conditions
2091
- };
2255
+ });
2092
2256
  },
2093
2257
  setColumnWidth(path, width) {
2094
2258
  writeColumnWidth(path, width);
@@ -2179,12 +2343,28 @@ function createTableState(opts) {
2179
2343
  function applyUrlQuery(urlString, opts) {
2180
2344
  captureUrlBaseline();
2181
2345
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2182
- const cols = allColumns.value;
2346
+ const known = knownFields.value;
2183
2347
  const parsed = urlQueryStringToState(urlString, {
2184
- knownFields: cols.length > 0 ? cols.filter((c) => !c.local).map((c) => c.path) : void 0,
2348
+ knownFields: known?.server,
2349
+ localFields: known ? localColumnPaths.value : void 0,
2185
2350
  sync: urlQuerySync
2186
2351
  });
2187
2352
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2353
+ if (parsed.unknown || parsed.unknownSorters) {
2354
+ const residual = parsed.unknown?.map((u) => u.expr) ?? [];
2355
+ const sorters = parsed.unknownSorters ?? [];
2356
+ const fields = new Set(parsed.unknown?.flatMap((u) => u.fields));
2357
+ for (const s of sorters) fields.add(s.field);
2358
+ reportFieldsDropped({
2359
+ source: "url",
2360
+ fields: [...fields],
2361
+ columns: [],
2362
+ filterFields: [],
2363
+ filters: {},
2364
+ residual,
2365
+ sorters
2366
+ });
2367
+ }
2188
2368
  const urlResidual = parsed.residual ?? [];
2189
2369
  const residualPaths = new Set(urlResidual.flatMap((expr) => filterExprFields(expr)));
2190
2370
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
@@ -2493,4 +2673,4 @@ function useRegisterMainActionListener(state, listener, enabled) {
2493
2673
  });
2494
2674
  }
2495
2675
  //#endregion
2496
- export { intentToScope as C, ROW_ACTIONS_PATH as D, triggerAction as E, ROW_ACTIONS_TYPE as O, intentClass as S, isPlainLeftClick as T, applyRowsGate as _, useTableContext as a, createNavigateGestures as b, DEFAULT_AVAILABLE_ASPECTS as c, ownerNameOf as d, readPresetLabel as f, applyRowGate as g, rowActionHref as h, useRegisterMainActionListener as i, ASPECT_ICONS as l, onSelectControlKeydown as m, createTableState as n, useTableContextOptional as o, INTERACTIVE_SELECTOR as p, provideTableContext as r, warnUnsupportedFilter as s, createStaticTableState as t, ASPECT_LABELS as u, ariaLabelFor as v, isModClick as w, extractIdentifier as x, collectIdentifiers as y };
2676
+ export { ROW_ACTIONS_PATH as A, createNavigateGestures as C, isModClick as D, intentToScope as E, isPlainLeftClick as O, collectIdentifiers as S, intentClass as T, toSelectAllState as _, useTableContext as a, applyRowsGate as b, warnUnsupportedFilter as c, ASPECT_LABELS as d, ownerNameOf as f, toggleRowAt as g, onSelectControlKeydown as h, useRegisterMainActionListener as i, ROW_ACTIONS_TYPE as j, triggerAction as k, DEFAULT_AVAILABLE_ASPECTS as l, INTERACTIVE_SELECTOR as m, createTableState as n, useTableContextOptional as o, readPresetLabel as p, provideTableContext as r, warnFieldsDropped as s, createStaticTableState as t, ASPECT_ICONS as u, rowActionHref as v, extractIdentifier as w, ariaLabelFor as x, applyRowGate as y };
@@ -1,4 +1,4 @@
1
- const require_use_table_state = require("./use-table-state-pF_9gnTG.cjs");
1
+ const require_use_table_state = require("./use-table-state-BiklfjP0.cjs");
2
2
  const require_use_table_selection = require("./use-table-selection-DtCV3rhu.cjs");
3
3
  let _atscript_ui = require("@atscript/ui");
4
4
  let vue = require("vue");
@@ -435,7 +435,8 @@ function useTable(url, opts) {
435
435
  urlQueryReady: opts?.urlQueryReady,
436
436
  onUrlQueryChange: opts?.onUrlQueryChange,
437
437
  urlQuerySync: opts?.urlQuerySync,
438
- onUnsupportedFilter: opts?.onUnsupportedFilter
438
+ onUnsupportedFilter: opts?.onUnsupportedFilter,
439
+ onFieldsDropped: opts?.onFieldsDropped
439
440
  },
440
441
  window: {
441
442
  blockSize: opts?.blockSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/vue-table",
3
- "version": "0.1.140",
3
+ "version": "0.1.142",
4
4
  "description": "Type-driven table components for Atscript with Vue 3",
5
5
  "keywords": [
6
6
  "atscript",
@@ -167,16 +167,16 @@
167
167
  "access": "public"
168
168
  },
169
169
  "dependencies": {
170
- "@atscript/ui": "^0.1.140",
171
- "@atscript/ui-table": "^0.1.140",
172
- "@atscript/vue-form": "^0.1.140"
170
+ "@atscript/vue-form": "^0.1.142",
171
+ "@atscript/ui-table": "^0.1.142",
172
+ "@atscript/ui": "^0.1.142"
173
173
  },
174
174
  "devDependencies": {
175
- "@atscript/db-client": "^0.1.133",
175
+ "@atscript/db-client": "^0.1.136",
176
176
  "@atscript/typescript": "^0.1.93",
177
177
  "@tanstack/vue-virtual": "^3.13.26",
178
- "@uniqu/core": "^0.1.10",
179
- "@uniqu/url": "^0.1.10",
178
+ "@uniqu/core": "^0.1.11",
179
+ "@uniqu/url": "^0.1.11",
180
180
  "@vitejs/plugin-vue": "^6",
181
181
  "@vue/test-utils": "^2",
182
182
  "@vueuse/core": "^14.3.0",
@@ -189,9 +189,9 @@
189
189
  "vunor": "^0.3.0"
190
190
  },
191
191
  "peerDependencies": {
192
- "@atscript/db-client": "^0.1.133",
192
+ "@atscript/db-client": "^0.1.136",
193
193
  "@tanstack/vue-virtual": "^3.13.26",
194
- "@uniqu/url": "^0.1.10",
194
+ "@uniqu/url": "^0.1.11",
195
195
  "@vueuse/core": "^14.3.0",
196
196
  "reka-ui": "^2.9.8",
197
197
  "vue": "^3"