@atscript/vue-table 0.1.131 → 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-DTGYOsZh.d.mts → as-table-actions.vue-CckRRsL6.d.cts} +1 -1
  54. package/dist/{as-table-actions.vue-WXkllHVw.d.cts → as-table-actions.vue-DsqauvXR.d.mts} +1 -1
  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-BqZl6JQ0.d.mts → as-table-root.vue-BPU92_Ns.d.mts} +59 -16
  72. package/dist/{as-table-root.vue-umgSvVUB.d.cts → as-table-root.vue-szvjZsCn.d.cts} +59 -16
  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-DxtfL26P.d.mts → as-table.vue-4WhMA8AM.d.mts} +27 -3
  80. package/dist/{as-table.vue-D9cCjbtr.d.cts → 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-BpvgmsC1.d.mts → as-window-table.vue-CWsJvOkL.d.mts} +26 -3
  88. package/dist/{as-window-table.vue-BavmqDeH.d.cts → as-window-table.vue-h1bdyMv7.d.cts} +26 -3
  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
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { a as useTableContext, m as extractIdentifier, n as createTableState, o as useTableContextOptional, t as createStaticTableState, x as ROW_ACTIONS_TYPE } from "./use-table-state-BxX3SpeW.mjs";
1
+ import { D as ROW_ACTIONS_TYPE, a as useTableContext, b as extractIdentifier, n as createTableState, o as useTableContextOptional, t as createStaticTableState } from "./use-table-state-DLJbW753.mjs";
2
2
  import { n as useTableSelection } from "./use-table-selection-C84z4OZy.mjs";
3
- import { a as AS_PRESETS_APP, i as useLocalDraft, n as useTable, o as injectPresetsApp, r as usePresets, t as clearTableCache } from "./use-table-CyFcVtUN.mjs";
4
- import { t as useTableNavBridge } from "./use-table-nav-bridge-DeGeaQX-.mjs";
5
- import { t as useTableComponent } from "./use-table-component-K6xj1obF.mjs";
3
+ import { a as AS_PRESETS_APP, i as useLocalDraft, n as useTable, o as injectPresetsApp, r as usePresets, t as clearTableCache } from "./use-table-Bk-jSxOM.mjs";
4
+ import { t as useTableNavBridge } from "./use-table-nav-bridge-B9SSEajW.mjs";
5
+ import { t as useTableComponent } from "./use-table-component-KuzZxAP8.mjs";
6
6
  import { t as getCellValue } from "./get-cell-value-C8LLc_Rr.mjs";
7
7
  import { t as as_cell_array_default } from "./as-cell-array-2qmJ1lT2.mjs";
8
8
  import { t as as_cell_date_default } from "./as-cell-date-BwFgi6O0.mjs";
@@ -12,24 +12,24 @@ import { t as as_cell_number_default } from "./as-cell-number-DKRaS9pM.mjs";
12
12
  import { t as formatCellValue } from "./format-cell-D1Ziv96Q.mjs";
13
13
  import { t as as_cell_union_default } from "./as-cell-union-C9FukGqF.mjs";
14
14
  import { t as as_column_menu_default } from "./as-column-menu-kTIQfLTi.mjs";
15
- import { t as as_config_dialog_default } from "./as-config-dialog-BehfqIGd.mjs";
16
- import { t as as_confirm_dialog_default } from "./as-confirm-dialog-bpN4A-KN.mjs";
15
+ import { t as as_config_dialog_default } from "./as-config-dialog-BKsm8OHR.mjs";
16
+ import { t as as_confirm_dialog_default } from "./as-confirm-dialog-BV01t7dn.mjs";
17
17
  import { t as as_filter_input_default } from "./as-filter-input-BM23TNeh.mjs";
18
- import { t as as_filter_dialog_default } from "./as-filter-dialog-D1qzq-8b.mjs";
19
- import { t as as_table_header_cell_default } from "./as-table-header-cell-Bdhl5Lox.mjs";
20
- import { t as as_window_table_default } from "./as-window-table-DWujiUQy.mjs";
21
- import { t as as_filter_field_default } from "./as-filter-field-DirBmnbb.mjs";
22
- import { t as as_filters_default } from "./as-filters-beIud9sF.mjs";
23
- import { t as as_preset_dialog_default } from "./as-preset-dialog-CDqCAR7N.mjs";
24
- import { t as as_row_actions_default } from "./as-row-actions-xqJeAdQl.mjs";
18
+ import { t as as_filter_dialog_default } from "./as-filter-dialog-Ckrl79kU.mjs";
19
+ import { t as as_table_header_cell_default } from "./as-table-header-cell-q-c_QklI.mjs";
20
+ import { t as as_window_table_default } from "./as-window-table-S1XWpDHQ.mjs";
21
+ import { t as as_filter_field_default } from "./as-filter-field-C-IFnj8K.mjs";
22
+ import { t as as_filters_default } from "./as-filters-DJEQj7_p.mjs";
23
+ import { t as as_preset_dialog_default } from "./as-preset-dialog-CCIacZ2C.mjs";
24
+ import { t as as_row_actions_default } from "./as-row-actions-pukI1JT7.mjs";
25
25
  import { t as as_table_cell_value_default } from "./as-table-cell-value-CPcFxSOj.mjs";
