@atscript/vue-table 0.1.143 → 0.1.145

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/as-action-form-dialog.cjs +1 -1
  2. package/dist/as-action-form-dialog.mjs +1 -1
  3. package/dist/{as-action-menu-content-miQYVP7x.mjs → as-action-menu-content-DoZnQP0I.mjs} +37 -25
  4. package/dist/{as-action-menu-content-BaNFBYn3.cjs → as-action-menu-content-FQ2miGid.cjs} +36 -24
  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-Ca3wSDxw.d.mts → as-column-menu.vue-ByTECTbX.d.cts} +2 -2
  8. package/dist/{as-column-menu.vue-Co7FtiEM.d.cts → as-column-menu.vue-TChT482i.d.mts} +2 -2
  9. package/dist/{as-config-dialog-CVSOrS9O.cjs → as-config-dialog-CpW1Qemf.cjs} +2 -2
  10. package/dist/{as-config-dialog-Di02V3bs.mjs → as-config-dialog-ObHtungs.mjs} +2 -2
  11. package/dist/as-config-dialog.cjs +1 -1
  12. package/dist/as-config-dialog.mjs +1 -1
  13. package/dist/{as-confirm-dialog-fCwo1ty9.cjs → as-confirm-dialog-D3Vu1Rb_.cjs} +1 -1
  14. package/dist/{as-confirm-dialog-C9Qat2rG.mjs → as-confirm-dialog-zg7jmS3u.mjs} +1 -1
  15. package/dist/as-confirm-dialog.cjs +1 -1
  16. package/dist/as-confirm-dialog.mjs +1 -1
  17. package/dist/{as-filter-dialog-DYpbsNDx.cjs → as-filter-dialog-C4EyRr2_.cjs} +7 -7
  18. package/dist/{as-filter-dialog-DdtrTT5w.mjs → as-filter-dialog-CWy0mjZl.mjs} +7 -7
  19. package/dist/as-filter-dialog.cjs +1 -1
  20. package/dist/as-filter-dialog.mjs +1 -1
  21. package/dist/{as-filter-field-4-qFRsJx.cjs → as-filter-field-BQ9UlV0y.cjs} +3 -3
  22. package/dist/{as-filter-field-DEQVodKG.mjs → as-filter-field-CW1_GHZT.mjs} +3 -3
  23. package/dist/as-filter-field.cjs +1 -1
  24. package/dist/as-filter-field.mjs +1 -1
  25. package/dist/{as-filters-Dd_j6_6G.mjs → as-filters-BLFSS_VP.mjs} +4 -4
  26. package/dist/{as-filters-C6ZkLrf4.cjs → as-filters-DUfFkXAo.cjs} +4 -4
  27. package/dist/as-filters.cjs +1 -1
  28. package/dist/as-filters.mjs +1 -1
  29. package/dist/{as-preset-dialog-SYA-6lGR.cjs → as-preset-dialog-BlXqTZiT.cjs} +1 -1
  30. package/dist/{as-preset-dialog-Bap48oUv.mjs → as-preset-dialog-CI2Hw8vi.mjs} +1 -1
  31. package/dist/as-preset-dialog.cjs +1 -1
  32. package/dist/as-preset-dialog.mjs +1 -1
  33. package/dist/{as-preset-picker-BdKTZa95.cjs → as-preset-picker-CVgyZBG7.cjs} +1 -1
  34. package/dist/{as-preset-picker-NBrvsE6s.mjs → as-preset-picker-CbZDGPaf.mjs} +1 -1
  35. package/dist/as-preset-picker.cjs +1 -1
  36. package/dist/as-preset-picker.mjs +1 -1
  37. package/dist/{as-residual-filter-CZ-Bytdj.cjs → as-residual-filter-C8n7kdGW.cjs} +1 -1
  38. package/dist/{as-residual-filter-BFnbI3h3.mjs → as-residual-filter-CUTouIn5.mjs} +1 -1
  39. package/dist/as-residual-filter.cjs +1 -1
  40. package/dist/as-residual-filter.mjs +1 -1
  41. package/dist/{as-row-actions-C3xHkX53.cjs → as-row-actions-DfUL9sYB.cjs} +21 -38
  42. package/dist/{as-row-actions-KeoHo9Ex.mjs → as-row-actions-ed1P6GEJ.mjs} +22 -39
  43. package/dist/as-row-actions.cjs +1 -1
  44. package/dist/as-row-actions.d.cts +1 -1
  45. package/dist/as-row-actions.d.mts +1 -1
  46. package/dist/as-row-actions.mjs +1 -1
  47. package/dist/{as-row-actions.vue-nmaDuxBT.d.mts → as-row-actions.vue-Ba3VO9W-.d.cts} +1 -1
  48. package/dist/{as-row-actions.vue-D3_z3ytk.d.cts → as-row-actions.vue-CKOnE0eb.d.mts} +1 -1
  49. package/dist/{as-select-cell-KD_ns8DS.mjs → as-select-cell-BMptU5sQ.mjs} +4 -4
  50. package/dist/{as-select-cell-XnzNuW2v.cjs → as-select-cell-DwMfqBMV.cjs} +4 -4
  51. package/dist/{as-table-ay1yel3u.cjs → as-table-BjKzKB0a.cjs} +3 -3
  52. package/dist/{as-table-NHqyekNd.mjs → as-table-DGWfzV4w.mjs} +3 -3
  53. package/dist/{as-table-actions-BpEFcsgh.mjs → as-table-actions-DQco4pGu.mjs} +16 -29
  54. package/dist/{as-table-actions-UQmFtRdJ.cjs → as-table-actions-DplblFlK.cjs} +15 -28
  55. package/dist/as-table-actions.cjs +1 -1
  56. package/dist/as-table-actions.d.cts +1 -1
  57. package/dist/as-table-actions.d.mts +1 -1
  58. package/dist/as-table-actions.mjs +1 -1
  59. package/dist/{as-table-actions.vue-BG3JVaBQ.d.mts → as-table-actions.vue-BL4xy2-d.d.mts} +2 -2
  60. package/dist/{as-table-actions.vue-DcuzNZoC.d.cts → as-table-actions.vue-DgMw3xJn.d.cts} +2 -2
  61. package/dist/{as-table-base-umzg4PBR.cjs → as-table-base-CNAn-vpa.cjs} +2 -2
  62. package/dist/{as-table-base-89DkDZ-1.mjs → as-table-base-lfyjPgsO.mjs} +2 -2
  63. package/dist/{as-table-header-cell-f3YpgJX1.cjs → as-table-header-cell-BwaOPYwI.cjs} +1 -1
  64. package/dist/{as-table-header-cell-DU3ws2x9.mjs → as-table-header-cell-Csv1KQDa.mjs} +1 -1
  65. package/dist/as-table-header-cell.cjs +1 -1
  66. package/dist/as-table-header-cell.d.cts +1 -1
  67. package/dist/as-table-header-cell.d.mts +1 -1
  68. package/dist/as-table-header-cell.mjs +1 -1
  69. package/dist/{as-table-header-cell.vue-5VGRc23o.d.mts → as-table-header-cell.vue-DU5h-2C_.d.cts} +2 -2
  70. package/dist/{as-table-header-cell.vue-XR90UlHO.d.cts → as-table-header-cell.vue-KkANwimG.d.mts} +2 -2
  71. package/dist/{as-table-root-47hiBEnq.mjs → as-table-root-BreMYhnv.mjs} +4 -4
  72. package/dist/{as-table-root-DDeGktcl.cjs → as-table-root-DXewWV9h.cjs} +4 -4
  73. package/dist/as-table-root.cjs +1 -1
  74. package/dist/as-table-root.d.cts +1 -1
  75. package/dist/as-table-root.d.mts +1 -1
  76. package/dist/as-table-root.mjs +1 -1
  77. package/dist/{as-table-root.vue-yZ7ZcwdT.d.cts → as-table-root.vue-CXaFNCMi.d.cts} +3 -3
  78. package/dist/{as-table-root.vue-BGx453_Q.d.mts → as-table-root.vue-DTos5nn3.d.mts} +3 -3
  79. package/dist/as-table.cjs +1 -1
  80. package/dist/as-table.d.cts +1 -1
  81. package/dist/as-table.d.mts +1 -1
  82. package/dist/as-table.mjs +1 -1
  83. package/dist/{as-table.vue-pYZ4eGni.d.cts → as-table.vue-Cn1CXJ1Y.d.mts} +3 -3
  84. package/dist/{as-table.vue-BgcFEBxt.d.mts → as-table.vue-EfLWpNy3.d.cts} +3 -3
  85. package/dist/{as-window-table-hOYLNhX3.cjs → as-window-table-Bj1dXOS6.cjs} +3 -3
  86. package/dist/{as-window-table-C312T_bc.mjs → as-window-table-JOTOi_Nm.mjs} +3 -3
  87. package/dist/as-window-table.cjs +1 -1
  88. package/dist/as-window-table.d.cts +1 -1
  89. package/dist/as-window-table.d.mts +1 -1
  90. package/dist/as-window-table.mjs +1 -1
  91. package/dist/{as-window-table.vue-6RsX1T1D.d.cts → as-window-table.vue-DCd8FtpC.d.cts} +2 -2
  92. package/dist/{as-window-table.vue-DZ23YTmK.d.mts → as-window-table.vue-DN5hed6Y.d.mts} +2 -2
  93. package/dist/index.cjs +19 -18
  94. package/dist/index.d.cts +9 -9
  95. package/dist/index.d.mts +9 -9
  96. package/dist/index.mjs +19 -19
  97. package/dist/{types-BU7J-a1r.d.mts → types-BEZ4_erB.d.mts} +86 -11
  98. package/dist/{types-D4dK49sA.d.cts → types-D1NxhrpW.d.cts} +86 -11
  99. package/dist/{use-table-DlqGHjPh.mjs → use-table-BTsNGBVx.mjs} +1 -1
  100. package/dist/{use-table-yZNJTfRq.cjs → use-table-W0KmaJ6L.cjs} +1 -1
  101. package/dist/{use-table-column-handlers-0myVzrZR.mjs → use-table-column-handlers-BitvioH4.mjs} +1 -1
  102. package/dist/{use-table-column-handlers-BHMCo_HR.cjs → use-table-column-handlers-DksXpbON.cjs} +1 -1
  103. package/dist/{use-table-component-IziAm0vk.cjs → use-table-component-BvAC82_c.cjs} +1 -1
  104. package/dist/{use-table-component-TLwFq4rx.mjs → use-table-component-DRMXTk9M.mjs} +1 -1
  105. package/dist/{use-table-nav-bridge-CkQqimdS.mjs → use-table-nav-bridge-Bw0NFDSf.mjs} +1 -1
  106. package/dist/{use-table-nav-bridge-QU4jKhqx.cjs → use-table-nav-bridge-CPI1tql6.cjs} +1 -1
  107. package/dist/{use-table-state-BiklfjP0.cjs → use-table-state-Ddr2UZzB.cjs} +298 -88
  108. package/dist/{use-table-state-Cq-3tbVr.mjs → use-table-state-DuY-Mf8e.mjs} +271 -85
  109. package/package.json +8 -8
