@atscript/vue-table 0.1.140 → 0.1.141

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 (109) 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-KNJr1EkO.mjs → as-action-menu-content-6dR5q8bs.mjs} +1 -1
  4. package/dist/{as-action-menu-content-B5RFt7Hc.cjs → as-action-menu-content-AKbIp4Ni.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-DKQNY5gh.d.cts → as-column-menu.vue-DapdB_qF.d.cts} +1 -1
  8. package/dist/{as-column-menu.vue-hOmuVcKf.d.mts → as-column-menu.vue-Ys_sx0VE.d.mts} +1 -1
  9. package/dist/{as-config-dialog-B1KJJSHE.cjs → as-config-dialog-Chpw_2tu.cjs} +2 -2
  10. package/dist/{as-config-dialog-JXNkCCrh.mjs → as-config-dialog-UeMU7jTP.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-SIqCOw2p.mjs} +1 -1
  14. package/dist/{as-confirm-dialog-a736BLOm.cjs → as-confirm-dialog-hHYVEX9E.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-C3wltqrN.mjs → as-filter-dialog-DVRhCx6O.mjs} +7 -7
  18. package/dist/{as-filter-dialog-IflKkOd8.cjs → as-filter-dialog-Xvj5q09A.cjs} +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-B7b2sEG0.cjs} +3 -3
  22. package/dist/{as-filter-field-D_6DlFcp.mjs → as-filter-field-D4vQCOEU.mjs} +3 -3
  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-CWQgQBCH.cjs} +4 -4
  26. package/dist/{as-filters-BDzQ2SM_.mjs → as-filters-CudeInPr.mjs} +4 -4
  27. package/dist/as-filters.cjs +1 -1
  28. package/dist/as-filters.mjs +1 -1
  29. package/dist/{as-preset-dialog-D_w4cxKS.mjs → as-preset-dialog-BsWSuY9N.mjs} +1 -1
  30. package/dist/{as-preset-dialog-BgZB8xyk.cjs → as-preset-dialog-l81WXBmf.cjs} +1 -1
  31. package/dist/as-preset-dialog.cjs +1 -1
  32. package/dist/as-preset-dialog.mjs +1 -1
  33. package/dist/{as-preset-picker-B8d6l3U1.mjs → as-preset-picker-BTkMqSxH.mjs} +27 -21
  34. package/dist/{as-preset-picker-XnGrqHyF.cjs → as-preset-picker-Bp4xMTua.cjs} +27 -21
  35. package/dist/as-preset-picker.cjs +1 -1
  36. package/dist/as-preset-picker.mjs +1 -1
  37. package/dist/{as-residual-filter-ihvXCtpH.cjs → as-residual-filter-BJ4cwFP4.cjs} +1 -1
  38. package/dist/{as-residual-filter-C65QsWxr.mjs → as-residual-filter-D1SGFQ1p.mjs} +1 -1
  39. package/dist/as-residual-filter.cjs +1 -1
  40. package/dist/as-residual-filter.mjs +1 -1
  41. package/dist/{as-row-actions-C6N0lcZb.cjs → as-row-actions-9p_BJPga.cjs} +2 -2
  42. package/dist/{as-row-actions-xQ9M1PrO.mjs → as-row-actions-CH8WZ5kN.mjs} +2 -2
  43. package/dist/as-row-actions.cjs +1 -1
  44. package/dist/as-row-actions.d.cts +1 -1
  45. package/dist/as-row-actions.d.mts +1 -1
  46. package/dist/as-row-actions.mjs +1 -1
  47. package/dist/{as-row-actions.vue-C8vslg0n.d.mts → as-row-actions.vue-BOURuyjv.d.mts} +1 -1
  48. package/dist/{as-row-actions.vue-B3XAxDAL.d.cts → as-row-actions.vue-BezMFOr6.d.cts} +1 -1
  49. package/dist/{as-table-B49x0hDM.mjs → as-table-DmmXWq94.mjs} +3 -3
  50. package/dist/{as-table-actions-B2Hy3h7v.mjs → as-table-actions-CFBAxKIS.mjs} +2 -2
  51. package/dist/{as-table-actions-B-jY3s6f.cjs → as-table-actions-oxVqSH-G.cjs} +2 -2
  52. package/dist/as-table-actions.cjs +1 -1
  53. package/dist/as-table-actions.d.cts +1 -1
  54. package/dist/as-table-actions.d.mts +1 -1
  55. package/dist/as-table-actions.mjs +1 -1
  56. package/dist/{as-table-actions.vue-BgTJuXEH.d.cts → as-table-actions.vue-CrZtcxAY.d.mts} +2 -2
  57. package/dist/{as-table-actions.vue-BnLH2NqO.d.mts → as-table-actions.vue-Dls_1Vze.d.cts} +2 -2
  58. package/dist/{as-table-base-ceywcVRq.cjs → as-table-base--MfU8LVN.cjs} +2 -2
  59. package/dist/{as-table-base-DErJfFYz.mjs → as-table-base-Cl1XJu4H.mjs} +2 -2
  60. package/dist/{as-table-ouPxssqv.cjs → as-table-fOBpTDrI.cjs} +3 -3
  61. package/dist/{as-table-header-cell-ZCk3XD_d.cjs → as-table-header-cell-10DQEs00.cjs} +1 -1
  62. package/dist/{as-table-header-cell-Ccf06b2_.mjs → as-table-header-cell-M8g9X9dR.mjs} +1 -1
  63. package/dist/as-table-header-cell.cjs +1 -1
  64. package/dist/as-table-header-cell.d.cts +1 -1
  65. package/dist/as-table-header-cell.d.mts +1 -1
  66. package/dist/as-table-header-cell.mjs +1 -1
  67. package/dist/{as-table-header-cell.vue-C7jNmdDh.d.mts → as-table-header-cell.vue-D-iI_EJY.d.cts} +2 -2
  68. package/dist/{as-table-header-cell.vue-DkClJBAT.d.cts → as-table-header-cell.vue-SMBpzqU2.d.mts} +2 -2
  69. package/dist/{as-table-root-CG7bLzcQ.mjs → as-table-root-Bl423S7X.mjs} +8 -5
  70. package/dist/{as-table-root-_58hdHAi.cjs → as-table-root-CGXKfCIk.cjs} +8 -5
  71. package/dist/as-table-root.cjs +1 -1
  72. package/dist/as-table-root.d.cts +1 -1
  73. package/dist/as-table-root.d.mts +1 -1
  74. package/dist/as-table-root.mjs +1 -1
  75. package/dist/{as-table-root.vue-DpPRMy3v.d.mts → as-table-root.vue-BROdsTqv.d.mts} +34 -8
  76. package/dist/{as-table-root.vue-D8hH1xGK.d.cts → as-table-root.vue-D5H8uXFK.d.cts} +34 -8
  77. package/dist/{as-table-status-2rA91OFT.mjs → as-table-status-Bl9XmWqc.mjs} +4 -4
  78. package/dist/{as-table-status-UWTPJJUL.cjs → as-table-status-bbuFDaaK.cjs} +4 -4
  79. package/dist/as-table.cjs +1 -1
  80. package/dist/as-table.d.cts +1 -1
  81. package/dist/as-table.d.mts +1 -1
  82. package/dist/as-table.mjs +1 -1
  83. package/dist/{as-table.vue-CudFoZTF.d.mts → as-table.vue-B533pYs6.d.cts} +7 -7
  84. package/dist/{as-table.vue-Bv6sKnL5.d.cts → as-table.vue-B_7Y82yW.d.mts} +7 -7
  85. package/dist/{as-window-table-KyS6YUpa.mjs → as-window-table-CkTJOvu9.mjs} +3 -3
  86. package/dist/{as-window-table-C9YtzepV.cjs → as-window-table-Dpbezh9D.cjs} +3 -3
  87. package/dist/as-window-table.cjs +1 -1
  88. package/dist/as-window-table.d.cts +1 -1
  89. package/dist/as-window-table.d.mts +1 -1
  90. package/dist/as-window-table.mjs +1 -1
  91. package/dist/{as-window-table.vue-COVl3die.d.mts → as-window-table.vue-Bar8mWRL.d.cts} +6 -6
  92. package/dist/{as-window-table.vue-CRWdhOgC.d.cts → as-window-table.vue-OL4KC7Y-.d.mts} +6 -6
  93. package/dist/index.cjs +18 -18
  94. package/dist/index.d.cts +9 -9
  95. package/dist/index.d.mts +9 -9
  96. package/dist/index.mjs +18 -18
  97. package/dist/{types-D7qVxU_y.d.mts → types-Cdk5OH1-.d.cts} +42 -5
  98. package/dist/{types-BCixOa8x.d.cts → types-Ev-A5h_D.d.mts} +42 -5
  99. package/dist/{use-table-2xPxoNRX.mjs → use-table-BG3IZyCO.mjs} +3 -2
  100. package/dist/{use-table-column-handlers-rqFo9HLz.cjs → use-table-column-handlers-CWHRcQbE.cjs} +1 -1
  101. package/dist/{use-table-column-handlers-p_xK-l0e.mjs → use-table-column-handlers-CaT7dYnI.mjs} +1 -1
  102. package/dist/{use-table-component-BYO0zMQj.mjs → use-table-component-OTdgA202.mjs} +1 -1
  103. package/dist/{use-table-component-XQ-Qt_sp.cjs → use-table-component-Tc_ZvZua.cjs} +1 -1
  104. package/dist/{use-table-nav-bridge-BDBnRc4C.mjs → use-table-nav-bridge-BNjwR3qy.mjs} +1 -1
  105. package/dist/{use-table-nav-bridge-DvrsPanF.cjs → use-table-nav-bridge-ByQo00IH.cjs} +1 -1
  106. package/dist/{use-table-B45WZLwg.cjs → use-table-rc6_sYU6.cjs} +3 -2
  107. package/dist/{use-table-state-BzjbnpBA.mjs → use-table-state-BgLIOO31.mjs} +161 -20
  108. package/dist/{use-table-state-pF_9gnTG.cjs → use-table-state-CgXY-ruN.cjs} +165 -18
  109. package/package.json +4 -4