26
- import { t as as_table_root_default } from "./as-table-root-BLReUbRH.mjs";
27
- import { t as as_table_default } from "./as-table-CDSG2yED.mjs";
28
- import { t as as_table_actions_default } from "./as-table-actions-DMxeaczm.mjs";
29
- import { t as as_preset_picker_default } from "./as-preset-picker-CISaxEZ0.mjs";
26
+ import { t as as_table_root_default } from "./as-table-root-Bpsrp0Vz.mjs";
27
+ import { t as as_table_default } from "./as-table-D5_bQ0p5.mjs";
28
+ import { t as as_table_actions_default } from "./as-table-actions-DKYK_mTO.mjs";
29
+ import { t as as_preset_picker_default } from "./as-preset-picker-DGSqQl9A.mjs";
30
30
  import { getDefaultClientFactory, resetDefaultClientFactory, setDefaultClientFactory } from "@atscript/ui";
31
31
  import { computed, effectScope, ref, shallowRef } from "vue";
32
- import { AppPrefsClient, PRESET_ASPECTS, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, columnFilterType, conditionsForType, defaultCondition, isAuthError, isFilled, isSystemPresetId, resolveSystemPresets } from "@atscript/ui-table";
32
+ import { AppPrefsClient, PRESET_ASPECTS, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, columnFilterType, conditionsForType, defaultCondition, isFilled, isSystemPresetId, isUnavailableError, resolveSystemPresets, urlQueryConsumesKey } from "@atscript/ui-table";
33
33
  import { StorageSerializers, useEventBus, useStorage } from "@vueuse/core";
34
34
  //#region src/composables/use-table-filter.ts
35
35
  /**
@@ -187,11 +187,19 @@ function findCleanEq(segment) {
187
187
  * dependency on `vue-router` is added to `@atscript/vue-table`. Consumers
188
188
  * pass in their already-resolved `useRoute()` and `useRouter()` instances.
189
189
  *
190
- * Scope: **owns the whole query string**. The getter returns the entire
191
- * `route.query` serialized; the setter replaces `route.query` wholesale.
192
- * Apps that need the table to coexist with non-table query params should
193
- * write their own `computed` instead — that pattern is small and keeps the
194
- * library's contract crisp.
190
+ * Scope: the bridge owns **only what the table reads back** (since 0.1.133).
191
+ * A key is the table's iff `urlQueryStringToState` would consume it — see
192
+ * `urlQueryConsumesKey` — or the bridge has written it before. Read and
193
+ * write therefore agree in both directions: a key the parser ignores (a host
194
+ * flag, an unrecognised `$control`) is never removed, and a key the parser
195
+ * reads is the bridge's to remove. Every write merges into the current
196
+ * `route.query`, in place.
197
+ *
198
+ * Without a `prefix` the bridge cannot tell a plain `field=value` filter from
199
+ * a page-owned flag of the same shape, so a plain key that was already in the
200
+ * URL when the bridge mounted counts as foreign until the bridge writes it
201
+ * itself. Pass `prefix` when a route carries a host key that could collide
202
+ * with a column path, or when it carries two tables.
195
203
  *
196
204
  * @example
197
205
  * ```vue
@@ -199,6 +207,9 @@ function findCleanEq(segment) {
199
207
  * import { useRoute, useRouter } from 'vue-router';
200
208
  * import { useTableUrlQuery } from '@atscript/vue-table';
201
209
  * const urlQuery = useTableUrlQuery(useRoute(), useRouter());
210
+ * // Two tables on one route:
211
+ * // const left = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'a' });
212
+ * // const right = useTableUrlQuery(useRoute(), useRouter(), { prefix: 'b' });
202
213
  * <\/script>
203
214
  * <template>
204
215
  * <AsTableRoot v-model:url-query="urlQuery" url="/db/products" .../>
@@ -207,25 +218,46 @@ function findCleanEq(segment) {
207
218
  */
