@atscript/vue-table 0.1.132 → 0.1.134

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 (163) 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-C5qx9MPE.cjs → as-action-menu-content-C_D_Fepo.cjs} +1 -1
  4. package/dist/{as-action-menu-content-4UfcabUe.mjs → as-action-menu-content-rim8hAO9.mjs} +1 -1
  5. package/dist/{as-cell-array-2qmJ1lT2.mjs → as-cell-array-BWHkJX3I.mjs} +2 -2
  6. package/dist/{as-cell-array-iPeUnk2G.cjs → as-cell-array-D-8Pqlgo.cjs} +2 -2
  7. package/dist/as-cell-array.cjs +1 -1
  8. package/dist/as-cell-array.mjs +1 -1
  9. package/dist/{as-cell-date-DyixWKuS.cjs → as-cell-date-DviMJEqs.cjs} +2 -2
  10. package/dist/{as-cell-date-BwFgi6O0.mjs → as-cell-date-mGlnDeLP.mjs} +2 -2
  11. package/dist/as-cell-date.cjs +1 -1
  12. package/dist/as-cell-date.mjs +1 -1
  13. package/dist/{as-cell-json--95Xq6yq.mjs → as-cell-json-2I081xnO.mjs} +2 -2
  14. package/dist/{as-cell-json-Bvv2P7YV.cjs → as-cell-json-CaVB6e7V.cjs} +2 -2
  15. package/dist/as-cell-json.cjs +1 -1
  16. package/dist/as-cell-json.mjs +1 -1
  17. package/dist/{as-cell-number-DuPMOesI.cjs → as-cell-number-BnYbxOiG.cjs} +2 -2
  18. package/dist/{as-cell-number-DKRaS9pM.mjs → as-cell-number-CP2AZI9g.mjs} +2 -2
  19. package/dist/as-cell-number.cjs +1 -1
  20. package/dist/as-cell-number.mjs +1 -1
  21. package/dist/{as-cell-union-DJNsEKTi.cjs → as-cell-union-BjMP91PS.cjs} +3 -3
  22. package/dist/{as-cell-union-C9FukGqF.mjs → as-cell-union-NHpDCXty.mjs} +3 -3
  23. package/dist/as-cell-union.cjs +1 -1
  24. package/dist/as-cell-union.mjs +1 -1
  25. package/dist/as-column-menu.cjs +1 -1
  26. package/dist/as-column-menu.d.cts +1 -1
  27. package/dist/as-column-menu.d.mts +1 -1
  28. package/dist/as-column-menu.mjs +1 -1
  29. package/dist/{as-column-menu.vue-u_DNy3f-.d.cts → as-column-menu.vue-De7SRhj6.d.cts} +1 -1
  30. package/dist/{as-column-menu.vue-DL5N9mKR.d.mts → as-column-menu.vue-Dg0H1dOd.d.mts} +1 -1
  31. package/dist/{as-config-dialog-BehfqIGd.mjs → as-config-dialog-BUBHiVlf.mjs} +56 -35
  32. package/dist/{as-config-dialog-BOYWPYYH.cjs → as-config-dialog-DvBptYMS.cjs} +55 -34
  33. package/dist/as-config-dialog.cjs +1 -1
  34. package/dist/as-config-dialog.mjs +1 -1
  35. package/dist/{as-confirm-dialog-UwhDHysk.cjs → as-confirm-dialog-ByMfIG0A.cjs} +12 -9
  36. package/dist/{as-confirm-dialog-bpN4A-KN.mjs → as-confirm-dialog-G4ZoL_Xo.mjs} +12 -9
  37. package/dist/as-confirm-dialog.cjs +1 -1
  38. package/dist/as-confirm-dialog.mjs +1 -1
  39. package/dist/{as-filter-dialog-D1qzq-8b.mjs → as-filter-dialog-BJWH8cvc.mjs} +34 -26
  40. package/dist/{as-filter-dialog-Dh2Ylthb.cjs → as-filter-dialog-C2oyT0yt.cjs} +33 -25
  41. package/dist/as-filter-dialog.cjs +1 -1
  42. package/dist/as-filter-dialog.mjs +1 -1
  43. package/dist/{as-filter-field-DirBmnbb.mjs → as-filter-field-B2skq4K0.mjs} +4 -4
  44. package/dist/{as-filter-field-DRsDJdpx.cjs → as-filter-field-DxGfasQj.cjs} +4 -4
  45. package/dist/as-filter-field.cjs +1 -1
  46. package/dist/as-filter-field.mjs +1 -1
  47. package/dist/as-filter-input.cjs +1 -1
  48. package/dist/as-filter-input.mjs +1 -1
  49. package/dist/as-filters-Bhte22La.cjs +130 -0
  50. package/dist/as-filters-DogNVlqr.mjs +125 -0
  51. package/dist/as-filters.cjs +1 -1
  52. package/dist/as-filters.d.cts +1 -1
  53. package/dist/as-filters.d.mts +1 -1
  54. package/dist/as-filters.mjs +1 -1
  55. package/dist/as-filters.vue-BG_ysCkC.d.mts +65 -0
  56. package/dist/as-filters.vue-DcxDnWAU.d.cts +65 -0
  57. package/dist/{as-preset-dialog-CDqCAR7N.mjs → as-preset-dialog-BwHV6Y77.mjs} +71 -21
  58. package/dist/{as-preset-dialog-DpM06jqR.cjs → as-preset-dialog-D2a6nxEX.cjs} +75 -25
  59. package/dist/as-preset-dialog.cjs +1 -1
  60. package/dist/as-preset-dialog.mjs +1 -1
  61. package/dist/{as-preset-picker-CISaxEZ0.mjs → as-preset-picker-B0JnyAPA.mjs} +39 -15
  62. package/dist/{as-preset-picker-jUS_YDX8.cjs → as-preset-picker-DbkmapBx.cjs} +45 -21
  63. package/dist/as-preset-picker.cjs +1 -1
  64. package/dist/as-preset-picker.mjs +1 -1
  65. package/dist/{as-row-actions-xqJeAdQl.mjs → as-row-actions-BkIsJVBm.mjs} +24 -11
  66. package/dist/{as-row-actions-Bm6Bzyno.cjs → as-row-actions-CRg8LHGk.cjs} +24 -11
  67. package/dist/as-row-actions.cjs +1 -1
  68. package/dist/as-row-actions.d.cts +1 -1
  69. package/dist/as-row-actions.d.mts +1 -1
  70. package/dist/as-row-actions.mjs +1 -1
  71. package/dist/{as-row-actions.vue-Bvcc2tUN.d.cts → as-row-actions.vue-VpJsBAJ4.d.mts} +8 -0
  72. package/dist/{as-row-actions.vue-DUSDuoR2.d.mts → as-row-actions.vue-dn_DKcMm.d.cts} +8 -0
  73. package/dist/{as-table-CDSG2yED.mjs → as-table-BEvsX87E.mjs} +26 -8
  74. package/dist/{as-table-actions-DMxeaczm.mjs → as-table-actions-B5upF1bQ.mjs} +69 -18
  75. package/dist/{as-table-actions-gV4lqqxc.cjs → as-table-actions-B6sKd4sl.cjs} +68 -17
  76. package/dist/as-table-actions.cjs +1 -1
  77. package/dist/as-table-actions.d.cts +1 -1
  78. package/dist/as-table-actions.d.mts +1 -1
  79. package/dist/as-table-actions.mjs +1 -1
  80. package/dist/{as-table-actions.vue-B-cgiCXP.d.cts → as-table-actions.vue-CoxhpIlJ.d.mts} +2 -2
  81. package/dist/{as-table-actions.vue-ClMa0OD1.d.mts → as-table-actions.vue-Kic8m28V.d.cts} +2 -2
  82. package/dist/{as-table-base-BaTE3VSN.mjs → as-table-base-BxKBdIdS.mjs} +142 -47
  83. package/dist/{as-table-base-RG2TEIgV.cjs → as-table-base-DLuhKz69.cjs} +142 -47
  84. package/dist/{as-table-cell-value-D1JkTQt0.cjs → as-table-cell-value-BAi5yadc.cjs} +2 -2
  85. package/dist/{as-table-cell-value-CPcFxSOj.mjs → as-table-cell-value-BVcfhhCb.mjs} +2 -2
  86. package/dist/as-table-cell-value.cjs +1 -1
  87. package/dist/as-table-cell-value.mjs +1 -1
  88. package/dist/{as-table-header-cell-CIqlO6Lw.cjs → as-table-header-cell-Bv6q5iKA.cjs} +2 -2
  89. package/dist/{as-table-header-cell-Bdhl5Lox.mjs → as-table-header-cell-Xd3cvnII.mjs} +2 -2
  90. package/dist/as-table-header-cell.cjs +1 -1
  91. package/dist/as-table-header-cell.d.cts +1 -1
  92. package/dist/as-table-header-cell.d.mts +1 -1
  93. package/dist/as-table-header-cell.mjs +1 -1
  94. package/dist/{as-table-header-cell.vue-BCszzxtU.d.mts → as-table-header-cell.vue-AS6u4e6v.d.cts} +2 -2
  95. package/dist/{as-table-header-cell.vue-BzTzfGRo.d.cts → as-table-header-cell.vue-zKOmlzhT.d.mts} +2 -2
  96. package/dist/{as-table-root-BLReUbRH.mjs → as-table-root-C1A6JkwL.mjs} +112 -10
  97. package/dist/{as-table-root-DLJ_3Jf4.cjs → as-table-root-DlYjnZax.cjs} +112 -10
  98. package/dist/as-table-root.cjs +1 -1
  99. package/dist/as-table-root.d.cts +1 -1
  100. package/dist/as-table-root.d.mts +1 -1
  101. package/dist/as-table-root.mjs +1 -1
  102. package/dist/{as-table-root.vue-QMT93qPk.d.mts → as-table-root.vue-CWk9YgpG.d.mts} +257 -41
  103. package/dist/{as-table-root.vue-D0T3n-Vm.d.cts → as-table-root.vue-D94eypcL.d.cts} +258 -42
  104. package/dist/{as-table-status-Du4WLF4k.cjs → as-table-status-B2HPerJT.cjs} +54 -38
  105. package/dist/{as-table-status-DGTZlpyq.mjs → as-table-status-QzlBdSiR.mjs} +54 -38
  106. package/dist/{as-table-BWUPzjRn.cjs → as-table-vJbZyZVx.cjs} +26 -8
  107. package/dist/as-table.cjs +1 -1
  108. package/dist/as-table.d.cts +1 -1
  109. package/dist/as-table.d.mts +1 -1
  110. package/dist/as-table.mjs +1 -1
  111. package/dist/{as-table.vue-Ds7MEp37.d.cts → as-table.vue-BB1vaNL6.d.cts} +28 -4
  112. package/dist/{as-table.vue-Ce5606ZO.d.mts → as-table.vue-BUDFTCpo.d.mts} +28 -4
  113. package/dist/{as-window-table-DWujiUQy.mjs → as-window-table-BH0tDDnr.mjs} +107 -24
  114. package/dist/{as-window-table-jD_y9yet.cjs → as-window-table-Bl_q72WL.cjs} +107 -24
  115. package/dist/as-window-table.cjs +1 -1
  116. package/dist/as-window-table.d.cts +1 -1
  117. package/dist/as-window-table.d.mts +1 -1
  118. package/dist/as-window-table.mjs +1 -1
  119. package/dist/{as-window-table.vue-DfZF1Mux.d.mts → as-window-table.vue-Be2IA0Eb.d.cts} +27 -4
  120. package/dist/{as-window-table.vue-Bhf9BK8E.d.cts → as-window-table.vue-CLqFM2ff.d.mts} +27 -4
  121. package/dist/error-message-CfKpn_5w.cjs +18 -0
  122. package/dist/error-message-gNEXnnbb.mjs +13 -0
  123. package/dist/index.cjs +237 -41
  124. package/dist/index.d.cts +10 -10
  125. package/dist/index.d.mts +10 -10
  126. package/dist/index.mjs +230 -43
  127. package/dist/{types-ByU4O0Yf.d.mts → types--ex8BJIO.d.cts} +263 -5
  128. package/dist/{types-CTMxE_zA.d.cts → types-CBGaTR2Z.d.mts} +263 -5
  129. package/dist/{use-table-CyFcVtUN.mjs → use-table-BFhbfWgY.mjs} +48 -23
  130. package/dist/{use-table-component-K6xj1obF.mjs → use-table-component-CRmLHdqi.mjs} +1 -1
  131. package/dist/{use-table-component-DO_kvKSf.cjs → use-table-component-DS5NJPzs.cjs} +1 -1
  132. package/dist/{use-table-nav-bridge-uOQUDooh.cjs → use-table-nav-bridge-DS14D_rG.cjs} +1 -1
  133. package/dist/{use-table-nav-bridge-DeGeaQX-.mjs → use-table-nav-bridge-DzJN5v9a.mjs} +1 -1
  134. package/dist/{use-table-nvBq1dZ4.cjs → use-table-nvuZdgUd.cjs} +51 -20
  135. package/dist/{use-table-state-BxX3SpeW.mjs → use-table-state-mBXx11aH.mjs} +501 -105
  136. package/dist/{use-table-state-Oi2ds4_a.cjs → use-table-state-mhm-YWnu.cjs} +541 -109
  137. package/package.json +9 -9
  138. package/dist/as-filters-DCZTHjf5.cjs +0 -51
  139. package/dist/as-filters-beIud9sF.mjs +0 -46
  140. package/dist/as-filters.vue-DmECMd1v.d.mts +0 -10
  141. package/dist/as-filters.vue-fv-tRL2H.d.cts +0 -10
  142. package/dist/preset-aspect-display-IICxAlVo.mjs +0 -43
  143. package/dist/preset-aspect-display-TWwf6j1B.cjs +0 -72
  144. /package/dist/{as-cell-json-popover-Dkdv5Y9E.mjs → as-cell-json-popover-CH5bAcLE.mjs} +0 -0
  145. /package/dist/{as-cell-json-popover-BM2QLgfk.cjs → as-cell-json-popover-vty5vMjW.cjs} +0 -0
  146. /package/dist/{as-column-menu-kTIQfLTi.mjs → as-column-menu-BMTS3DO2.mjs} +0 -0
  147. /package/dist/{as-column-menu-fZVbJ9lU.cjs → as-column-menu-D_8NxujZ.cjs} +0 -0
  148. /package/dist/{as-filter-input-BM23TNeh.mjs → as-filter-input-C8KmOus8.mjs} +0 -0
  149. /package/dist/{as-filter-input-DJu2k9gO.cjs → as-filter-input-DUqpkTkg.cjs} +0 -0
  150. /package/dist/{format-cell-D1Ziv96Q.mjs → format-cell-Bx7wNc4X.mjs} +0 -0
  151. /package/dist/{format-cell-vCNTh4WZ.cjs → format-cell-DaxV6WIJ.cjs} +0 -0
  152. /package/dist/{get-cell-value-4rHKP-TG.cjs → get-cell-value-CJT4fk4y.cjs} +0 -0
  153. /package/dist/{get-cell-value-C8LLc_Rr.mjs → get-cell-value-Cb3X8nUn.mjs} +0 -0
  154. /package/dist/{use-cell-locale-MQXoONh5.mjs → use-cell-locale-CxQOQsns.mjs} +0 -0
  155. /package/dist/{use-cell-locale-BGBRQB7s.cjs → use-cell-locale-caG2Fzpc.cjs} +0 -0
  156. /package/dist/{use-has-emit-listener-hhOH8CBI.cjs → use-has-emit-listener-BaGFZDyM.cjs} +0 -0
  157. /package/dist/{use-has-emit-listener-BTMDIv2e.mjs → use-has-emit-listener-DrBbsLHQ.mjs} +0 -0
  158. /package/dist/{use-seed-on-open-Dg9I89fU.cjs → use-seed-on-open-9TSm1Vbv.cjs} +0 -0
  159. /package/dist/{use-seed-on-open-DO3A1kIb.mjs → use-seed-on-open-DSh4XFyp.mjs} +0 -0
  160. /package/dist/{use-table-column-handlers-ehreEzcx.cjs → use-table-column-handlers-BMrP0WY2.cjs} +0 -0
  161. /package/dist/{use-table-column-handlers-CpiiIniD.mjs → use-table-column-handlers-Bx3JERqs.mjs} +0 -0
  162. /package/dist/{use-table-selection-Cmiu1h4S.cjs → use-table-selection-DtCV3rhu.cjs} +0 -0
  163. /package/dist/{use-table-selection-C84z4OZy.mjs → use-table-selection-ULbKT2xW.mjs} +0 -0
