@atscript/vue-table 0.1.142 → 0.1.144
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.
- package/dist/as-action-form-dialog.cjs +1 -1
- package/dist/as-action-form-dialog.mjs +1 -1
- package/dist/{as-action-menu-content-miQYVP7x.mjs → as-action-menu-content-DoZnQP0I.mjs} +37 -25
- package/dist/{as-action-menu-content-BaNFBYn3.cjs → as-action-menu-content-FQ2miGid.cjs} +36 -24
- package/dist/as-column-menu.d.cts +1 -1
- package/dist/as-column-menu.d.mts +1 -1
- package/dist/{as-column-menu.vue-Ca3wSDxw.d.mts → as-column-menu.vue-ByTECTbX.d.cts} +2 -2
- package/dist/{as-column-menu.vue-Co7FtiEM.d.cts → as-column-menu.vue-TChT482i.d.mts} +2 -2
- package/dist/{as-config-dialog-CVSOrS9O.cjs → as-config-dialog-CpW1Qemf.cjs} +2 -2
- package/dist/{as-config-dialog-Di02V3bs.mjs → as-config-dialog-ObHtungs.mjs} +2 -2
- package/dist/as-config-dialog.cjs +1 -1
- package/dist/as-config-dialog.mjs +1 -1
- package/dist/{as-confirm-dialog-fCwo1ty9.cjs → as-confirm-dialog-D3Vu1Rb_.cjs} +1 -1
- package/dist/{as-confirm-dialog-C9Qat2rG.mjs → as-confirm-dialog-zg7jmS3u.mjs} +1 -1
- package/dist/as-confirm-dialog.cjs +1 -1
- package/dist/as-confirm-dialog.mjs +1 -1
- package/dist/{as-filter-dialog-DYpbsNDx.cjs → as-filter-dialog-C4EyRr2_.cjs} +7 -7
- package/dist/{as-filter-dialog-DdtrTT5w.mjs → as-filter-dialog-CWy0mjZl.mjs} +7 -7
- package/dist/as-filter-dialog.cjs +1 -1
- package/dist/as-filter-dialog.mjs +1 -1
- package/dist/{as-filter-field-4-qFRsJx.cjs → as-filter-field-BQ9UlV0y.cjs} +3 -3
- package/dist/{as-filter-field-DEQVodKG.mjs → as-filter-field-CW1_GHZT.mjs} +3 -3
- package/dist/as-filter-field.cjs +1 -1
- package/dist/as-filter-field.mjs +1 -1
- package/dist/{as-filters-Dd_j6_6G.mjs → as-filters-BLFSS_VP.mjs} +4 -4
- package/dist/{as-filters-C6ZkLrf4.cjs → as-filters-DUfFkXAo.cjs} +4 -4
- package/dist/as-filters.cjs +1 -1
- package/dist/as-filters.mjs +1 -1
- package/dist/{as-preset-dialog-SYA-6lGR.cjs → as-preset-dialog-BlXqTZiT.cjs} +1 -1
- package/dist/{as-preset-dialog-Bap48oUv.mjs → as-preset-dialog-CI2Hw8vi.mjs} +1 -1
- package/dist/as-preset-dialog.cjs +1 -1
- package/dist/as-preset-dialog.mjs +1 -1
- package/dist/{as-preset-picker-BdKTZa95.cjs → as-preset-picker-CVgyZBG7.cjs} +1 -1
- package/dist/{as-preset-picker-NBrvsE6s.mjs → as-preset-picker-CbZDGPaf.mjs} +1 -1
- package/dist/as-preset-picker.cjs +1 -1
- package/dist/as-preset-picker.mjs +1 -1
- package/dist/{as-residual-filter-CZ-Bytdj.cjs → as-residual-filter-C8n7kdGW.cjs} +1 -1
- package/dist/{as-residual-filter-BFnbI3h3.mjs → as-residual-filter-CUTouIn5.mjs} +1 -1
- package/dist/as-residual-filter.cjs +1 -1
- package/dist/as-residual-filter.mjs +1 -1
- package/dist/{as-row-actions-C3xHkX53.cjs → as-row-actions-DfUL9sYB.cjs} +21 -38
- package/dist/{as-row-actions-KeoHo9Ex.mjs → as-row-actions-ed1P6GEJ.mjs} +22 -39
- package/dist/as-row-actions.cjs +1 -1
- package/dist/as-row-actions.d.cts +1 -1
- package/dist/as-row-actions.d.mts +1 -1
- package/dist/as-row-actions.mjs +1 -1
- package/dist/{as-row-actions.vue-nmaDuxBT.d.mts → as-row-actions.vue-Ba3VO9W-.d.cts} +1 -1
- package/dist/{as-row-actions.vue-D3_z3ytk.d.cts → as-row-actions.vue-CKOnE0eb.d.mts} +1 -1
- package/dist/{as-select-cell-KD_ns8DS.mjs → as-select-cell-BMptU5sQ.mjs} +4 -4
- package/dist/{as-select-cell-XnzNuW2v.cjs → as-select-cell-DwMfqBMV.cjs} +4 -4
- package/dist/{as-table-ay1yel3u.cjs → as-table-BjKzKB0a.cjs} +3 -3
- package/dist/{as-table-NHqyekNd.mjs → as-table-DGWfzV4w.mjs} +3 -3
- package/dist/{as-table-actions-BpEFcsgh.mjs → as-table-actions-DQco4pGu.mjs} +16 -29
- package/dist/{as-table-actions-UQmFtRdJ.cjs → as-table-actions-DplblFlK.cjs} +15 -28
- package/dist/as-table-actions.cjs +1 -1
- package/dist/as-table-actions.d.cts +1 -1
- package/dist/as-table-actions.d.mts +1 -1
- package/dist/as-table-actions.mjs +1 -1
- package/dist/{as-table-actions.vue-BG3JVaBQ.d.mts → as-table-actions.vue-CxHr8Oox.d.mts} +2 -2
- package/dist/{as-table-actions.vue-DcuzNZoC.d.cts → as-table-actions.vue-KSNe1uxh.d.cts} +2 -2
- package/dist/{as-table-base-umzg4PBR.cjs → as-table-base-CNAn-vpa.cjs} +2 -2
- package/dist/{as-table-base-89DkDZ-1.mjs → as-table-base-lfyjPgsO.mjs} +2 -2
- package/dist/{as-table-header-cell-f3YpgJX1.cjs → as-table-header-cell-BwaOPYwI.cjs} +1 -1
- package/dist/{as-table-header-cell-DU3ws2x9.mjs → as-table-header-cell-Csv1KQDa.mjs} +1 -1
- package/dist/as-table-header-cell.cjs +1 -1
- package/dist/as-table-header-cell.d.cts +1 -1
- package/dist/as-table-header-cell.d.mts +1 -1
- package/dist/as-table-header-cell.mjs +1 -1
- package/dist/{as-table-header-cell.vue-5VGRc23o.d.mts → as-table-header-cell.vue-DU5h-2C_.d.cts} +2 -2
- package/dist/{as-table-header-cell.vue-XR90UlHO.d.cts → as-table-header-cell.vue-KkANwimG.d.mts} +2 -2
- package/dist/{as-table-root-47hiBEnq.mjs → as-table-root-BreMYhnv.mjs} +4 -4
- package/dist/{as-table-root-DDeGktcl.cjs → as-table-root-DXewWV9h.cjs} +4 -4
- package/dist/as-table-root.cjs +1 -1
- package/dist/as-table-root.d.cts +1 -1
- package/dist/as-table-root.d.mts +1 -1
- package/dist/as-table-root.mjs +1 -1
- package/dist/{as-table-root.vue-yZ7ZcwdT.d.cts → as-table-root.vue-CXaFNCMi.d.cts} +3 -3
- package/dist/{as-table-root.vue-BGx453_Q.d.mts → as-table-root.vue-DTos5nn3.d.mts} +3 -3
- package/dist/as-table.cjs +1 -1
- package/dist/as-table.d.cts +1 -1
- package/dist/as-table.d.mts +1 -1
- package/dist/as-table.mjs +1 -1
- package/dist/{as-table.vue-wdFf4hMW.d.mts → as-table.vue-BBb5Tmts.d.cts} +2 -2
- package/dist/{as-table.vue-CwI5Z0y5.d.cts → as-table.vue-DVVC5219.d.mts} +2 -2
- package/dist/{as-window-table-hOYLNhX3.cjs → as-window-table-Bj1dXOS6.cjs} +3 -3
- package/dist/{as-window-table-C312T_bc.mjs → as-window-table-JOTOi_Nm.mjs} +3 -3
- package/dist/as-window-table.cjs +1 -1
- package/dist/as-window-table.d.cts +1 -1
- package/dist/as-window-table.d.mts +1 -1
- package/dist/as-window-table.mjs +1 -1
- package/dist/{as-window-table.vue-D6UDA6td.d.cts → as-window-table.vue-fnBNP4dW.d.mts} +2 -2
- package/dist/{as-window-table.vue-Deg1mj3t.d.mts → as-window-table.vue-hk-Ea173.d.cts} +2 -2
- package/dist/index.cjs +19 -18
- package/dist/index.d.cts +9 -9
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +19 -19
- package/dist/{types-BU7J-a1r.d.mts → types-BEZ4_erB.d.mts} +86 -11
- package/dist/{types-D4dK49sA.d.cts → types-D1NxhrpW.d.cts} +86 -11
- package/dist/{use-table-DlqGHjPh.mjs → use-table-BTsNGBVx.mjs} +1 -1
- package/dist/{use-table-yZNJTfRq.cjs → use-table-W0KmaJ6L.cjs} +1 -1
- package/dist/{use-table-column-handlers-0myVzrZR.mjs → use-table-column-handlers-BitvioH4.mjs} +1 -1
- package/dist/{use-table-column-handlers-BHMCo_HR.cjs → use-table-column-handlers-DksXpbON.cjs} +1 -1
- package/dist/{use-table-component-IziAm0vk.cjs → use-table-component-BvAC82_c.cjs} +1 -1
- package/dist/{use-table-component-TLwFq4rx.mjs → use-table-component-DRMXTk9M.mjs} +1 -1
- package/dist/{use-table-nav-bridge-CkQqimdS.mjs → use-table-nav-bridge-Bw0NFDSf.mjs} +1 -1
- package/dist/{use-table-nav-bridge-QU4jKhqx.cjs → use-table-nav-bridge-CPI1tql6.cjs} +1 -1
- package/dist/{use-table-state-BiklfjP0.cjs → use-table-state-Ddr2UZzB.cjs} +298 -88
- package/dist/{use-table-state-Cq-3tbVr.mjs → use-table-state-DuY-Mf8e.mjs} +271 -85
- 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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
|
32
|
-
|
|
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
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
77
|
-
* server `$actions`: an action is
|
|
78
|
-
* it, even if absent from the rest.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
152
|
-
* `state.
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
238
|
-
if (
|
|
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,
|
|
248
|
-
if (
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
671
|
-
* the active row
|
|
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
|
-
|
|
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: (
|
|
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, "
|
|
2898
|
+
Object.defineProperty(exports, "applyRowsGate", {
|
|
2713
2899
|
enumerable: true,
|
|
2714
2900
|
get: function() {
|
|
2715
|
-
return
|
|
2901
|
+
return applyRowsGate;
|
|
2716
2902
|
}
|
|
2717
2903
|
});
|
|
2718
|
-
Object.defineProperty(exports, "
|
|
2904
|
+
Object.defineProperty(exports, "ariaDisabled", {
|
|
2719
2905
|
enumerable: true,
|
|
2720
2906
|
get: function() {
|
|
2721
|
-
return
|
|
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() {
|