208
219
  function useTableUrlQuery(route, router, opts = {}) {
209
220
  const navigate = (opts.mode ?? "replace") === "push" ? router.push.bind(router) : router.replace.bind(router);
221
+ const prefix = opts.prefix ? `${opts.prefix}.` : "";
222
+ const written = /* @__PURE__ */ new Set();
223
+ function isOwn(key) {
224
+ if (prefix) return key.startsWith(prefix);
225
+ return written.has(key) || urlQueryConsumesKey(key);
226
+ }
210
227
  return computed({
211
228
  get: () => {
212
229
  const q = route.query;
213
230
  const parts = [];
214
231
  for (const key in q) {
232
+ if (prefix && !key.startsWith(prefix)) continue;
233
+ const own = prefix ? key.slice(prefix.length) : key;
215
234
  const v = q[key];
216
- if (Array.isArray(v)) for (const item of v) parts.push(item == null ? key : `${key}=${item}`);
217
- else if (v == null) parts.push(key);
218
- else parts.push(`${key}=${v}`);
235
+ if (Array.isArray(v)) for (const item of v) parts.push(item == null ? own : `${own}=${item}`);
236
+ else if (v == null) parts.push(own);
237
+ else parts.push(`${own}=${v}`);
219
238
  }
220
239
  return parts.join("&");
221
240
  },
222
241
  set: (urlString) => {
223
- const query = {};
242
+ const serialized = /* @__PURE__ */ new Map();
224
243
  for (const segment of splitSegments(urlString)) {
225
244
  const eqIdx = findCleanEq(segment);
226
- if (eqIdx > 0) query[segment.slice(0, eqIdx)] = segment.slice(eqIdx + 1);
227
- else query[segment] = null;
245
+ const key = eqIdx > 0 ? `${prefix}${segment.slice(0, eqIdx)}` : `${prefix}${segment}`;
246
+ serialized.set(key, eqIdx > 0 ? segment.slice(eqIdx + 1) : null);
247
+ written.add(key);
248
+ }
249
+ const query = {};
250
+ for (const key in route.query) {
251
+ if (!isOwn(key)) {
252
+ query[key] = route.query[key];
253
+ continue;
254
+ }
255
+ if (serialized.has(key)) {
256
+ query[key] = serialized.get(key);
257
+ serialized.delete(key);
258
+ }
228
259
  }
260
+ for (const [key, value] of serialized) query[key] = value;
229
261
  navigate({ query });
230
262
  }
231
263
  });
@@ -316,7 +348,7 @@ function createInstance(app, opts) {
316
348
  cache.value = { ...result.prefs };
317
349
  existing.value = result.row;
318
350
  } catch (err) {
319
- if (isAuthError(err)) {
351
+ if (isUnavailableError(err)) {
320
352
  denied.value = true;
321
353
  clearCache();
322
354
  existing.value = null;
@@ -1,4 +1,4 @@
1
- import { Component, ComputedRef, Ref, ShallowRef } from "vue";
1
+ import { Component, ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
2
2
  import { ClientError, TDbActionInfo, TDbActionProcessor, TDbDeleteResult } from "@atscript/db-client";
3
3
  import { ColumnDef, PaginationControl, SortControl, TableDef } from "@atscript/ui";
4
4
  import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, FieldFilters, PresetAspect, PresetCapabilities, PresetSnapshot, SelectionMode, SystemPreset, SystemPresetInput, TableStateMethods } from "@atscript/ui-table";
@@ -190,14 +190,62 @@ interface PresetConfig {
190
190
  * Add `'itemsPerPage'` for paginated tables; drop any aspect this app doesn't use.
191
191
  */
192
192
  aspects?: PresetAspect[];
193
+ /**
194
+ * Aspects a SYSTEM preset owns. Intersected with `aspects`; defaults to all
195
+ * of them. Narrow it to e.g. `['filterOps']` when system presets are
196
+ * filter-only views that must not reset the user's columns, displayed
197
+ * filters, sorters or page size. Since 0.1.133.
198
+ */
199
+ systemAspects?: PresetAspect[];
193
200
  /**
194
201
  * Opt-in localStorage overlay for the user's in-flight tweaks (default
195
202
  * `false`). Persists `columns` / `filters` / `sorters` / opt-in
196
203
  * `itemsPerPage`; `filterOps` / `searchTerm` / pagination are NOT persisted.
197
204
  */
198
205
  persistDrafts?: boolean;
206
+ /**
207
+ * Identity the draft is stored under — pass the signed-in user's id
208
+ * whenever `persistDrafts` is on, so a shared browser never restores the
209
+ * previous user's draft. Omitted keeps the pre-0.1.133 unscoped key.
210
+ * Since 0.1.133.
211
+ */
212
+ draftScope?: MaybeRefOrGetter<string | undefined>;
199
213
  }
200
214
  type QueryErrorKind = "initial" | "query" | "queryNext" | "loadRange";
215
+ /**
216
+ * Context handed to the per-row hooks of `<AsTable>` (`rowSelectable`,
217
+ * `rowClass`, `rowAttrs`). Since 0.1.133.
218
+ */
219
+ interface RowHookContext {
220
+ /** Position of the row in the rows the renderer is currently rendering. */
221
+ index: number;
222
+ /**
223
+ * Whether the row's pk is in `state.selectedRows` right now. A getter —
224
+ * reading it subscribes that hook call to the selection, so predicates
225
+ * that ignore it are not re-run when the selection changes.
226
+ */
227
+ readonly selected: boolean;
228
+ }
229
+ /**
230
+ * Per-row selectability predicate. Return `false` — or a string, which is
231
+ * surfaced as the disabled reason on the selection control — to make the row
232
+ * not selectable; `true` (or omitting the prop) keeps it selectable.
233
+ * Since 0.1.133.
234
+ */
235
+ type RowSelectableHook = (row: Record<string, unknown>, ctx: RowHookContext) => boolean | string | undefined | void;
236
+ /** Extra classes for the row element. Since 0.1.133. */
237
+ type RowClassHook = (row: Record<string, unknown>, ctx: RowHookContext) => string | string[] | Record<string, boolean> | undefined;
238
+ /**
239
+ * Extra attributes for the row element. The framework's own `id`, `role`,
240
+ * `aria-*`, `data-*`, `class` and `style` always win. Since 0.1.133.
241
+ */
242
+ type RowAttrsHook = (row: Record<string, unknown>, ctx: RowHookContext) => Record<string, unknown> | undefined;
243
+ /** Normalised {@link RowSelectableHook} verdict. */
244
+ interface RowSelectableVerdict {
245
+ ok: boolean;
246
+ /** Disabled reason, when the predicate returned a string. */
247
+ reason?: string;
248
+ }
201
249
  /** Controls which sections appear in the column header dropdown menu. */
202
250
  interface ColumnMenuConfig {
203
251
  sort?: boolean;
@@ -373,6 +421,31 @@ interface ReactiveTableState extends TableStateMethods {
373
421
  * naturally without consulting mode.
374
422
  */
375
423
  isPkSelected: (pk: unknown) => boolean;
424
+ /**
425
+ * Per-row selectability predicate — writable ref owned by the renderer,
426
+ * like {@link rowDelete}. `<AsTable>` / `<AsWindowTable>` push their
427
+ * `:row-selectable` prop into it, and every selection path in the model
428
+ * gates on it. Since 0.1.133.
429
+ */
430
+ rowSelectable: Ref<RowSelectableHook | undefined>;
431
+ /**
432
+ * Normalised `rowSelectable` verdict for one row. `index` is the row's
433
+ * position in what the renderer renders (absolute in window mode).
434
+ * Since 0.1.133.
435
+ */
436
+ isRowSelectable: (row: Record<string, unknown>, index: number) => RowSelectableVerdict;
437
+ /** The subset of `rows` the `rowSelectable` predicate lets through. Since 0.1.133. */
438
+ selectableRows: (rows: readonly Record<string, unknown>[]) => Record<string, unknown>[];
439
+ /** How many of `rows` the `rowSelectable` predicate lets through. Since 0.1.133. */
440
+ selectableCount: (rows: readonly Record<string, unknown>[]) => number;
441
+ /**
442
+ * Select every eligible row in `rows`. An ineligible row that is already
443
+ * selected stays selected. `indexOf` maps a position in `rows` to the
444
+ * index the `rowSelectable` predicate sees — windowed callers pass it
445
+ * because their rows come from a cache keyed by absolute index.
446
+ * Since 0.1.133.
447
+ */
448
+ selectAll: (rows: readonly Record<string, unknown>[], indexOf?: (position: number) => number) => void;
376
449
  /** Column currently open in the filter dialog (null when closed). */
377
450
  filterDialogColumn: Ref<ColumnDef | null>;
378
451
  /** Absolute index of the keyboard-active row. -1 = nothing active. */
@@ -398,7 +471,10 @@ interface ReactiveTableState extends TableStateMethods {
398
471
  setActive: (absIndex: number) => void;
399
472
  /** Reset the active-row index to `-1`. */
400
473
  clearActive: () => void;
401
- /** Toggle selection of the active row's PK in `selectedRows`. */
474
+ /**
475
+ * Toggle selection of the active row's PK in `selectedRows`. No-op in
476
+ * `select="none"` and on a row the `rowSelectable` predicate rejects.
477
+ */
402
478
  toggleActiveSelection: (mode: SelectionMode) => void;
403
479
  /** Ask the rendering component to emit `main-action` for the active row. */
404
480
  requestMainAction: (event: KeyboardEvent | MouseEvent) => void;
@@ -415,6 +491,12 @@ interface ReactiveTableState extends TableStateMethods {
415
491
  * consumers should not mutate directly.
416
492
  */
417
493
  confirmRequest: Ref<ConfirmRequest | null>;
494
+ /**
495
+ * The prompt request the dialog SURFACE renders: the last non-null
496
+ * `confirmRequest`, retained after it is answered so the copy survives the
497
+ * exit animation. Internal — used by the dialog. @since 0.1.133
498
+ */
499
+ confirmDisplay: Ref<ConfirmRequest | null>;
418
500
  /**
419
501
  * Open the in-app confirm dialog with `message` as the body. Resolves
420
502
  * `true` on accept, `false` on cancel/dismiss. Public — consumers can
@@ -431,12 +513,24 @@ interface ReactiveTableState extends TableStateMethods {
431
513
  acceptPrompt: () => void;
432
514
  /** Resolve the active prompt with `false`. Internal — used by the dialog. */
433
515
  dismissPrompt: () => void;
516
+ /**
517
+ * Clear `confirmDisplay` once the dialog surface has left (bound to the
518
+ * content's `after-leave`). A no-op while a prompt is pending. Internal.
519
+ * @since 0.1.133
520
+ */
521
+ releaseConfirm: () => void;
434
522
  /**
435
523
  * Currently pending action-form request — `null` when no form is open.
436
524
  * Writes are owned by `requestActionInput()` / `acceptActionForm()` /
437
525
  * `dismissActionForm()`; consumers should not mutate directly.
438
526
  */
439
527
  actionFormRequest: Ref<ActionFormRequest | null>;
528
+ /**
529
+ * The form request the dialog SURFACE renders: the last non-null
530
+ * `actionFormRequest`, retained after it is answered so the title, id chips
531
+ * and form survive the exit animation. Internal. @since 0.1.133
532
+ */
533
+ actionFormDisplay: Ref<ActionFormRequest | null>;
440
534
  /**
441
535
  * Open the action-form dialog. Resolves with the submitted form payload
442
536
  * on accept, or `null` on cancel/dismiss.
@@ -449,6 +543,12 @@ interface ReactiveTableState extends TableStateMethods {
449
543
  acceptActionForm: (input: unknown) => void;
450
544
  /** Resolve the active form request with `null`. Internal — used by the dialog. */
451
545
  dismissActionForm: () => void;
546
+ /**
547
+ * Clear `actionFormDisplay` (and the loaded schema/draft) once the dialog
548
+ * surface has left. A no-op while a request is pending. Internal.
549
+ * @since 0.1.133
550
+ */
551
+ releaseActionForm: () => void;
452
552
  /**
453
553
  * Hydrate state from a URL query string produced by `stateToUrlQueryString`.
454
554
  * Replaces filters / sorters / search / pagination with values decoded from
@@ -487,9 +587,22 @@ interface PresetSurface {
487
587
  * `['columns', 'filters', 'filterOps', 'sorters']`.
488
588
  */
489
589
  availableAspects: PresetAspect[];
590
+ /**
591
+ * Aspects a SYSTEM preset owns — subset of `availableAspects`, equal to it
592
+ * unless `preset.systemAspects` narrowed it. Drives apply/reset, the dirty
593
+ * baseline, the picker/dialog badges and the Save-As defaults for system
594
+ * presets. Since 0.1.133.
595
+ */
596
+ systemAspects: PresetAspect[];
597
+ /**
598
+ * Aspects the preset `id` owns — the ones applying it writes. System →
599
+ * `systemAspects`, stored → what its snapshot claims, `null` (or an id
600
+ * with no row) → `availableAspects`. Since 0.1.133.
601
+ */
602
+ ownedAspects: (id: string | null) => PresetAspect[];
490
603
  /**
491
604
  * False when `preset` config is absent OR the initial load returned
492
- * 401/403 OR any other error. The picker / dialog should hide their UI.
605
+ * 401/403/404 OR any other error. The picker / dialog should hide their UI.
493
606
  */
494
607
  available: ComputedRef<boolean>;
495
608
  /** Currently active preset id (system `'sys:*'` or stored). null = no active. */
@@ -548,6 +661,15 @@ interface PresetSurface {
548
661
  * feature is off.
549
662
  */
550
663
  batch: <T>(fn: () => Promise<T>) => Promise<T>;
664
+ /**
665
+ * Error thrown by the most recent mutator, or null — the SINGLE channel
666
+ * for preset write failures. Cleared when an OUTERMOST mutator starts, so
667
+ * a `batch()` counts as one user action: a failure early in a batch is not
668
+ * wiped by a later write inside it, and the batch's last failure wins.
669
+ * Mutators rethrow as well, so a caller can handle a single call; this ref
670
+ * is for UI that only needs to render the failure. Since 0.1.133.
671
+ */
672
+ lastError: Ref<Error | null>;
551
673
  }
552
674
  //#endregion
553
- export { TableNavBridge as _, EnterAction as a, NavKeyOptions as c, ReactiveTableState as d, RowDeleteOpt as f, TableActionsState as g, TVueTableActionInfo as h, ConfirmOptions as i, PresetConfig as l, TAsTableControls as m, ColumnMenuConfig as n, InvokeOpts as o, TAsCellTypeComponents as p, ConfigTab as r, MainActionRequest as s, ActionResult as t, QueryErrorKind as u };
675
+ export { TableNavBridge as S, RowSelectableVerdict as _, EnterAction as a, TVueTableActionInfo as b, NavKeyOptions as c, ReactiveTableState as d, RowAttrsHook as f, RowSelectableHook as g, RowHookContext as h, ConfirmOptions as i, PresetConfig as l, RowDeleteOpt as m, ColumnMenuConfig as n, InvokeOpts as o, RowClassHook as p, ConfigTab as r, MainActionRequest as s, ActionResult as t, QueryErrorKind as u, TAsCellTypeComponents as v, TableActionsState as x, TAsTableControls as y };
@@ -1,5 +1,5 @@
1
1
  import { ColumnDef, PaginationControl, SortControl, TableDef } from "@atscript/ui";
2
- import { Component, ComputedRef, Ref, ShallowRef } from "vue";
2
+ import { Component, ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
3
3
  import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, FieldFilters, PresetAspect, PresetCapabilities, PresetSnapshot, SelectionMode, SystemPreset, SystemPresetInput, TableStateMethods } from "@atscript/ui-table";
4
4
  import { ClientError, TDbActionInfo, TDbActionProcessor, TDbDeleteResult } from "@atscript/db-client";
5
5
 
@@ -190,14 +190,62 @@ interface PresetConfig {
190
190
  * Add `'itemsPerPage'` for paginated tables; drop any aspect this app doesn't use.
191
191
  */
192
192
  aspects?: PresetAspect[];
193
+ /**
194
+ * Aspects a SYSTEM preset owns. Intersected with `aspects`; defaults to all
195
+ * of them. Narrow it to e.g. `['filterOps']` when system presets are
196
+ * filter-only views that must not reset the user's columns, displayed
197
+ * filters, sorters or page size. Since 0.1.133.
198
+ */
199
+ systemAspects?: PresetAspect[];
193
200
  /**
194
201
  * Opt-in localStorage overlay for the user's in-flight tweaks (default
195
202
  * `false`). Persists `columns` / `filters` / `sorters` / opt-in
196
203
  * `itemsPerPage`; `filterOps` / `searchTerm` / pagination are NOT persisted.
197
204
  */
198
205
  persistDrafts?: boolean;
206
+ /**
207
+ * Identity the draft is stored under — pass the signed-in user's id
208
+ * whenever `persistDrafts` is on, so a shared browser never restores the
209
+ * previous user's draft. Omitted keeps the pre-0.1.133 unscoped key.
210
+ * Since 0.1.133.
211
+ */
212
+ draftScope?: MaybeRefOrGetter<string | undefined>;
199
213
  }
200
214
  type QueryErrorKind = "initial" | "query" | "queryNext" | "loadRange";
215
+ /**
216
+ * Context handed to the per-row hooks of `<AsTable>` (`rowSelectable`,
217
+ * `rowClass`, `rowAttrs`). Since 0.1.133.
218
+ */
219
+ interface RowHookContext {
220
+ /** Position of the row in the rows the renderer is currently rendering. */
221
+ index: number;
222
+ /**
223
+ * Whether the row's pk is in `state.selectedRows` right now. A getter —
224
+ * reading it subscribes that hook call to the selection, so predicates
225
+ * that ignore it are not re-run when the selection changes.
226
+ */
227
+ readonly selected: boolean;
228
+ }
229
+ /**
230
+ * Per-row selectability predicate. Return `false` — or a string, which is
231
+ * surfaced as the disabled reason on the selection control — to make the row
232
+ * not selectable; `true` (or omitting the prop) keeps it selectable.
233
+ * Since 0.1.133.
234
+ */
235
+ type RowSelectableHook = (row: Record<string, unknown>, ctx: RowHookContext) => boolean | string | undefined | void;
236
+ /** Extra classes for the row element. Since 0.1.133. */
237
+ type RowClassHook = (row: Record<string, unknown>, ctx: RowHookContext) => string | string[] | Record<string, boolean> | undefined;
238
+ /**
239
+ * Extra attributes for the row element. The framework's own `id`, `role`,
240
+ * `aria-*`, `data-*`, `class` and `style` always win. Since 0.1.133.
241
+ */
242
+ type RowAttrsHook = (row: Record<string, unknown>, ctx: RowHookContext) => Record<string, unknown> | undefined;
243
+ /** Normalised {@link RowSelectableHook} verdict. */
244
+ interface RowSelectableVerdict {
245
+ ok: boolean;
246
+ /** Disabled reason, when the predicate returned a string. */
247
+ reason?: string;
248
+ }
201
249
  /** Controls which sections appear in the column header dropdown menu. */
202
250
  interface ColumnMenuConfig {
203
251
  sort?: boolean;
@@ -373,6 +421,31 @@ interface ReactiveTableState extends TableStateMethods {
373
421
  * naturally without consulting mode.
374
422
  */
375
423
  isPkSelected: (pk: unknown) => boolean;
424
+ /**
425
+ * Per-row selectability predicate — writable ref owned by the renderer,
426
+ * like {@link rowDelete}. `<AsTable>` / `<AsWindowTable>` push their
427
+ * `:row-selectable` prop into it, and every selection path in the model
428
+ * gates on it. Since 0.1.133.
429
+ */
430
+ rowSelectable: Ref<RowSelectableHook | undefined>;
431
+ /**
432
+ * Normalised `rowSelectable` verdict for one row. `index` is the row's
433
+ * position in what the renderer renders (absolute in window mode).
434
+ * Since 0.1.133.
435
+ */
436
+ isRowSelectable: (row: Record<string, unknown>, index: number) => RowSelectableVerdict;
437
+ /** The subset of `rows` the `rowSelectable` predicate lets through. Since 0.1.133. */
438
+ selectableRows: (rows: readonly Record<string, unknown>[]) => Record<string, unknown>[];
439
+ /** How many of `rows` the `rowSelectable` predicate lets through. Since 0.1.133. */
440
+ selectableCount: (rows: readonly Record<string, unknown>[]) => number;
441
+ /**
442
+ * Select every eligible row in `rows`. An ineligible row that is already
443
+ * selected stays selected. `indexOf` maps a position in `rows` to the
444
+ * index the `rowSelectable` predicate sees — windowed callers pass it
445
+ * because their rows come from a cache keyed by absolute index.
446
+ * Since 0.1.133.
447
+ */
448
+ selectAll: (rows: readonly Record<string, unknown>[], indexOf?: (position: number) => number) => void;
376
449
  /** Column currently open in the filter dialog (null when closed). */
377
450
  filterDialogColumn: Ref<ColumnDef | null>;
378
451
  /** Absolute index of the keyboard-active row. -1 = nothing active. */
@@ -398,7 +471,10 @@ interface ReactiveTableState extends TableStateMethods {
398
471
  setActive: (absIndex: number) => void;
399
472
  /** Reset the active-row index to `-1`. */
400
473
  clearActive: () => void;
401
- /** Toggle selection of the active row's PK in `selectedRows`. */
474
+ /**
475
+ * Toggle selection of the active row's PK in `selectedRows`. No-op in
476
+ * `select="none"` and on a row the `rowSelectable` predicate rejects.
477
+ */
402
478
  toggleActiveSelection: (mode: SelectionMode) => void;
403
479
  /** Ask the rendering component to emit `main-action` for the active row. */
404
480
  requestMainAction: (event: KeyboardEvent | MouseEvent) => void;
@@ -415,6 +491,12 @@ interface ReactiveTableState extends TableStateMethods {
415
491
  * consumers should not mutate directly.
416
492
  */
417
493
  confirmRequest: Ref<ConfirmRequest | null>;
494
+ /**
495
+ * The prompt request the dialog SURFACE renders: the last non-null
496
+ * `confirmRequest`, retained after it is answered so the copy survives the
497
+ * exit animation. Internal — used by the dialog. @since 0.1.133
498
+ */
499
+ confirmDisplay: Ref<ConfirmRequest | null>;
418
500
  /**
419
501
  * Open the in-app confirm dialog with `message` as the body. Resolves
420
502
  * `true` on accept, `false` on cancel/dismiss. Public — consumers can
@@ -431,12 +513,24 @@ interface ReactiveTableState extends TableStateMethods {
431
513
  acceptPrompt: () => void;
432
514
  /** Resolve the active prompt with `false`. Internal — used by the dialog. */
433
515
  dismissPrompt: () => void;
516
+ /**
517
+ * Clear `confirmDisplay` once the dialog surface has left (bound to the
518
+ * content's `after-leave`). A no-op while a prompt is pending. Internal.
519
+ * @since 0.1.133
520
+ */
521
+ releaseConfirm: () => void;
434
522
  /**
435
523
  * Currently pending action-form request — `null` when no form is open.
436
524
  * Writes are owned by `requestActionInput()` / `acceptActionForm()` /
437
525
  * `dismissActionForm()`; consumers should not mutate directly.
438
526
  */
439
527
  actionFormRequest: Ref<ActionFormRequest | null>;
528
+ /**
529
+ * The form request the dialog SURFACE renders: the last non-null
530
+ * `actionFormRequest`, retained after it is answered so the title, id chips
531
+ * and form survive the exit animation. Internal. @since 0.1.133
532
+ */
533
+ actionFormDisplay: Ref<ActionFormRequest | null>;
440
534
  /**
441
535
  * Open the action-form dialog. Resolves with the submitted form payload
442
536
  * on accept, or `null` on cancel/dismiss.
@@ -449,6 +543,12 @@ interface ReactiveTableState extends TableStateMethods {
449
543
  acceptActionForm: (input: unknown) => void;
450
544
  /** Resolve the active form request with `null`. Internal — used by the dialog. */
451
545
  dismissActionForm: () => void;
546
+ /**
547
+ * Clear `actionFormDisplay` (and the loaded schema/draft) once the dialog
548
+ * surface has left. A no-op while a request is pending. Internal.
549
+ * @since 0.1.133
550
+ */
551
+ releaseActionForm: () => void;
452
552
  /**
453
553
  * Hydrate state from a URL query string produced by `stateToUrlQueryString`.
454
554
  * Replaces filters / sorters / search / pagination with values decoded from
@@ -487,9 +587,22 @@ interface PresetSurface {
487
587
  * `['columns', 'filters', 'filterOps', 'sorters']`.
488
588
  */
489
589
  availableAspects: PresetAspect[];
590
+ /**
591
+ * Aspects a SYSTEM preset owns — subset of `availableAspects`, equal to it
592
+ * unless `preset.systemAspects` narrowed it. Drives apply/reset, the dirty
593
+ * baseline, the picker/dialog badges and the Save-As defaults for system
594
+ * presets. Since 0.1.133.
595
+ */
596
+ systemAspects: PresetAspect[];
597
+ /**
598
+ * Aspects the preset `id` owns — the ones applying it writes. System →
599
+ * `systemAspects`, stored → what its snapshot claims, `null` (or an id
600
+ * with no row) → `availableAspects`. Since 0.1.133.
601
+ */
602
+ ownedAspects: (id: string | null) => PresetAspect[];
490
603
  /**
491
604
  * False when `preset` config is absent OR the initial load returned
492
- * 401/403 OR any other error. The picker / dialog should hide their UI.
605
+ * 401/403/404 OR any other error. The picker / dialog should hide their UI.
493
606
  */
494
607
  available: ComputedRef<boolean>;
495
608
  /** Currently active preset id (system `'sys:*'` or stored). null = no active. */
@@ -548,6 +661,15 @@ interface PresetSurface {
548
661
  * feature is off.
549
662
  */
550
663
  batch: <T>(fn: () => Promise<T>) => Promise<T>;
664
+ /**
665
+ * Error thrown by the most recent mutator, or null — the SINGLE channel
666
+ * for preset write failures. Cleared when an OUTERMOST mutator starts, so
667
+ * a `batch()` counts as one user action: a failure early in a batch is not
668
+ * wiped by a later write inside it, and the batch's last failure wins.
669
+ * Mutators rethrow as well, so a caller can handle a single call; this ref
670
+ * is for UI that only needs to render the failure. Since 0.1.133.
671
+ */
672
+ lastError: Ref<Error | null>;
551
673
  }
552
674
  //#endregion
553
- export { TableNavBridge as _, EnterAction as a, NavKeyOptions as c, ReactiveTableState as d, RowDeleteOpt as f, TableActionsState as g, TVueTableActionInfo as h, ConfirmOptions as i, PresetConfig as l, TAsTableControls as m, ColumnMenuConfig as n, InvokeOpts as o, TAsCellTypeComponents as p, ConfigTab as r, MainActionRequest as s, ActionResult as t, QueryErrorKind as u };
675
+ export { TableNavBridge as S, RowSelectableVerdict as _, EnterAction as a, TVueTableActionInfo as b, NavKeyOptions as c, ReactiveTableState as d, RowAttrsHook as f, RowSelectableHook as g, RowHookContext as h, ConfirmOptions as i, PresetConfig as l, RowDeleteOpt as m, ColumnMenuConfig as n, InvokeOpts as o, RowClassHook as p, ConfigTab as r, MainActionRequest as s, ActionResult as t, QueryErrorKind as u, TAsCellTypeComponents as v, TableActionsState as x, TAsTableControls as y };
@@ -1,8 +1,8 @@
1
- import { n as createTableState, r as provideTableContext, s as DEFAULT_AVAILABLE_ASPECTS } from "./use-table-state-BxX3SpeW.mjs";
1
+ import { n as createTableState, r as provideTableContext, s as DEFAULT_AVAILABLE_ASPECTS } from "./use-table-state-DLJbW753.mjs";
2
2
  import { n as useTableSelection } from "./use-table-selection-C84z4OZy.mjs";
3
3
  import { createTableDef, getMetaEntry, resetMetaCache } from "@atscript/ui";
4
- import { computed, inject, isRef, ref, shallowRef, watch } from "vue";
5
- import { PresetsClient, STANDARD_PRESET_ID, debounce, deserializeDraft, isAuthError, isEmptyDraft, isSystemPresetId, resolveSystemPresets, serializeDraft, stableStringify } from "@atscript/ui-table";
4
+ import { computed, inject, isRef, ref, shallowRef, toValue, watch } from "vue";
5
+ import { PresetsClient, STANDARD_PRESET_ID, debounce, deserializeDraft, isEmptyDraft, isSystemPresetId, isUnavailableError, resolveSystemPresets, serializeDraft, stableStringify } from "@atscript/ui-table";
6
6
  //#region src/composables/as-presets-app.ts
7
7
  /**
8
8
  * App-wide injection key for the presets system. The host app calls
@@ -37,11 +37,16 @@ function injectPresetsApp(explicit) {
37
37
  const DEFAULT_DEBOUNCE_MS = 300;
38
38
  /**
39
39
  * localStorage overlay manager for table presets. One overlay per
40
- * `(app, tableKey)`; switching presets clears it (caller's responsibility
41
- * — this composable only tracks state, not which preset is active).
40
+ * `(app, scope, tableKey)`; switching presets clears it (caller's
41
+ * responsibility — this composable only tracks state, not which preset is
42
+ * active).
42
43
  */
43
44
  function useLocalDraft(opts) {
44
- const key = `as-table-draft:${opts.app}:${opts.tableKey}`;
45
+ /** Resolved per call — `scope` is reactive. */
46
+ function storageKey() {
47
+ const scope = toValue(opts.scope);
48
+ return scope ? `as-table-draft:${opts.app}:${scope}:${opts.tableKey}` : `as-table-draft:${opts.app}:${opts.tableKey}`;
49
+ }
45
50
  const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
46
51
  const storage = resolveStorage(opts.storage);
47
52
  function isEnabled() {
@@ -52,7 +57,7 @@ function useLocalDraft(opts) {
52
57
  function readDraft() {
53
58
  if (!storage) return null;
54
59
  try {
55
- const raw = storage.getItem(key);
60
+ const raw = storage.getItem(storageKey());
56
61
  if (!raw) return null;
57
62
  const parsed = JSON.parse(raw);
58
63
  if (!parsed || typeof parsed !== "object") return null;
@@ -65,7 +70,7 @@ function useLocalDraft(opts) {
65
70
  function writeDraft(draft) {
66
71
  if (!storage) return;
67
72
  try {
68
- storage.setItem(key, JSON.stringify(draft));
73
+ storage.setItem(storageKey(), JSON.stringify(draft));
69
74
  } catch (err) {
70
75
  console.warn("[vue-table] useLocalDraft: localStorage write failed", err);
71
76
  }
@@ -73,7 +78,7 @@ function useLocalDraft(opts) {
73
78
  function clear() {
74
79
  if (!storage) return;
75
80
  try {
76
- storage.removeItem(key);
81
+ storage.removeItem(storageKey());
77
82
  } catch {}
78
83
  }
79
84
  function hydrate(applied) {
@@ -114,9 +119,17 @@ function useLocalDraft(opts) {
114
119
  }
115
120
  const flushDebounced = debounce(((current, preset) => flush(current, preset)), debounceMs);
116
121
  const stop = watch(() => [currentSnapshot(), activePresetSnapshot()], ([current, preset]) => flushDebounced(current, preset), { flush: "post" });
122
+ const stopScope = watch(() => toValue(opts.scope), () => {
123
+ flushDebounced.cancel();
124
+ lastPresetRef = null;
125
+ lastPresetSerialized = "";
126
+ const existing = readDraft();
127
+ lastDraftSerialized = existing && !isEmptyDraft(existing) ? stableStringify(existing) : "";
128
+ }, { flush: "post" });
117
129
  return () => {
118
130
  flushDebounced.cancel();
119
131
  stop();
132
+ stopScope();
120
133
  };
121
134
  }
122
135
  return {
@@ -250,7 +263,7 @@ function usePresets(opts) {
250
263
  userConf.value = result.userConf;
251
264
  if (result.capabilities !== void 0) capabilities.value = result.capabilities;
252
265
  } catch (err) {
253
- if (isAuthError(err)) {
266
+ if (isUnavailableError(err)) {
254
267
  denied.value = true;
255
268
  presets.value = [];
256
269
  userConf.value = null;
@@ -376,7 +389,8 @@ function useTable(url, opts) {
376
389
  app: injectPresetsApp(preset.app),
377
390
  tableKey: preset.tableKey,
378
391
  enabled: preset.persistDrafts ?? false,
379
- availableAspects: preset.aspects ?? DEFAULT_AVAILABLE_ASPECTS
392
+ availableAspects: preset.aspects ?? DEFAULT_AVAILABLE_ASPECTS,
393
+ scope: preset.draftScope
380
394
  }) : null;
381
395
  const { state, internals } = createTableState({
382
396
  client,
@@ -417,6 +431,7 @@ function useTable(url, opts) {
417
431
  presetsHandle,
418
432
  draftHandle,
419
433
  availableAspects: preset?.aspects,
434
+ systemAspects: preset?.systemAspects,
420
435
  persistDrafts: preset?.persistDrafts ?? false
421
436
  }
422
437
  });