@@ -1,6 +1,7 @@
1
+ import { t as getCellValue } from "./get-cell-value-Cb3X8nUn.mjs";
1
2
  import { navigateHrefFor } from "@atscript/ui";
2
3
  import { computed, inject, nextTick, onBeforeUnmount, onScopeDispose, provide, ref, shallowRef, toValue, watch } from "vue";
3
- import { STANDARD_PRESET_ID, blockStartFor, buildTableQuery, debounce, fromWireSnapshot, isDirtyAgainst, isFilled, isSystemPresetId, pageAlignedBlocksFor, planFetch, reconcileColumnWidthDefaults, resolveAspectGate, resolveSystemPresets, sameColumnSet, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
4
+ import { STANDARD_PRESET_ID, blockStartFor, buildTableQuery, cellAsString, debounce, derivePresetAspects, fromWireSnapshot, isDirtyAgainst, isFilled, isSystemPresetId, mergeDisplayColumns, mergeSorters, pageAlignedBlocksFor, planFetch, reconcileColumnWidthDefaults, resolveAspectGate, resolveSystemPresets, rowsToPks, sameColumnSet, sortRowsLocally, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
4
5
  import { formatIdentifier } from "@atscript/db-client";
5
6
  //#region src/types.ts
6
7
  /** UI-side sentinel for the synthesised row-delete processor. */
@@ -43,18 +44,25 @@ function rowActionGate(row) {
43
44
  return names ? gateFor(new Set(names)) : null;
44
45
  }
45
46
  /**
46
- * Filter a `{default, others, rows}` triple through an availability `gate`.
47
- * When `gate` is `null`, returns the input `buckets` reference unchanged so
48
- * source-array references stay stable downstream (no spurious recomputes in
49
- * consumers that compare array identity). Shared by the single-row
50
- * (`applyRowGate`) and bulk (`applyRowsGate`) paths.
47
+ * Walk a `{default, others, rows}` triple once: keep the actions `gate`
48
+ * admits, then run each survivor through `map`. Both are optional — with
49
+ * neither, the input `buckets` reference comes back unchanged so source-array
50
+ * references stay stable downstream (no spurious recomputes in consumers that
51
+ * compare array identity). Shared by the single-row (`applyRowGate`), bulk
52
+ * (`applyRowsGate`) and per-screen-policy paths.
51
53
  */
52
- function applyGate(buckets, gate) {
53
- if (!gate) return buckets;
54
+ function applyGate(buckets, gate, map) {
55
+ if (!gate && !map) return buckets;
56
+ const keep = gate ?? (() => true);
57
+ const walk = (list) => {
58
+ const kept = gate ? list.filter(keep) : list;
59
+ return map ? kept.map(map) : kept;
60
+ };
61
+ const def = buckets.default;
54
62
  return {
55
- default: buckets.default && gate(buckets.default) ? buckets.default : void 0,
56
- others: buckets.others.filter(gate),
57
- rows: buckets.rows.filter(gate)
63
+ default: def && keep(def) ? map ? map(def) : def : void 0,
64
+ others: walk(buckets.others),
65
+ rows: walk(buckets.rows)
58
66
  };
59
67
  }
60
68
  /**
@@ -281,12 +289,16 @@ async function triggerAction(state, action, ctx, event) {
281
289
  if (input === null) return;
282
290
  state.actions.invoke(action, pk, {
283
291
  event,
284
- input
292
+ input,
293
+ row: ctx.row
285
294
  });
286
295
  return;
287
296
  }
288
297
  if (!await confirmAction(state, action, ctx)) return;
289
- state.actions.invoke(action, pk, { event });
298
+ state.actions.invoke(action, pk, {
299
+ event,
300
+ row: ctx.row
301
+ });
290
302
  }
291
303
  /**
292
304
  * Pick the `pk` argument to forward to `state.actions.invoke` based on the
@@ -308,6 +320,75 @@ function idsForAction(level, pk) {
308
320
  return pk === void 0 ? [] : [pk];
309
321
  }
310
322
  //#endregion
323
+ //#region src/composables/state/row-actions-config.ts
324
+ /** The app definition behind a client-only action, or `undefined` for a server action. */
325
+ function localActionOf(action) {
326
+ return action.$local;
327
+ }
328
+ /** Wrap an app-owned action in the descriptor shape the row-actions cell renders. */
329
+ function toLocalActionInfo(local) {
330
+ return {
331
+ name: local.name,
332
+ label: local.label,
333
+ level: "row",
334
+ processor: "custom",
335
+ value: "",
336
+ icon: local.icon,
337
+ intent: local.intent,
338
+ promptText: local.promptText,
339
+ $local: local
340
+ };
341
+ }
342
+ /**
343
+ * Render-time href for a row action, whichever kind it is: a client-only
344
+ * action builds its own from the row — still mapped through
345
+ * `state.resolveHref`, so router base paths apply exactly as they do for a
346
+ * server `navigate` action — and a server action goes through
347
+ * {@link actionHref}.
348
+ */
349
+ function rowActionHref(state, action, id, row) {
350
+ const local = localActionOf(action);
351
+ if (local) return local.href && row !== void 0 ? state.resolveHref(local.href(row)) : void 0;
352
+ return actionHref(state, action, id);
353
+ }
354
+ const EMPTY = Object.freeze([]);
355
+ /** The identity policy — used when no `RowActionsConfig` is wired. */
356
+ const PASS_THROUGH = {
357
+ apply: (buckets) => buckets,
358
+ extra: () => EMPTY
359
+ };
360
+ /** Compile a per-screen {@link RowActionsConfig} into a {@link RowActionsPolicy}. */
361
+ function compileRowActionsConfig(config) {
362
+ if (!config) return PASS_THROUGH;
363
+ const include = config.include ? new Set(config.include) : null;
364
+ const exclude = config.exclude ? new Set(config.exclude) : null;
365
+ const overrides = config.overrides;
366
+ const gate = (a) => (!include || include.has(a.name)) && !exclude?.has(a.name);
367
+ const decorate = overrides ? (action) => {
368
+ const patch = overrides[action.name];
369
+ if (!patch) return action;
370
+ const next = { ...action };
371
+ for (const key of [
372
+ "label",
373
+ "icon",
374
+ "intent",
375
+ "promptText"
376
+ ]) {
377
+ const value = patch[key];
378
+ if (value !== void 0) next[key] = value;
379
+ }
380
+ return next;
381
+ } : void 0;
382
+ const locals = (config.extra ?? []).filter((local) => !exclude?.has(local.name));
383
+ return {
384
+ apply: (buckets) => applyGate(buckets, gate, decorate),
385
+ extra: (row) => {
386
+ if (locals.length === 0) return EMPTY;
387
+ return (row === void 0 ? locals : locals.filter((l) => l.enabled?.(row) !== false)).map(toLocalActionInfo);
388
+ }
389
+ };
390
+ }
391
+ //#endregion
311
392
  //#region src/composables/state/create-actions.ts
312
393
  const REMOVE_NAME = REMOVE_PROCESSOR;
313
394
  const REMOVE_DEFAULTS = {
@@ -340,13 +421,16 @@ function createActions(opts) {
340
421
  let result;
341
422
  try {
342
423
  switch (action.processor) {
343
- case "custom":
424
+ case "custom": {
425
+ const local = localActionOf(action);
426
+ if (local?.onInvoke) await local.onInvoke(callOpts?.row ?? {}, Array.isArray(pk) ? pk[0] : pk);
344
427
  result = {
345
428
  ok: true,
346
429
  kind: "custom",
347
430
  dispatched: true
348
431
  };
349
432
  break;
433
+ }
350
434
  case "navigate":
351
435
  await opts.client.action(action.name, pk);
352
436
  result = {
@@ -478,7 +562,18 @@ function buildRemoveAction(opts) {
478
562
  }
479
563
  //#endregion
480
564
  //#region src/composables/state/create-selection.ts
481
- function createSelectionApi(opts, getActiveRow) {
565
+ /** Shared verdicts — frozen, so the common case allocates nothing per row. */
566
+ const SELECTABLE = Object.freeze({ ok: true });
567
+ const NOT_SELECTABLE = Object.freeze({ ok: false });
568
+ /** Normalise a {@link RowSelectableHook} return value. `""` carries no reason. */
569
+ function verdictOf(value) {
570
+ if (value === void 0 || value === true) return SELECTABLE;
571
+ return value ? {
572
+ ok: false,
573
+ reason: value
574
+ } : NOT_SELECTABLE;
575
+ }
576
+ function createSelectionApi(opts, getActiveRow, activeIndex) {
482
577
  const selectedRows = opts?.selectedRows ?? shallowRef([]);
483
578
  const selectedCount = computed(() => selectedRows.value.length);
484
579
  const rowValueFn = opts?.rowValueFn ?? ((row) => row);
@@ -486,10 +581,49 @@ function createSelectionApi(opts, getActiveRow) {
486
581
  function isPkSelected(pk) {
487
582
  return selectedSet.value.has(pk);
488
583
  }
584
+ const rowSelectable = ref();
585
+ function isRowSelectable(row, index) {
586
+ const hook = rowSelectable.value;
587
+ if (!hook) return SELECTABLE;
588
+ return verdictOf(hook(row, {
589
+ index,
590
+ get selected() {
591
+ return isPkSelected(rowValueFn(row));
592
+ }
593
+ }));
594
+ }
595
+ function selectableRows(rows) {
596
+ if (!rowSelectable.value) return rows;
597
+ return rows.filter((row, index) => isRowSelectable(row, index).ok);
598
+ }
599
+ function selectableCount(rows) {
600
+ if (!rowSelectable.value) return rows.length;
601
+ let n = 0;
602
+ for (let i = 0; i < rows.length; i++) if (isRowSelectable(rows[i], i).ok) n++;
603
+ return n;
604
+ }
605
+ 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;
621
+ }
489
622
  function toggleActiveSelection(mode) {
490
623
  if (mode === "none") return;
491
624
  const row = getActiveRow();
492
625
  if (row === void 0) return;
626
+ if (!isRowSelectable(row, activeIndex.value).ok) return;
493
627
  selectedRows.value = togglePk(selectedRows.value, rowValueFn(row), mode);
494
628
  }
495
629
  return {
@@ -498,6 +632,11 @@ function createSelectionApi(opts, getActiveRow) {
498
632
  selectedSet,
499
633
  rowValueFn,
500
634
  isPkSelected,
635
+ rowSelectable,
636
+ isRowSelectable,
637
+ selectableRows,
638
+ selectableCount,
639
+ selectAll,
501
640
  toggleActiveSelection
502
641
  };
503
642
  }
@@ -551,6 +690,38 @@ function createMainActionRegistry(opts) {
551
690
  }
552
691
  //#endregion
553
692
  //#region src/composables/state/create-nav-controller.ts
693
+ /**
694
+ * Elements that own Enter / Space (and a pointer drag) themselves.
695
+ * `[contenteditable]` is matched separately because `contenteditable="false"`
696
+ * must NOT count.
697
+ */
698
+ const INTERACTIVE_SELECTOR = "button, a[href], input, select, textarea, summary, [role=button], [role=link], [role=checkbox], [role=menuitem], [role=switch], [role=tab], [role=option]";
699
+ /**
700
+ * True when the key event started on an interactive element inside a cell,
701
+ * so table nav must not consume Enter / Space. The walk runs from
702
+ * `event.target` up to `event.currentTarget` — the element the handler is
703
+ * bound on — and stops at the cell / row boundary, so a wrapper outside the
704
+ * table can never make a whole table inert. A handler bound ON the
705
+ * interactive element itself (the search-input bridge) is therefore never
706
+ * guarded: the walk has nothing to cross. An interactive element opts BACK
707
+ * IN to table nav — e.g. a cell whose button should not swallow Enter —
708
+ * with `data-as-nav-keys`.
709
+ */
710
+ function isInteractiveKeyTarget(event) {
711
+ let el = event.target;
712
+ const stop = event.currentTarget;
713
+ while (el && el !== stop && typeof el.matches === "function") {
714
+ const tag = el.tagName;
715
+ if (tag === "TD" || tag === "TH" || tag === "TR" || tag === "TBODY" || tag === "TABLE") return false;
716
+ if (el.matches("button, a[href], input, select, textarea, summary, [role=button], [role=link], [role=checkbox], [role=menuitem], [role=switch], [role=tab], [role=option]") || isEditable(el)) return !el.hasAttribute("data-as-nav-keys");
717
+ el = el.parentElement;
718
+ }
719
+ return false;
720
+ }
721
+ function isEditable(el) {
722
+ const attr = el.getAttribute("contenteditable");
723
+ return attr !== null && attr !== "false";
724
+ }
554
725
  function createNavController(inputs) {
555
726
  const { activeIndex, navMode, totalCount, results, viewportRowCount, topIndex, hasMainActionAvailable, requestMainAction, toggleActiveSelection } = inputs;
556
727
  const navViewportRowCount = ref(0);
@@ -600,6 +771,7 @@ function createNavController(inputs) {
600
771
  const enterAction = opts?.enterAction ?? "main-action";
601
772
  const mode = opts?.mode ?? "none";
602
773
  const key = event.key;
774
+ const guarded = (key === "Enter" || key === " ") && isInteractiveKeyTarget(event);
603
775
  const meta = event.metaKey;
604
776
  const ctrl = event.ctrlKey;
605
777
  const alt = event.altKey;
@@ -650,11 +822,13 @@ function createNavController(inputs) {
650
822
  return;
651
823
  case " ":
652
824
  if (mode === "none") return;
825
+ if (guarded) return;
653
826
  event.preventDefault();
654
827
  toggleActiveSelection(mode);
655
828
  return;
656
829
  case "Enter":
657
830
  if (enterAction === "passthrough") return;
831
+ if (guarded) return;
658
832
  event.preventDefault();
659
833
  if (enterAction === "toggle-select") {
660
834
  toggleActiveSelection(mode);
@@ -679,6 +853,67 @@ function createNavController(inputs) {
679
853
  handleNavKey
680
854
  };
681
855
  }
856
+ /**
857
+ * `@keydown` handler for a row's SELECTION CONTROL (the leading checkbox),
858
+ * shared by `<AsTableBase>` and `<AsWindowTableBase>`. The control carries
859
+ * `role="checkbox"`, so the tbody-level nav handler deliberately leaves
860
+ * Space to it (see {@link isInteractiveKeyTarget}) — this is where it lands.
861
+ *
862
+ * Keys other than Space, and `select="none"`, fall through untouched; only
863
+ * once the press is ours do we consume it, so the Reka-wrapped rendering
864
+ * 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
+ */
868
+ function onSelectControlKeydown(event, index, mode, state) {
869
+ if (event.key !== " ") return;
870
+ if (mode === "none") return;
871
+ event.preventDefault();
872
+ event.stopPropagation();
873
+ state.setActive(index);
874
+ state.toggleActiveSelection(mode);
875
+ }
876
+ //#endregion
877
+ //#region src/composables/preset-aspect-display.ts
878
+ const ASPECT_LABELS = {
879
+ columns: "Displayed Columns",
880
+ filters: "Displayed Filters",
881
+ filterOps: "Filter conditions",
882
+ sorters: "Sorters",
883
+ itemsPerPage: "Page size"
884
+ };
885
+ const ASPECT_ICONS = {
886
+ columns: "i-as-columns",
887
+ filters: "i-as-filter",
888
+ filterOps: "i-as-filter-ops",
889
+ sorters: "i-as-sorters",
890
+ itemsPerPage: "i-as-pin"
891
+ };
892
+ /**
893
+ * Resolve the aspects a stored preset claims. Prefers the server-stamped
894
+ * `aspects` column (cheap), falls back to deriving from `data.content` for
895
+ * older rows or first paint after save before reload settles.
896
+ */
897
+ function aspectsOf(row, availableAspects) {
898
+ if (Array.isArray(row.aspects) && row.aspects.length > 0) return row.aspects.filter((a) => availableAspects.includes(a));
899
+ const wire = row.data?.content;
900
+ if (!wire) return [];
901
+ return derivePresetAspects(wire).filter((a) => availableAspects.includes(a));
902
+ }
903
+ function readPresetLabel(row) {
904
+ return row.label ?? row.data?.label ?? row.id;
905
+ }
906
+ /**
907
+ * Display name for the row's owner. `userLabel` (server-stamped via the
908
+ * host's `getUserLabel` hook) is preferred so the UI shows "Alice" rather
909
+ * than `usr_abc123`. `fallback` is returned when no name is available —
910
+ * picker uses `""`, dialog uses `"—"`.
911
+ */
912
+ function ownerNameOf(row, fallback = "") {
913
+ if (typeof row.userLabel === "string" && row.userLabel.length > 0) return row.userLabel;
914
+ if (typeof row.user === "string" && row.user.length > 0) return row.user;
915
+ return fallback;
916
+ }
682
917
  //#endregion
683
918
  //#region src/composables/state/create-preset-state.ts
684
919
  const DEFAULT_AVAILABLE_ASPECTS = [
@@ -705,6 +940,26 @@ function defaultColumnPaths(o) {
705
940
  function createPresetState(opts) {
706
941
  const availableAspects = opts.availableAspects ?? DEFAULT_AVAILABLE_ASPECTS;
707
942
  const availableSet = new Set(availableAspects);
943
+ const systemAspects = opts.systemAspects ? availableAspects.filter((a) => opts.systemAspects.includes(a)) : availableAspects;
944
+ const lastError = ref(null);
945
+ let trackDepth = 0;
946
+ /**
947
+ * Every public mutator runs through here: it is the one place a preset
948
+ * write failure is recorded. Clears `lastError` on the outermost entry,
949
+ * records the failure and RETHROWS so the caller can react too.
950
+ */
951
+ async function track(fn) {
952
+ if (trackDepth === 0) lastError.value = null;
953
+ trackDepth++;
954
+ try {
955
+ return await fn();
956
+ } catch (err) {
957
+ lastError.value = err instanceof Error ? err : new Error(String(err));
958
+ throw err;
959
+ } finally {
960
+ trackDepth--;
961
+ }
962
+ }
708
963
  const dialogOpen = ref(false);
709
964
  const gate = ref(false);
710
965
  const ready = computed(() => opts.presetsHandle ? gate.value : true);
@@ -795,6 +1050,20 @@ function createPresetState(opts) {
795
1050
  if (!mask) return true;
796
1051
  return mask[aspect] === true;
797
1052
  }
1053
+ /**
1054
+ * Aspects the preset `id` OWNS, i.e. the ones applying it may write:
1055
+ * a system preset owns `systemAspects`, a stored preset owns what its
1056
+ * snapshot claims, and "no preset" (a raw snapshot, or an id with no row)
1057
+ * owns everything available. The one answer every aspect-scoped surface
1058
+ * asks for — apply, system-snapshot expansion, the picker badges and
1059
+ * Save-as mask, the manage dialog rows.
1060
+ */
1061
+ function ownedAspects(id) {
1062
+ if (!id) return availableAspects;
1063
+ if (isSystemPresetId(id)) return systemAspects;
1064
+ const row = presetsById.value.get(id);
1065
+ return row ? aspectsOf(row, availableAspects) : availableAspects;
1066
+ }
798
1067
  function captureSnapshot(mask) {
799
1068
  const out = {};
800
1069
  for (const aspect of availableAspects) {
@@ -834,7 +1103,7 @@ function createPresetState(opts) {
834
1103
  function apply(idOrSnapshot) {
835
1104
  const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
836
1105
  const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && isSystemPresetId(nextActiveId);
837
- for (const aspect of availableAspects) {
1106
+ for (const aspect of ownedAspects(nextActiveId)) {
838
1107
  const value = snapshot[aspect];
839
1108
  ASPECT_HANDLERS[aspect].apply(opts, value, isSystem);
840
1109
  }
@@ -860,9 +1129,12 @@ function createPresetState(opts) {
860
1129
  * "claims all aspects in `availableAspects`" per spec §3.7. Stored presets
861
1130
  * keep dict-form opt-in (untouched).
862
1131
  */
863
- function expandSystemSnapshot(snapshot) {
864
- const out = { ...snapshot };
865
- for (const aspect of availableAspects) {
1132
+ function expandSystemSnapshot(snapshot, id) {
1133
+ const out = {};
1134
+ const owned = ownedAspects(id);
1135
+ const ownedSet = new Set(owned);
1136
+ for (const [key, value] of Object.entries(snapshot)) if (ownedSet.has(key)) out[key] = value;
1137
+ for (const aspect of owned) {
866
1138
  const handler = ASPECT_HANDLERS[aspect];
867
1139
  if (!handler.expandDefault) continue;
868
1140
  if (out[aspect] !== void 0) continue;
@@ -873,7 +1145,7 @@ function createPresetState(opts) {
873
1145
  const activeSnapshot = computed(() => {
874
1146
  const id = activeId.value;
875
1147
  if (!id) return {};
876
- if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {});
1148
+ if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {}, id);
877
1149
  const row = presetsById.value.get(id);
878
1150
  if (!row) return {};
879
1151
  const wire = row.data?.content;
@@ -884,48 +1156,64 @@ function createPresetState(opts) {
884
1156
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
885
1157
  return opts.presetsHandle;
886
1158
  }
887
- async function saveActive() {
888
- const handle = requirePresets();
889
- const id = activeId.value;
890
- if (!id) throw new Error("[vue-table] saveActive: no active preset");
891
- if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
892
- const existing = activeSnapshot.value;
893
- const mask = {};
894
- for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
895
- await handle.savePreset(captureSnapshot(mask));
896
- clearLocalDraft();
897
- }
898
- async function saveAs(label, saveOpts = {}) {
899
- const id = await requirePresets().savePresetAs(label, captureSnapshot(saveOpts.aspects), { public: saveOpts.public });
900
- clearLocalDraft();
901
- return id;
902
- }
903
- async function rename(id, label) {
904
- await requirePresets().renamePreset(id, label);
905
- }
906
- async function remove(id) {
907
- const handle = requirePresets();
908
- const wasActive = activeId.value === id;
909
- await handle.deletePreset(id);
910
- if (wasActive) {
911
- apply(STANDARD_PRESET_ID);
1159
+ function saveActive() {
1160
+ return track(async () => {
1161
+ const handle = requirePresets();
1162
+ const id = activeId.value;
1163
+ if (!id) throw new Error("[vue-table] saveActive: no active preset");
1164
+ if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
1165
+ const existing = activeSnapshot.value;
1166
+ const mask = {};
1167
+ for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
1168
+ await handle.savePreset(captureSnapshot(mask));
912
1169
  clearLocalDraft();
913
- }
1170
+ });
914
1171
  }
915
- async function togglePublic(id) {
916
- await requirePresets().togglePublic(id);
1172
+ function saveAs(label, saveOpts = {}) {
1173
+ return track(async () => {
1174
+ const id = await requirePresets().savePresetAs(label, captureSnapshot(saveOpts.aspects), { public: saveOpts.public });
1175
+ clearLocalDraft();
1176
+ return id;
1177
+ });
917
1178
  }
918
- async function setDefault(id) {
919
- await requirePresets().setDefault(id);
1179
+ function rename(id, label) {
1180
+ return track(async () => {
1181
+ await requirePresets().renamePreset(id, label);
1182
+ });
920
1183
  }
921
- async function toggleFav(id) {
922
- await requirePresets().toggleFav(id);
1184
+ function remove(id) {
1185
+ return track(async () => {
1186
+ const handle = requirePresets();
1187
+ const wasActive = activeId.value === id;
1188
+ await handle.deletePreset(id);
1189
+ if (wasActive) {
1190
+ apply(STANDARD_PRESET_ID);
1191
+ clearLocalDraft();
1192
+ }
1193
+ });
923
1194
  }
924
- async function setFavorites(ids) {
925
- await requirePresets().setFavorites(ids);
1195
+ function togglePublic(id) {
1196
+ return track(async () => {
1197
+ await requirePresets().togglePublic(id);
1198
+ });
1199
+ }
1200
+ function setDefault(id) {
1201
+ return track(async () => {
1202
+ await requirePresets().setDefault(id);
1203
+ });
1204
+ }
1205
+ function toggleFav(id) {
1206
+ return track(async () => {
1207
+ await requirePresets().toggleFav(id);
1208
+ });
1209
+ }
1210
+ function setFavorites(ids) {
1211
+ return track(async () => {
1212
+ await requirePresets().setFavorites(ids);
1213
+ });
926
1214
  }
927
1215
  function batch(fn) {
928
- return opts.presetsHandle ? opts.presetsHandle.batch(fn) : fn();
1216
+ return track(() => opts.presetsHandle ? opts.presetsHandle.batch(fn) : fn());
929
1217
  }
930
1218
  function resolveDefaultId() {
931
1219
  const pinned = (userConf.value?.data)?.defaultPresetId;
@@ -941,7 +1229,7 @@ function createPresetState(opts) {
941
1229
  stop();
942
1230
  const id = resolveDefaultId();
943
1231
  const { snapshot, nextActiveId } = resolveSnapshot(id);
944
- const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot) : snapshot;
1232
+ const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot, id) : snapshot;
945
1233
  const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
946
1234
  apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
947
1235
  activeId.value = nextActiveId;
@@ -960,6 +1248,8 @@ function createPresetState(opts) {
960
1248
  systemPresets,
961
1249
  systemPresetsById,
962
1250
  availableAspects,
1251
+ systemAspects,
1252
+ ownedAspects,
963
1253
  available,
964
1254
  activeId,
965
1255
  activeSnapshot,
@@ -982,7 +1272,8 @@ function createPresetState(opts) {
982
1272
  setDefault,
983
1273
  toggleFav,
984
1274
  setFavorites,
985
- batch
1275
+ batch,
1276
+ lastError
986
1277
  },
987
1278
  internals: {
988
1279
  gate,
@@ -1233,17 +1524,21 @@ const DEFAULT_BLOCK_SIZE = 100;
1233
1524
  const DEFAULT_DRAG_RELEASE_DEBOUNCE_MS = 300;
1234
1525
  const DEFAULT_ITEMS_PER_PAGE = 25;
1235
1526
  let _tblUid = 0;
1527
+ /** Shared empty sorter list — keeps `splitSorters` identity-stable. */
1528
+ const EMPTY_SORTERS = Object.freeze([]);
1236
1529
  /**
1237
- * Coerce a primitive cell value to a string for the static-mode query
1238
- * function's substring search and locale-aware sort. Objects fall back to
1239
- * `""` so `'[object Object]'` never leaks into the search index. Module
1240
- * scope so it's not recreated on every fetch.
1530
+ * Build the value reader used for in-memory sorting: a display column's
1531
+ * `sortValue(row)` wins for its own key, everything else reads the row by
1532
+ * path. Returns `getCellValue` itself when no display column defines one.
1241
1533
  */
1242
- function cellAsString(v) {
1243
- if (v == null) return "";
1244
- if (typeof v === "string") return v;
1245
- if (typeof v === "number" || typeof v === "boolean") return v.toString();
1246
- return "";
1534
+ function createSortValueReader(display) {
1535
+ const map = /* @__PURE__ */ new Map();
1536
+ for (const d of display ?? []) if (d.sortValue) map.set(d.key, d.sortValue);
1537
+ if (map.size === 0) return getCellValue;
1538
+ return (row, field) => {
1539
+ const fn = map.get(field);
1540
+ return fn ? fn(row) : getCellValue(row, field);
1541
+ };
1247
1542
  }
1248
1543
  /**
1249
1544
  * Promise-based dialog slot. A second `request()` while one is open
@@ -1252,17 +1547,20 @@ function cellAsString(v) {
1252
1547
  */
1253
1548
  function createRequestSlot(cancelValue) {
1254
1549
  const r = shallowRef(null);
1550
+ const display = shallowRef(null);
1255
1551
  function request(body) {
1256
1552
  if (r.value) {
1257
1553
  r.value.resolve(cancelValue);
1258
1554
  r.value = null;
1259
1555
  }
1260
- return new Promise((resolve) => {
1556
+ const promise = new Promise((resolve) => {
1261
1557
  r.value = {
1262
1558
  ...body,
1263
1559
  resolve
1264
1560
  };
1265
1561
  });
1562
+ display.value = r.value;
1563
+ return promise;
1266
1564
  }
1267
1565
  function accept(value) {
1268
1566
  const req = r.value;
@@ -1276,11 +1574,16 @@ function createRequestSlot(cancelValue) {
1276
1574
  r.value = null;
1277
1575
  req.resolve(cancelValue);
1278
1576
  }
1577
+ function release() {
1578
+ if (r.value === null) display.value = null;
1579
+ }
1279
1580
  return {
1280
1581
  ref: r,
1582
+ display,
1281
1583
  request,
1282
1584
  accept,
1283
- dismiss
1585
+ dismiss,
1586
+ release
1284
1587
  };
1285
1588
  }
1286
1589
  function createTableState(opts) {
@@ -1311,6 +1614,13 @@ function createTableState(opts) {
1311
1614
  });
1312
1615
  const rowDelete = ref(false);
1313
1616
  const includeActions = ref(false);
1617
+ const rowActions = ref(void 0);
1618
+ /**
1619
+ * The per-screen row-action policy, compiled once per config change instead
1620
+ * of once per row per render — and read by every surface that renders row
1621
+ * actions, so the cell and the selection toolbar can't drift apart.
1622
+ */
1623
+ const rowActionsPolicy = computed(() => compileRowActionsConfig(rowActions.value));
1314
1624
  const filters = shallowRef({});
1315
1625
  const results = shallowRef([]);
1316
1626
  const resultsStart = ref(0);
@@ -1360,6 +1670,7 @@ function createTableState(opts) {
1360
1670
  presetsHandle: opts.preset?.presetsHandle,
1361
1671
  draftHandle: opts.preset?.draftHandle,
1362
1672
  availableAspects: opts.preset?.availableAspects,
1673
+ systemAspects: opts.preset?.systemAspects,
1363
1674
  persistDrafts: opts.preset?.persistDrafts,
1364
1675
  fallbackSystemPresets: opts.preset?.fallbackSystemPresets
1365
1676
  });
@@ -1372,8 +1683,10 @@ function createTableState(opts) {
1372
1683
  message
1373
1684
  });
1374
1685
  }
1686
+ const confirmDisplay = promptSlot.display;
1375
1687
  const acceptPrompt = () => promptSlot.accept(true);
1376
1688
  const dismissPrompt = promptSlot.dismiss;
1689
+ const releaseConfirm = promptSlot.release;
1377
1690
  const formSlot = createRequestSlot(null);
1378
1691
  const actionFormRequest = formSlot.ref;
1379
1692
  function requestActionInput(action, ctx) {
@@ -1383,8 +1696,10 @@ function createTableState(opts) {
1383
1696
  preferredId: ctx.preferredId
1384
1697
  });
1385
1698
  }
1699
+ const actionFormDisplay = formSlot.display;
1386
1700
  const acceptActionForm = formSlot.accept;
1387
1701
  const dismissActionForm = formSlot.dismiss;
1702
+ const releaseActionForm = formSlot.release;
1388
1703
  const stateUid = `tbl-${++_tblUid}`;
1389
1704
  function rowId(absIndex) {
1390
1705
  return `${stateUid}-row-${absIndex}`;
@@ -1392,23 +1707,65 @@ function createTableState(opts) {
1392
1707
  let generation = 0;
1393
1708
  let queryDetected = false;
1394
1709
  let skipPaginationWatch = 0;
1395
- function buildCurrentQuery() {
1710
+ /**
1711
+ * Paths of client-owned columns — they have no server field behind them, so
1712
+ * they are stripped from `$select` and from `$sort`. Recomputed only when
1713
+ * the column set changes.
1714
+ */
1715
+ const localColumnPaths = computed(() => {
1716
+ const out = /* @__PURE__ */ new Set();
1717
+ for (const c of allColumns.value) if (c.local) out.add(c.path);
1718
+ return out;
1719
+ });
1720
+ /**
1721
+ * The active sorters split by who applies them: `server` goes into `$sort`,
1722
+ * `local` targets a client-owned column and is applied in memory. One pass,
1723
+ * one dependency — the two halves are complements.
1724
+ */
1725
+ const splitSorters = computed(() => {
1726
+ const paths = localColumnPaths.value;
1727
+ if (paths.size === 0) return {
1728
+ local: EMPTY_SORTERS,
1729
+ server: sorters.value
1730
+ };
1731
+ const local = [];
1732
+ const server = [];
1733
+ for (const s of sorters.value) (paths.has(s.field) ? local : server).push(s);
1734
+ return {
1735
+ local,
1736
+ server
1737
+ };
1738
+ });
1739
+ /** `true` while the query deliberately drops user sorters to keep search relevance. */
1740
+ function sortersIgnored() {
1741
+ return ignoreSortersWhenSearched.value && !!searchTerm.value;
1742
+ }
1743
+ /** Reads the value a sorter orders by — a display column's `sortValue` wins. */
1744
+ const sortValueOf = createSortValueReader(opts.displayColumns);
1745
+ function applyLocalSort(rows) {
1746
+ if (queryOpts?.preSorted) return rows;
1747
+ if (splitSorters.value.local.length === 0 || sortersIgnored()) return rows;
1748
+ return sortRowsLocally(rows, queryOpts?.forceSorters?.length ? mergeSorters(queryOpts.forceSorters, sorters.value) : sorters.value, sortValueOf);
1749
+ }
1750
+ function buildCurrentQuery(buildOpts) {
1396
1751
  const available = tableDef.value?.fetchableFields ?? new Set(allColumns.value.map((c) => c.path));
1397
1752
  const extra = /* @__PURE__ */ new Set();
1398
1753
  for (const c of columns.value) for (const p of c.selectWith ?? []) if (available.has(p)) extra.add(p);
1399
1754
  if (queryOpts?.alwaysSelected) {
1400
1755
  for (const p of queryOpts.alwaysSelected) if (available.has(p)) extra.add(p);
1401
1756
  }
1757
+ const localPaths = localColumnPaths.value;
1758
+ const requested = buildOpts?.columnPaths ?? columnNames.value;
1402
1759
  return buildTableQuery({
1403
- visibleColumnPaths: columnNames.value,
1760
+ visibleColumnPaths: localPaths.size ? requested.filter((p) => !localPaths.has(p)) : requested,
1404
1761
  extraSelect: extra.size ? [...extra] : void 0,
1405
- sorters: sorters.value,
1762
+ sorters: splitSorters.value.server,
1406
1763
  forceSorters: queryOpts?.forceSorters,
1407
1764
  filters: filters.value,
1408
1765
  forceFilters: queryOpts?.forceFilters,
1409
1766
  search: searchTerm.value || void 0,
1410
- ignoreSorters: ignoreSortersWhenSearched.value && !!searchTerm.value,
1411
- includeActions: includeActions.value
1767
+ ignoreSorters: sortersIgnored(),
1768
+ includeActions: buildOpts?.includeActions ?? includeActions.value
1412
1769
  });
1413
1770
  }
1414
1771
  function dispatchPages(query, page, size) {
@@ -1426,6 +1783,10 @@ function createTableState(opts) {
1426
1783
  /** -1 == nothing active. */
1427
1784
  const activeIndex = ref(-1);
1428
1785
  const navMode = ref("pagination");
1786
+ const isQueryBlocked = computed(() => {
1787
+ const blocked = queryOpts?.blockQuery;
1788
+ return typeof blocked === "function" ? !!blocked() : !!blocked;
1789
+ });
1429
1790
  const { windowCache, windowLoading, errors, topIndex, viewportRowCount, dataAt, loadingAt, errorAt, loadRange, queryNext, clearSettlements, resetWindow, disposeDebounces } = createWindowFetcher({
1430
1791
  blockSize,
1431
1792
  dragReleaseDebounceMs,
@@ -1435,7 +1796,7 @@ function createTableState(opts) {
1435
1796
  resultsStart,
1436
1797
  queryingNext,
1437
1798
  getGeneration: () => generation,
1438
- isQueryBlocked: () => !!queryOpts?.blockQuery,
1799
+ isQueryBlocked: () => isQueryBlocked.value,
1439
1800
  buildCurrentQuery,
1440
1801
  dispatchPages,
1441
1802
  reportError
@@ -1445,7 +1806,7 @@ function createTableState(opts) {
1445
1806
  if (idx < 0) return void 0;
1446
1807
  return navMode.value === "window" ? dataAt(idx) : results.value[idx];
1447
1808
  }
1448
- const { selectedRows, selectedCount, rowValueFn, isPkSelected, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow);
1809
+ const { selectedRows, selectedCount, rowValueFn, isPkSelected, rowSelectable, isRowSelectable, selectableRows, selectableCount, selectAll, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow, activeIndex);
1449
1810
  const { actions } = createActions({
1450
1811
  tableDef,
1451
1812
  client,
@@ -1481,7 +1842,7 @@ function createTableState(opts) {
1481
1842
  toggleActiveSelection
1482
1843
  });
1483
1844
  async function runQuery(kind, silent = false) {
1484
- if (queryOpts?.blockQuery) return;
1845
+ if (isQueryBlocked.value) return;
1485
1846
  mustRefresh.value = false;
1486
1847
  if (!silent && kind !== "initial" && topIndex.value !== 0) topIndex.value = 0;
1487
1848
  const thisGen = ++generation;
@@ -1522,7 +1883,7 @@ function createTableState(opts) {
1522
1883
  let pendingSilent = true;
1523
1884
  let queryFlushScheduled = false;
1524
1885
  function scheduleQuery(kind = "query", opts) {
1525
- if (queryOpts?.blockQuery) return;
1886
+ if (isQueryBlocked.value) return;
1526
1887
  if (tableDef.value === null) return;
1527
1888
  pendingScheduledKind = pendingScheduledKind ?? kind;
1528
1889
  if (!(opts?.silent ?? false)) {
@@ -1552,7 +1913,7 @@ function createTableState(opts) {
1552
1913
  async function queryImmediate(opts) {
1553
1914
  pendingScheduledKind = null;
1554
1915
  pendingSilent = true;
1555
- if (queryOpts?.blockQuery) return;
1916
+ if (isQueryBlocked.value) return;
1556
1917
  if (tableDef.value === null) return;
1557
1918
  await runQuery("query", opts?.silent ?? false);
1558
1919
  }
@@ -1610,7 +1971,13 @@ function createTableState(opts) {
1610
1971
  rowValueFn,
1611
1972
  resolveHref: opts.actions?.resolveHref ?? ((url) => url),
1612
1973
  isPkSelected,
1974
+ rowSelectable,
1975
+ isRowSelectable,
1976
+ selectableRows,
1977
+ selectableCount,
1978
+ selectAll,
1613
1979
  rowDelete,
1980
+ rowActions,
1614
1981
  includeActions,
1615
1982
  activeIndex,
1616
1983
  navMode,
@@ -1625,18 +1992,27 @@ function createTableState(opts) {
1625
1992
  registerMainActionListener,
1626
1993
  actions,
1627
1994
  confirmRequest,
1995
+ confirmDisplay,
1628
1996
  prompt: promptFn,
1629
1997
  acceptPrompt,
1630
1998
  dismissPrompt,
1999
+ releaseConfirm,
1631
2000
  actionFormRequest,
2001
+ actionFormDisplay,
1632
2002
  requestActionInput,
1633
2003
  acceptActionForm,
1634
2004
  dismissActionForm,
2005
+ releaseActionForm,
1635
2006
  query,
1636
2007
  queryImmediate,
1637
2008
  queryNext,
1638
2009
  loadRange,
1639
2010
  invalidate,
2011
+ buildQuery: buildCurrentQuery,
2012
+ fetchPage: dispatchPages,
2013
+ localColumnPaths,
2014
+ applyLocalSort,
2015
+ rowActionsPolicy,
1640
2016
  dataAt,
1641
2017
  loadingAt,
1642
2018
  errorAt,
@@ -1777,7 +2153,7 @@ function createTableState(opts) {
1777
2153
  lastEmittedUrl = serializeStateForUrl();
1778
2154
  nextTick(() => {
1779
2155
  hydratingFromUrl = false;
1780
- if (wasQueryDetected && !queryOpts?.blockQuery && tableDef.value !== null) scheduleQuery();
2156
+ if (wasQueryDetected && !isQueryBlocked.value && tableDef.value !== null) scheduleQuery();
1781
2157
  });
1782
2158
  }
1783
2159
  const debouncedFilterQuery = debounce(() => {
@@ -1795,6 +2171,11 @@ function createTableState(opts) {
1795
2171
  if (sortersEqual(prev, next)) return;
1796
2172
  if (searchTerm.value) setIgnoreSortersInternal(false);
1797
2173
  if (!queryDetected) return;
2174
+ const paths = localColumnPaths.value;
2175
+ if (paths.size > 0) {
2176
+ const keep = (s) => !paths.has(s.field);
2177
+ if (sortersEqual(prev.filter(keep), next.filter(keep))) return;
2178
+ }
1798
2179
  requestRefresh();
1799
2180
  }, { immediate: false });
1800
2181
  watch(() => searchTerm.value, (next, prev) => {
@@ -1838,9 +2219,11 @@ function createTableState(opts) {
1838
2219
  watch([
1839
2220
  () => tableDef.value,
1840
2221
  () => queryOpts?.urlQueryReady?.value ?? true,
1841
- presetGateOpen
1842
- ], ([def, urlReady, presetReady]) => {
2222
+ presetGateOpen,
2223
+ () => isQueryBlocked.value
2224
+ ], ([def, urlReady, presetReady, blocked]) => {
1843
2225
  if (queryDetected) return;
2226
+ if (blocked) return;
1844
2227
  if (def === null || !urlReady || !presetReady) return;
1845
2228
  if (queryOpts?.queryOnMount === false) return;
1846
2229
  if (allColumns.value.length === 0) return;
@@ -1848,6 +2231,11 @@ function createTableState(opts) {
1848
2231
  queryDetected = true;
1849
2232
  scheduleQuery("initial");
1850
2233
  });
2234
+ watch(() => isQueryBlocked.value, (blocked) => {
2235
+ if (blocked || !queryDetected) return;
2236
+ if (tableDef.value === null) return;
2237
+ scheduleQuery();
2238
+ });
1851
2239
  onScopeDispose(() => {
1852
2240
  debouncedFilterQuery.cancel();
1853
2241
  disposeDebounces();
@@ -1858,10 +2246,11 @@ function createTableState(opts) {
1858
2246
  state,
1859
2247
  internals: {
1860
2248
  init(def) {
1861
- allColumns.value = def.columns;
1862
- const reconciled = reconcileColumnWidthDefaults(def.columns, columnWidths.value);
2249
+ const merged = mergeDisplayColumns(def.columns, opts.displayColumns ?? []);
2250
+ allColumns.value = merged;
2251
+ const reconciled = reconcileColumnWidthDefaults(merged, columnWidths.value);
1863
2252
  if (reconciled !== columnWidths.value) columnWidths.value = reconciled;
1864
- if (columnNames.value.length === 0) columnNames.value = def.columns.map((c) => c.path);
2253
+ if (columnNames.value.length === 0) columnNames.value = merged.map((c) => c.path);
1865
2254
  tableDef.value = def;
1866
2255
  },
1867
2256
  resetPagination
@@ -1875,6 +2264,7 @@ function createTableState(opts) {
1875
2264
  */
1876
2265
  function createStaticTableState(opts) {
1877
2266
  let _state = null;
2267
+ let fetcher = null;
1878
2268
  const queryFn = (q, page, size) => {
1879
2269
  if (!_state) return Promise.resolve({
1880
2270
  data: [],
@@ -1883,13 +2273,21 @@ function createStaticTableState(opts) {
1883
2273
  itemsPerPage: size,
1884
2274
  pages: 1
1885
2275
  });
1886
- return buildStaticQueryFn(opts, _state)(q, page, size);
2276
+ fetcher ??= buildStaticQueryFn(opts, _state);
2277
+ return fetcher(q, page, size);
1887
2278
  };
1888
2279
  const result = createTableState({
1889
2280
  client: {},
1890
2281
  selection: opts.selection,
1891
2282
  limit: opts.limit,
1892
- query: { fn: queryFn }
2283
+ displayColumns: opts.displayColumns,
2284
+ model: opts.model,
2285
+ actions: opts.actions,
2286
+ query: {
2287
+ fn: queryFn,
2288
+ queryOnMount: opts.queryOnMount,
2289
+ preSorted: true
2290
+ }
1893
2291
  });
1894
2292
  _state = result.state;
1895
2293
  result.state.loadingMetadata.value = false;
@@ -1921,26 +2319,24 @@ function createStaticTableState(opts) {
1921
2319
  }
1922
2320
  function buildStaticQueryFn(opts, state) {
1923
2321
  const searchPaths = opts.searchPaths ?? [];
2322
+ const sortValueOf = createSortValueReader(opts.displayColumns);
2323
+ let sortedFrom = null;
2324
+ let sortedBy = null;
2325
+ let sorted = [];
2326
+ function sortedRows(all) {
2327
+ const by = state.sorters.value;
2328
+ if (all !== sortedFrom || by !== sortedBy) {
2329
+ sorted = sortRowsLocally(all, by, sortValueOf);
2330
+ sortedFrom = all;
2331
+ sortedBy = by;
2332
+ }
2333
+ return sorted;
2334
+ }
1924
2335
  return (_query, page, size) => {
1925
- let filtered = opts.rows;
2336
+ const all = toValue(opts.rows);
2337
+ let filtered = opts.localSort === false ? all : sortedRows(all);
1926
2338
  const term = state.searchTerm.value.trim().toLowerCase();
1927
- if (term && searchPaths.length > 0) filtered = filtered.filter((row) => searchPaths.some((p) => cellAsString(row[p]).toLowerCase().includes(term)));
1928
- const active = state.sorters.value;
1929
- if (active.length > 0) filtered = filtered.toSorted((a, b) => {
1930
- for (const s of active) {
1931
- const dir = s.direction === "desc" ? -1 : 1;
1932
- const av = a[s.field];
1933
- const bv = b[s.field];
1934
- if (typeof av === "number" && typeof bv === "number") {
1935
- if (av < bv) return -dir;
1936
- if (av > bv) return dir;
1937
- } else {
1938
- const cmp = cellAsString(av).localeCompare(cellAsString(bv));
1939
- if (cmp !== 0) return cmp * dir;
1940
- }
1941
- }
1942
- return 0;
1943
- });
2339
+ if (term && searchPaths.length > 0) filtered = filtered.filter((row) => searchPaths.some((p) => cellAsString(getCellValue(row, p)).toLowerCase().includes(term)));
1944
2340
  const start = (page - 1) * size;
1945
2341
  return Promise.resolve({
1946
2342
  data: filtered.slice(start, start + size),
@@ -1994,4 +2390,4 @@ function useRegisterMainActionListener(state, listener, enabled) {
1994
2390
  });
1995
2391
  }
1996
2392
  //#endregion
1997
- export { isModClick as _, useTableContext as a, ROW_ACTIONS_PATH as b, actionHref as c, ariaLabelFor as d, collectIdentifiers as f, intentToScope as g, intentClass as h, useRegisterMainActionListener as i, applyRowGate as l, extractIdentifier as m, createTableState as n, useTableContextOptional as o, createNavigateGestures as p, provideTableContext as r, DEFAULT_AVAILABLE_ASPECTS as s, createStaticTableState as t, applyRowsGate as u, isPlainLeftClick as v, ROW_ACTIONS_TYPE as x, triggerAction as y };
2393
+ export { isModClick as C, ROW_ACTIONS_TYPE as D, ROW_ACTIONS_PATH as E, intentToScope as S, triggerAction as T, ariaLabelFor as _, useTableContext as a, extractIdentifier as b, ASPECT_ICONS as c, readPresetLabel as d, INTERACTIVE_SELECTOR as f, applyRowsGate as g, applyRowGate as h, useRegisterMainActionListener as i, ASPECT_LABELS as l, rowActionHref as m, createTableState as n, useTableContextOptional as o, onSelectControlKeydown as p, provideTableContext as r, DEFAULT_AVAILABLE_ASPECTS as s, createStaticTableState as t, ownerNameOf as u, collectIdentifiers as v, isPlainLeftClick as w, intentClass as x, createNavigateGestures as y };