@atscript/vue-table 0.1.58
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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/as-action-form-dialog.cjs +221 -0
- package/dist/as-action-form-dialog.d.cts +6 -0
- package/dist/as-action-form-dialog.d.mts +7 -0
- package/dist/as-action-form-dialog.mjs +221 -0
- package/dist/as-action-menu-content-CXsdPn42.mjs +104 -0
- package/dist/as-action-menu-content-CyUfBrWH.cjs +109 -0
- package/dist/as-cell-array-CXeZzZqt.cjs +69 -0
- package/dist/as-cell-array-DOQKR6t5.mjs +64 -0
- package/dist/as-cell-array.cjs +3 -0
- package/dist/as-cell-array.d.cts +2 -0
- package/dist/as-cell-array.d.mts +2 -0
- package/dist/as-cell-array.mjs +3 -0
- package/dist/as-cell-array.vue-DZGM2VHh.d.mts +12 -0
- package/dist/as-cell-array.vue-pFs5GcCR.d.cts +12 -0
- package/dist/as-cell-date-CTrvxS1h.cjs +78 -0
- package/dist/as-cell-date-Cq49RHmL.mjs +73 -0
- package/dist/as-cell-date.cjs +3 -0
- package/dist/as-cell-date.d.cts +2 -0
- package/dist/as-cell-date.d.mts +2 -0
- package/dist/as-cell-date.mjs +3 -0
- package/dist/as-cell-date.vue-BBSps2B2.d.cts +12 -0
- package/dist/as-cell-date.vue-Zlt4mHWb.d.mts +12 -0
- package/dist/as-cell-json-BynWIs1d.mjs +37 -0
- package/dist/as-cell-json-DvHvQ6IL.cjs +42 -0
- package/dist/as-cell-json-popover-BWdNs1YU.cjs +70 -0
- package/dist/as-cell-json-popover-DUq25I0L.mjs +65 -0
- package/dist/as-cell-json.cjs +3 -0
- package/dist/as-cell-json.d.cts +2 -0
- package/dist/as-cell-json.d.mts +2 -0
- package/dist/as-cell-json.mjs +3 -0
- package/dist/as-cell-json.vue-C6wg4ARZ.d.cts +12 -0
- package/dist/as-cell-json.vue-CESWuCer.d.mts +12 -0
- package/dist/as-cell-number-0_WrSCzu.cjs +76 -0
- package/dist/as-cell-number-Bc1C97Vg.mjs +71 -0
- package/dist/as-cell-number.cjs +3 -0
- package/dist/as-cell-number.d.cts +2 -0
- package/dist/as-cell-number.d.mts +2 -0
- package/dist/as-cell-number.mjs +3 -0
- package/dist/as-cell-number.vue-1Oq7nVI3.d.mts +12 -0
- package/dist/as-cell-number.vue-CJ2K5zeM.d.cts +12 -0
- package/dist/as-cell-union-C1w3B38J.mjs +88 -0
- package/dist/as-cell-union-CFAI0utz.cjs +93 -0
- package/dist/as-cell-union.cjs +4 -0
- package/dist/as-cell-union.d.cts +2 -0
- package/dist/as-cell-union.d.mts +2 -0
- package/dist/as-cell-union.mjs +4 -0
- package/dist/as-cell-union.vue-CslPM_c2.d.cts +12 -0
- package/dist/as-cell-union.vue-NnDmQZOA.d.mts +12 -0
- package/dist/as-column-menu-CH9Htz0Q.cjs +220 -0
- package/dist/as-column-menu-DCfhorMP.mjs +215 -0
- package/dist/as-column-menu.cjs +2 -0
- package/dist/as-column-menu.d.cts +2 -0
- package/dist/as-column-menu.d.mts +2 -0
- package/dist/as-column-menu.mjs +2 -0
- package/dist/as-column-menu.vue-C9e6wJ3z.d.mts +47 -0
- package/dist/as-column-menu.vue-o0qFdzoL.d.cts +47 -0
- package/dist/as-config-dialog-d2k7_l0U.cjs +699 -0
- package/dist/as-config-dialog-vRklaKbi.mjs +688 -0
- package/dist/as-config-dialog.cjs +3 -0
- package/dist/as-config-dialog.d.cts +2 -0
- package/dist/as-config-dialog.d.mts +2 -0
- package/dist/as-config-dialog.mjs +3 -0
- package/dist/as-config-dialog.vue-C6Q62xF5.d.mts +7 -0
- package/dist/as-config-dialog.vue-DvvJi3xx.d.cts +7 -0
- package/dist/as-confirm-dialog-BLh3Ju4-.mjs +52 -0
- package/dist/as-confirm-dialog-BgpIEE2z.cjs +57 -0
- package/dist/as-confirm-dialog.cjs +3 -0
- package/dist/as-confirm-dialog.d.cts +2 -0
- package/dist/as-confirm-dialog.d.mts +2 -0
- package/dist/as-confirm-dialog.mjs +3 -0
- package/dist/as-confirm-dialog.vue-CXxLpzbu.d.cts +7 -0
- package/dist/as-confirm-dialog.vue-pas8jGhv.d.mts +7 -0
- package/dist/as-filter-dialog-C0HMpUPT.mjs +610 -0
- package/dist/as-filter-dialog-DcGvIV3h.cjs +621 -0
- package/dist/as-filter-dialog.cjs +15 -0
- package/dist/as-filter-dialog.d.cts +2 -0
- package/dist/as-filter-dialog.d.mts +2 -0
- package/dist/as-filter-dialog.mjs +15 -0
- package/dist/as-filter-dialog.vue-BV2J8PgZ.d.cts +7 -0
- package/dist/as-filter-dialog.vue-RDZjp4gJ.d.mts +7 -0
- package/dist/as-filter-field-B_tYzvvl.cjs +984 -0
- package/dist/as-filter-field-Bqvu2ASN.mjs +943 -0
- package/dist/as-filter-field.cjs +9 -0
- package/dist/as-filter-field.d.cts +2 -0
- package/dist/as-filter-field.d.mts +2 -0
- package/dist/as-filter-field.mjs +9 -0
- package/dist/as-filter-field.vue-ByQ8xIGq.d.cts +11 -0
- package/dist/as-filter-field.vue-QY8wi5S5.d.mts +11 -0
- package/dist/as-filter-input--nr72iwX.cjs +106 -0
- package/dist/as-filter-input-P1i0CW2-.mjs +101 -0
- package/dist/as-filter-input.cjs +2 -0
- package/dist/as-filter-input.d.cts +2 -0
- package/dist/as-filter-input.d.mts +2 -0
- package/dist/as-filter-input.mjs +2 -0
- package/dist/as-filter-input.vue-CBQ71eNg.d.mts +18 -0
- package/dist/as-filter-input.vue-CS4nOk_Q.d.cts +18 -0
- package/dist/as-filters-Bxa9ZEMm.mjs +44 -0
- package/dist/as-filters-xRT2qv56.cjs +49 -0
- package/dist/as-filters.cjs +10 -0
- package/dist/as-filters.d.cts +2 -0
- package/dist/as-filters.d.mts +2 -0
- package/dist/as-filters.mjs +10 -0
- package/dist/as-filters.vue-BsMgYUcX.d.mts +10 -0
- package/dist/as-filters.vue-fv-tRL2H.d.cts +10 -0
- package/dist/as-preset-dialog-BaTfwMnh.cjs +569 -0
- package/dist/as-preset-dialog-BdDRgwf_.mjs +564 -0
- package/dist/as-preset-dialog.cjs +4 -0
- package/dist/as-preset-dialog.d.cts +2 -0
- package/dist/as-preset-dialog.d.mts +2 -0
- package/dist/as-preset-dialog.mjs +4 -0
- package/dist/as-preset-dialog.vue-Bzv-ON9W.d.mts +7 -0
- package/dist/as-preset-dialog.vue-DP9fy00Y.d.cts +7 -0
- package/dist/as-preset-picker-BQbNEiy9.mjs +427 -0
- package/dist/as-preset-picker-Ce3crTQy.cjs +432 -0
- package/dist/as-preset-picker.cjs +4 -0
- package/dist/as-preset-picker.d.cts +2 -0
- package/dist/as-preset-picker.d.mts +2 -0
- package/dist/as-preset-picker.mjs +4 -0
- package/dist/as-preset-picker.vue-CTBk6leV.d.mts +7 -0
- package/dist/as-preset-picker.vue-DfXS3pGl.d.cts +7 -0
- package/dist/as-row-actions-B6Kob6gt.cjs +120 -0
- package/dist/as-row-actions-CeWBBGqh.mjs +115 -0
- package/dist/as-row-actions.cjs +4 -0
- package/dist/as-row-actions.d.cts +2 -0
- package/dist/as-row-actions.d.mts +2 -0
- package/dist/as-row-actions.mjs +4 -0
- package/dist/as-row-actions.vue-BPaQfGev.d.mts +11 -0
- package/dist/as-row-actions.vue-Bvcc2tUN.d.cts +11 -0
- package/dist/as-table-Cnw2fOqZ.mjs +204 -0
- package/dist/as-table-DlDFxdXI.cjs +209 -0
- package/dist/as-table-actions-BK1Thy2G.cjs +142 -0
- package/dist/as-table-actions-BpMiNFni.mjs +137 -0
- package/dist/as-table-actions.cjs +4 -0
- package/dist/as-table-actions.d.cts +2 -0
- package/dist/as-table-actions.d.mts +2 -0
- package/dist/as-table-actions.mjs +4 -0
- package/dist/as-table-actions.vue-B7Q-JA3z.d.cts +47 -0
- package/dist/as-table-actions.vue-Bs1Jl1ep.d.mts +47 -0
- package/dist/as-table-base-D0k4k7k_.mjs +646 -0
- package/dist/as-table-base-VIz-B_6_.cjs +651 -0
- package/dist/as-table-cell-value-B1CiJYFn.mjs +26 -0
- package/dist/as-table-cell-value-CuxRtFn9.cjs +31 -0
- package/dist/as-table-cell-value.cjs +3 -0
- package/dist/as-table-cell-value.d.cts +2 -0
- package/dist/as-table-cell-value.d.mts +2 -0
- package/dist/as-table-cell-value.mjs +3 -0
- package/dist/as-table-cell-value.vue-BgFDv2JQ.d.cts +12 -0
- package/dist/as-table-cell-value.vue-BuPCQ8YA.d.mts +12 -0
- package/dist/as-table-header-cell-C3zeZUZo.cjs +117 -0
- package/dist/as-table-header-cell-CBn_ioCe.mjs +112 -0
- package/dist/as-table-header-cell.cjs +3 -0
- package/dist/as-table-header-cell.d.cts +2 -0
- package/dist/as-table-header-cell.d.mts +2 -0
- package/dist/as-table-header-cell.mjs +3 -0
- package/dist/as-table-header-cell.vue-Bc_DSsGY.d.cts +31 -0
- package/dist/as-table-header-cell.vue-DNMOHfek.d.mts +31 -0
- package/dist/as-table-root-Br6WcGRo.cjs +263 -0
- package/dist/as-table-root-gG7pTIdD.mjs +258 -0
- package/dist/as-table-root.cjs +28 -0
- package/dist/as-table-root.d.cts +2 -0
- package/dist/as-table-root.d.mts +2 -0
- package/dist/as-table-root.mjs +28 -0
- package/dist/as-table-root.vue-5_OhVwse.d.mts +2258 -0
- package/dist/as-table-root.vue-CSqEtIll.d.cts +2258 -0
- package/dist/as-table-status-BjRGGuhC.mjs +683 -0
- package/dist/as-table-status-DWYoJIMC.cjs +724 -0
- package/dist/as-table.cjs +10 -0
- package/dist/as-table.d.cts +2 -0
- package/dist/as-table.d.mts +2 -0
- package/dist/as-table.mjs +10 -0
- package/dist/as-table.vue-BTYg-e3Z.d.mts +81 -0
- package/dist/as-table.vue-wdRARLIe.d.cts +81 -0
- package/dist/as-window-table-CKIfo3M_.mjs +709 -0
- package/dist/as-window-table-DE7_NyEP.cjs +714 -0
- package/dist/as-window-table.cjs +9 -0
- package/dist/as-window-table.d.cts +2 -0
- package/dist/as-window-table.d.mts +2 -0
- package/dist/as-window-table.mjs +9 -0
- package/dist/as-window-table.vue-Bf8xGC9M.d.mts +86 -0
- package/dist/as-window-table.vue-CA8qsrz4.d.cts +86 -0
- package/dist/format-cell-B2xMDYO9.mjs +27 -0
- package/dist/format-cell-D4mqaN0E.cjs +32 -0
- package/dist/get-cell-value-CZSVfDLg.cjs +19 -0
- package/dist/get-cell-value-DiH84HKL.mjs +14 -0
- package/dist/index.cjs +598 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +505 -0
- package/dist/preset-aspect-display-BYeiSgcc.mjs +43 -0
- package/dist/preset-aspect-display-y8aal_EF.cjs +72 -0
- package/dist/types-BvvXN72P.d.mts +531 -0
- package/dist/types-CNMmF6W2.d.cts +531 -0
- package/dist/use-cell-locale-1uQaFTLQ.mjs +23 -0
- package/dist/use-cell-locale-B480_QYK.cjs +34 -0
- package/dist/use-table-column-handlers-CGYAY2xH.cjs +65 -0
- package/dist/use-table-column-handlers-t6xi1yCE.mjs +54 -0
- package/dist/use-table-state-C4JbonEZ.mjs +1822 -0
- package/dist/use-table-state-MU-vuzui.cjs +1917 -0
- package/package.json +195 -0
- package/styles.d.ts +2 -0
|
@@ -0,0 +1,1822 @@
|
|
|
1
|
+
import { getVisibleColumns } from "@atscript/ui";
|
|
2
|
+
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 { formatIdentifier } from "@atscript/db-client";
|
|
5
|
+
//#region src/types.ts
|
|
6
|
+
/** UI-side sentinel for the synthesised row-delete processor. */
|
|
7
|
+
const REMOVE_PROCESSOR = "__remove";
|
|
8
|
+
/** Column `path` and cell-type for the synthesised row-actions pseudo-column. */
|
|
9
|
+
const ROW_ACTIONS_PATH = "__actions";
|
|
10
|
+
const ROW_ACTIONS_TYPE = "__actions";
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/composables/state/intent-scope.ts
|
|
13
|
+
/**
|
|
14
|
+
* Processors exempt from the per-row `$actions` gate: `navigate`/`custom`
|
|
15
|
+
* have no backend to ask, and `__remove` is synthesised client-side.
|
|
16
|
+
*/
|
|
17
|
+
const EXEMPT_PROCESSORS = new Set([
|
|
18
|
+
"navigate",
|
|
19
|
+
"custom",
|
|
20
|
+
REMOVE_PROCESSOR
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Build a per-row availability predicate from `row.$actions: string[]`
|
|
24
|
+
* (server-evaluated names of NOT-disabled row/rows-level actions for that
|
|
25
|
+
* row). Returns `null` when the row carries no `$actions` (legacy server,
|
|
26
|
+
* `?$actions` opt-out, or table-level surfaces) so callers can skip the
|
|
27
|
+
* filter pass and keep the source-array reference stable.
|
|
28
|
+
*/
|
|
29
|
+
function rowActionGate(row) {
|
|
30
|
+
const raw = row?.$actions;
|
|
31
|
+
if (!Array.isArray(raw)) return null;
|
|
32
|
+
const allowed = new Set(raw);
|
|
33
|
+
return (a) => EXEMPT_PROCESSORS.has(a.processor) || allowed.has(a.name);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply the per-row `$actions` gate to a `{default, others, rows}` triple.
|
|
37
|
+
* When the row carries no `$actions`, returns the input unchanged so
|
|
38
|
+
* source-array references stay stable downstream (no spurious recomputes
|
|
39
|
+
* in consumers that compare array identity).
|
|
40
|
+
*/
|
|
41
|
+
function applyRowGate(buckets, row) {
|
|
42
|
+
const gate = rowActionGate(row);
|
|
43
|
+
if (!gate) return buckets;
|
|
44
|
+
return {
|
|
45
|
+
default: buckets.default && gate(buckets.default) ? buckets.default : void 0,
|
|
46
|
+
others: buckets.others.filter(gate),
|
|
47
|
+
rows: buckets.rows.filter(gate)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Map `TDbActionInfo['intent']` (positive/negative/warning/primary/secondary)
|
|
52
|
+
* onto a vunor scope name accepted by `state.prompt()`. Used by row-actions
|
|
53
|
+
* and table-actions cells when surfacing an action's confirmation dialog —
|
|
54
|
+
* `negative → error`, `positive → good`, `warning → warn`, the rest pass
|
|
55
|
+
* through verbatim. Undefined intent → undefined scope (button stays default
|
|
56
|
+
* primary via the dialog's `c8-filled` chrome).
|
|
57
|
+
*/
|
|
58
|
+
function intentToScope(intent) {
|
|
59
|
+
switch (intent) {
|
|
60
|
+
case "positive": return "good";
|
|
61
|
+
case "negative": return "error";
|
|
62
|
+
case "warning": return "warn";
|
|
63
|
+
case "primary": return "primary";
|
|
64
|
+
case "secondary": return "secondary";
|
|
65
|
+
default: return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the identifier object to forward to `client.action` / `client.remove`.
|
|
70
|
+
*
|
|
71
|
+
* Per `@atscript/db-client` invariant #11, identifier bodies are object-only
|
|
72
|
+
* — never bare scalars, even for single-field PK tables. This helper accepts:
|
|
73
|
+
* - a row-shaped object (default `rowValueFn`) → picks `preferredId` fields;
|
|
74
|
+
* - a scalar value when `preferredId` has exactly one field (consumers that
|
|
75
|
+
* override `rowValueFn` to return the PK scalar) → wraps it.
|
|
76
|
+
*
|
|
77
|
+
* Returns `undefined` when no `preferredId` is declared, the source is
|
|
78
|
+
* `null`/`undefined`, or a scalar can't be paired with a single-field
|
|
79
|
+
* identifier.
|
|
80
|
+
*/
|
|
81
|
+
function extractIdentifier(source, preferredId) {
|
|
82
|
+
if (source === void 0 || source === null) return void 0;
|
|
83
|
+
if (preferredId.length === 0) return void 0;
|
|
84
|
+
if (typeof source === "object" && !Array.isArray(source)) {
|
|
85
|
+
const row = source;
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const k of preferredId) out[k] = row[k];
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
if (preferredId.length === 1) return { [preferredId[0]]: source };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Map a list of sources (full row objects or scalar `rowValueFn` values)
|
|
94
|
+
* through `extractIdentifier`. Scalar sources rehydrate from
|
|
95
|
+
* `state.windowCache` via `state.rowValueFn` so consumers that override
|
|
96
|
+
* `rowValueFn` to return a scalar can still reconstruct multi-field
|
|
97
|
+
* identifiers; the lookup `Map` is built lazily so all-object source
|
|
98
|
+
* lists pay nothing.
|
|
99
|
+
*/
|
|
100
|
+
function collectIdentifiers(state, sources, preferredId) {
|
|
101
|
+
if (preferredId.length === 0 || sources.length === 0) return [];
|
|
102
|
+
const out = [];
|
|
103
|
+
let lookup = null;
|
|
104
|
+
for (const s of sources) {
|
|
105
|
+
if (s === void 0 || s === null) continue;
|
|
106
|
+
let row;
|
|
107
|
+
if (typeof s === "object") row = s;
|
|
108
|
+
else {
|
|
109
|
+
if (lookup === null) {
|
|
110
|
+
const fn = state.rowValueFn;
|
|
111
|
+
lookup = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const r of state.windowCache.value.values()) lookup.set(fn(r), r);
|
|
113
|
+
}
|
|
114
|
+
row = lookup.get(s);
|
|
115
|
+
}
|
|
116
|
+
const id = extractIdentifier(row ?? s, preferredId);
|
|
117
|
+
if (id) out.push(id);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function ariaLabelFor(action) {
|
|
122
|
+
return action.label || action.name;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Compose the runtime intent class — `as-{prefix}-intent-{intent}` — applied
|
|
126
|
+
* by both `<AsRowActions>` and `<AsTableActions>` on buttons + menu items.
|
|
127
|
+
* Returns `undefined` when the action declares no intent so callers can
|
|
128
|
+
* spread the result into a class array without conditional plumbing.
|
|
129
|
+
*/
|
|
130
|
+
function intentClass(prefix, action) {
|
|
131
|
+
return action.intent ? `${prefix}-intent-${action.intent}` : void 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run `state.prompt()` if the action declares a `promptText`. Resolves
|
|
135
|
+
* `true` on accept, or `true` immediately when no prompt is needed.
|
|
136
|
+
*
|
|
137
|
+
* `promptText` may be a string or `[singular, plural]` tuple. Tuple form
|
|
138
|
+
* picks `singular` when there is at most one identifier, `plural` otherwise.
|
|
139
|
+
* Substitutions:
|
|
140
|
+
* - `$1` → `formatIdentifier(ctx.identifiers[0], ctx.preferredId)`
|
|
141
|
+
* - `$N` → `String(ctx.identifiers.length)`
|
|
142
|
+
*/
|
|
143
|
+
async function confirmAction(state, action, ctx) {
|
|
144
|
+
const raw = action.promptText;
|
|
145
|
+
if (!raw) return true;
|
|
146
|
+
const count = ctx.identifiers.length;
|
|
147
|
+
const message = substitute(Array.isArray(raw) ? count <= 1 ? raw[0] : raw[1] : raw, ctx);
|
|
148
|
+
return state.prompt(message, { scope: intentToScope(action.intent) });
|
|
149
|
+
}
|
|
150
|
+
/** Substitute `$1` and `$N` into a prompt-text template. */
|
|
151
|
+
function substitute(template, ctx) {
|
|
152
|
+
return template.replace(/\$1/g, () => formatIdentifier(ctx.identifiers[0], ctx.preferredId)).replace(/\$N/g, () => String(ctx.identifiers.length));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Dispatch user-initiated invocation: actions with `inputForm` open the form
|
|
156
|
+
* dialog (the form IS the confirm surface, so `promptText` is ignored);
|
|
157
|
+
* others run `confirmAction()`. Cancelling either dialog short-circuits.
|
|
158
|
+
*/
|
|
159
|
+
async function triggerAction(state, action, ctx, event) {
|
|
160
|
+
const pk = pkForLevel(action.level, ctx.identifiers);
|
|
161
|
+
if (action.inputForm) {
|
|
162
|
+
const input = await state.requestActionInput(action, ctx);
|
|
163
|
+
if (input === null) return;
|
|
164
|
+
state.actions.invoke(action, pk, {
|
|
165
|
+
event,
|
|
166
|
+
input
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!await confirmAction(state, action, ctx)) return;
|
|
171
|
+
state.actions.invoke(action, pk, { event });
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Pick the `pk` argument to forward to `state.actions.invoke` based on the
|
|
175
|
+
* action's level. `'table'` → `undefined`; `'row'` → first identifier;
|
|
176
|
+
* `'rows'` → full array.
|
|
177
|
+
*/
|
|
178
|
+
function pkForLevel(level, ids) {
|
|
179
|
+
if (level === "table") return void 0;
|
|
180
|
+
if (level === "row") return ids[0];
|
|
181
|
+
return ids;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Inverse of `pkForLevel`: shape the `ids[]` surfaced by the `@action` emit
|
|
185
|
+
* from the `pk` value passed to `invoke`.
|
|
186
|
+
*/
|
|
187
|
+
function idsForAction(level, pk) {
|
|
188
|
+
if (level === "table") return [];
|
|
189
|
+
if (level === "rows") return Array.isArray(pk) ? pk : pk === void 0 ? [] : [pk];
|
|
190
|
+
return pk === void 0 ? [] : [pk];
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/composables/state/create-actions.ts
|
|
194
|
+
const REMOVE_NAME = REMOVE_PROCESSOR;
|
|
195
|
+
const REMOVE_DEFAULTS = {
|
|
196
|
+
label: "Delete",
|
|
197
|
+
icon: "i-as-trash",
|
|
198
|
+
intent: "negative",
|
|
199
|
+
promptText: ["Delete item $1?", "Delete $N items?"]
|
|
200
|
+
};
|
|
201
|
+
function createActions(opts) {
|
|
202
|
+
const invoking = shallowRef(/* @__PURE__ */ new Set());
|
|
203
|
+
const lastResult = shallowRef(/* @__PURE__ */ new Map());
|
|
204
|
+
const groups = computed(() => buildGroups(opts.tableDef.value, opts.rowDelete()));
|
|
205
|
+
watch(opts.tableDef, () => {
|
|
206
|
+
if (lastResult.value.size > 0) lastResult.value = /* @__PURE__ */ new Map();
|
|
207
|
+
});
|
|
208
|
+
function setInvoking(name, on) {
|
|
209
|
+
const next = new Set(invoking.value);
|
|
210
|
+
if (on) next.add(name);
|
|
211
|
+
else next.delete(name);
|
|
212
|
+
invoking.value = next;
|
|
213
|
+
}
|
|
214
|
+
function setLastResult(name, result) {
|
|
215
|
+
const next = new Map(lastResult.value);
|
|
216
|
+
next.set(name, result);
|
|
217
|
+
lastResult.value = next;
|
|
218
|
+
}
|
|
219
|
+
async function invoke(action, pk, callOpts) {
|
|
220
|
+
const name = action.name;
|
|
221
|
+
setInvoking(name, true);
|
|
222
|
+
let result;
|
|
223
|
+
try {
|
|
224
|
+
switch (action.processor) {
|
|
225
|
+
case "custom":
|
|
226
|
+
result = {
|
|
227
|
+
ok: true,
|
|
228
|
+
kind: "custom",
|
|
229
|
+
dispatched: true
|
|
230
|
+
};
|
|
231
|
+
break;
|
|
232
|
+
case "navigate":
|
|
233
|
+
await opts.client.action(action.name, pk);
|
|
234
|
+
result = {
|
|
235
|
+
ok: true,
|
|
236
|
+
kind: "navigate"
|
|
237
|
+
};
|
|
238
|
+
break;
|
|
239
|
+
case REMOVE_PROCESSOR:
|
|
240
|
+
result = {
|
|
241
|
+
ok: true,
|
|
242
|
+
kind: "remove",
|
|
243
|
+
data: await opts.client.remove(pk)
|
|
244
|
+
};
|
|
245
|
+
break;
|
|
246
|
+
default: {
|
|
247
|
+
const data = await opts.client.action(action.name, pk, callOpts?.input);
|
|
248
|
+
result = {
|
|
249
|
+
ok: true,
|
|
250
|
+
kind: "backend",
|
|
251
|
+
data,
|
|
252
|
+
message: typeof data === "object" && data !== null && "message" in data ? data.message : void 0
|
|
253
|
+
};
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
result = {
|
|
259
|
+
ok: false,
|
|
260
|
+
kind: "error",
|
|
261
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
setLastResult(name, result);
|
|
265
|
+
setInvoking(name, false);
|
|
266
|
+
if (callOpts?.suppressRefresh !== true && opts.refreshOnAction() !== false && result.ok && (result.kind === "backend" || result.kind === "remove")) opts.scheduleQuery("query");
|
|
267
|
+
if (opts.onResolved) opts.onResolved(action, idsForAction(action.level, pk), result, callOpts?.event);
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
actions: {
|
|
272
|
+
get table() {
|
|
273
|
+
return groups.value.table;
|
|
274
|
+
},
|
|
275
|
+
get row() {
|
|
276
|
+
return groups.value.row;
|
|
277
|
+
},
|
|
278
|
+
get rows() {
|
|
279
|
+
return groups.value.rows;
|
|
280
|
+
},
|
|
281
|
+
get default() {
|
|
282
|
+
return groups.value.default;
|
|
283
|
+
},
|
|
284
|
+
get others() {
|
|
285
|
+
return groups.value.others;
|
|
286
|
+
},
|
|
287
|
+
get cellRow() {
|
|
288
|
+
return groups.value.cellRow;
|
|
289
|
+
},
|
|
290
|
+
invoke,
|
|
291
|
+
invoking,
|
|
292
|
+
lastResult
|
|
293
|
+
},
|
|
294
|
+
groups
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const EMPTY_GROUPS = Object.freeze({
|
|
298
|
+
table: [],
|
|
299
|
+
row: [],
|
|
300
|
+
rows: [],
|
|
301
|
+
default: {},
|
|
302
|
+
others: {
|
|
303
|
+
table: [],
|
|
304
|
+
row: [],
|
|
305
|
+
rows: []
|
|
306
|
+
},
|
|
307
|
+
cellRow: []
|
|
308
|
+
});
|
|
309
|
+
function buildGroups(def, rowDelete) {
|
|
310
|
+
if (def === null) return EMPTY_GROUPS;
|
|
311
|
+
const srcActions = def.actions ?? {
|
|
312
|
+
table: [],
|
|
313
|
+
row: [],
|
|
314
|
+
rows: [],
|
|
315
|
+
default: {}
|
|
316
|
+
};
|
|
317
|
+
const table = srcActions.table;
|
|
318
|
+
const row = srcActions.row.slice();
|
|
319
|
+
const rows = srcActions.rows;
|
|
320
|
+
if (rowDelete && def.canRemove) row.push(buildRemoveAction(rowDelete === true ? {} : rowDelete));
|
|
321
|
+
const defTable = srcActions.default.table;
|
|
322
|
+
const defRow = srcActions.default.row;
|
|
323
|
+
const defRows = srcActions.default.rows;
|
|
324
|
+
const othersTable = defTable ? table.filter((a) => a !== defTable) : table;
|
|
325
|
+
const othersRow = defRow ? row.filter((a) => a !== defRow) : row;
|
|
326
|
+
const othersRows = defRows ? rows.filter((a) => a !== defRows) : rows;
|
|
327
|
+
const cellRow = defRow ? [
|
|
328
|
+
defRow,
|
|
329
|
+
...othersRow,
|
|
330
|
+
...rows
|
|
331
|
+
] : othersRow.length > 0 || rows.length > 0 ? [...othersRow, ...rows] : [];
|
|
332
|
+
return {
|
|
333
|
+
table,
|
|
334
|
+
row,
|
|
335
|
+
rows,
|
|
336
|
+
default: {
|
|
337
|
+
table: defTable,
|
|
338
|
+
row: defRow,
|
|
339
|
+
rows: defRows
|
|
340
|
+
},
|
|
341
|
+
others: {
|
|
342
|
+
table: othersTable,
|
|
343
|
+
row: othersRow,
|
|
344
|
+
rows: othersRows
|
|
345
|
+
},
|
|
346
|
+
cellRow
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function buildRemoveAction(opts) {
|
|
350
|
+
return {
|
|
351
|
+
name: REMOVE_NAME,
|
|
352
|
+
label: opts.label ?? REMOVE_DEFAULTS.label,
|
|
353
|
+
level: "row",
|
|
354
|
+
processor: REMOVE_PROCESSOR,
|
|
355
|
+
value: "",
|
|
356
|
+
icon: opts.icon ?? REMOVE_DEFAULTS.icon,
|
|
357
|
+
intent: opts.intent ?? REMOVE_DEFAULTS.intent,
|
|
358
|
+
promptText: opts.confirm ?? REMOVE_DEFAULTS.promptText
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/composables/state/create-selection.ts
|
|
363
|
+
function createSelectionApi(opts, getActiveRow) {
|
|
364
|
+
const selectedRows = opts?.selectedRows ?? shallowRef([]);
|
|
365
|
+
const selectedCount = computed(() => selectedRows.value.length);
|
|
366
|
+
const rowValueFn = opts?.rowValueFn ?? ((row) => row);
|
|
367
|
+
const selectedSet = computed(() => new Set(selectedRows.value));
|
|
368
|
+
function isPkSelected(pk) {
|
|
369
|
+
return selectedSet.value.has(pk);
|
|
370
|
+
}
|
|
371
|
+
function toggleActiveSelection(mode) {
|
|
372
|
+
if (mode === "none") return;
|
|
373
|
+
const row = getActiveRow();
|
|
374
|
+
if (row === void 0) return;
|
|
375
|
+
selectedRows.value = togglePk(selectedRows.value, rowValueFn(row), mode);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
selectedRows,
|
|
379
|
+
selectedCount,
|
|
380
|
+
selectedSet,
|
|
381
|
+
rowValueFn,
|
|
382
|
+
isPkSelected,
|
|
383
|
+
toggleActiveSelection
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/composables/state/create-main-action-registry.ts
|
|
388
|
+
/**
|
|
389
|
+
* Listener registry for the `main-action` event. When listeners are present,
|
|
390
|
+
* `requestMainAction` builds a `MainActionRequest` and dispatches it. When no
|
|
391
|
+
* listener is registered, falls back to invoking `actions.default.row` against
|
|
392
|
+
* the active row's PK (if both are defined). The fallback path SHALL NOT
|
|
393
|
+
* construct a `MainActionRequest` payload — there is nothing to receive it.
|
|
394
|
+
*/
|
|
395
|
+
function createMainActionRegistry(opts) {
|
|
396
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
397
|
+
const hasMainActionListener = ref(false);
|
|
398
|
+
const hasMainActionAvailable = computed(() => hasMainActionListener.value || opts.getDefaultRowAction?.() !== void 0);
|
|
399
|
+
function registerMainActionListener(cb) {
|
|
400
|
+
listeners.add(cb);
|
|
401
|
+
hasMainActionListener.value = listeners.size > 0;
|
|
402
|
+
let disposed = false;
|
|
403
|
+
return () => {
|
|
404
|
+
if (disposed) return;
|
|
405
|
+
disposed = true;
|
|
406
|
+
listeners.delete(cb);
|
|
407
|
+
hasMainActionListener.value = listeners.size > 0;
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function requestMainAction(event) {
|
|
411
|
+
const abs = opts.getActiveIndex();
|
|
412
|
+
if (abs < 0) return;
|
|
413
|
+
const row = opts.getActiveRow();
|
|
414
|
+
if (row === void 0) return;
|
|
415
|
+
if (listeners.size > 0) {
|
|
416
|
+
const req = {
|
|
417
|
+
row,
|
|
418
|
+
absIndex: abs,
|
|
419
|
+
event
|
|
420
|
+
};
|
|
421
|
+
for (const cb of listeners) cb(req);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const fallback = opts.getDefaultRowAction?.();
|
|
425
|
+
if (fallback && opts.invokeFallback) opts.invokeFallback(fallback, row, event);
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
hasMainActionListener,
|
|
429
|
+
hasMainActionAvailable,
|
|
430
|
+
registerMainActionListener,
|
|
431
|
+
requestMainAction
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/composables/state/create-nav-controller.ts
|
|
436
|
+
function createNavController(inputs) {
|
|
437
|
+
const { activeIndex, totalCount, results, viewportRowCount, topIndex, hasMainActionAvailable, requestMainAction, toggleActiveSelection } = inputs;
|
|
438
|
+
const navMode = ref("pagination");
|
|
439
|
+
const navViewportRowCount = ref(0);
|
|
440
|
+
function clampActive(idx) {
|
|
441
|
+
let upper;
|
|
442
|
+
if (navMode.value === "window") upper = totalCount.value;
|
|
443
|
+
else {
|
|
444
|
+
const r = results.value.length;
|
|
445
|
+
const t = totalCount.value;
|
|
446
|
+
if (r === 0) upper = t;
|
|
447
|
+
else if (t === 0) upper = 0;
|
|
448
|
+
else upper = Math.min(r, t);
|
|
449
|
+
}
|
|
450
|
+
if (upper === 0) return -1;
|
|
451
|
+
if (idx === -1) return -1;
|
|
452
|
+
if (idx < 0) return 0;
|
|
453
|
+
if (idx > upper - 1) return upper - 1;
|
|
454
|
+
return idx;
|
|
455
|
+
}
|
|
456
|
+
function setActive(absIndex) {
|
|
457
|
+
const next = clampActive(absIndex);
|
|
458
|
+
if (next !== activeIndex.value) activeIndex.value = next;
|
|
459
|
+
}
|
|
460
|
+
function clearActive() {
|
|
461
|
+
if (activeIndex.value !== -1) activeIndex.value = -1;
|
|
462
|
+
}
|
|
463
|
+
watch([
|
|
464
|
+
() => totalCount.value,
|
|
465
|
+
() => results.value.length,
|
|
466
|
+
() => navMode.value
|
|
467
|
+
], () => setActive(activeIndex.value));
|
|
468
|
+
function pageStep() {
|
|
469
|
+
return Math.max(viewportRowCount.value, navViewportRowCount.value, 10) - 1;
|
|
470
|
+
}
|
|
471
|
+
function activeBase() {
|
|
472
|
+
return activeIndex.value < 0 ? topIndex.value : activeIndex.value;
|
|
473
|
+
}
|
|
474
|
+
function navStep(delta) {
|
|
475
|
+
if (activeIndex.value < 0) setActive(topIndex.value);
|
|
476
|
+
else setActive(activeIndex.value + delta);
|
|
477
|
+
}
|
|
478
|
+
function navPage(delta) {
|
|
479
|
+
setActive(activeBase() + delta);
|
|
480
|
+
}
|
|
481
|
+
function handleNavKey(event, opts) {
|
|
482
|
+
if (totalCount.value === 0) return;
|
|
483
|
+
const enterAction = opts?.enterAction ?? "main-action";
|
|
484
|
+
const mode = opts?.mode ?? "none";
|
|
485
|
+
const key = event.key;
|
|
486
|
+
const meta = event.metaKey;
|
|
487
|
+
const ctrl = event.ctrlKey;
|
|
488
|
+
const alt = event.altKey;
|
|
489
|
+
if (key === "ArrowDown" && (meta || ctrl)) {
|
|
490
|
+
event.preventDefault();
|
|
491
|
+
setActive(totalCount.value - 1);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (key === "ArrowUp" && (meta || ctrl)) {
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
setActive(0);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (key === "ArrowDown" && alt) {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
navPage(pageStep());
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (key === "ArrowUp" && alt) {
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
navPage(-pageStep());
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
switch (key) {
|
|
510
|
+
case "ArrowDown":
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
navStep(1);
|
|
513
|
+
return;
|
|
514
|
+
case "ArrowUp":
|
|
515
|
+
event.preventDefault();
|
|
516
|
+
navStep(-1);
|
|
517
|
+
return;
|
|
518
|
+
case "PageDown":
|
|
519
|
+
event.preventDefault();
|
|
520
|
+
navPage(pageStep());
|
|
521
|
+
return;
|
|
522
|
+
case "PageUp":
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
navPage(-pageStep());
|
|
525
|
+
return;
|
|
526
|
+
case "Home":
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
setActive(0);
|
|
529
|
+
return;
|
|
530
|
+
case "End":
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
setActive(totalCount.value - 1);
|
|
533
|
+
return;
|
|
534
|
+
case " ":
|
|
535
|
+
if (mode === "none") return;
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
toggleActiveSelection(mode);
|
|
538
|
+
return;
|
|
539
|
+
case "Enter":
|
|
540
|
+
if (enterAction === "passthrough") return;
|
|
541
|
+
event.preventDefault();
|
|
542
|
+
if (enterAction === "toggle-select") {
|
|
543
|
+
toggleActiveSelection(mode);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (mode !== "none") {
|
|
547
|
+
toggleActiveSelection(mode);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (hasMainActionAvailable.value) requestMainAction(event);
|
|
551
|
+
return;
|
|
552
|
+
case "Escape":
|
|
553
|
+
case "Esc":
|
|
554
|
+
clearActive();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
navMode,
|
|
560
|
+
navViewportRowCount,
|
|
561
|
+
setActive,
|
|
562
|
+
clearActive,
|
|
563
|
+
handleNavKey
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/composables/state/create-preset-state.ts
|
|
568
|
+
const DEFAULT_AVAILABLE_ASPECTS = [
|
|
569
|
+
"columns",
|
|
570
|
+
"filters",
|
|
571
|
+
"filterOps",
|
|
572
|
+
"sorters"
|
|
573
|
+
];
|
|
574
|
+
/**
|
|
575
|
+
* Default `columnNames` for a system preset that doesn't specify them — every
|
|
576
|
+
* column whose schema declares it visible (i.e. lacking `@ui.table.hidden`).
|
|
577
|
+
* Used by both `apply` (no-explicit-columnNames fallback) and `expandDefault`,
|
|
578
|
+
* so the two paths can never drift apart.
|
|
579
|
+
*/
|
|
580
|
+
function defaultVisibleColumnPaths(o) {
|
|
581
|
+
const out = [];
|
|
582
|
+
for (const col of o.allColumns.value) if (col.visible) out.push(col.path);
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Slice that turns `usePresets` + `useLocalDraft` into the table-state
|
|
587
|
+
* surface declared on `ReactiveTableState`. Pure mutation: writes to the
|
|
588
|
+
* model arrays but never calls `query()` — the root watcher reacts.
|
|
589
|
+
*/
|
|
590
|
+
function createPresetState(opts) {
|
|
591
|
+
const availableAspects = opts.availableAspects ?? DEFAULT_AVAILABLE_ASPECTS;
|
|
592
|
+
const availableSet = new Set(availableAspects);
|
|
593
|
+
const dialogOpen = ref(false);
|
|
594
|
+
const gate = ref(false);
|
|
595
|
+
const ready = computed(() => opts.presetsHandle ? gate.value : true);
|
|
596
|
+
const handle = opts.presetsHandle ?? createInertHandle(opts.fallbackSystemPresets);
|
|
597
|
+
const { presets, presetsById, userConf, capabilities, systemPresets, systemPresetsById, available, currentUser, activePresetId: activeId } = handle;
|
|
598
|
+
const canSaveActive = computed(() => {
|
|
599
|
+
const id = activeId.value;
|
|
600
|
+
if (!id || isSystemPresetId(id)) return false;
|
|
601
|
+
return opts.presetsHandle ? opts.presetsHandle.isOwned(id) : false;
|
|
602
|
+
});
|
|
603
|
+
const ASPECT_HANDLERS = {
|
|
604
|
+
columns: {
|
|
605
|
+
capture(o) {
|
|
606
|
+
const overrides = {};
|
|
607
|
+
for (const col of o.allColumns.value) {
|
|
608
|
+
const entry = o.columnWidths.value[col.path];
|
|
609
|
+
if (entry && entry.w !== entry.d) overrides[col.path] = entry.w;
|
|
610
|
+
}
|
|
611
|
+
const columns = { columnNames: [...o.columnNames.value] };
|
|
612
|
+
if (Object.keys(overrides).length > 0) columns.columnWidths = overrides;
|
|
613
|
+
return columns;
|
|
614
|
+
},
|
|
615
|
+
apply(o, value, isSystem) {
|
|
616
|
+
if (!value && !isSystem) return;
|
|
617
|
+
const cols = value;
|
|
618
|
+
const columnNames = cols?.columnNames ?? defaultVisibleColumnPaths(o);
|
|
619
|
+
const columnWidths = cols?.columnWidths;
|
|
620
|
+
o.columnNames.value = [...columnNames];
|
|
621
|
+
const next = {};
|
|
622
|
+
for (const [path, entry] of Object.entries(o.columnWidths.value)) next[path] = {
|
|
623
|
+
w: entry.d,
|
|
624
|
+
d: entry.d
|
|
625
|
+
};
|
|
626
|
+
if (columnWidths) for (const [path, w] of Object.entries(columnWidths)) {
|
|
627
|
+
const existing = next[path];
|
|
628
|
+
if (existing) next[path] = {
|
|
629
|
+
w,
|
|
630
|
+
d: existing.d
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
o.columnWidths.value = next;
|
|
634
|
+
},
|
|
635
|
+
expandDefault(o) {
|
|
636
|
+
return { columnNames: defaultVisibleColumnPaths(o) };
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
filters: {
|
|
640
|
+
capture: (o) => [...o.filterFields.value],
|
|
641
|
+
apply(o, value, isSystem) {
|
|
642
|
+
if (!value && !isSystem) return;
|
|
643
|
+
o.filterFields.value = value ? [...value] : [];
|
|
644
|
+
},
|
|
645
|
+
expandDefault: () => []
|
|
646
|
+
},
|
|
647
|
+
filterOps: {
|
|
648
|
+
capture(o) {
|
|
649
|
+
const dict = {};
|
|
650
|
+
for (const [field, conditions] of Object.entries(o.filters.value)) dict[field] = conditions;
|
|
651
|
+
return dict;
|
|
652
|
+
},
|
|
653
|
+
apply(o, value, isSystem) {
|
|
654
|
+
if (!value && !isSystem) return;
|
|
655
|
+
o.filters.value = value ? { ...value } : {};
|
|
656
|
+
},
|
|
657
|
+
expandDefault: () => ({})
|
|
658
|
+
},
|
|
659
|
+
sorters: {
|
|
660
|
+
capture: (o) => [...o.sorters.value],
|
|
661
|
+
apply(o, value, isSystem) {
|
|
662
|
+
if (!value && !isSystem) return;
|
|
663
|
+
o.sorters.value = value ? [...value] : [];
|
|
664
|
+
},
|
|
665
|
+
expandDefault: () => []
|
|
666
|
+
},
|
|
667
|
+
itemsPerPage: {
|
|
668
|
+
capture: (o) => o.pagination.value.itemsPerPage,
|
|
669
|
+
apply(o, value) {
|
|
670
|
+
if (value === void 0) return;
|
|
671
|
+
o.pagination.value = {
|
|
672
|
+
page: 1,
|
|
673
|
+
itemsPerPage: value
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
function maskAllows(aspect, mask) {
|
|
679
|
+
if (!availableSet.has(aspect)) return false;
|
|
680
|
+
if (!mask) return true;
|
|
681
|
+
return mask[aspect] === true;
|
|
682
|
+
}
|
|
683
|
+
function captureSnapshot(mask) {
|
|
684
|
+
const out = {};
|
|
685
|
+
for (const aspect of availableAspects) {
|
|
686
|
+
if (!maskAllows(aspect, mask)) continue;
|
|
687
|
+
out[aspect] = ASPECT_HANDLERS[aspect].capture(opts);
|
|
688
|
+
}
|
|
689
|
+
return out;
|
|
690
|
+
}
|
|
691
|
+
function resolveSnapshot(idOrSnapshot) {
|
|
692
|
+
if (typeof idOrSnapshot !== "string") return {
|
|
693
|
+
snapshot: idOrSnapshot,
|
|
694
|
+
nextActiveId: activeId.value
|
|
695
|
+
};
|
|
696
|
+
const id = idOrSnapshot;
|
|
697
|
+
if (isSystemPresetId(id)) {
|
|
698
|
+
const sp = systemPresetsById.value.get(id);
|
|
699
|
+
if (sp) return {
|
|
700
|
+
snapshot: sp.content,
|
|
701
|
+
nextActiveId: id
|
|
702
|
+
};
|
|
703
|
+
return {
|
|
704
|
+
snapshot: systemPresetsById.value.get(STANDARD_PRESET_ID)?.content ?? {},
|
|
705
|
+
nextActiveId: STANDARD_PRESET_ID
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const row = presetsById.value.get(id);
|
|
709
|
+
if (!row) return {
|
|
710
|
+
snapshot: {},
|
|
711
|
+
nextActiveId: null
|
|
712
|
+
};
|
|
713
|
+
const wire = row.data?.content;
|
|
714
|
+
return {
|
|
715
|
+
snapshot: wire ? fromWireSnapshot(wire) : {},
|
|
716
|
+
nextActiveId: id
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function apply(idOrSnapshot) {
|
|
720
|
+
const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
|
|
721
|
+
const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && isSystemPresetId(nextActiveId);
|
|
722
|
+
for (const aspect of availableAspects) {
|
|
723
|
+
const value = snapshot[aspect];
|
|
724
|
+
ASPECT_HANDLERS[aspect].apply(opts, value, isSystem);
|
|
725
|
+
}
|
|
726
|
+
if (snapshot.itemsPerPage === void 0) {
|
|
727
|
+
const current = opts.pagination.value;
|
|
728
|
+
if (current.page !== 1) opts.pagination.value = {
|
|
729
|
+
...current,
|
|
730
|
+
page: 1
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (typeof idOrSnapshot === "string") activeId.value = nextActiveId;
|
|
734
|
+
}
|
|
735
|
+
function resetActive() {
|
|
736
|
+
const id = activeId.value;
|
|
737
|
+
if (!id) return;
|
|
738
|
+
apply(id);
|
|
739
|
+
}
|
|
740
|
+
function clearLocalDraft() {
|
|
741
|
+
opts.draftHandle?.clear();
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Fill missing aspects with factory defaults so a system preset's snapshot
|
|
745
|
+
* "claims all aspects in `availableAspects`" per spec §3.7. Stored presets
|
|
746
|
+
* keep dict-form opt-in (untouched).
|
|
747
|
+
*/
|
|
748
|
+
function expandSystemSnapshot(snapshot) {
|
|
749
|
+
const out = { ...snapshot };
|
|
750
|
+
for (const aspect of availableAspects) {
|
|
751
|
+
const handler = ASPECT_HANDLERS[aspect];
|
|
752
|
+
if (!handler.expandDefault) continue;
|
|
753
|
+
if (out[aspect] !== void 0) continue;
|
|
754
|
+
out[aspect] = handler.expandDefault(opts);
|
|
755
|
+
}
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
const activeSnapshot = computed(() => {
|
|
759
|
+
const id = activeId.value;
|
|
760
|
+
if (!id) return {};
|
|
761
|
+
if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {});
|
|
762
|
+
const row = presetsById.value.get(id);
|
|
763
|
+
if (!row) return {};
|
|
764
|
+
const wire = row.data?.content;
|
|
765
|
+
return wire ? fromWireSnapshot(wire) : {};
|
|
766
|
+
});
|
|
767
|
+
const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()));
|
|
768
|
+
function requirePresets() {
|
|
769
|
+
if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
|
|
770
|
+
return opts.presetsHandle;
|
|
771
|
+
}
|
|
772
|
+
async function saveActive() {
|
|
773
|
+
const handle = requirePresets();
|
|
774
|
+
const id = activeId.value;
|
|
775
|
+
if (!id) throw new Error("[vue-table] saveActive: no active preset");
|
|
776
|
+
if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
|
|
777
|
+
const existing = activeSnapshot.value;
|
|
778
|
+
const mask = {};
|
|
779
|
+
for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
|
|
780
|
+
await handle.savePreset(captureSnapshot(mask));
|
|
781
|
+
clearLocalDraft();
|
|
782
|
+
}
|
|
783
|
+
async function saveAs(label, saveOpts = {}) {
|
|
784
|
+
const id = await requirePresets().savePresetAs(label, captureSnapshot(saveOpts.aspects), { public: saveOpts.public });
|
|
785
|
+
clearLocalDraft();
|
|
786
|
+
return id;
|
|
787
|
+
}
|
|
788
|
+
async function rename(id, label) {
|
|
789
|
+
await requirePresets().renamePreset(id, label);
|
|
790
|
+
}
|
|
791
|
+
async function remove(id) {
|
|
792
|
+
const handle = requirePresets();
|
|
793
|
+
const wasActive = activeId.value === id;
|
|
794
|
+
await handle.deletePreset(id);
|
|
795
|
+
if (wasActive) {
|
|
796
|
+
apply(STANDARD_PRESET_ID);
|
|
797
|
+
clearLocalDraft();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function togglePublic(id) {
|
|
801
|
+
await requirePresets().togglePublic(id);
|
|
802
|
+
}
|
|
803
|
+
async function setDefault(id) {
|
|
804
|
+
await requirePresets().setDefault(id);
|
|
805
|
+
}
|
|
806
|
+
async function toggleFav(id) {
|
|
807
|
+
await requirePresets().toggleFav(id);
|
|
808
|
+
}
|
|
809
|
+
async function setFavorites(ids) {
|
|
810
|
+
await requirePresets().setFavorites(ids);
|
|
811
|
+
}
|
|
812
|
+
function batch(fn) {
|
|
813
|
+
return opts.presetsHandle ? opts.presetsHandle.batch(fn) : fn();
|
|
814
|
+
}
|
|
815
|
+
function resolveDefaultId() {
|
|
816
|
+
const pinned = (userConf.value?.data)?.defaultPresetId;
|
|
817
|
+
if (!pinned) return STANDARD_PRESET_ID;
|
|
818
|
+
if (isSystemPresetId(pinned)) return systemPresetsById.value.has(pinned) ? pinned : STANDARD_PRESET_ID;
|
|
819
|
+
return presetsById.value.has(pinned) ? pinned : STANDARD_PRESET_ID;
|
|
820
|
+
}
|
|
821
|
+
function bootstrap() {
|
|
822
|
+
if (!opts.presetsHandle) return;
|
|
823
|
+
const handle = opts.presetsHandle;
|
|
824
|
+
const stop = watch(() => handle.loading.value, (loading) => {
|
|
825
|
+
if (loading) return;
|
|
826
|
+
stop();
|
|
827
|
+
const id = resolveDefaultId();
|
|
828
|
+
const { snapshot, nextActiveId } = resolveSnapshot(id);
|
|
829
|
+
const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot) : snapshot;
|
|
830
|
+
const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
|
|
831
|
+
apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
|
|
832
|
+
activeId.value = nextActiveId;
|
|
833
|
+
if (draftEnabled) opts.draftHandle.watchAndPersist(() => captureSnapshot(), () => activeSnapshot.value);
|
|
834
|
+
nextTick(() => {
|
|
835
|
+
gate.value = true;
|
|
836
|
+
});
|
|
837
|
+
}, { immediate: true });
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
slice: {
|
|
841
|
+
presets,
|
|
842
|
+
presetsById,
|
|
843
|
+
userConf,
|
|
844
|
+
capabilities,
|
|
845
|
+
systemPresets,
|
|
846
|
+
systemPresetsById,
|
|
847
|
+
availableAspects,
|
|
848
|
+
available,
|
|
849
|
+
activeId,
|
|
850
|
+
activeSnapshot,
|
|
851
|
+
isDirty,
|
|
852
|
+
canSaveActive,
|
|
853
|
+
currentUser,
|
|
854
|
+
isOwned: (id) => handle.isOwned(id),
|
|
855
|
+
dialogOpen,
|
|
856
|
+
ready,
|
|
857
|
+
captureSnapshot,
|
|
858
|
+
apply,
|
|
859
|
+
resetActive,
|
|
860
|
+
clearLocalDraft,
|
|
861
|
+
resolveDefaultId,
|
|
862
|
+
saveActive,
|
|
863
|
+
saveAs,
|
|
864
|
+
rename,
|
|
865
|
+
remove,
|
|
866
|
+
togglePublic,
|
|
867
|
+
setDefault,
|
|
868
|
+
toggleFav,
|
|
869
|
+
setFavorites,
|
|
870
|
+
batch
|
|
871
|
+
},
|
|
872
|
+
internals: {
|
|
873
|
+
gate,
|
|
874
|
+
bootstrap
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function inertMutator() {
|
|
879
|
+
throw new Error("[vue-table] presets feature not configured (preset.url missing)");
|
|
880
|
+
}
|
|
881
|
+
function createInertHandle(fallbackSystemPresets) {
|
|
882
|
+
const systemList = fallbackSystemPresets ?? resolveSystemPresets();
|
|
883
|
+
const systemPresets = computed(() => systemList);
|
|
884
|
+
const systemPresetsById = computed(() => {
|
|
885
|
+
const map = /* @__PURE__ */ new Map();
|
|
886
|
+
for (const sp of systemList) map.set(sp.id, sp);
|
|
887
|
+
return map;
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
presets: shallowRef([]),
|
|
891
|
+
presetsById: computed(() => /* @__PURE__ */ new Map()),
|
|
892
|
+
userConf: shallowRef(null),
|
|
893
|
+
capabilities: ref(null),
|
|
894
|
+
systemPresets,
|
|
895
|
+
systemPresetsById,
|
|
896
|
+
available: computed(() => false),
|
|
897
|
+
loading: ref(false),
|
|
898
|
+
error: ref(null),
|
|
899
|
+
currentUser: computed(() => null),
|
|
900
|
+
activePresetId: ref(null),
|
|
901
|
+
activePreset: computed(() => null),
|
|
902
|
+
isOwned: () => false,
|
|
903
|
+
reload: async () => {},
|
|
904
|
+
batch: (fn) => fn(),
|
|
905
|
+
savePreset: inertMutator,
|
|
906
|
+
savePresetAs: inertMutator,
|
|
907
|
+
renamePreset: inertMutator,
|
|
908
|
+
deletePreset: inertMutator,
|
|
909
|
+
togglePublic: inertMutator,
|
|
910
|
+
setDefault: inertMutator,
|
|
911
|
+
toggleFav: inertMutator,
|
|
912
|
+
setFavorites: inertMutator
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/composables/state/create-window-fetcher.ts
|
|
917
|
+
function createWindowFetcher(inputs) {
|
|
918
|
+
const { blockSize, dragReleaseDebounceMs, tableDef, totalCount, results, resultsStart, queryingNext, getGeneration, isQueryBlocked, buildCurrentQuery, dispatchPages, reportError } = inputs;
|
|
919
|
+
const windowCache = shallowRef(/* @__PURE__ */ new Map());
|
|
920
|
+
/** Per-block firstIndex values currently in flight. `loadingAt(absIdx)`
|
|
921
|
+
* derives membership via `blockStartFor(absIdx, blockSize)`. */
|
|
922
|
+
const windowLoading = shallowRef(/* @__PURE__ */ new Set());
|
|
923
|
+
const errors = shallowRef(/* @__PURE__ */ new Map());
|
|
924
|
+
const topIndex = ref(0);
|
|
925
|
+
const viewportRowCount = ref(0);
|
|
926
|
+
const settlements = [];
|
|
927
|
+
let flushScheduled = false;
|
|
928
|
+
function flushSettlements() {
|
|
929
|
+
flushScheduled = false;
|
|
930
|
+
if (settlements.length === 0) return;
|
|
931
|
+
const currentGen = getGeneration();
|
|
932
|
+
let applied = false;
|
|
933
|
+
let nextCache = windowCache.value;
|
|
934
|
+
let nextLoading = windowLoading.value;
|
|
935
|
+
let nextErrors = errors.value;
|
|
936
|
+
let totalCountChanged = false;
|
|
937
|
+
let nextTotal = totalCount.value;
|
|
938
|
+
for (const s of settlements) {
|
|
939
|
+
if (s.gen !== currentGen) continue;
|
|
940
|
+
if (!applied) {
|
|
941
|
+
applied = true;
|
|
942
|
+
nextCache = new Map(windowCache.value);
|
|
943
|
+
nextLoading = new Set(windowLoading.value);
|
|
944
|
+
nextErrors = new Map(errors.value);
|
|
945
|
+
}
|
|
946
|
+
nextLoading.delete(s.firstIndex);
|
|
947
|
+
if (s.kind === "ok") {
|
|
948
|
+
for (let i = 0; i < s.rows.length; i++) nextCache.set(s.firstIndex + i, s.rows[i]);
|
|
949
|
+
nextErrors.delete(s.firstIndex);
|
|
950
|
+
nextTotal = s.count;
|
|
951
|
+
totalCountChanged = true;
|
|
952
|
+
} else {
|
|
953
|
+
nextErrors.set(s.firstIndex, s.error);
|
|
954
|
+
reportError(s.error, s.sourceKind);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
settlements.length = 0;
|
|
958
|
+
if (!applied) return;
|
|
959
|
+
windowCache.value = nextCache;
|
|
960
|
+
windowLoading.value = nextLoading;
|
|
961
|
+
errors.value = nextErrors;
|
|
962
|
+
if (totalCountChanged && nextTotal !== totalCount.value) totalCount.value = nextTotal;
|
|
963
|
+
const fwd = walkForwardAbsorb(results.value, resultsStart.value, nextCache);
|
|
964
|
+
const bwd = walkBackwardAbsorb(fwd.newResults, fwd.newResultsStart, nextCache);
|
|
965
|
+
if (bwd.newResults !== results.value) {
|
|
966
|
+
results.value = bwd.newResults;
|
|
967
|
+
resultsStart.value = bwd.newResultsStart;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function scheduleFlush() {
|
|
971
|
+
if (flushScheduled) return;
|
|
972
|
+
flushScheduled = true;
|
|
973
|
+
queueMicrotask(flushSettlements);
|
|
974
|
+
}
|
|
975
|
+
function loadRangeInternal(skip, limit, kind) {
|
|
976
|
+
if (isQueryBlocked()) return Promise.resolve();
|
|
977
|
+
const blocks = pageAlignedBlocksFor(skip, limit, blockSize);
|
|
978
|
+
if (blocks.length === 0) return Promise.resolve();
|
|
979
|
+
const cache = windowCache.value;
|
|
980
|
+
const loading = windowLoading.value;
|
|
981
|
+
const missing = [];
|
|
982
|
+
for (const b of blocks) {
|
|
983
|
+
if (loading.has(b.firstIndex)) continue;
|
|
984
|
+
const end = b.firstIndex + blockSize;
|
|
985
|
+
for (let idx = b.firstIndex; idx < end; idx++) if (!cache.has(idx)) {
|
|
986
|
+
missing.push(b);
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (missing.length === 0) return Promise.resolve();
|
|
991
|
+
const thisGen = getGeneration();
|
|
992
|
+
const baseQuery = buildCurrentQuery();
|
|
993
|
+
const nextLoading = new Set(windowLoading.value);
|
|
994
|
+
const prevErrors = errors.value;
|
|
995
|
+
let nextErrors = prevErrors;
|
|
996
|
+
let errorsChanged = false;
|
|
997
|
+
for (const b of missing) {
|
|
998
|
+
nextLoading.add(b.firstIndex);
|
|
999
|
+
if (prevErrors.has(b.firstIndex)) {
|
|
1000
|
+
if (!errorsChanged) {
|
|
1001
|
+
nextErrors = new Map(prevErrors);
|
|
1002
|
+
errorsChanged = true;
|
|
1003
|
+
}
|
|
1004
|
+
nextErrors.delete(b.firstIndex);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
windowLoading.value = nextLoading;
|
|
1008
|
+
if (errorsChanged) errors.value = nextErrors;
|
|
1009
|
+
const promises = missing.map(async (b) => {
|
|
1010
|
+
try {
|
|
1011
|
+
const { data, count } = await dispatchPages(baseQuery, b.page, blockSize);
|
|
1012
|
+
if (thisGen !== getGeneration()) return;
|
|
1013
|
+
settlements.push({
|
|
1014
|
+
kind: "ok",
|
|
1015
|
+
gen: thisGen,
|
|
1016
|
+
firstIndex: b.firstIndex,
|
|
1017
|
+
rows: data,
|
|
1018
|
+
count
|
|
1019
|
+
});
|
|
1020
|
+
scheduleFlush();
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
if (thisGen !== getGeneration()) return;
|
|
1023
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1024
|
+
settlements.push({
|
|
1025
|
+
kind: "err",
|
|
1026
|
+
gen: thisGen,
|
|
1027
|
+
firstIndex: b.firstIndex,
|
|
1028
|
+
error,
|
|
1029
|
+
sourceKind: kind
|
|
1030
|
+
});
|
|
1031
|
+
scheduleFlush();
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
return Promise.allSettled(promises).then(() => void 0);
|
|
1035
|
+
}
|
|
1036
|
+
function loadRange(skip, limit) {
|
|
1037
|
+
return loadRangeInternal(skip, limit, "loadRange");
|
|
1038
|
+
}
|
|
1039
|
+
function queryNext() {
|
|
1040
|
+
if (queryingNext.value) return;
|
|
1041
|
+
if (isQueryBlocked()) return;
|
|
1042
|
+
queryingNext.value = true;
|
|
1043
|
+
loadRangeInternal(resultsStart.value + results.value.length, blockSize, "queryNext").finally(() => {
|
|
1044
|
+
queryingNext.value = false;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
function dataAt(absIdx) {
|
|
1048
|
+
return windowCache.value.get(absIdx);
|
|
1049
|
+
}
|
|
1050
|
+
function loadingAt(absIdx) {
|
|
1051
|
+
if (blockSize <= 0) return false;
|
|
1052
|
+
return windowLoading.value.has(blockStartFor(absIdx, blockSize));
|
|
1053
|
+
}
|
|
1054
|
+
function errorAt(absIdx) {
|
|
1055
|
+
if (blockSize <= 0) return null;
|
|
1056
|
+
return errors.value.get(blockStartFor(absIdx, blockSize)) ?? null;
|
|
1057
|
+
}
|
|
1058
|
+
function clearSettlements() {
|
|
1059
|
+
settlements.length = 0;
|
|
1060
|
+
}
|
|
1061
|
+
function resetWindow() {
|
|
1062
|
+
settlements.length = 0;
|
|
1063
|
+
windowCache.value = /* @__PURE__ */ new Map();
|
|
1064
|
+
windowLoading.value = /* @__PURE__ */ new Set();
|
|
1065
|
+
errors.value = /* @__PURE__ */ new Map();
|
|
1066
|
+
}
|
|
1067
|
+
let pendingJumpPlan = null;
|
|
1068
|
+
const debouncedJumpDispatch = debounce(() => {
|
|
1069
|
+
const plan = pendingJumpPlan;
|
|
1070
|
+
pendingJumpPlan = null;
|
|
1071
|
+
if (plan) loadRange(plan.skip, plan.limit);
|
|
1072
|
+
}, dragReleaseDebounceMs);
|
|
1073
|
+
const prefetchBuffer = Math.max(1, Math.floor(blockSize / 4));
|
|
1074
|
+
watch([() => topIndex.value, () => viewportRowCount.value], () => {
|
|
1075
|
+
if (tableDef.value === null) return;
|
|
1076
|
+
if (viewportRowCount.value <= 0) return;
|
|
1077
|
+
const plan = planFetch({
|
|
1078
|
+
top: topIndex.value,
|
|
1079
|
+
viewport: viewportRowCount.value,
|
|
1080
|
+
totalCount: totalCount.value,
|
|
1081
|
+
cache: windowCache.value,
|
|
1082
|
+
blockSize,
|
|
1083
|
+
buffer: prefetchBuffer
|
|
1084
|
+
});
|
|
1085
|
+
if (plan === null) return;
|
|
1086
|
+
if (plan.mode === "jump") {
|
|
1087
|
+
pendingJumpPlan = plan;
|
|
1088
|
+
debouncedJumpDispatch();
|
|
1089
|
+
} else {
|
|
1090
|
+
if (windowLoading.value.has(blockStartFor(plan.skip, blockSize))) return;
|
|
1091
|
+
loadRange(plan.skip, plan.limit);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
function disposeDebounces() {
|
|
1095
|
+
debouncedJumpDispatch.cancel();
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
windowCache,
|
|
1099
|
+
windowLoading,
|
|
1100
|
+
errors,
|
|
1101
|
+
topIndex,
|
|
1102
|
+
viewportRowCount,
|
|
1103
|
+
dataAt,
|
|
1104
|
+
loadingAt,
|
|
1105
|
+
errorAt,
|
|
1106
|
+
loadRange,
|
|
1107
|
+
queryNext,
|
|
1108
|
+
clearSettlements,
|
|
1109
|
+
resetWindow,
|
|
1110
|
+
disposeDebounces
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/composables/use-table-state.ts
|
|
1115
|
+
const TABLE_KEY = "__as_table";
|
|
1116
|
+
const FILTER_DEBOUNCE_MS = 500;
|
|
1117
|
+
const DEFAULT_BLOCK_SIZE = 100;
|
|
1118
|
+
const DEFAULT_DRAG_RELEASE_DEBOUNCE_MS = 300;
|
|
1119
|
+
const DEFAULT_ITEMS_PER_PAGE = 25;
|
|
1120
|
+
let _tblUid = 0;
|
|
1121
|
+
/**
|
|
1122
|
+
* Coerce a primitive cell value to a string for the static-mode query
|
|
1123
|
+
* function's substring search and locale-aware sort. Objects fall back to
|
|
1124
|
+
* `""` so `'[object Object]'` never leaks into the search index. Module
|
|
1125
|
+
* scope so it's not recreated on every fetch.
|
|
1126
|
+
*/
|
|
1127
|
+
function cellAsString(v) {
|
|
1128
|
+
if (v == null) return "";
|
|
1129
|
+
if (typeof v === "string") return v;
|
|
1130
|
+
if (typeof v === "number" || typeof v === "boolean") return v.toString();
|
|
1131
|
+
return "";
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Promise-based dialog slot. A second `request()` while one is open
|
|
1135
|
+
* auto-resolves the prior one with `cancelValue` (the user couldn't have
|
|
1136
|
+
* answered both). `dismiss()` is the same path; `accept(v)` resolves with `v`.
|
|
1137
|
+
*/
|
|
1138
|
+
function createRequestSlot(cancelValue) {
|
|
1139
|
+
const r = shallowRef(null);
|
|
1140
|
+
function request(body) {
|
|
1141
|
+
if (r.value) {
|
|
1142
|
+
r.value.resolve(cancelValue);
|
|
1143
|
+
r.value = null;
|
|
1144
|
+
}
|
|
1145
|
+
return new Promise((resolve) => {
|
|
1146
|
+
r.value = {
|
|
1147
|
+
...body,
|
|
1148
|
+
resolve
|
|
1149
|
+
};
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
function accept(value) {
|
|
1153
|
+
const req = r.value;
|
|
1154
|
+
if (!req) return;
|
|
1155
|
+
r.value = null;
|
|
1156
|
+
req.resolve(value);
|
|
1157
|
+
}
|
|
1158
|
+
function dismiss() {
|
|
1159
|
+
const req = r.value;
|
|
1160
|
+
if (!req) return;
|
|
1161
|
+
r.value = null;
|
|
1162
|
+
req.resolve(cancelValue);
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
ref: r,
|
|
1166
|
+
request,
|
|
1167
|
+
accept,
|
|
1168
|
+
dismiss
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
function createTableState(opts) {
|
|
1172
|
+
const client = opts.client;
|
|
1173
|
+
const modelOpts = opts.model;
|
|
1174
|
+
const selectionOpts = opts.selection;
|
|
1175
|
+
const windowOpts = opts.window;
|
|
1176
|
+
const queryOpts = opts.query;
|
|
1177
|
+
const blockSize = windowOpts?.blockSize ?? DEFAULT_BLOCK_SIZE;
|
|
1178
|
+
const dragReleaseDebounceMs = windowOpts?.dragReleaseDebounceMs ?? DEFAULT_DRAG_RELEASE_DEBOUNCE_MS;
|
|
1179
|
+
const tableDef = shallowRef(null);
|
|
1180
|
+
const loadingMetadata = ref(true);
|
|
1181
|
+
const allColumns = shallowRef([]);
|
|
1182
|
+
const filterFields = modelOpts?.filterFields ?? shallowRef([]);
|
|
1183
|
+
const columnNames = modelOpts?.columnNames ?? shallowRef([]);
|
|
1184
|
+
const columnWidths = modelOpts?.columnWidths ?? ref({});
|
|
1185
|
+
const sorters = modelOpts?.sorters ?? shallowRef([]);
|
|
1186
|
+
const columns = computed(() => {
|
|
1187
|
+
const all = allColumns.value;
|
|
1188
|
+
if (all.length === 0 || columnNames.value.length === 0) return [];
|
|
1189
|
+
const map = new Map(all.map((c) => [c.path, c]));
|
|
1190
|
+
const result = [];
|
|
1191
|
+
for (const name of columnNames.value) {
|
|
1192
|
+
const col = map.get(name);
|
|
1193
|
+
if (col) result.push(col);
|
|
1194
|
+
}
|
|
1195
|
+
return result;
|
|
1196
|
+
});
|
|
1197
|
+
const rowDelete = ref(false);
|
|
1198
|
+
const includeActions = ref(false);
|
|
1199
|
+
const filters = shallowRef({});
|
|
1200
|
+
const results = shallowRef([]);
|
|
1201
|
+
const resultsStart = ref(0);
|
|
1202
|
+
const querying = ref(false);
|
|
1203
|
+
const queryingNext = ref(false);
|
|
1204
|
+
const totalCount = ref(0);
|
|
1205
|
+
const loadedCount = computed(() => results.value.length);
|
|
1206
|
+
const pagination = ref({
|
|
1207
|
+
page: 1,
|
|
1208
|
+
itemsPerPage: opts.limit ?? DEFAULT_ITEMS_PER_PAGE
|
|
1209
|
+
});
|
|
1210
|
+
const queryError = ref(null);
|
|
1211
|
+
const metadataError = ref(null);
|
|
1212
|
+
const lastError = ref(null);
|
|
1213
|
+
function reportError(error, kind) {
|
|
1214
|
+
lastError.value = {
|
|
1215
|
+
error,
|
|
1216
|
+
kind
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const mustRefresh = ref(false);
|
|
1220
|
+
const searchTerm = ref("");
|
|
1221
|
+
const configDialogOpen = ref(false);
|
|
1222
|
+
const configTab = ref("columns");
|
|
1223
|
+
const filterDialogColumn = ref(null);
|
|
1224
|
+
const { slice: presetSlice, internals: presetInternals } = createPresetState({
|
|
1225
|
+
columnNames,
|
|
1226
|
+
columnWidths,
|
|
1227
|
+
filterFields,
|
|
1228
|
+
filters,
|
|
1229
|
+
sorters,
|
|
1230
|
+
pagination,
|
|
1231
|
+
allColumns,
|
|
1232
|
+
presetsHandle: opts.preset?.presetsHandle,
|
|
1233
|
+
draftHandle: opts.preset?.draftHandle,
|
|
1234
|
+
availableAspects: opts.preset?.availableAspects,
|
|
1235
|
+
persistDrafts: opts.preset?.persistDrafts,
|
|
1236
|
+
fallbackSystemPresets: opts.preset?.fallbackSystemPresets
|
|
1237
|
+
});
|
|
1238
|
+
presetInternals.bootstrap();
|
|
1239
|
+
const promptSlot = createRequestSlot(false);
|
|
1240
|
+
const confirmRequest = promptSlot.ref;
|
|
1241
|
+
function promptFn(message, opts = {}) {
|
|
1242
|
+
return promptSlot.request({
|
|
1243
|
+
...opts,
|
|
1244
|
+
message
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
const acceptPrompt = () => promptSlot.accept(true);
|
|
1248
|
+
const dismissPrompt = promptSlot.dismiss;
|
|
1249
|
+
const formSlot = createRequestSlot(null);
|
|
1250
|
+
const actionFormRequest = formSlot.ref;
|
|
1251
|
+
function requestActionInput(action, ctx) {
|
|
1252
|
+
return formSlot.request({
|
|
1253
|
+
action,
|
|
1254
|
+
identifiers: ctx.identifiers,
|
|
1255
|
+
preferredId: ctx.preferredId
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
const acceptActionForm = formSlot.accept;
|
|
1259
|
+
const dismissActionForm = formSlot.dismiss;
|
|
1260
|
+
const stateUid = `tbl-${++_tblUid}`;
|
|
1261
|
+
function rowId(absIndex) {
|
|
1262
|
+
return `${stateUid}-row-${absIndex}`;
|
|
1263
|
+
}
|
|
1264
|
+
let generation = 0;
|
|
1265
|
+
let queryDetected = false;
|
|
1266
|
+
let skipPaginationWatch = 0;
|
|
1267
|
+
function buildCurrentQuery() {
|
|
1268
|
+
return buildTableQuery({
|
|
1269
|
+
visibleColumnPaths: columnNames.value,
|
|
1270
|
+
sorters: sorters.value,
|
|
1271
|
+
forceSorters: queryOpts?.forceSorters,
|
|
1272
|
+
filters: filters.value,
|
|
1273
|
+
forceFilters: queryOpts?.forceFilters,
|
|
1274
|
+
search: searchTerm.value || void 0,
|
|
1275
|
+
includeActions: includeActions.value
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function dispatchPages(query, page, size) {
|
|
1279
|
+
return (queryOpts?.fn ?? ((q, p, s) => client.pages(q, p, s)))(query, page, size);
|
|
1280
|
+
}
|
|
1281
|
+
function resetPagination() {
|
|
1282
|
+
if (pagination.value.page !== 1) {
|
|
1283
|
+
skipPaginationWatch++;
|
|
1284
|
+
pagination.value = {
|
|
1285
|
+
...pagination.value,
|
|
1286
|
+
page: 1
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/** -1 == nothing active. */
|
|
1291
|
+
const activeIndex = ref(-1);
|
|
1292
|
+
const { windowCache, windowLoading, errors, topIndex, viewportRowCount, dataAt, loadingAt, errorAt, loadRange, queryNext, clearSettlements, resetWindow, disposeDebounces } = createWindowFetcher({
|
|
1293
|
+
blockSize,
|
|
1294
|
+
dragReleaseDebounceMs,
|
|
1295
|
+
tableDef,
|
|
1296
|
+
totalCount,
|
|
1297
|
+
results,
|
|
1298
|
+
resultsStart,
|
|
1299
|
+
queryingNext,
|
|
1300
|
+
getGeneration: () => generation,
|
|
1301
|
+
isQueryBlocked: () => !!queryOpts?.blockQuery,
|
|
1302
|
+
buildCurrentQuery,
|
|
1303
|
+
dispatchPages,
|
|
1304
|
+
reportError
|
|
1305
|
+
});
|
|
1306
|
+
function getActiveRow() {
|
|
1307
|
+
const abs = activeIndex.value;
|
|
1308
|
+
if (abs < 0) return void 0;
|
|
1309
|
+
return dataAt(abs);
|
|
1310
|
+
}
|
|
1311
|
+
const { selectedRows, selectedCount, rowValueFn, isPkSelected, toggleActiveSelection } = createSelectionApi(selectionOpts, getActiveRow);
|
|
1312
|
+
const { actions } = createActions({
|
|
1313
|
+
tableDef,
|
|
1314
|
+
client,
|
|
1315
|
+
rowDelete: () => rowDelete.value,
|
|
1316
|
+
scheduleQuery,
|
|
1317
|
+
refreshOnAction: () => opts.actions?.refreshOnAction?.(),
|
|
1318
|
+
onResolved: opts.actions?.onResolved
|
|
1319
|
+
});
|
|
1320
|
+
let stateRef = null;
|
|
1321
|
+
const { hasMainActionListener, hasMainActionAvailable, registerMainActionListener, requestMainAction } = createMainActionRegistry({
|
|
1322
|
+
getActiveIndex: () => activeIndex.value,
|
|
1323
|
+
getActiveRow,
|
|
1324
|
+
getDefaultRowAction: () => actions.default.row,
|
|
1325
|
+
invokeFallback: (action, row, event) => {
|
|
1326
|
+
if (!stateRef) return;
|
|
1327
|
+
const preferredId = tableDef.value?.preferredId ?? [];
|
|
1328
|
+
const identifiers = collectIdentifiers(stateRef, [row], preferredId);
|
|
1329
|
+
triggerAction(stateRef, action, {
|
|
1330
|
+
identifiers,
|
|
1331
|
+
preferredId
|
|
1332
|
+
}, event);
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
const { navMode, navViewportRowCount, setActive, clearActive, handleNavKey } = createNavController({
|
|
1336
|
+
activeIndex,
|
|
1337
|
+
totalCount,
|
|
1338
|
+
results,
|
|
1339
|
+
viewportRowCount,
|
|
1340
|
+
topIndex,
|
|
1341
|
+
hasMainActionAvailable,
|
|
1342
|
+
requestMainAction,
|
|
1343
|
+
toggleActiveSelection
|
|
1344
|
+
});
|
|
1345
|
+
async function runQuery(kind) {
|
|
1346
|
+
if (queryOpts?.blockQuery) return;
|
|
1347
|
+
mustRefresh.value = false;
|
|
1348
|
+
if (kind !== "initial" && topIndex.value !== 0) topIndex.value = 0;
|
|
1349
|
+
const thisGen = ++generation;
|
|
1350
|
+
clearSettlements();
|
|
1351
|
+
querying.value = true;
|
|
1352
|
+
queryDetected = true;
|
|
1353
|
+
try {
|
|
1354
|
+
const query = buildCurrentQuery();
|
|
1355
|
+
const { page, itemsPerPage } = pagination.value;
|
|
1356
|
+
const newResultsStart = (page - 1) * itemsPerPage;
|
|
1357
|
+
const fetchSize = viewportRowCount.value > 0 ? blockSize : itemsPerPage;
|
|
1358
|
+
const { data, count } = await dispatchPages(query, fetchSize === itemsPerPage ? page : Math.floor(newResultsStart / fetchSize) + 1, fetchSize);
|
|
1359
|
+
if (thisGen !== generation) return;
|
|
1360
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
1361
|
+
for (let i = 0; i < data.length; i++) fresh.set(newResultsStart + i, data[i]);
|
|
1362
|
+
windowCache.value = fresh;
|
|
1363
|
+
windowLoading.value = /* @__PURE__ */ new Set();
|
|
1364
|
+
errors.value = /* @__PURE__ */ new Map();
|
|
1365
|
+
results.value = data;
|
|
1366
|
+
resultsStart.value = newResultsStart;
|
|
1367
|
+
totalCount.value = count;
|
|
1368
|
+
queryError.value = null;
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
if (thisGen !== generation) return;
|
|
1371
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1372
|
+
queryError.value = error;
|
|
1373
|
+
results.value = [];
|
|
1374
|
+
windowCache.value = /* @__PURE__ */ new Map();
|
|
1375
|
+
windowLoading.value = /* @__PURE__ */ new Set();
|
|
1376
|
+
totalCount.value = 0;
|
|
1377
|
+
reportError(error, kind);
|
|
1378
|
+
} finally {
|
|
1379
|
+
if (thisGen === generation) querying.value = false;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
let pendingScheduledKind = null;
|
|
1383
|
+
let queryFlushScheduled = false;
|
|
1384
|
+
function scheduleQuery(kind = "query") {
|
|
1385
|
+
if (queryOpts?.blockQuery) return;
|
|
1386
|
+
if (tableDef.value === null) return;
|
|
1387
|
+
pendingScheduledKind = pendingScheduledKind ?? kind;
|
|
1388
|
+
querying.value = true;
|
|
1389
|
+
if (queryFlushScheduled) return;
|
|
1390
|
+
queryFlushScheduled = true;
|
|
1391
|
+
queueMicrotask(() => {
|
|
1392
|
+
queryFlushScheduled = false;
|
|
1393
|
+
const k = pendingScheduledKind;
|
|
1394
|
+
pendingScheduledKind = null;
|
|
1395
|
+
if (k === null) return;
|
|
1396
|
+
if (tableDef.value === null) return;
|
|
1397
|
+
runQuery(k);
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
function query() {
|
|
1401
|
+
scheduleQuery("query");
|
|
1402
|
+
}
|
|
1403
|
+
function requestRefresh() {
|
|
1404
|
+
mustRefresh.value = true;
|
|
1405
|
+
scheduleQuery();
|
|
1406
|
+
}
|
|
1407
|
+
async function queryImmediate() {
|
|
1408
|
+
pendingScheduledKind = null;
|
|
1409
|
+
if (queryOpts?.blockQuery) return;
|
|
1410
|
+
if (tableDef.value === null) return;
|
|
1411
|
+
await runQuery("query");
|
|
1412
|
+
}
|
|
1413
|
+
function invalidate() {
|
|
1414
|
+
generation++;
|
|
1415
|
+
results.value = [];
|
|
1416
|
+
resetWindow();
|
|
1417
|
+
resultsStart.value = (pagination.value.page - 1) * pagination.value.itemsPerPage;
|
|
1418
|
+
totalCount.value = 0;
|
|
1419
|
+
}
|
|
1420
|
+
function writeColumnWidth(path, width) {
|
|
1421
|
+
const entry = columnWidths.value[path];
|
|
1422
|
+
if (!entry || entry.w === width) return;
|
|
1423
|
+
columnWidths.value = {
|
|
1424
|
+
...columnWidths.value,
|
|
1425
|
+
[path]: {
|
|
1426
|
+
...entry,
|
|
1427
|
+
w: width
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
const state = {
|
|
1432
|
+
tableDef,
|
|
1433
|
+
loadingMetadata,
|
|
1434
|
+
columnNames,
|
|
1435
|
+
columns,
|
|
1436
|
+
allColumns,
|
|
1437
|
+
columnWidths,
|
|
1438
|
+
filterFields,
|
|
1439
|
+
filters,
|
|
1440
|
+
sorters,
|
|
1441
|
+
results,
|
|
1442
|
+
resultsStart,
|
|
1443
|
+
windowCache,
|
|
1444
|
+
windowLoading,
|
|
1445
|
+
topIndex,
|
|
1446
|
+
viewportRowCount,
|
|
1447
|
+
navViewportRowCount,
|
|
1448
|
+
querying,
|
|
1449
|
+
queryingNext,
|
|
1450
|
+
totalCount,
|
|
1451
|
+
loadedCount,
|
|
1452
|
+
pagination,
|
|
1453
|
+
queryError,
|
|
1454
|
+
metadataError,
|
|
1455
|
+
lastError,
|
|
1456
|
+
mustRefresh,
|
|
1457
|
+
searchTerm,
|
|
1458
|
+
configDialogOpen,
|
|
1459
|
+
configTab,
|
|
1460
|
+
filterDialogColumn,
|
|
1461
|
+
selectedRows,
|
|
1462
|
+
selectedCount,
|
|
1463
|
+
rowValueFn,
|
|
1464
|
+
isPkSelected,
|
|
1465
|
+
rowDelete,
|
|
1466
|
+
includeActions,
|
|
1467
|
+
activeIndex,
|
|
1468
|
+
navMode,
|
|
1469
|
+
hasMainActionListener,
|
|
1470
|
+
rowId,
|
|
1471
|
+
setActive,
|
|
1472
|
+
clearActive,
|
|
1473
|
+
toggleActiveSelection,
|
|
1474
|
+
requestMainAction,
|
|
1475
|
+
handleNavKey,
|
|
1476
|
+
registerMainActionListener,
|
|
1477
|
+
actions,
|
|
1478
|
+
confirmRequest,
|
|
1479
|
+
prompt: promptFn,
|
|
1480
|
+
acceptPrompt,
|
|
1481
|
+
dismissPrompt,
|
|
1482
|
+
actionFormRequest,
|
|
1483
|
+
requestActionInput,
|
|
1484
|
+
acceptActionForm,
|
|
1485
|
+
dismissActionForm,
|
|
1486
|
+
query,
|
|
1487
|
+
queryImmediate,
|
|
1488
|
+
queryNext,
|
|
1489
|
+
loadRange,
|
|
1490
|
+
invalidate,
|
|
1491
|
+
dataAt,
|
|
1492
|
+
loadingAt,
|
|
1493
|
+
errorAt,
|
|
1494
|
+
resetFilters() {
|
|
1495
|
+
if (Object.keys(filters.value).length === 0) return;
|
|
1496
|
+
filters.value = {};
|
|
1497
|
+
},
|
|
1498
|
+
showConfigDialog(tab) {
|
|
1499
|
+
configTab.value = tab ?? "columns";
|
|
1500
|
+
configDialogOpen.value = true;
|
|
1501
|
+
},
|
|
1502
|
+
addFilterField(path) {
|
|
1503
|
+
if (!filterFields.value.includes(path)) filterFields.value = [...filterFields.value, path];
|
|
1504
|
+
},
|
|
1505
|
+
removeFilterField(path) {
|
|
1506
|
+
if (!filterFields.value.includes(path)) return;
|
|
1507
|
+
filterFields.value = filterFields.value.filter((f) => f !== path);
|
|
1508
|
+
},
|
|
1509
|
+
setFieldFilter(path, conditions) {
|
|
1510
|
+
if (!conditions.some(isFilled)) {
|
|
1511
|
+
if (!(path in filters.value)) return;
|
|
1512
|
+
const { [path]: _, ...rest } = filters.value;
|
|
1513
|
+
filters.value = rest;
|
|
1514
|
+
} else filters.value = {
|
|
1515
|
+
...filters.value,
|
|
1516
|
+
[path]: conditions
|
|
1517
|
+
};
|
|
1518
|
+
},
|
|
1519
|
+
setColumnWidth(path, width) {
|
|
1520
|
+
writeColumnWidth(path, width);
|
|
1521
|
+
},
|
|
1522
|
+
resetColumnWidth(path) {
|
|
1523
|
+
const entry = columnWidths.value[path];
|
|
1524
|
+
if (entry) writeColumnWidth(path, entry.d);
|
|
1525
|
+
},
|
|
1526
|
+
removeFieldFilter(path) {
|
|
1527
|
+
const { [path]: _, ...rest } = filters.value;
|
|
1528
|
+
filters.value = rest;
|
|
1529
|
+
},
|
|
1530
|
+
openFilterDialog(column) {
|
|
1531
|
+
filterDialogColumn.value = column;
|
|
1532
|
+
},
|
|
1533
|
+
closeFilterDialog() {
|
|
1534
|
+
filterDialogColumn.value = null;
|
|
1535
|
+
},
|
|
1536
|
+
applyUrlQuery,
|
|
1537
|
+
preset: presetSlice
|
|
1538
|
+
};
|
|
1539
|
+
stateRef = state;
|
|
1540
|
+
let lastEmittedUrl = "";
|
|
1541
|
+
let hydratingFromUrl = false;
|
|
1542
|
+
const urlDefaultItemsPerPage = opts.limit ?? DEFAULT_ITEMS_PER_PAGE;
|
|
1543
|
+
const urlQuerySync = queryOpts?.urlQuerySync;
|
|
1544
|
+
function serializeStateForUrl() {
|
|
1545
|
+
return stateToUrlQueryString({
|
|
1546
|
+
filters: filters.value,
|
|
1547
|
+
sorters: sorters.value,
|
|
1548
|
+
page: pagination.value.page,
|
|
1549
|
+
itemsPerPage: pagination.value.itemsPerPage,
|
|
1550
|
+
searchTerm: searchTerm.value
|
|
1551
|
+
}, {
|
|
1552
|
+
defaultItemsPerPage: urlDefaultItemsPerPage,
|
|
1553
|
+
sync: urlQuerySync
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
function urlsEquivalent(a, b) {
|
|
1557
|
+
if (a === b) return true;
|
|
1558
|
+
try {
|
|
1559
|
+
return decodeURIComponent(a) === decodeURIComponent(b);
|
|
1560
|
+
} catch {
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function emitUrlIfChanged() {
|
|
1565
|
+
if (!queryOpts?.onUrlQueryChange) return;
|
|
1566
|
+
if (hydratingFromUrl) return;
|
|
1567
|
+
if (queryOpts.urlQueryReady && !queryOpts.urlQueryReady.value) return;
|
|
1568
|
+
const next = serializeStateForUrl();
|
|
1569
|
+
if (next === lastEmittedUrl) return;
|
|
1570
|
+
lastEmittedUrl = next;
|
|
1571
|
+
queryOpts.onUrlQueryChange(next);
|
|
1572
|
+
}
|
|
1573
|
+
function applyUrlQuery(urlString) {
|
|
1574
|
+
if (urlsEquivalent(urlString, lastEmittedUrl)) return;
|
|
1575
|
+
const cols = allColumns.value;
|
|
1576
|
+
const parsed = urlQueryStringToState(urlString, {
|
|
1577
|
+
knownFields: cols.length > 0 ? cols.map((c) => c.path) : void 0,
|
|
1578
|
+
sync: urlQuerySync
|
|
1579
|
+
});
|
|
1580
|
+
const currentItemsPerPage = pagination.value.itemsPerPage;
|
|
1581
|
+
const skip = parsed.skip ?? 0;
|
|
1582
|
+
const nextPage = skip > 0 && currentItemsPerPage > 0 ? Math.floor(skip / currentItemsPerPage) + 1 : 1;
|
|
1583
|
+
const wasQueryDetected = queryDetected;
|
|
1584
|
+
hydratingFromUrl = true;
|
|
1585
|
+
const filtersGate = resolveAspectGate(urlQuerySync?.filters);
|
|
1586
|
+
if (filtersGate !== "none") {
|
|
1587
|
+
const next = { ...filters.value };
|
|
1588
|
+
for (const path in parsed.filters) next[path] = parsed.filters[path];
|
|
1589
|
+
filters.value = next;
|
|
1590
|
+
}
|
|
1591
|
+
if (resolveAspectGate(urlQuerySync?.sorters) !== "none") {
|
|
1592
|
+
const urlFields = /* @__PURE__ */ new Set();
|
|
1593
|
+
for (const s of parsed.sorters) urlFields.add(s.field);
|
|
1594
|
+
const merged = [...sorters.value.filter((s) => !urlFields.has(s.field)), ...parsed.sorters];
|
|
1595
|
+
if (!sortersEqual(sorters.value, merged)) sorters.value = merged;
|
|
1596
|
+
}
|
|
1597
|
+
if (urlQuerySync?.search !== false) {
|
|
1598
|
+
if (searchTerm.value !== parsed.searchTerm) searchTerm.value = parsed.searchTerm;
|
|
1599
|
+
}
|
|
1600
|
+
if (urlQuerySync?.pagination !== false) {
|
|
1601
|
+
if (pagination.value.page !== nextPage) pagination.value = {
|
|
1602
|
+
...pagination.value,
|
|
1603
|
+
page: nextPage
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
if (filtersGate !== "none") {
|
|
1607
|
+
const present = new Set(filterFields.value);
|
|
1608
|
+
let merged = null;
|
|
1609
|
+
for (const f in parsed.filters) {
|
|
1610
|
+
if (present.has(f)) continue;
|
|
1611
|
+
(merged ??= filterFields.value.slice()).push(f);
|
|
1612
|
+
present.add(f);
|
|
1613
|
+
}
|
|
1614
|
+
if (merged) filterFields.value = merged;
|
|
1615
|
+
}
|
|
1616
|
+
lastEmittedUrl = serializeStateForUrl();
|
|
1617
|
+
nextTick(() => {
|
|
1618
|
+
hydratingFromUrl = false;
|
|
1619
|
+
if (wasQueryDetected && !queryOpts?.blockQuery && tableDef.value !== null) scheduleQuery();
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
const debouncedFilterQuery = debounce(() => {
|
|
1623
|
+
if (queryDetected) scheduleQuery();
|
|
1624
|
+
}, FILTER_DEBOUNCE_MS);
|
|
1625
|
+
watch([() => filters.value, () => searchTerm.value], () => {
|
|
1626
|
+
if (hydratingFromUrl) return;
|
|
1627
|
+
if (!queryDetected) return;
|
|
1628
|
+
mustRefresh.value = true;
|
|
1629
|
+
resetPagination();
|
|
1630
|
+
debouncedFilterQuery();
|
|
1631
|
+
});
|
|
1632
|
+
watch(() => sorters.value, (next, prev) => {
|
|
1633
|
+
if (hydratingFromUrl) return;
|
|
1634
|
+
if (sortersEqual(prev, next)) return;
|
|
1635
|
+
if (!queryDetected) return;
|
|
1636
|
+
requestRefresh();
|
|
1637
|
+
}, { immediate: false });
|
|
1638
|
+
watch(() => columnNames.value, (next, prev) => {
|
|
1639
|
+
if (!queryDetected) return;
|
|
1640
|
+
if (sameColumnSet(prev, next)) return;
|
|
1641
|
+
requestRefresh();
|
|
1642
|
+
}, { immediate: false });
|
|
1643
|
+
watch(() => pagination.value, (next, prev) => {
|
|
1644
|
+
if (skipPaginationWatch > 0) {
|
|
1645
|
+
skipPaginationWatch--;
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
if (hydratingFromUrl) return;
|
|
1649
|
+
if (next.page === prev.page && next.itemsPerPage === prev.itemsPerPage) return;
|
|
1650
|
+
if (!queryDetected) return;
|
|
1651
|
+
scheduleQuery();
|
|
1652
|
+
});
|
|
1653
|
+
watch([
|
|
1654
|
+
() => filters.value,
|
|
1655
|
+
() => sorters.value,
|
|
1656
|
+
() => searchTerm.value,
|
|
1657
|
+
() => pagination.value
|
|
1658
|
+
], () => emitUrlIfChanged());
|
|
1659
|
+
watch(() => includeActions.value, () => {
|
|
1660
|
+
if (!queryDetected) return;
|
|
1661
|
+
requestRefresh();
|
|
1662
|
+
});
|
|
1663
|
+
const presetGateOpen = () => opts.preset?.presetsHandle ? presetInternals.gate.value : true;
|
|
1664
|
+
watch([
|
|
1665
|
+
() => tableDef.value,
|
|
1666
|
+
() => queryOpts?.urlQueryReady?.value ?? true,
|
|
1667
|
+
presetGateOpen
|
|
1668
|
+
], ([def, urlReady, presetReady]) => {
|
|
1669
|
+
if (queryDetected) return;
|
|
1670
|
+
if (def === null || !urlReady || !presetReady) return;
|
|
1671
|
+
if (queryOpts?.queryOnMount === false) return;
|
|
1672
|
+
if (allColumns.value.length === 0) return;
|
|
1673
|
+
if (results.value.length !== 0) return;
|
|
1674
|
+
queryDetected = true;
|
|
1675
|
+
scheduleQuery("initial");
|
|
1676
|
+
});
|
|
1677
|
+
onScopeDispose(() => {
|
|
1678
|
+
debouncedFilterQuery.cancel();
|
|
1679
|
+
disposeDebounces();
|
|
1680
|
+
promptSlot.dismiss();
|
|
1681
|
+
formSlot.dismiss();
|
|
1682
|
+
});
|
|
1683
|
+
return {
|
|
1684
|
+
state,
|
|
1685
|
+
internals: {
|
|
1686
|
+
init(def) {
|
|
1687
|
+
allColumns.value = def.columns;
|
|
1688
|
+
const reconciled = reconcileColumnWidthDefaults(def.columns, columnWidths.value);
|
|
1689
|
+
if (reconciled !== columnWidths.value) columnWidths.value = reconciled;
|
|
1690
|
+
if (columnNames.value.length === 0) columnNames.value = getVisibleColumns(def).map((c) => c.path);
|
|
1691
|
+
tableDef.value = def;
|
|
1692
|
+
},
|
|
1693
|
+
resetPagination
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Build a `ReactiveTableState` backed by an in-memory row list. Used by the
|
|
1699
|
+
* enum value-help branch (`column.options`) where there's no client and no
|
|
1700
|
+
* metadata fetch — sorting/searching/pagination run locally against `rows`.
|
|
1701
|
+
*/
|
|
1702
|
+
function createStaticTableState(opts) {
|
|
1703
|
+
let _state = null;
|
|
1704
|
+
const queryFn = (q, page, size) => {
|
|
1705
|
+
if (!_state) return Promise.resolve({
|
|
1706
|
+
data: [],
|
|
1707
|
+
count: 0,
|
|
1708
|
+
page,
|
|
1709
|
+
itemsPerPage: size,
|
|
1710
|
+
pages: 1
|
|
1711
|
+
});
|
|
1712
|
+
return buildStaticQueryFn(opts, _state)(q, page, size);
|
|
1713
|
+
};
|
|
1714
|
+
const result = createTableState({
|
|
1715
|
+
client: {},
|
|
1716
|
+
selection: opts.selection,
|
|
1717
|
+
limit: opts.limit,
|
|
1718
|
+
query: { fn: queryFn }
|
|
1719
|
+
});
|
|
1720
|
+
_state = result.state;
|
|
1721
|
+
result.state.loadingMetadata.value = false;
|
|
1722
|
+
result.internals.init({
|
|
1723
|
+
type: void 0,
|
|
1724
|
+
columns: opts.columns,
|
|
1725
|
+
flatMap: /* @__PURE__ */ new Map(),
|
|
1726
|
+
primaryKeys: [],
|
|
1727
|
+
preferredId: [],
|
|
1728
|
+
crud: {
|
|
1729
|
+
query: [],
|
|
1730
|
+
pages: [],
|
|
1731
|
+
one: []
|
|
1732
|
+
},
|
|
1733
|
+
canRemove: false,
|
|
1734
|
+
actions: {
|
|
1735
|
+
table: [],
|
|
1736
|
+
row: [],
|
|
1737
|
+
rows: [],
|
|
1738
|
+
default: {}
|
|
1739
|
+
},
|
|
1740
|
+
searchable: (opts.searchPaths?.length ?? 0) > 0,
|
|
1741
|
+
vectorSearchable: false,
|
|
1742
|
+
searchIndexes: [],
|
|
1743
|
+
relations: []
|
|
1744
|
+
});
|
|
1745
|
+
return result;
|
|
1746
|
+
}
|
|
1747
|
+
function buildStaticQueryFn(opts, state) {
|
|
1748
|
+
const searchPaths = opts.searchPaths ?? [];
|
|
1749
|
+
return (_query, page, size) => {
|
|
1750
|
+
let filtered = opts.rows;
|
|
1751
|
+
const term = state.searchTerm.value.trim().toLowerCase();
|
|
1752
|
+
if (term && searchPaths.length > 0) filtered = filtered.filter((row) => searchPaths.some((p) => cellAsString(row[p]).toLowerCase().includes(term)));
|
|
1753
|
+
const active = state.sorters.value;
|
|
1754
|
+
if (active.length > 0) filtered = filtered.toSorted((a, b) => {
|
|
1755
|
+
for (const s of active) {
|
|
1756
|
+
const dir = s.direction === "desc" ? -1 : 1;
|
|
1757
|
+
const av = a[s.field];
|
|
1758
|
+
const bv = b[s.field];
|
|
1759
|
+
if (typeof av === "number" && typeof bv === "number") {
|
|
1760
|
+
if (av < bv) return -dir;
|
|
1761
|
+
if (av > bv) return dir;
|
|
1762
|
+
} else {
|
|
1763
|
+
const cmp = cellAsString(av).localeCompare(cellAsString(bv));
|
|
1764
|
+
if (cmp !== 0) return cmp * dir;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return 0;
|
|
1768
|
+
});
|
|
1769
|
+
const start = (page - 1) * size;
|
|
1770
|
+
return Promise.resolve({
|
|
1771
|
+
data: filtered.slice(start, start + size),
|
|
1772
|
+
count: filtered.length,
|
|
1773
|
+
page,
|
|
1774
|
+
itemsPerPage: size,
|
|
1775
|
+
pages: Math.max(1, Math.ceil(filtered.length / size))
|
|
1776
|
+
});
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
/** Provide the full table context to the component subtree. */
|
|
1780
|
+
function provideTableContext(ctx) {
|
|
1781
|
+
provide(TABLE_KEY, ctx);
|
|
1782
|
+
}
|
|
1783
|
+
/** Inject the full table context (throws if used outside as-table-root). */
|
|
1784
|
+
function useTableContext() {
|
|
1785
|
+
const ctx = inject(TABLE_KEY);
|
|
1786
|
+
if (!ctx) throw new Error("[vue-table] useTableContext() called outside of <as-table-root>.");
|
|
1787
|
+
return ctx;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Inject the table context if present; return undefined when no
|
|
1791
|
+
* `<as-table-root>` ancestor provided one. Use from components that may mount
|
|
1792
|
+
* inside or outside a table subtree (`<AsTableBase>` in combobox/listbox modes,
|
|
1793
|
+
* external composables that probe for context).
|
|
1794
|
+
*/
|
|
1795
|
+
function useTableContextOptional() {
|
|
1796
|
+
return inject(TABLE_KEY);
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Register `listener` as a main-action handler whenever `enabled` is truthy.
|
|
1800
|
+
* Reactive — toggling `enabled` registers / disposes live. Skipping
|
|
1801
|
+
* registration when `enabled` is false is what lets `handleNavKey` fall
|
|
1802
|
+
* back to `toggle-select` semantics; see `requestMainAction` early-return
|
|
1803
|
+
* gate. Callers detect "did the parent bind `@main-action`?" via
|
|
1804
|
+
* `useHasEmitListener("onMainAction")`.
|
|
1805
|
+
*/
|
|
1806
|
+
function useRegisterMainActionListener(state, listener, enabled) {
|
|
1807
|
+
let dispose = null;
|
|
1808
|
+
const stop = watch(() => toValue(enabled), (on) => {
|
|
1809
|
+
if (on && !dispose) dispose = state.registerMainActionListener(listener);
|
|
1810
|
+
else if (!on && dispose) {
|
|
1811
|
+
dispose();
|
|
1812
|
+
dispose = null;
|
|
1813
|
+
}
|
|
1814
|
+
}, { immediate: true });
|
|
1815
|
+
onBeforeUnmount(() => {
|
|
1816
|
+
stop();
|
|
1817
|
+
dispose?.();
|
|
1818
|
+
dispose = null;
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
//#endregion
|
|
1822
|
+
export { useTableContext as a, applyRowGate as c, extractIdentifier as d, intentClass as f, ROW_ACTIONS_TYPE as g, ROW_ACTIONS_PATH as h, useRegisterMainActionListener as i, ariaLabelFor as l, triggerAction as m, createTableState as n, useTableContextOptional as o, intentToScope as p, provideTableContext as r, DEFAULT_AVAILABLE_ASPECTS as s, createStaticTableState as t, collectIdentifiers as u };
|