@@ -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, cellAsString, 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. */
@@ -1101,8 +1101,25 @@ function createPresetState(opts) {
1101
1101
  nextActiveId: id
1102
1102
  };
1103
1103
  }
1104
+ /**
1105
+ * The one write-side prune: drop what names a field this table cannot use,
1106
+ * and report it. A no-op before the table definition loads — bootstrap
1107
+ * waits for it, and the query gate covers any other early writer.
1108
+ */
1109
+ function pruneForApply(snapshot, presetId) {
1110
+ const known = opts.knownFields?.value;
1111
+ if (!known) return snapshot;
1112
+ const { snapshot: pruned, dropped } = prunePresetSnapshot(snapshot, known);
1113
+ if (dropped) opts.onFieldsDropped?.({
1114
+ ...dropped,
1115
+ source: "preset",
1116
+ presetId: presetId ?? void 0
1117
+ });
1118
+ return pruned;
1119
+ }
1104
1120
  function apply(idOrSnapshot) {
1105
- const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
1121
+ const { snapshot: resolved, nextActiveId } = resolveSnapshot(idOrSnapshot);
1122
+ const snapshot = pruneForApply(resolved, nextActiveId);
1106
1123
  const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && isSystemPresetId(nextActiveId);
1107
1124
  for (const aspect of ownedAspects(nextActiveId)) {
1108
1125
  const value = snapshot[aspect];
@@ -1143,7 +1160,8 @@ function createPresetState(opts) {
1143
1160
  }
1144
1161
  return out;
1145
1162
  }
1146
- const activeSnapshot = computed(() => {
1163
+ /** The active preset's snapshot as stored — the base `saveActive` merges back into. */
1164
+ const storedActiveSnapshot = computed(() => {
1147
1165
  const id = activeId.value;
1148
1166
  if (!id) return {};
1149
1167
  if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {}, id);
@@ -1152,6 +1170,24 @@ function createPresetState(opts) {
1152
1170
  const wire = row.data?.content;
1153
1171
  return wire ? fromWireSnapshot(wire) : {};
1154
1172
  });
1173
+ const prunedActive = computed(() => {
1174
+ const known = opts.knownFields?.value;
1175
+ const stored = storedActiveSnapshot.value;
1176
+ return known ? prunePresetSnapshot(stored, known) : {
1177
+ snapshot: stored,
1178
+ dropped: null
1179
+ };
1180
+ });
1181
+ const activeSnapshot = computed(() => prunedActive.value.snapshot);
1182
+ const droppedFields = computed(() => {
1183
+ const dropped = prunedActive.value.dropped;
1184
+ const presetId = activeId.value;
1185
+ return dropped && presetId ? {
1186
+ ...dropped,
1187
+ source: "preset",
1188
+ presetId
1189
+ } : null;
1190
+ });
1155
1191
  const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1156
1192
  function requirePresets() {
1157
1193
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
@@ -1163,10 +1199,12 @@ function createPresetState(opts) {
1163
1199
  const id = activeId.value;
1164
1200
  if (!id) throw new Error("[vue-table] saveActive: no active preset");
1165
1201
  if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
1166
- const existing = activeSnapshot.value;
1202
+ const existing = storedActiveSnapshot.value;
1167
1203
  const mask = {};
1168
1204
  for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
1169
- await handle.savePreset(captureSnapshot(mask));
1205
+ const captured = captureSnapshot(mask);
1206
+ const dropped = prunedActive.value.dropped;
1207
+ await handle.savePreset(dropped ? restoreDroppedEntries(captured, dropped) : captured);
1170
1208
  clearLocalDraft();
1171
1209
  });
1172
1210
  }
@@ -1225,20 +1263,25 @@ function createPresetState(opts) {
1225
1263
  function bootstrap() {
1226
1264
  if (!opts.presetsHandle) return;
1227
1265
  const handle = opts.presetsHandle;
1228
- const stop = watch(() => handle.loading.value, (loading) => {
1229
- if (loading) return;
1230
- stop();
1266
+ const knownReady = () => !opts.knownFields || opts.knownFields.value !== null;
1267
+ let done = false;
1268
+ let stop;
1269
+ stop = watch(() => !handle.loading.value && knownReady(), (ready) => {
1270
+ if (!ready || done) return;
1271
+ done = true;
1272
+ stop?.();
1231
1273
  const id = resolveDefaultId();
1232
1274
  const { snapshot, nextActiveId } = resolveSnapshot(id);
1233
1275
  const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot, id) : snapshot;
1234
1276
  const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
1235
- apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
1277
+ apply(pruneForApply(draftEnabled ? opts.draftHandle.hydrate(base) : base, nextActiveId));
1236
1278
  activeId.value = nextActiveId;
1237
1279
  if (draftEnabled) opts.draftHandle.watchAndPersist(() => captureSnapshot(), () => activeSnapshot.value);
1238
1280
  nextTick(() => {
1239
1281
  gate.value = true;
1240
1282
  });
1241
1283
  }, { immediate: true });
1284
+ if (done) stop();
1242
1285
  }
1243
1286
  return {
1244
1287
  slice: {
@@ -1254,6 +1297,7 @@ function createPresetState(opts) {
1254
1297
  available,
1255
1298
  activeId,
1256
1299
  activeSnapshot,
1300
+ droppedFields,
1257
1301
  isDirty,
1258
1302
  canSaveActive,
1259
1303
  currentUser,
@@ -1607,6 +1651,15 @@ function warnUnsupportedFilter(issue) {
1607
1651
  if (!DEV) return;
1608
1652
  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
1653
  }
1654
+ /**
1655
+ * The report for fields the table left out when nobody handles it
1656
+ * (`onFieldsDropped` / `@fields-dropped`): a dev-mode warning.
1657
+ */
1658
+ function warnFieldsDropped(report) {
1659
+ if (!DEV) return;
1660
+ const from = report.presetId ? `${report.source} "${report.presetId}"` : report.source;
1661
+ console.warn(`[vue-table] Left out fields this table cannot use (${from}): ${report.fields.join(", ")}.`);
1662
+ }
1610
1663
  function createTableState(opts) {
1611
1664
  const client = opts.client;
1612
1665
  const modelOpts = opts.model;
@@ -1681,6 +1734,28 @@ function createTableState(opts) {
1681
1734
  const configDialogOpen = ref(false);
1682
1735
  const configTab = ref("columns");
1683
1736
  const filterDialogColumn = ref(null);
1737
+ /**
1738
+ * The paths the table can use, from its column list: every column, and the
1739
+ * server-backed ones. `null` until the table definition loads. Presets,
1740
+ * drafts, URLs and the query itself are all pruned against it.
1741
+ */
1742
+ const knownFields = computed(() => {
1743
+ const all = allColumns.value;
1744
+ if (all.length === 0) return null;
1745
+ const columns = /* @__PURE__ */ new Set();
1746
+ const server = /* @__PURE__ */ new Set();
1747
+ for (const c of all) {
1748
+ columns.add(c.path);
1749
+ if (!c.local) server.add(c.path);
1750
+ }
1751
+ return {
1752
+ columns,
1753
+ server
1754
+ };
1755
+ });
1756
+ function reportFieldsDropped(report) {
1757
+ (queryOpts?.onFieldsDropped ?? warnFieldsDropped)(report);
1758
+ }
1684
1759
  const { slice: presetSlice, internals: presetInternals } = createPresetState({
1685
1760
  columnNames,
1686
1761
  columnWidths,
@@ -1695,7 +1770,9 @@ function createTableState(opts) {
1695
1770
  availableAspects: opts.preset?.availableAspects,
1696
1771
  systemAspects: opts.preset?.systemAspects,
1697
1772
  persistDrafts: opts.preset?.persistDrafts,
1698
- fallbackSystemPresets: opts.preset?.fallbackSystemPresets
1773
+ fallbackSystemPresets: opts.preset?.fallbackSystemPresets,
1774
+ knownFields,
1775
+ onFieldsDropped: reportFieldsDropped
1699
1776
  });
1700
1777
  presetInternals.bootstrap();
1701
1778
  const promptSlot = createRequestSlot(false);
@@ -1737,7 +1814,10 @@ function createTableState(opts) {
1737
1814
  */
1738
1815
  const localColumnPaths = computed(() => {
1739
1816
  const out = /* @__PURE__ */ new Set();
1740
- for (const c of allColumns.value) if (c.local) out.add(c.path);
1817
+ const known = knownFields.value;
1818
+ if (known) {
1819
+ for (const p of known.columns) if (!known.server.has(p)) out.add(p);
1820
+ }
1741
1821
  return out;
1742
1822
  });
1743
1823
  /**
@@ -1770,22 +1850,67 @@ function createTableState(opts) {
1770
1850
  if (splitSorters.value.local.length === 0 || sortersIgnored()) return rows;
1771
1851
  return sortRowsLocally(rows, queryOpts?.forceSorters?.length ? mergeSorters(queryOpts.forceSorters, sorters.value) : sorters.value, sortValueOf);
1772
1852
  }
1853
+ /**
1854
+ * The query's view of the model, gated to the fields this caller can use.
1855
+ * This is the guarantee that no table state sends a field the server does
1856
+ * not expose: presets and URLs are pruned on apply, but `setFieldFilter`,
1857
+ * `addFilterField`, `v-model` writes, `setResidualFilters` and the like
1858
+ * write straight to the model. `$select` is gated by the column list
1859
+ * (`knownFields.columns`, then client-owned columns are stripped); filters
1860
+ * and residual conditions by the server-backed columns. `forceFilters` /
1861
+ * `forceSorters` are app-authored and never gated. Memoized: pages,
1862
+ * window blocks and exports reuse it.
1863
+ */
1864
+ const queryModel = computed(() => {
1865
+ const model = {
1866
+ columnNames: columnNames.value,
1867
+ filters: filters.value,
1868
+ sorters: splitSorters.value.server,
1869
+ residual: residualFilters.value,
1870
+ stripped: []
1871
+ };
1872
+ const known = knownFields.value;
1873
+ if (!known) return model;
1874
+ const { snapshot, dropped } = prunePresetSnapshot({
1875
+ columns: { columnNames: model.columnNames },
1876
+ filterOps: model.filters,
1877
+ sorters: model.sorters
1878
+ }, known);
1879
+ const residual = pruneResidualFilters(model.residual, known.server);
1880
+ return {
1881
+ columnNames: model.columnNames,
1882
+ filters: snapshot.filterOps,
1883
+ sorters: snapshot.sorters,
1884
+ residual: residual.kept,
1885
+ stripped: [...new Set([...dropped?.fields ?? [], ...residual.dropped?.fields ?? []])]
1886
+ };
1887
+ });
1888
+ /** Stripped path sets already warned about — one dev warning per set. */
1889
+ const warnedStripped = /* @__PURE__ */ new Set();
1773
1890
  function buildCurrentQuery(buildOpts) {
1774
- const available = tableDef.value?.fetchableFields ?? new Set(allColumns.value.map((c) => c.path));
1891
+ const available = tableDef.value?.fetchableFields ?? knownFields.value?.columns ?? /* @__PURE__ */ new Set();
1775
1892
  const extra = /* @__PURE__ */ new Set();
1776
1893
  for (const c of columns.value) for (const p of c.selectWith ?? []) if (available.has(p)) extra.add(p);
1777
1894
  if (queryOpts?.alwaysSelected) {
1778
1895
  for (const p of queryOpts.alwaysSelected) if (available.has(p)) extra.add(p);
1779
1896
  }
1897
+ const model = queryModel.value;
1898
+ if (DEV && model.stripped.length > 0) {
1899
+ const key = model.stripped.join(",");
1900
+ if (!warnedStripped.has(key)) {
1901
+ warnedStripped.add(key);
1902
+ console.warn(`[vue-table] Query leaves out fields this table cannot use: ${model.stripped.join(", ")}.`);
1903
+ }
1904
+ }
1905
+ const known = knownFields.value;
1780
1906
  const localPaths = localColumnPaths.value;
1781
- const requested = buildOpts?.columnPaths ?? columnNames.value;
1782
1907
  return buildTableQuery({
1783
- visibleColumnPaths: localPaths.size ? requested.filter((p) => !localPaths.has(p)) : requested,
1908
+ visibleColumnPaths: (buildOpts?.columnPaths ?? model.columnNames).filter((p) => !localPaths.has(p) && (!known || known.columns.has(p))),
1784
1909
  extraSelect: extra.size ? [...extra] : void 0,
1785
- sorters: splitSorters.value.server,
1910
+ sorters: model.sorters,
1786
1911
  forceSorters: queryOpts?.forceSorters,
1787
- filters: filters.value,
1788
- residualFilters: residualFilters.value,
1912
+ filters: model.filters,
1913
+ residualFilters: model.residual,
1789
1914
  forceFilters: queryOpts?.forceFilters,
1790
1915
  search: searchTerm.value || void 0,
1791
1916
  ignoreSorters: sortersIgnored(),
@@ -2179,12 +2304,28 @@ function createTableState(opts) {
2179
2304
  function applyUrlQuery(urlString, opts) {
2180
2305
  captureUrlBaseline();
2181
2306
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2182
- const cols = allColumns.value;
2307
+ const known = knownFields.value;
2183
2308
  const parsed = urlQueryStringToState(urlString, {
2184
- knownFields: cols.length > 0 ? cols.filter((c) => !c.local).map((c) => c.path) : void 0,
2309
+ knownFields: known?.server,
2310
+ localFields: known ? localColumnPaths.value : void 0,
2185
2311
  sync: urlQuerySync
2186
2312
  });
2187
2313
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2314
+ if (parsed.unknown || parsed.unknownSorters) {
2315
+ const residual = parsed.unknown?.map((u) => u.expr) ?? [];
2316
+ const sorters = parsed.unknownSorters ?? [];
2317
+ const fields = new Set(parsed.unknown?.flatMap((u) => u.fields));
2318
+ for (const s of sorters) fields.add(s.field);
2319
+ reportFieldsDropped({
2320
+ source: "url",
2321
+ fields: [...fields],
2322
+ columns: [],
2323
+ filterFields: [],
2324
+ filters: {},
2325
+ residual,
2326
+ sorters
2327
+ });
2328
+ }
2188
2329
  const urlResidual = parsed.residual ?? [];
2189
2330
  const residualPaths = new Set(urlResidual.flatMap((expr) => filterExprFields(expr)));
2190
2331
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
@@ -2493,4 +2634,4 @@ function useRegisterMainActionListener(state, listener, enabled) {
2493
2634
  });
2494
2635
  }
2495
2636
  //#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 };
2637
+ export { intentClass as C, triggerAction as D, isPlainLeftClick as E, ROW_ACTIONS_PATH as O, extractIdentifier as S, isModClick as T, applyRowGate as _, useTableContext as a, collectIdentifiers as b, warnUnsupportedFilter as c, ASPECT_LABELS as d, ownerNameOf as f, rowActionHref as g, onSelectControlKeydown as h, useRegisterMainActionListener as i, ROW_ACTIONS_TYPE 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, applyRowsGate as v, intentToScope as w, createNavigateGestures as x, ariaLabelFor as y };
@@ -1101,8 +1101,25 @@ function createPresetState(opts) {
1101
1101
  nextActiveId: id
1102
1102
  };
1103
1103
  }
1104
+ /**
1105
+ * The one write-side prune: drop what names a field this table cannot use,
1106
+ * and report it. A no-op before the table definition loads — bootstrap
1107
+ * waits for it, and the query gate covers any other early writer.
1108
+ */
1109
+ function pruneForApply(snapshot, presetId) {
1110
+ const known = opts.knownFields?.value;
1111
+ if (!known) return snapshot;
1112
+ const { snapshot: pruned, dropped } = (0, _atscript_ui_table.prunePresetSnapshot)(snapshot, known);
1113
+ if (dropped) opts.onFieldsDropped?.({
1114
+ ...dropped,
1115
+ source: "preset",
1116
+ presetId: presetId ?? void 0
1117
+ });
1118
+ return pruned;
1119
+ }
1104
1120
  function apply(idOrSnapshot) {
1105
- const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
1121
+ const { snapshot: resolved, nextActiveId } = resolveSnapshot(idOrSnapshot);
1122
+ const snapshot = pruneForApply(resolved, nextActiveId);
1106
1123
  const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && (0, _atscript_ui_table.isSystemPresetId)(nextActiveId);
1107
1124
  for (const aspect of ownedAspects(nextActiveId)) {
1108
1125
  const value = snapshot[aspect];
@@ -1143,7 +1160,8 @@ function createPresetState(opts) {
1143
1160
  }
1144
1161
  return out;
1145
1162
  }
1146
- const activeSnapshot = (0, vue.computed)(() => {
1163
+ /** The active preset's snapshot as stored — the base `saveActive` merges back into. */
1164
+ const storedActiveSnapshot = (0, vue.computed)(() => {
1147
1165
  const id = activeId.value;
1148
1166
  if (!id) return {};
1149
1167
  if ((0, _atscript_ui_table.isSystemPresetId)(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {}, id);
@@ -1152,6 +1170,24 @@ function createPresetState(opts) {
1152
1170
  const wire = row.data?.content;
1153
1171
  return wire ? (0, _atscript_ui_table.fromWireSnapshot)(wire) : {};
1154
1172
  });
1173
+ const prunedActive = (0, vue.computed)(() => {
1174
+ const known = opts.knownFields?.value;
1175
+ const stored = storedActiveSnapshot.value;
1176
+ return known ? (0, _atscript_ui_table.prunePresetSnapshot)(stored, known) : {
1177
+ snapshot: stored,
1178
+ dropped: null
1179
+ };
1180
+ });
1181
+ const activeSnapshot = (0, vue.computed)(() => prunedActive.value.snapshot);
1182
+ const droppedFields = (0, vue.computed)(() => {
1183
+ const dropped = prunedActive.value.dropped;
1184
+ const presetId = activeId.value;
1185
+ return dropped && presetId ? {
1186
+ ...dropped,
1187
+ source: "preset",
1188
+ presetId
1189
+ } : null;
1190
+ });
1155
1191
  const isDirty = (0, vue.computed)(() => (0, _atscript_ui_table.isDirtyAgainst)(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1156
1192
  function requirePresets() {
1157
1193
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
@@ -1163,10 +1199,12 @@ function createPresetState(opts) {
1163
1199
  const id = activeId.value;
1164
1200
  if (!id) throw new Error("[vue-table] saveActive: no active preset");
1165
1201
  if ((0, _atscript_ui_table.isSystemPresetId)(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
1166
- const existing = activeSnapshot.value;
1202
+ const existing = storedActiveSnapshot.value;
1167
1203
  const mask = {};
1168
1204
  for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
1169
- await handle.savePreset(captureSnapshot(mask));
1205
+ const captured = captureSnapshot(mask);
1206
+ const dropped = prunedActive.value.dropped;
1207
+ await handle.savePreset(dropped ? (0, _atscript_ui_table.restoreDroppedEntries)(captured, dropped) : captured);
1170
1208
  clearLocalDraft();
1171
1209
  });
1172
1210
  }
@@ -1225,20 +1263,25 @@ function createPresetState(opts) {
1225
1263
  function bootstrap() {
1226
1264
  if (!opts.presetsHandle) return;
1227
1265
  const handle = opts.presetsHandle;
1228
- const stop = (0, vue.watch)(() => handle.loading.value, (loading) => {
1229
- if (loading) return;
1230
- stop();
1266
+ const knownReady = () => !opts.knownFields || opts.knownFields.value !== null;
1267
+ let done = false;
1268
+ let stop;
1269
+ stop = (0, vue.watch)(() => !handle.loading.value && knownReady(), (ready) => {
1270
+ if (!ready || done) return;
1271
+ done = true;
1272
+ stop?.();
1231
1273
  const id = resolveDefaultId();
1232
1274
  const { snapshot, nextActiveId } = resolveSnapshot(id);
1233
1275
  const base = (0, _atscript_ui_table.isSystemPresetId)(id) ? expandSystemSnapshot(snapshot, id) : snapshot;
1234
1276
  const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
1235
- apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
1277
+ apply(pruneForApply(draftEnabled ? opts.draftHandle.hydrate(base) : base, nextActiveId));
1236
1278
  activeId.value = nextActiveId;
1237
1279
  if (draftEnabled) opts.draftHandle.watchAndPersist(() => captureSnapshot(), () => activeSnapshot.value);
1238
1280
  (0, vue.nextTick)(() => {
1239
1281
  gate.value = true;
1240
1282
  });
1241
1283
  }, { immediate: true });
1284
+ if (done) stop();
1242
1285
  }
1243
1286
  return {
1244
1287
  slice: {
@@ -1254,6 +1297,7 @@ function createPresetState(opts) {
1254
1297
  available,
1255
1298
  activeId,
1256
1299
  activeSnapshot,
1300
+ droppedFields,
1257
1301
  isDirty,
1258
1302
  canSaveActive,
1259
1303
  currentUser,
@@ -1607,6 +1651,15 @@ function warnUnsupportedFilter(issue) {
1607
1651
  if (!_atscript_ui_table.DEV) return;
1608
1652
  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
1653
  }
1654
+ /**
1655
+ * The report for fields the table left out when nobody handles it
1656
+ * (`onFieldsDropped` / `@fields-dropped`): a dev-mode warning.
1657
+ */
1658
+ function warnFieldsDropped(report) {
1659
+ if (!_atscript_ui_table.DEV) return;
1660
+ const from = report.presetId ? `${report.source} "${report.presetId}"` : report.source;
1661
+ console.warn(`[vue-table] Left out fields this table cannot use (${from}): ${report.fields.join(", ")}.`);
1662
+ }
1610
1663
  function createTableState(opts) {
1611
1664
  const client = opts.client;
1612
1665
  const modelOpts = opts.model;
@@ -1681,6 +1734,28 @@ function createTableState(opts) {
1681
1734
  const configDialogOpen = (0, vue.ref)(false);
1682
1735
  const configTab = (0, vue.ref)("columns");
1683
1736
  const filterDialogColumn = (0, vue.ref)(null);
1737
+ /**
1738
+ * The paths the table can use, from its column list: every column, and the
1739
+ * server-backed ones. `null` until the table definition loads. Presets,
1740
+ * drafts, URLs and the query itself are all pruned against it.
1741
+ */
1742
+ const knownFields = (0, vue.computed)(() => {
1743
+ const all = allColumns.value;
1744
+ if (all.length === 0) return null;
1745
+ const columns = /* @__PURE__ */ new Set();
1746
+ const server = /* @__PURE__ */ new Set();
1747
+ for (const c of all) {
1748
+ columns.add(c.path);
1749
+ if (!c.local) server.add(c.path);
1750
+ }
1751
+ return {
1752
+ columns,
1753
+ server
1754
+ };
1755
+ });
1756
+ function reportFieldsDropped(report) {
1757
+ (queryOpts?.onFieldsDropped ?? warnFieldsDropped)(report);
1758
+ }
1684
1759
  const { slice: presetSlice, internals: presetInternals } = createPresetState({
1685
1760
  columnNames,
1686
1761
  columnWidths,
@@ -1695,7 +1770,9 @@ function createTableState(opts) {
1695
1770
  availableAspects: opts.preset?.availableAspects,
1696
1771
  systemAspects: opts.preset?.systemAspects,
1697
1772
  persistDrafts: opts.preset?.persistDrafts,
1698
- fallbackSystemPresets: opts.preset?.fallbackSystemPresets
1773
+ fallbackSystemPresets: opts.preset?.fallbackSystemPresets,
1774
+ knownFields,
1775
+ onFieldsDropped: reportFieldsDropped
1699
1776
  });
1700
1777
  presetInternals.bootstrap();
1701
1778
  const promptSlot = createRequestSlot(false);
@@ -1737,7 +1814,10 @@ function createTableState(opts) {
1737
1814
  */
1738
1815
  const localColumnPaths = (0, vue.computed)(() => {
1739
1816
  const out = /* @__PURE__ */ new Set();
1740
- for (const c of allColumns.value) if (c.local) out.add(c.path);
1817
+ const known = knownFields.value;
1818
+ if (known) {
1819
+ for (const p of known.columns) if (!known.server.has(p)) out.add(p);
1820
+ }
1741
1821
  return out;
1742
1822
  });
1743
1823
  /**
@@ -1770,22 +1850,67 @@ function createTableState(opts) {
1770
1850
  if (splitSorters.value.local.length === 0 || sortersIgnored()) return rows;
1771
1851
  return (0, _atscript_ui_table.sortRowsLocally)(rows, queryOpts?.forceSorters?.length ? (0, _atscript_ui_table.mergeSorters)(queryOpts.forceSorters, sorters.value) : sorters.value, sortValueOf);
1772
1852
  }
1853
+ /**
1854
+ * The query's view of the model, gated to the fields this caller can use.
1855
+ * This is the guarantee that no table state sends a field the server does
1856
+ * not expose: presets and URLs are pruned on apply, but `setFieldFilter`,
1857
+ * `addFilterField`, `v-model` writes, `setResidualFilters` and the like
1858
+ * write straight to the model. `$select` is gated by the column list
1859
+ * (`knownFields.columns`, then client-owned columns are stripped); filters
1860
+ * and residual conditions by the server-backed columns. `forceFilters` /
1861
+ * `forceSorters` are app-authored and never gated. Memoized: pages,
1862
+ * window blocks and exports reuse it.
1863
+ */
1864
+ const queryModel = (0, vue.computed)(() => {
1865
+ const model = {
1866
+ columnNames: columnNames.value,
1867
+ filters: filters.value,
1868
+ sorters: splitSorters.value.server,
1869
+ residual: residualFilters.value,
1870
+ stripped: []
1871
+ };
1872
+ const known = knownFields.value;
1873
+ if (!known) return model;
1874
+ const { snapshot, dropped } = (0, _atscript_ui_table.prunePresetSnapshot)({
1875
+ columns: { columnNames: model.columnNames },
1876
+ filterOps: model.filters,
1877
+ sorters: model.sorters
1878
+ }, known);
1879
+ const residual = (0, _atscript_ui_table.pruneResidualFilters)(model.residual, known.server);
1880
+ return {
1881
+ columnNames: model.columnNames,
1882
+ filters: snapshot.filterOps,
1883
+ sorters: snapshot.sorters,
1884
+ residual: residual.kept,
1885
+ stripped: [...new Set([...dropped?.fields ?? [], ...residual.dropped?.fields ?? []])]
1886
+ };
1887
+ });
1888
+ /** Stripped path sets already warned about — one dev warning per set. */
1889
+ const warnedStripped = /* @__PURE__ */ new Set();
1773
1890
  function buildCurrentQuery(buildOpts) {
1774
- const available = tableDef.value?.fetchableFields ?? new Set(allColumns.value.map((c) => c.path));
1891
+ const available = tableDef.value?.fetchableFields ?? knownFields.value?.columns ?? /* @__PURE__ */ new Set();
1775
1892
  const extra = /* @__PURE__ */ new Set();
1776
1893
  for (const c of columns.value) for (const p of c.selectWith ?? []) if (available.has(p)) extra.add(p);
1777
1894
  if (queryOpts?.alwaysSelected) {
1778
1895
  for (const p of queryOpts.alwaysSelected) if (available.has(p)) extra.add(p);
1779
1896
  }
1897
+ const model = queryModel.value;
1898
+ if (_atscript_ui_table.DEV && model.stripped.length > 0) {
1899
+ const key = model.stripped.join(",");
1900
+ if (!warnedStripped.has(key)) {
1901
+ warnedStripped.add(key);
1902
+ console.warn(`[vue-table] Query leaves out fields this table cannot use: ${model.stripped.join(", ")}.`);
1903
+ }
1904
+ }
1905
+ const known = knownFields.value;
1780
1906
  const localPaths = localColumnPaths.value;
1781
- const requested = buildOpts?.columnPaths ?? columnNames.value;
1782
1907
  return (0, _atscript_ui_table.buildTableQuery)({
1783
- visibleColumnPaths: localPaths.size ? requested.filter((p) => !localPaths.has(p)) : requested,
1908
+ visibleColumnPaths: (buildOpts?.columnPaths ?? model.columnNames).filter((p) => !localPaths.has(p) && (!known || known.columns.has(p))),
1784
1909
  extraSelect: extra.size ? [...extra] : void 0,
1785
- sorters: splitSorters.value.server,
1910
+ sorters: model.sorters,
1786
1911
  forceSorters: queryOpts?.forceSorters,
1787
- filters: filters.value,
1788
- residualFilters: residualFilters.value,
1912
+ filters: model.filters,
1913
+ residualFilters: model.residual,
1789
1914
  forceFilters: queryOpts?.forceFilters,
1790
1915
  search: searchTerm.value || void 0,
1791
1916
  ignoreSorters: sortersIgnored(),
@@ -2179,12 +2304,28 @@ function createTableState(opts) {
2179
2304
  function applyUrlQuery(urlString, opts) {
2180
2305
  captureUrlBaseline();
2181
2306
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2182
- const cols = allColumns.value;
2307
+ const known = knownFields.value;
2183
2308
  const parsed = (0, _atscript_ui_table.urlQueryStringToState)(urlString, {
2184
- knownFields: cols.length > 0 ? cols.filter((c) => !c.local).map((c) => c.path) : void 0,
2309
+ knownFields: known?.server,
2310
+ localFields: known ? localColumnPaths.value : void 0,
2185
2311
  sync: urlQuerySync
2186
2312
  });
2187
2313
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2314
+ if (parsed.unknown || parsed.unknownSorters) {
2315
+ const residual = parsed.unknown?.map((u) => u.expr) ?? [];
2316
+ const sorters = parsed.unknownSorters ?? [];
2317
+ const fields = new Set(parsed.unknown?.flatMap((u) => u.fields));
2318
+ for (const s of sorters) fields.add(s.field);
2319
+ reportFieldsDropped({
2320
+ source: "url",
2321
+ fields: [...fields],
2322
+ columns: [],
2323
+ filterFields: [],
2324
+ filters: {},
2325
+ residual,
2326
+ sorters
2327
+ });
2328
+ }
2188
2329
  const urlResidual = parsed.residual ?? [];
2189
2330
  const residualPaths = new Set(urlResidual.flatMap((expr) => (0, _atscript_ui_table.filterExprFields)(expr)));
2190
2331
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
@@ -2655,6 +2796,12 @@ Object.defineProperty(exports, "useTableContextOptional", {
2655
2796
  return useTableContextOptional;
2656
2797
  }
2657
2798
  });
2799
+ Object.defineProperty(exports, "warnFieldsDropped", {
2800
+ enumerable: true,
2801
+ get: function() {
2802
+ return warnFieldsDropped;
2803
+ }
2804
+ });
2658
2805
  Object.defineProperty(exports, "warnUnsupportedFilter", {
2659
2806
  enumerable: true,
2660
2807
  get: function() {
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.141",
4
4
  "description": "Type-driven table components for Atscript with Vue 3",
5
5
  "keywords": [
6
6
  "atscript",
@@ -167,9 +167,9 @@
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/ui": "^0.1.141",
171
+ "@atscript/ui-table": "^0.1.141",
172
+ "@atscript/vue-form": "^0.1.141"
173
173
  },
174
174
  "devDependencies": {
175
175
  "@atscript/db-client": "^0.1.133",