@@ -22,79 +22,169 @@ function actionNamesOf(row) {
22
22
  return Array.isArray(raw) ? raw : null;
23
23
  }
24
24
  /**
25
- * Predicate factory: an action is available when its name is in `allowed`. The
26
- * synthesised remove action (`REMOVE_PROCESSOR`) is the sole exemption — it is
27
- * built client-side and its name never appears in the server's `$actions`, so
28
- * its visibility is governed by `canRemove` (the server still authorises the
29
- * actual delete at call time). Shared by the single-row and bulk gates.
25
+ * A row's raw `$disabledReasons` object — action name → the reason its
26
+ * `disabled` predicate returned for that row (`@atscript/db` 0.1.141+) — or
27
+ * `null` when it is missing or not a plain object. Read lazily (no copy): the
28
+ * gates look names up with {@link ownReason}.
30
29
  */
31
- function gateFor(allowed) {
32
- return (a) => a.processor === "__remove" || allowed.has(a.name);
30
+ function reasonsObjectOf(row) {
31
+ const raw = row?.$disabledReasons;
32
+ return raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
33
33
  }
34
34
  /**
35
- * Build a per-row availability predicate from `row.$actions`. Every
36
- * server-declared row/rows action — regardless of processor (`backend`,
37
- * `navigate`, `custom`, …) — is gated by its per-row `$actions` verdict: the
38
- * server augmenter evaluates each action's `disabled` predicate for the row and
39
- * emits the surviving names. Returns `null` when the row carries no `$actions`
35
+ * `raw[name]` when it is an OWN non-empty string, else `undefined` — so an
36
+ * action named after an `Object.prototype` member (`constructor`,
37
+ * `toString`) never reads an inherited function as its verdict, and a
38
+ * malformed value can never surface an action (it only decides between
39
+ * disabled and hidden).
40
+ */
41
+ function ownReason(raw, name) {
42
+ if (!Object.hasOwn(raw, name)) return void 0;
43
+ const reason = raw[name];
44
+ return typeof reason === "string" && reason !== "" ? reason : void 0;
45
+ }
46
+ /**
47
+ * Apply one verdict to `a`: `false` → `undefined` (dropped), a non-empty
48
+ * string → a COPY carrying `disabledReason` (shared source descriptors are
49
+ * never mutated), anything else → `a` itself.
50
+ */
51
+ function withVerdict(a, verdict) {
52
+ if (verdict === false) return void 0;
53
+ return typeof verdict === "string" && verdict !== "" ? {
54
+ ...a,
55
+ disabledReason: verdict
56
+ } : a;
57
+ }
58
+ /**
59
+ * Gate factory: an action is available when its name is in `allowed`; one
60
+ * that is not but has a reason (`reasonOf`) is shown disabled with it; any
61
+ * other is hidden (disabled without a reason). The synthesised remove action
62
+ * (`REMOVE_PROCESSOR`) is the sole exemption — it is built client-side and its
63
+ * name never appears in the server's `$actions`, so its visibility is
64
+ * governed by `canRemove` (the server still authorises the actual delete at
65
+ * call time). Shared by the single-row and bulk gates.
66
+ */
67
+ function gateFor(allowed, reasonOf) {
68
+ return (a) => a.processor === "__remove" || allowed.has(a.name) || (reasonOf(a.name) ?? false);
69
+ }
70
+ const NO_REASON = () => void 0;
71
+ /**
72
+ * Build a per-row availability gate from `row.$actions` + `row.$disabledReasons`.
73
+ * Every server-declared row/rows action — regardless of processor (`backend`,
74
+ * `navigate`, `custom`, …) — is gated by its per-row verdict: the server
75
+ * augmenter evaluates each action's `disabled` predicate for the row and emits
76
+ * the surviving names, plus the reason of each action disabled WITH one.
77
+ * In `$actions` → enabled; in `$disabledReasons` → disabled with the reason;
78
+ * in neither → hidden. Returns `null` when the row carries no `$actions`
40
79
  * (see {@link actionNamesOf}).
41
80
  */
42
81
  function rowActionGate(row) {
43
82
  const names = actionNamesOf(row);
44
- return names ? gateFor(new Set(names)) : null;
83
+ if (!names) return null;
84
+ const raw = reasonsObjectOf(row);
85
+ return gateFor(new Set(names), raw ? (name) => ownReason(raw, name) : NO_REASON);
45
86
  }
46
87
  /**
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
88
+ * `true` when `action` is a gated copy kept DISABLED (`disabledReason`) —
89
+ * rendered, never triggered. The one predicate every surface checks.
90
+ */
91
+ function isActionDisabled(action) {
92
+ return !!action.disabledReason;
93
+ }
94
+ /**
95
+ * Walk a `{default, others, rows}` triple once: apply `gate`'s verdict to
96
+ * each action ({@link withVerdict}), then run each survivor through `map`.
97
+ * Both are optional. Source references stay stable downstream (no spurious
98
+ * recomputes in consumers that compare array identity): an array whose every
99
+ * action comes back unchanged is returned as is, and so is the whole
100
+ * `buckets` object when nothing changed. Shared by the single-row, bulk
52
101
  * (`applyRowsGate`) and per-screen-policy paths.
53
102
  */
54
103
  function applyGate(buckets, gate, map) {
55
104
  if (!gate && !map) return buckets;
56
- const keep = gate ?? (() => true);
105
+ const admit = (a) => {
106
+ const kept = gate ? withVerdict(a, gate(a)) : a;
107
+ return kept && map ? map(kept) : kept;
108
+ };
57
109
  const walk = (list) => {
58
- const kept = gate ? list.filter(keep) : list;
59
- return map ? kept.map(map) : kept;
110
+ let out = null;
111
+ for (let i = 0; i < list.length; i++) {
112
+ const a = list[i];
113
+ const kept = admit(a);
114
+ if (out === null) {
115
+ if (kept === a) continue;
116
+ out = list.slice(0, i);
117
+ }
118
+ if (kept) out.push(kept);
119
+ }
120
+ return out ?? list;
60
121
  };
61
- const def = buckets.default;
62
- return {
63
- default: def && keep(def) ? map ? map(def) : def : void 0,
64
- others: walk(buckets.others),
65
- rows: walk(buckets.rows)
122
+ const def = buckets.default && admit(buckets.default);
123
+ const others = walk(buckets.others);
124
+ const rows = walk(buckets.rows);
125
+ return def === buckets.default && others === buckets.others && rows === buckets.rows ? buckets : {
126
+ default: def,
127
+ others,
128
+ rows
66
129
  };
67
130
  }
68
- /**
69
- * Apply the single-row {@link rowActionGate} to a `{default, others, rows}`
70
- * triple; identity-stable when the row carries no `$actions` (see {@link applyGate}).
71
- */
72
- function applyRowGate(buckets, row) {
73
- return applyGate(buckets, rowActionGate(row));
131
+ /** Distinct reasons listed on a bulk surface before the rest collapse into "+N more". */
132
+ const MAX_BULK_REASONS = 3;
133
+ function joinReasons(reasons) {
134
+ const list = [...reasons];
135
+ if (list.length <= MAX_BULK_REASONS) return list.join("; ");
136
+ const rest = list.length - MAX_BULK_REASONS;
137
+ return `${list.slice(0, MAX_BULK_REASONS).join("; ")}; +${rest} more`;
74
138
  }
75
139
  /**
76
- * Build a BULK availability predicate from the UNION of every selected row's
77
- * server `$actions`: an action is shown when AT LEAST ONE selected row allows
78
- * it, even if absent from the rest. This is safe because the `@atscript/db`
79
- * layer re-filters each row server-side at invoke time, so a subset-enabled
80
- * action simply no-ops on the rows that don't qualify — strictly better UX
81
- * than hiding an action a portion of the selection can use. The synthesised
82
- * remove action (`REMOVE_PROCESSOR`) is the sole exemption: it is built
83
- * client-side and never appears in any server `$actions`. Returns `null` when
84
- * NO selected row carries a `$actions` array (legacy server / `?$actions`
85
- * opt-out) so callers skip the filter pass and keep array identity stable;
86
- * rows with an empty `$actions: []` still count as "the server spoke" and
87
- * therefore disable normal actions.
140
+ * Build a BULK availability gate from the UNION of every selected row's
141
+ * server `$actions`: an action is enabled when AT LEAST ONE selected row allows
142
+ * it, even if absent from the rest. The server re-checks every row at invoke
143
+ * time (`onDisabledRows`: `'skip'` runs on the qualifying rows, `'reject'` —
144
+ * the default — answers 409 with the per-row reasons, surfaced as the
145
+ * action's error result), so offering an action part of the selection can use
146
+ * is strictly better UX than hiding it. An action NO selected row allows is
147
+ * shown disabled when at least one row gave a reason (the distinct reasons
148
+ * joined, capped at {@link MAX_BULK_REASONS}), hidden otherwise. The
149
+ * synthesised remove action (`REMOVE_PROCESSOR`) is the sole exemption: it is
150
+ * built client-side and never appears in any server `$actions`. Returns `null`
151
+ * when NO row carries a `$actions` array (legacy server / `?$actions` opt-out)
152
+ * so callers skip the filter pass and keep array identity stable; rows with an
153
+ * empty `$actions: []` still count as "the server spoke" and therefore disable
154
+ * normal actions. Pass only rows that are loaded — see `state.selectedRowObjects`.
88
155
  */
89
156
  function rowsActionGate(rows) {
90
157
  let union = null;
158
+ let reasons = null;
91
159
  for (const row of rows) {
92
160
  const names = actionNamesOf(row);
93
161
  if (!names) continue;
94
- if (union === null) union = /* @__PURE__ */ new Set();
162
+ union ??= /* @__PURE__ */ new Set();
95
163
  for (const name of names) union.add(name);
164
+ const raw = reasonsObjectOf(row);
165
+ if (!raw) continue;
166
+ for (const name of Object.keys(raw)) {
167
+ const reason = raw[name];
168
+ if (typeof reason !== "string" || reason === "") continue;
169
+ reasons ??= /* @__PURE__ */ new Map();
170
+ let set = reasons.get(name);
171
+ if (!set) reasons.set(name, set = /* @__PURE__ */ new Set());
172
+ set.add(reason);
173
+ }
96
174
  }
97
- return union ? gateFor(union) : null;
175
+ if (!union) return null;
176
+ if (!reasons) return gateFor(union, NO_REASON);
177
+ const byName = reasons;
178
+ const joined = /* @__PURE__ */ new Map();
179
+ return gateFor(union, (name) => {
180
+ let text = joined.get(name);
181
+ if (text === void 0) {
182
+ const set = byName.get(name);
183
+ if (!set) return void 0;
184
+ joined.set(name, text = joinReasons(set));
185
+ }
186
+ return text;
187
+ });
98
188
  }
99
189
  /**
100
190
  * Apply the BULK union gate to a `{default, others, rows}` triple. See
@@ -148,35 +238,64 @@ function extractIdentifier(source, preferredId) {
148
238
  }
149
239
  /**
150
240
  * Map a list of sources (full row objects or scalar `rowValueFn` values)
151
- * through `extractIdentifier`. Scalar sources rehydrate from
152
- * `state.windowCache` via `state.rowValueFn` so consumers that override
153
- * `rowValueFn` to return a scalar can still reconstruct multi-field
154
- * identifiers; the lookup `Map` is built lazily so all-object source
155
- * lists pay nothing.
241
+ * through `extractIdentifier`. Scalar sources resolve to their loaded row via
242
+ * `state.rowOf` so consumers that override `rowValueFn` to return a scalar
243
+ * can still reconstruct multi-field identifiers; a scalar whose row is not
244
+ * loaded falls back to itself (a single-field `preferredId` wraps it).
156
245
  */
157
246
  function collectIdentifiers(state, sources, preferredId) {
158
247
  if (preferredId.length === 0 || sources.length === 0) return [];
159
248
  const out = [];
160
- let lookup = null;
161
249
  for (const s of sources) {
162
250
  if (s === void 0 || s === null) continue;
163
- let row;
164
- if (typeof s === "object") row = s;
165
- else {
166
- if (lookup === null) {
167
- const fn = state.rowValueFn;
168
- lookup = /* @__PURE__ */ new Map();
169
- for (const r of state.windowCache.value.values()) lookup.set(fn(r), r);
170
- }
171
- row = lookup.get(s);
172
- }
173
- const id = extractIdentifier(row ?? s, preferredId);
251
+ const id = extractIdentifier(state.rowOf(s) ?? s, preferredId);
174
252
  if (id) out.push(id);
175
253
  }
176
254
  return out;
177
255
  }
256
+ /**
257
+ * Accessible name of an action trigger. A disabled action appends its reason
258
+ * (`"Ship, Order already shipped"` — the same `label, reason` shape as the
259
+ * selection checkbox of a non-selectable row), so screen readers announce WHY
260
+ * next to the disabled state.
261
+ */
178
262
  function ariaLabelFor(action) {
179
- return action.label || action.name;
263
+ const label = action.label || action.name;
264
+ return action.disabledReason ? `${label}, ${action.disabledReason}` : label;
265
+ }
266
+ /** `aria-disabled` value for a rendered action — `"true"` on a disabled copy, else unset. */
267
+ function ariaDisabled(action) {
268
+ return isActionDisabled(action) ? "true" : void 0;
269
+ }
270
+ /**
271
+ * Whether a trigger renders as a real anchor: it has a resolved `href` and no
272
+ * `promptText` (a confirm dialog must guard the navigation, so a prompted
273
+ * action stays a button — mod/middle-click on it still confirms → new tab).
274
+ * A disabled action never has an href (see `rowActionHref`), so it is never
275
+ * a link.
276
+ */
277
+ function isLinkTrigger(action, href) {
278
+ return href !== void 0 && !action.promptText;
279
+ }
280
+ /**
281
+ * Element + attributes of a primary action trigger (the single row-actions
282
+ * button, the toolbar default) — the one home for anchor-vs-button and the
283
+ * disabled rendering: a disabled action is an `aria-disabled` button (kept
284
+ * focusable so the reason in its accessible name / tooltip stays reachable).
285
+ */
286
+ function triggerBindings(action, href) {
287
+ const asLink = isLinkTrigger(action, href);
288
+ const label = ariaLabelFor(action);
289
+ return {
290
+ tag: asLink ? "a" : "button",
291
+ attrs: {
292
+ href: asLink ? href : void 0,
293
+ type: asLink ? void 0 : "button",
294
+ "aria-disabled": ariaDisabled(action),
295
+ "aria-label": label,
296
+ title: label
297
+ }
298
+ };
180
299
  }
181
300
  /**
182
301
  * Compose the runtime intent class — `as-{prefix}-intent-{intent}` — applied
@@ -222,6 +341,7 @@ function actionHref(state, action, id) {
222
341
  * owns the navigation.
223
342
  */
224
343
  async function openNavigateInNewTab(state, action, ctx, href) {
344
+ if (isActionDisabled(action)) return;
225
345
  if (!await confirmAction(state, action, ctx)) return;
226
346
  window.open(href, "_blank", "noopener,noreferrer");
227
347
  }
@@ -230,12 +350,14 @@ async function openNavigateInNewTab(state, action, ctx, href) {
230
350
  * shared by `<AsRowActions>` and `<AsTableActions>`. Parameterized only by
231
351
  * the surface's prompt-context getter and href resolver so the click-dispatch
232
352
  * policy — plain left → SPA invoke, cmd/ctrl or middle on a promptText
233
- * navigate button → confirm → new tab — has a single home.
353
+ * navigate button → confirm → new tab — has a single home. A disabled
354
+ * action needs no guard here: it has no href (`rowActionHref`), and the
355
+ * invoke path ends in `triggerAction`, which refuses it.
234
356
  */
235
357
  function createNavigateGestures(state, promptCtx, hrefFor) {
236
358
  return {
237
- onTriggerClick(action, href, asLink, event) {
238
- if (asLink) {
359
+ onTriggerClick(action, href, event) {
360
+ if (isLinkTrigger(action, href)) {
239
361
  if (!isPlainLeftClick(event)) return;
240
362
  event.preventDefault();
241
363
  } else if (href !== void 0 && isModClick(event)) {
@@ -244,8 +366,8 @@ function createNavigateGestures(state, promptCtx, hrefFor) {
244
366
  }
245
367
  triggerAction(state, action, promptCtx(), event);
246
368
  },
247
- onTriggerAuxClick(action, href, asLink, event) {
248
- if (asLink || href === void 0 || event.button !== 1) return;
369
+ onTriggerAuxClick(action, href, event) {
370
+ if (href === void 0 || isLinkTrigger(action, href) || event.button !== 1) return;
249
371
  event.preventDefault();
250
372
  openNavigateInNewTab(state, action, promptCtx(), href);
251
373
  },
@@ -280,9 +402,13 @@ function substitute(template, ctx) {
280
402
  /**
281
403
  * Dispatch user-initiated invocation: actions with `inputForm` open the form
282
404
  * dialog (the form IS the confirm surface, so `promptText` is ignored);
283
- * others run `confirmAction()`. Cancelling either dialog short-circuits.
405
+ * others run `confirmAction()`. Cancelling either dialog short-circuits. A
406
+ * disabled action (`disabledReason`) never runs: this is the choke point
407
+ * every built-in trigger — and a custom slot's `invoke` — funnels through
408
+ * (the other is `rowActionHref`, which gives it no link to follow).
284
409
  */
285
410
  async function triggerAction(state, action, ctx, event) {
411
+ if (isActionDisabled(action)) return;
286
412
  const pk = pkForLevel(action.level, ctx.identifiers);
287
413
  if (action.inputForm) {
288
414
  const input = await state.requestActionInput(action, ctx);
@@ -344,9 +470,12 @@ function toLocalActionInfo(local) {
344
470
  * action builds its own from the row — still mapped through
345
471
  * `state.resolveHref`, so router base paths apply exactly as they do for a
346
472
  * server `navigate` action — and a server action goes through
347
- * {@link actionHref}.
473
+ * {@link actionHref}. A disabled action has none — one of the two disabled
474
+ * choke points (the other is `triggerAction`): no href means no anchor, no
475
+ * new-tab gesture, nothing to follow.
348
476
  */
349
477
  function rowActionHref(state, action, id, row) {
478
+ if (isActionDisabled(action)) return void 0;
350
479
  const local = localActionOf(action);
351
480
  if (local) return local.href && row !== void 0 ? state.resolveHref(local.href(row)) : void 0;
352
481
  return actionHref(state, action, id);
@@ -354,7 +483,7 @@ function rowActionHref(state, action, id, row) {
354
483
  const EMPTY = Object.freeze([]);
355
484
  /** The identity policy — used when no `RowActionsConfig` is wired. */
356
485
  const PASS_THROUGH = {
357
- apply: (buckets) => buckets,
486
+ apply: (buckets, rowGate) => applyGate(buckets, rowGate ?? null),
358
487
  extra: () => EMPTY
359
488
  };
360
489
  /** Compile a per-screen {@link RowActionsConfig} into a {@link RowActionsPolicy}. */
@@ -363,7 +492,7 @@ function compileRowActionsConfig(config) {
363
492
  const include = config.include ? new Set(config.include) : null;
364
493
  const exclude = config.exclude ? new Set(config.exclude) : null;
365
494
  const overrides = config.overrides;
366
- const gate = (a) => (!include || include.has(a.name)) && !exclude?.has(a.name);
495
+ const allowed = (a) => (!include || include.has(a.name)) && !exclude?.has(a.name);
367
496
  const decorate = overrides ? (action) => {
368
497
  const patch = overrides[action.name];
369
498
  if (!patch) return action;
@@ -379,15 +508,46 @@ function compileRowActionsConfig(config) {
379
508
  }
380
509
  return next;
381
510
  } : void 0;
382
- const locals = (config.extra ?? []).filter((local) => !exclude?.has(local.name));
511
+ const locals = (config.extra ?? []).filter((local) => !exclude?.has(local.name)).map(toLocalActionInfo);
383
512
  return {
384
- apply: (buckets) => applyGate(buckets, gate, decorate),
513
+ apply: (buckets, rowGate) => applyGate(buckets, rowGate ? (a) => {
514
+ const verdict = rowGate(a);
515
+ return verdict !== false && allowed(a) ? verdict : false;
516
+ } : allowed, decorate),
385
517
  extra: (row) => {
386
518
  if (locals.length === 0) return EMPTY;
387
- return (row === void 0 ? locals : locals.filter((l) => l.enabled?.(row) !== false)).map(toLocalActionInfo);
519
+ if (row === void 0) return locals;
520
+ return applyGate({
521
+ default: void 0,
522
+ others: locals,
523
+ rows: EMPTY
524
+ }, (a) => a.$local.enabled?.(row) ?? true).others;
388
525
  }
389
526
  };
390
527
  }
528
+ /**
529
+ * The row actions a surface renders for `row` — the one shared per-row
530
+ * pipeline behind `<AsRowActions>`, the `<AsTableActions>` row level and the
531
+ * Enter / double-click main-action fallback: the row's server gate
532
+ * (`$actions` / `$disabledReasons`) and the root's `:row-actions` policy in a
533
+ * single pass, plus the policy's app-owned `extra` actions. Pass
534
+ * `withRows: false` to leave out the rows-level bucket.
535
+ */
536
+ function resolveRowActions(state, row, opts) {
537
+ const policy = state.rowActionsPolicy.value;
538
+ const { actions } = state;
539
+ const gated = policy.apply({
540
+ default: actions.default.row,
541
+ others: actions.others.row,
542
+ rows: opts?.withRows === false ? EMPTY : actions.rows
543
+ }, rowActionGate(row));
544
+ return {
545
+ default: gated.default,
546
+ others: gated.others,
547
+ rows: gated.rows,
548
+ extra: policy.extra(row)
549
+ };
550
+ }
391
551
  //#endregion
392
552
  //#region src/composables/state/create-actions.ts
393
553
  const REMOVE_NAME = REMOVE_PROCESSOR;
@@ -581,10 +741,28 @@ function verdictOf(value) {
581
741
  reason: value
582
742
  } : NOT_SELECTABLE;
583
743
  }
584
- function createSelectionApi(opts, getActiveRow, activeIndex) {
744
+ function createSelectionApi(opts, getActiveRow, activeIndex, windowCache) {
585
745
  const selectedRows = opts?.selectedRows ?? (0, vue.shallowRef)([]);
586
746
  const selectedCount = (0, vue.computed)(() => selectedRows.value.length);
587
747
  const rowValueFn = opts?.rowValueFn ?? ((row) => row);
748
+ const rowByValue = (0, vue.computed)(() => {
749
+ const map = /* @__PURE__ */ new Map();
750
+ for (const row of windowCache.value.values()) map.set(rowValueFn(row), row);
751
+ return map;
752
+ });
753
+ function rowOf(value) {
754
+ if (value === void 0 || value === null) return void 0;
755
+ if (typeof value === "object") return value;
756
+ return rowByValue.value.get(value);
757
+ }
758
+ const selectedRowObjects = (0, vue.computed)(() => {
759
+ const out = [];
760
+ for (const value of selectedRows.value) {
761
+ const row = rowOf(value);
762
+ if (row) out.push(row);
763
+ }
764
+ return out;
765
+ });
588
766
  const selectedSet = (0, vue.computed)(() => new Set(selectedRows.value));
589
767
  function isPkSelected(pk) {
590
768
  return selectedSet.value.has(pk);
@@ -650,6 +828,9 @@ function createSelectionApi(opts, getActiveRow, activeIndex) {
650
828
  selectedCount,
651
829
  selectedSet,
652
830
  rowValueFn,
831
+ rowByValue,
832
+ rowOf,
833
+ selectedRowObjects,
653
834
  isPkSelected,
654
835
  rowSelectable,
655
836
  isRowSelectable,
@@ -667,8 +848,8 @@ function createSelectionApi(opts, getActiveRow, activeIndex) {
667
848
  /**
668
849
  * Listener registry for the `main-action` event. When listeners are present,
669
850
  * `requestMainAction` builds a `MainActionRequest` and dispatches it. When no
670
- * listener is registered, falls back to invoking `actions.default.row` against
671
- * the active row's PK (if both are defined). The fallback path SHALL NOT
851
+ * listener is registered and the table declares a default row action, it
852
+ * hands the active row to `invokeFallback`. The fallback path SHALL NOT
672
853
  * construct a `MainActionRequest` payload — there is nothing to receive it.
673
854
  */
674
855
  function createMainActionRegistry(opts) {
@@ -700,8 +881,7 @@ function createMainActionRegistry(opts) {
700
881
  for (const cb of listeners) cb(req);
701
882
  return;
702
883
  }
703
- const fallback = opts.getDefaultRowAction?.();
704
- if (fallback && opts.invokeFallback) opts.invokeFallback(fallback, row, event);
884
+ if (opts.invokeFallback && opts.getDefaultRowAction?.()) opts.invokeFallback(row, event);
705
885
  }
706
886
  return {
707
887
  hasMainActionListener,
@@ -2007,7 +2187,7 @@ function createTableState(opts) {
2007
2187
  if (idx < 0) return void 0;
2008
2188
  return navMode.value === "window" ? dataAt(idx) : results.value[idx];
2009
2189
  }
2010
- const { selectedRows, selectedCount, rowValueFn, isPkSelected, rowSelectable, isRowSelectable, selectableRows, selectableCount, selectAll, deselectAll, toggleAll, clearSelection, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow, activeIndex);
2190
+ const { selectedRows, selectedCount, rowValueFn, rowByValue, rowOf, selectedRowObjects, isPkSelected, rowSelectable, isRowSelectable, selectableRows, selectableCount, selectAll, deselectAll, toggleAll, clearSelection, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow, activeIndex, windowCache);
2011
2191
  const { actions } = createActions({
2012
2192
  tableDef,
2013
2193
  client,
@@ -2021,13 +2201,16 @@ function createTableState(opts) {
2021
2201
  getActiveIndex: () => activeIndex.value,
2022
2202
  getActiveRow,
2023
2203
  getDefaultRowAction: () => actions.default.row,
2024
- invokeFallback: (action, row, event) => {
2204
+ invokeFallback: (row, event) => {
2025
2205
  if (!stateRef) return;
2206
+ const action = resolveRowActions(stateRef, row, { withRows: false }).default;
2207
+ if (!action) return;
2026
2208
  const preferredId = tableDef.value?.preferredId ?? [];
2027
2209
  const identifiers = collectIdentifiers(stateRef, [row], preferredId);
2028
2210
  triggerAction(stateRef, action, {
2029
2211
  identifiers,
2030
- preferredId
2212
+ preferredId,
2213
+ row
2031
2214
  }, event);
2032
2215
  }
2033
2216
  });
@@ -2175,6 +2358,9 @@ function createTableState(opts) {
2175
2358
  selectedRows,
2176
2359
  selectedCount,
2177
2360
  rowValueFn,
2361
+ rowByValue,
2362
+ rowOf,
2363
+ selectedRowObjects,
2178
2364
  resolveHref: opts.actions?.resolveHref ?? ((url) => url),
2179
2365
  isPkSelected,
2180
2366
  rowSelectable,
@@ -2709,16 +2895,16 @@ Object.defineProperty(exports, "ROW_ACTIONS_TYPE", {
2709
2895
  return ROW_ACTIONS_TYPE;
2710
2896
  }
2711
2897
  });
2712
- Object.defineProperty(exports, "applyRowGate", {
2898
+ Object.defineProperty(exports, "applyRowsGate", {
2713
2899
  enumerable: true,
2714
2900
  get: function() {
2715
- return applyRowGate;
2901
+ return applyRowsGate;
2716
2902
  }
2717
2903
  });
2718
- Object.defineProperty(exports, "applyRowsGate", {
2904
+ Object.defineProperty(exports, "ariaDisabled", {
2719
2905
  enumerable: true,
2720
2906
  get: function() {
2721
- return applyRowsGate;
2907
+ return ariaDisabled;
2722
2908
  }
2723
2909
  });
2724
2910
  Object.defineProperty(exports, "ariaLabelFor", {
@@ -2769,6 +2955,18 @@ Object.defineProperty(exports, "intentToScope", {
2769
2955
  return intentToScope;
2770
2956
  }
2771
2957
  });
2958
+ Object.defineProperty(exports, "isActionDisabled", {
2959
+ enumerable: true,
2960
+ get: function() {
2961
+ return isActionDisabled;
2962
+ }
2963
+ });
2964
+ Object.defineProperty(exports, "isLinkTrigger", {
2965
+ enumerable: true,
2966
+ get: function() {
2967
+ return isLinkTrigger;
2968
+ }
2969
+ });
2772
2970
  Object.defineProperty(exports, "isModClick", {
2773
2971
  enumerable: true,
2774
2972
  get: function() {
@@ -2805,6 +3003,12 @@ Object.defineProperty(exports, "readPresetLabel", {
2805
3003
  return readPresetLabel;
2806
3004
  }
2807
3005
  });
3006
+ Object.defineProperty(exports, "resolveRowActions", {
3007
+ enumerable: true,
3008
+ get: function() {
3009
+ return resolveRowActions;
3010
+ }
3011
+ });
2808
3012
  Object.defineProperty(exports, "rowActionHref", {
2809
3013
  enumerable: true,
2810
3014
  get: function() {
@@ -2829,6 +3033,12 @@ Object.defineProperty(exports, "triggerAction", {
2829
3033
  return triggerAction;
2830
3034
  }
2831
3035
  });
3036
+ Object.defineProperty(exports, "triggerBindings", {
3037
+ enumerable: true,
3038
+ get: function() {
3039
+ return triggerBindings;
3040
+ }
3041
+ });
2832
3042
  Object.defineProperty(exports, "useRegisterMainActionListener", {
2833
3043
  enumerable: true,
2834
3044
  get: function() {