@mono-lit/helper 0.0.8 → 0.0.10
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/accordion-core-DaNyPWRr.js +277 -0
- package/dist/alert-core-DWtHbX_-.js +134 -0
- package/dist/breadcrumb-list-core-Dh68T8Cf.js +747 -0
- package/dist/{mono-button-CLkHg3WW.js → button-core-DerXg84Q.js} +3 -276
- package/dist/button-dropdown-core-DKYEOyjy.js +654 -0
- package/dist/button-utils-D5-66M02.js +63 -0
- package/dist/card-utils-l80qAmrN.js +312 -0
- package/dist/chart-core-DV7hD3ZY.js +801 -0
- package/dist/checkbox-utils-DKdrhAzF.js +337 -0
- package/dist/chip-utils-DpRrd7Zj.js +403 -0
- package/dist/{data-source-controller-C6C4ej_M.js → data-source-controller-DS_JLrmS.js} +1 -1
- package/dist/{data-source-options-fxT5QIX4.js → data-source-options-DJUNU26C.js} +6 -6
- package/dist/{data-source-read-BL266IW0.js → data-source-read-uKJpRAzx.js} +2 -2
- package/dist/date-core-CyuSyvvG.js +734 -0
- package/dist/drawer-core-DbFWvNln.js +759 -0
- package/dist/dropdown-core-BCH1pK14.js +511 -0
- package/dist/dropdown-table-core-SfHUJVFq.js +1342 -0
- package/dist/export.js +1 -1
- package/dist/{field-path-BabiMrrA.js → field-path-DK2VBWHl.js} +1 -1
- package/dist/field-tooltip.css_raw-DVblekCl.js +5 -0
- package/dist/{field-validation.css_raw-Cx6SGrfP.js → field-validation.css_raw-BU7C6173.js} +1 -170
- package/dist/file-upload-core-Cz_dBQMx.js +624 -0
- package/dist/filter-builder-core-DyWjbBP7.js +381 -0
- package/dist/{filter-builder-B6Y2mmbM.js → filter-builder-vbqIuPhX.js} +1 -1
- package/dist/{filter-eval-ChDSaAUH.js → filter-eval-CqeBnMYh.js} +1 -1
- package/dist/{filter-odata-Bi27XUpQ.js → filter-odata-GsczYKNC.js} +1 -1
- package/dist/{hydration-flush-BaE9gN3C.js → hydration-flush-Volt9X7Q.js} +2 -1071
- package/dist/index.js +11 -11
- package/dist/{index.node-CLqE8F8P.js → index.node-Csy9zrr1.js} +4 -4
- package/dist/index.node.js +11 -11
- package/dist/input-utils-BglY-k0H.js +1145 -0
- package/dist/menu-core-DkAYuCZ2.js +831 -0
- package/dist/modal-core-CSBSPN_m.js +844 -0
- package/dist/mono-button-DVvZ03P-.js +280 -0
- package/dist/{mono-button.shadow-DqZMpcqY.js → mono-button.shadow-BANxMGln.js} +6 -594
- package/dist/{mono-data-chart-NKPdAgVq.js → mono-data-chart-ChF4J9RT.js} +6 -6
- package/dist/{mono-data-dropdown-DUXo_jxd.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
- package/dist/{mono-data-grid-Bb00QM72.js → mono-data-grid-DJkp96N-.js} +346 -51
- package/dist/{mono-event-BPku9HpS.js → mono-event-CAZf0F_k.js} +14 -9
- package/dist/{mono-form-controller-BrA10erm.js → mono-form-controller-DxZLvyg6.js} +2 -1
- package/dist/mono-table-checkbox-core-BKOMx8R5.js +5288 -0
- package/dist/nav-core-B5qU2dzn.js +225 -0
- package/dist/{normalize-error-D93O4q8g.js → normalize-error-BEcd1tmS.js} +2 -2
- package/dist/otp-core-DXg3vFLh.js +758 -0
- package/dist/radio-utils-Dk9Tl1jz.js +319 -0
- package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
- package/dist/select-utils-BgArYHk8.js +2140 -0
- package/dist/sidebar-core-CD6txDo3.js +583 -0
- package/dist/slider-core-vcWr321-.js +1014 -0
- package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
- package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
- package/dist/src/components/dropdown-table/index.d.ts +1 -1
- package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
- package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
- package/dist/src/components/form/form-types.d.ts +9 -3
- package/dist/src/components/input/input-core.d.ts +1 -1
- package/dist/src/components/input/input-types.d.ts +1 -1
- package/dist/src/components/otp/otp-core.d.ts +1 -1
- package/dist/src/components/select/select-core.d.ts +1 -1
- package/dist/src/components/select/select-types.d.ts +1 -1
- package/dist/src/components/slider/slider-core.d.ts +1 -1
- package/dist/src/components/slider/slider-types.d.ts +1 -1
- package/dist/src/components/table/filter-suggestion.d.ts +171 -0
- package/dist/src/components/table/index.d.ts +5 -4
- package/dist/src/components/table/index.shadow.d.ts +5 -4
- package/dist/src/components/table/mono-data-grid.d.ts +58 -3
- package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
- package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
- package/dist/src/components/table/table-types.d.ts +74 -4
- package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
- package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
- package/dist/src/components/textarea/textarea-core.d.ts +1 -1
- package/dist/src/composables/mono-event.d.ts +7 -1
- package/dist/src/entries/index.d.ts +8 -6
- package/dist/src/entries/index.node.d.ts +8 -6
- package/dist/src/search/index.d.ts +1 -1
- package/dist/src/utils/group-count.d.ts +36 -0
- package/dist/switch-utils-CTZ4id2V.js +338 -0
- package/dist/tabs-core-r5-B8k_N.js +317 -0
- package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
- package/dist/textarea-utils-VU1KeXm8.js +821 -0
- package/dist/ui/accordion.js +2 -274
- package/dist/ui/alert.js +2 -131
- package/dist/ui/breadcrumb.js +4 -745
- package/dist/ui/button-dropdown.js +4 -652
- package/dist/ui/button.js +2 -63
- package/dist/ui/card.js +2 -310
- package/dist/ui/chart.js +4 -800
- package/dist/ui/checkbox.js +4 -335
- package/dist/ui/chip.js +4 -402
- package/dist/ui/date.js +3 -732
- package/dist/ui/drawer.js +3 -757
- package/dist/ui/dropdown-table.js +5 -1341
- package/dist/ui/dropdown.js +4 -509
- package/dist/ui/file-upload.js +3 -622
- package/dist/ui/filter.js +5 -380
- package/dist/ui/form.js +1 -1
- package/dist/ui/index.css +130 -5
- package/dist/ui/input.js +3 -1141
- package/dist/ui/menu.js +4 -828
- package/dist/ui/modal.js +4 -843
- package/dist/ui/nav.js +4 -223
- package/dist/ui/otp.js +4 -756
- package/dist/ui/radio.js +4 -317
- package/dist/ui/rich-text-editor.js +4 -1278
- package/dist/ui/select.js +5 -2138
- package/dist/ui/shadow/accordion.js +4 -273
- package/dist/ui/shadow/alert.js +4 -130
- package/dist/ui/shadow/breadcrumb.js +5 -744
- package/dist/ui/shadow/button-dropdown.js +6 -652
- package/dist/ui/shadow/button.js +3 -63
- package/dist/ui/shadow/card.js +4 -309
- package/dist/ui/shadow/chart.js +6 -1576
- package/dist/ui/shadow/checkbox.js +4 -334
- package/dist/ui/shadow/chip.js +4 -396
- package/dist/ui/shadow/date.js +7 -731
- package/dist/ui/shadow/drawer.js +4 -756
- package/dist/ui/shadow/dropdown-table.js +8 -1762
- package/dist/ui/shadow/dropdown.js +4 -508
- package/dist/ui/shadow/file-upload.js +4 -620
- package/dist/ui/shadow/filter.js +7 -379
- package/dist/ui/shadow/input.js +6 -1140
- package/dist/ui/shadow/menu.js +5 -828
- package/dist/ui/shadow/modal.js +6 -1063
- package/dist/ui/shadow/nav.js +5 -223
- package/dist/ui/shadow/otp.js +6 -754
- package/dist/ui/shadow/radio.js +4 -316
- package/dist/ui/shadow/rich-text-editor.js +4 -1276
- package/dist/ui/shadow/select.js +6 -2136
- package/dist/ui/shadow/sidebar.js +5 -581
- package/dist/ui/shadow/slider.js +5 -1010
- package/dist/ui/shadow/switch.js +4 -335
- package/dist/ui/shadow/table.js +10 -4501
- package/dist/ui/shadow/tabs.js +6 -315
- package/dist/ui/shadow/tag-input.js +7 -3057
- package/dist/ui/shadow/textarea.js +6 -817
- package/dist/ui/sidebar.js +3 -581
- package/dist/ui/slider.js +4 -1012
- package/dist/ui/switch.js +4 -336
- package/dist/ui/table.js +9 -4502
- package/dist/ui/tabs.js +4 -315
- package/dist/ui/tag-input.js +5 -3058
- package/dist/ui/textarea.js +4 -819
- package/dist/vue.d.ts +3 -2
- package/package.json +3 -3
- package/dist/chip-strip-bNfLn52Z.js +0 -215
- package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
- package/dist/data-source-options-BM4r4PRw.js +0 -558
- package/dist/data-source-read-HyBCRg1t.js +0 -242
- package/dist/excel-BXzr3uI1.js +0 -784
- package/dist/export-ixRdCS2y.js +0 -2388
- package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
- package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
- package/dist/filter-builder-C0T3pMx2.js +0 -208
- package/dist/form-control-core-BbiACMtL.js +0 -226
- package/dist/icon-ClxnYhBG.js +0 -19
- package/dist/import-Dc4eXC02.js +0 -612
- package/dist/light-slots-_no0r2Z-.js +0 -246
- package/dist/markdown-uGlE_OfJ.js +0 -20191
- package/dist/mono-data-grid-Bkr-e1-i.js +0 -4043
- package/dist/normalize-error-BmLbgANw.js +0 -1048
package/dist/ui/table.js
CHANGED
|
@@ -1,1093 +1,15 @@
|
|
|
1
|
-
import { a as parseFieldPath, c as toODataClause, l as toODataSelector, n as isPath, r as mergePatch, s as setFieldValue, t as getFieldValue } from "../field-path-
|
|
2
|
-
import { a as buildGroups, c as isGroupNode, o as collectGroupPaths, s as flattenLeaves } from "../data-source-read-
|
|
3
|
-
import { A as resolveSearchEntries, D as isWildcardPattern, O as normalizeSearchExpr, c as resolveErrorMessages,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { _ as monoHostChildren
|
|
7
|
-
import { n as detachEventHandlers, t as applyProps } from "../element-props-xvw0EB7H.js";
|
|
1
|
+
import { a as parseFieldPath, c as toODataClause, l as toODataSelector, n as isPath, r as mergePatch, s as setFieldValue, t as getFieldValue } from "../field-path-DK2VBWHl.js";
|
|
2
|
+
import { a as buildGroups, c as isGroupNode, o as collectGroupPaths, s as flattenLeaves } from "../data-source-read-uKJpRAzx.js";
|
|
3
|
+
import { A as resolveSearchEntries, D as isWildcardPattern, O as normalizeSearchExpr, c as resolveErrorMessages, k as plainSearchColumns, l as setErrorMessages, n as describeError, r as errorStatus, s as normalizeError, t as DEFAULT_ERROR_MESSAGES, w as expandWildcard } from "../normalize-error-BEcd1tmS.js";
|
|
4
|
+
import { _ as monoArraySource } from "../data-source-options-DJUNU26C.js";
|
|
5
|
+
import { t as monoDataGrid } from "../mono-data-grid-DJkp96N-.js";
|
|
6
|
+
import { _ as monoHostChildren } from "../mono-ui-kvLVIf1V.js";
|
|
8
7
|
import { t as customElement } from "../mono-element-CKcT0rwv.js";
|
|
9
|
-
import {
|
|
10
|
-
import { t as dispatchMonoEvent } from "../mono-event-BPku9HpS.js";
|
|
11
|
-
import { t as isIconifyClass } from "../icon-ClxnYhBG.js";
|
|
8
|
+
import { t as __decorate } from "../decorate-DbYFpi8R.js";
|
|
12
9
|
import { n as bucketHasContent, o as placeLightSlots, r as captureLightSlots, s as placeSlotNode } from "../light-slots-B5Ck0t_E.js";
|
|
13
|
-
import { t as
|
|
14
|
-
import {
|
|
15
|
-
import { LitElement, html, isServer, nothing } from "lit";
|
|
16
|
-
import { property, query, state } from "lit/decorators.js";
|
|
17
|
-
import { ifDefined } from "lit/directives/if-defined.js";
|
|
18
|
-
import { styleMap } from "lit/directives/style-map.js";
|
|
10
|
+
import { a as MonoTableErrorCore, c as MonoTableSortCore, d as MonoTablePagingGroupCore, f as MonoTablePagingCore, i as MonoTableLoadingCore, l as MonoTableInfoCore, n as MonoTableDetailCore, o as MonoTableEmptyCore, p as MonoTableSearchCore, r as MonoTableSummaryCore, s as MonoTableThCore, t as MonoTableCheckboxCore, u as MonoTablePageSizeCore } from "../mono-table-checkbox-core-BKOMx8R5.js";
|
|
11
|
+
import { LitElement, html } from "lit";
|
|
19
12
|
|
|
20
|
-
//#region src/components/table/table-controller-core.ts
|
|
21
|
-
/**
|
|
22
|
-
* `MonoTableControllerCore` — the render-mode-agnostic plumbing shared by every
|
|
23
|
-
* `mono-table-*` element: the `dataGrid` controller property (bound via Vue
|
|
24
|
-
* `.prop`, so `attribute: false`), its hybrid alias, and the
|
|
25
|
-
* subscribe/unsubscribe lifecycle that re-renders the element whenever the
|
|
26
|
-
* controller notifies.
|
|
27
|
-
*
|
|
28
|
-
* The six per-element cores compose this base, so their light/shadow wrappers
|
|
29
|
-
* only ever call `MonoTable<El>Core(LitElement)`. Elements needing extra
|
|
30
|
-
* lifecycle work (e.g. `mono-table-paging-group` registers/clears group paging)
|
|
31
|
-
* override the lifecycle methods and chain `super.*` to keep this plumbing.
|
|
32
|
-
*
|
|
33
|
-
* SSR-safe: holds no `document`/`window` access. `dataGrid` is undefined on the
|
|
34
|
-
* server (a `.prop` binding can't cross Declarative Shadow DOM), so `_subscribe`
|
|
35
|
-
* is a no-op there and the element renders its deterministic empty shell.
|
|
36
|
-
*/
|
|
37
|
-
const MonoTableControllerCore = (superClass) => {
|
|
38
|
-
class MonoTableControllerCoreClass extends superClass {
|
|
39
|
-
constructor(...args) {
|
|
40
|
-
super(...args);
|
|
41
|
-
this._applyQueued = false;
|
|
42
|
-
defineHybridPropAliases(this, ["dataGrid"]);
|
|
43
|
-
defineHybridPropAlias(this, "controlTable", "dataGrid");
|
|
44
|
-
}
|
|
45
|
-
static {
|
|
46
|
-
this.monoPendingAuto = "data";
|
|
47
|
-
}
|
|
48
|
-
static {
|
|
49
|
-
this.monoPendingDraw = "css";
|
|
50
|
-
}
|
|
51
|
-
/** "First load done" for the automatic `pending` (re-checked on every controller notify). */
|
|
52
|
-
_monoPendingReady() {
|
|
53
|
-
const grid = this.dataGrid;
|
|
54
|
-
if (!grid) return monoPendingGrace(this);
|
|
55
|
-
if (grid.hasLoaded || grid.error) return true;
|
|
56
|
-
return monoPendingGrace(this) && !grid.loading;
|
|
57
|
-
}
|
|
58
|
-
connectedCallback() {
|
|
59
|
-
super.connectedCallback();
|
|
60
|
-
this._subscribe();
|
|
61
|
-
}
|
|
62
|
-
disconnectedCallback() {
|
|
63
|
-
this._off?.();
|
|
64
|
-
this._off = void 0;
|
|
65
|
-
super.disconnectedCallback();
|
|
66
|
-
}
|
|
67
|
-
willUpdate(changed) {
|
|
68
|
-
if (changed.has("dataGrid")) this._subscribe();
|
|
69
|
-
super.willUpdate?.(changed);
|
|
70
|
-
}
|
|
71
|
-
_subscribe() {
|
|
72
|
-
this._off?.();
|
|
73
|
-
detachEventHandlers(this);
|
|
74
|
-
this._lastStateKey = void 0;
|
|
75
|
-
this._off = this.dataGrid?.subscribe(() => {
|
|
76
|
-
if (this._stateUnchanged()) return;
|
|
77
|
-
this._scheduleApplyProps();
|
|
78
|
-
this.requestUpdate();
|
|
79
|
-
});
|
|
80
|
-
this._scheduleApplyProps();
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* What this element renders from the CONTROLLER, as a flat list — or `undefined` (the
|
|
84
|
-
* default) for "re-render on every notify", which is what every element did.
|
|
85
|
-
*
|
|
86
|
-
* An element that defines it skips a notify whose key equals the last one. Worth it for the
|
|
87
|
-
* per-row elements: every `<mono-table-checkbox>` re-rendered on every notify (a load flag, a
|
|
88
|
-
* summary, another row's tick), N updates for a change that moves one box. Its own props are
|
|
89
|
-
* unaffected — a Lit property change still updates it the normal way.
|
|
90
|
-
*/
|
|
91
|
-
_stateKey() {}
|
|
92
|
-
_stateUnchanged() {
|
|
93
|
-
const key = this._stateKey();
|
|
94
|
-
if (!key) return false;
|
|
95
|
-
const last = this._lastStateKey;
|
|
96
|
-
this._lastStateKey = key;
|
|
97
|
-
return !!last && last.length === key.length && key.every((v, i) => Object.is(v, last[i]));
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Drive the apply from `update()` as well as `_subscribe()`.
|
|
101
|
-
*
|
|
102
|
-
* `mono-table-paging` and `mono-table-loading` REPLACE `_subscribe` without
|
|
103
|
-
* chaining `super`, so hanging the apply off that alone silently skipped
|
|
104
|
-
* them — and the next core to override it would break the same way. Every
|
|
105
|
-
* element still reaches `update()` (nothing overrides it), and those cores
|
|
106
|
-
* call `requestUpdate()` on notify, so this path always runs.
|
|
107
|
-
*/
|
|
108
|
-
update(changed) {
|
|
109
|
-
this._scheduleApplyProps();
|
|
110
|
-
super.update(changed);
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Deferred + de-duplicated: the apply writes reactive props, and doing that
|
|
114
|
-
* inside the update cycle would trip Lit's change-in-update warning.
|
|
115
|
-
*/
|
|
116
|
-
_scheduleApplyProps() {
|
|
117
|
-
if (this._applyQueued) return;
|
|
118
|
-
if (typeof queueMicrotask !== "function") {
|
|
119
|
-
this._applyControllerProps();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
this._applyQueued = true;
|
|
123
|
-
queueMicrotask(() => {
|
|
124
|
-
this._applyQueued = false;
|
|
125
|
-
this._applyControllerProps();
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Pull this element's slice of `monoDataGrid({ props })` onto itself.
|
|
130
|
-
*
|
|
131
|
-
* The CONTROLLER WINS for keys it declares (matching `monoForm`'s `setProp`);
|
|
132
|
-
* keys it doesn't mention are left to whatever the template set. Every write
|
|
133
|
-
* is equality-guarded so a sync can't trigger another update cycle.
|
|
134
|
-
*/
|
|
135
|
-
_applyControllerProps() {
|
|
136
|
-
const slot = this._propsSlot;
|
|
137
|
-
const grid = this.dataGrid;
|
|
138
|
-
if (!slot || !grid?.props) return;
|
|
139
|
-
const all = grid.props();
|
|
140
|
-
let patch;
|
|
141
|
-
if (slot === "th" || slot === "sort" || slot === "summary") {
|
|
142
|
-
const field = this.field;
|
|
143
|
-
if (!field) return;
|
|
144
|
-
const col = (all.th ?? []).find((c) => c.field === field);
|
|
145
|
-
if (!col) return;
|
|
146
|
-
if (slot === "th") {
|
|
147
|
-
const { summary: _s, ...rest } = col;
|
|
148
|
-
patch = rest;
|
|
149
|
-
} else {
|
|
150
|
-
const sub = col[slot];
|
|
151
|
-
if (!sub) return;
|
|
152
|
-
patch = {
|
|
153
|
-
field,
|
|
154
|
-
...sub
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
} else patch = all[slot];
|
|
158
|
-
applyProps(this, patch);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
__decorate([property({ attribute: false })], MonoTableControllerCoreClass.prototype, "dataGrid", void 0);
|
|
162
|
-
return MonoTableControllerCoreClass;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
//#region src/components/table/mono-table-search-core.ts
|
|
167
|
-
/**
|
|
168
|
-
* `MonoTableSearchCore` — render-mode-agnostic logic for `mono-table-search`: a
|
|
169
|
-
* debounced search box wired to the controller.
|
|
170
|
-
*
|
|
171
|
-
* It renders **`mono-input`'s markup and class names** (`.mono-input` →
|
|
172
|
-
* `.mono-input-field` → `.mono-input-native`) rather than a bespoke box, so the
|
|
173
|
-
* whole size × color × variant matrix in `input.css` applies verbatim and a
|
|
174
|
-
* search sitting beside a `<mono-input>` in a toolbar matches it exactly. That is
|
|
175
|
-
* also why the prop names, attribute names and converters below mirror
|
|
176
|
-
* `input-core.ts` — markup is portable between the two elements.
|
|
177
|
-
*
|
|
178
|
-
* Deliberately NOT taken from `InputProps`: `type` (pinned to `search`),
|
|
179
|
-
* `required` / `pattern` / `min` / `max` / `step` (form-submit constraints with no
|
|
180
|
-
* meaning for a filter), and `value` / `modelValue` — the debounce below plus the
|
|
181
|
-
* controller own the search term.
|
|
182
|
-
*
|
|
183
|
-
* The magnifier and clear glyphs are delegated to `renderIcon()` (light: UnoCSS
|
|
184
|
-
* `.mono-icon i-mdi-*`; shadow: inline SVG) so the chrome is otherwise identical.
|
|
185
|
-
*/
|
|
186
|
-
const MonoTableSearchCore = (superClass) => {
|
|
187
|
-
class MonoTableSearchCoreClass extends MonoTableControllerCore(superClass) {
|
|
188
|
-
constructor(...args) {
|
|
189
|
-
super(...args);
|
|
190
|
-
this._propsSlot = "search";
|
|
191
|
-
this.searchValue = "";
|
|
192
|
-
this.searchExpr = "";
|
|
193
|
-
this.placeholder = "Search…";
|
|
194
|
-
this.disabled = false;
|
|
195
|
-
this.debounce = 300;
|
|
196
|
-
this.noIcon = false;
|
|
197
|
-
this.size = "md";
|
|
198
|
-
this.color = "primary";
|
|
199
|
-
this.variant = "outlined";
|
|
200
|
-
this.readonly = false;
|
|
201
|
-
this.clearable = false;
|
|
202
|
-
this.autofocus = false;
|
|
203
|
-
this.label = "";
|
|
204
|
-
this.helperText = "";
|
|
205
|
-
this.validationState = "default";
|
|
206
|
-
this.validationMessage = "";
|
|
207
|
-
this.error = false;
|
|
208
|
-
this.errorMessage = "";
|
|
209
|
-
this.success = false;
|
|
210
|
-
this.successMessage = "";
|
|
211
|
-
this.name = "";
|
|
212
|
-
this.autocomplete = "";
|
|
213
|
-
this.inputmode = "";
|
|
214
|
-
this.cssClass = {};
|
|
215
|
-
this.cssClassName = "";
|
|
216
|
-
this.suggestion = false;
|
|
217
|
-
this.multiContext = false;
|
|
218
|
-
this.suggestionTemplate = "Search {caption} for: {term}";
|
|
219
|
-
this.allFieldsLabel = "All fields";
|
|
220
|
-
this.maxChips = 1;
|
|
221
|
-
this.moreLabel = "See All";
|
|
222
|
-
this.filterLabel = "Filter";
|
|
223
|
-
this._value = "";
|
|
224
|
-
this._chips = [];
|
|
225
|
-
this._open = false;
|
|
226
|
-
this._activeIndex = 0;
|
|
227
|
-
this._zone = "input";
|
|
228
|
-
this._moreOpen = false;
|
|
229
|
-
this._chipsDragging = false;
|
|
230
|
-
this._filterSlotted = false;
|
|
231
|
-
this._filterOpen = false;
|
|
232
|
-
this._filterChip = null;
|
|
233
|
-
this._tabHeld = false;
|
|
234
|
-
this._tabConsumed = false;
|
|
235
|
-
this._chipsDragId = null;
|
|
236
|
-
this._chipsDragStartX = 0;
|
|
237
|
-
this._chipsDragStartScroll = 0;
|
|
238
|
-
this._chipsDragMoved = false;
|
|
239
|
-
this._onChipsPointerDown = (e) => {
|
|
240
|
-
if (e.button !== 0) return;
|
|
241
|
-
const strip = e.currentTarget;
|
|
242
|
-
if (strip.scrollWidth <= strip.clientWidth) return;
|
|
243
|
-
if (e.target.closest("button")) return;
|
|
244
|
-
this._chipsDragId = e.pointerId;
|
|
245
|
-
this._chipsDragStartX = e.clientX;
|
|
246
|
-
this._chipsDragStartScroll = strip.scrollLeft;
|
|
247
|
-
this._chipsDragMoved = false;
|
|
248
|
-
try {
|
|
249
|
-
strip.setPointerCapture?.(e.pointerId);
|
|
250
|
-
} catch {}
|
|
251
|
-
strip.addEventListener("pointermove", this._onChipsPointerMove);
|
|
252
|
-
strip.addEventListener("pointerup", this._onChipsPointerUp);
|
|
253
|
-
strip.addEventListener("pointercancel", this._onChipsPointerUp);
|
|
254
|
-
};
|
|
255
|
-
this._onChipsPointerMove = (e) => {
|
|
256
|
-
if (e.pointerId !== this._chipsDragId) return;
|
|
257
|
-
const strip = e.currentTarget;
|
|
258
|
-
const dx = e.clientX - this._chipsDragStartX;
|
|
259
|
-
if (!this._chipsDragMoved) {
|
|
260
|
-
if (Math.abs(dx) < 5) return;
|
|
261
|
-
this._chipsDragMoved = true;
|
|
262
|
-
this._chipsDragging = true;
|
|
263
|
-
}
|
|
264
|
-
strip.scrollLeft = this._chipsDragStartScroll - dx;
|
|
265
|
-
e.preventDefault();
|
|
266
|
-
};
|
|
267
|
-
this._onChipsPointerUp = (e) => {
|
|
268
|
-
const strip = e.currentTarget;
|
|
269
|
-
if (e.pointerId === this._chipsDragId) try {
|
|
270
|
-
if (strip.hasPointerCapture?.(e.pointerId)) strip.releasePointerCapture?.(e.pointerId);
|
|
271
|
-
} catch {}
|
|
272
|
-
strip.removeEventListener("pointermove", this._onChipsPointerMove);
|
|
273
|
-
strip.removeEventListener("pointerup", this._onChipsPointerUp);
|
|
274
|
-
strip.removeEventListener("pointercancel", this._onChipsPointerUp);
|
|
275
|
-
this._chipsDragId = null;
|
|
276
|
-
if (this._chipsDragMoved) {
|
|
277
|
-
this._detachChipsSwallow();
|
|
278
|
-
const swallow = (ev) => {
|
|
279
|
-
ev.preventDefault();
|
|
280
|
-
ev.stopPropagation();
|
|
281
|
-
this._chipsDragMoved = false;
|
|
282
|
-
this._chipsDragging = false;
|
|
283
|
-
this._detachChipsSwallow();
|
|
284
|
-
};
|
|
285
|
-
this._chipsSwallow = {
|
|
286
|
-
strip,
|
|
287
|
-
fn: swallow
|
|
288
|
-
};
|
|
289
|
-
strip.addEventListener("click", swallow, true);
|
|
290
|
-
} else this._chipsDragging = false;
|
|
291
|
-
};
|
|
292
|
-
this._chipsSwallow = null;
|
|
293
|
-
this._onKeydown = (e) => {
|
|
294
|
-
if (e.key === "Escape" && this._filterOpen && !this._open) {
|
|
295
|
-
e.preventDefault();
|
|
296
|
-
this._closeFilter();
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (e.key === "Escape" && this._moreOpen && !this._open) {
|
|
300
|
-
e.preventDefault();
|
|
301
|
-
this._closeMore();
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (!this._open) return;
|
|
305
|
-
if (e.key === "Escape") {
|
|
306
|
-
e.preventDefault();
|
|
307
|
-
this._closePanel();
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (e.key === "Tab") {
|
|
311
|
-
e.preventDefault();
|
|
312
|
-
if (!e.repeat) {
|
|
313
|
-
this._tabHeld = true;
|
|
314
|
-
this._tabConsumed = false;
|
|
315
|
-
}
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (this._tabHeld && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
319
|
-
e.preventDefault();
|
|
320
|
-
this._tabConsumed = true;
|
|
321
|
-
this._zone = e.key === "ArrowDown" ? "list" : "input";
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
325
|
-
if (this._zone !== "list") return;
|
|
326
|
-
e.preventDefault();
|
|
327
|
-
const n = this._suggestions().length;
|
|
328
|
-
if (!n) return;
|
|
329
|
-
const step = e.key === "ArrowDown" ? 1 : -1;
|
|
330
|
-
this._activeIndex = (this._activeIndex + step + n) % n;
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (e.key === "Enter") {
|
|
334
|
-
e.preventDefault();
|
|
335
|
-
this._acceptActive();
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
this._onKeyup = (e) => {
|
|
339
|
-
if (e.key !== "Tab" || !this._tabHeld) return;
|
|
340
|
-
this._tabHeld = false;
|
|
341
|
-
if (this._tabConsumed) return;
|
|
342
|
-
this._zone = this._zone === "input" ? "list" : "input";
|
|
343
|
-
};
|
|
344
|
-
defineHybridPropAliases(this, [
|
|
345
|
-
"searchValue",
|
|
346
|
-
"searchExpr",
|
|
347
|
-
"multiContext",
|
|
348
|
-
"suggestionTemplate",
|
|
349
|
-
"allFieldsLabel",
|
|
350
|
-
"maxChips",
|
|
351
|
-
"moreLabel",
|
|
352
|
-
"noIcon",
|
|
353
|
-
"helperText",
|
|
354
|
-
"validationState",
|
|
355
|
-
"validationMessage",
|
|
356
|
-
"errorMessage",
|
|
357
|
-
"successMessage",
|
|
358
|
-
"ariaLabelText",
|
|
359
|
-
"minLength",
|
|
360
|
-
"maxLength",
|
|
361
|
-
"minWidth",
|
|
362
|
-
"maxWidth",
|
|
363
|
-
"minHeight",
|
|
364
|
-
"maxHeight",
|
|
365
|
-
"cssClass"
|
|
366
|
-
]);
|
|
367
|
-
}
|
|
368
|
-
disconnectedCallback() {
|
|
369
|
-
if (this._timer) clearTimeout(this._timer);
|
|
370
|
-
this._teardownDocListener();
|
|
371
|
-
this._teardownMoreDocListener();
|
|
372
|
-
this._teardownFilterDocListener();
|
|
373
|
-
this._detachChipsSwallow();
|
|
374
|
-
super.disconnectedCallback();
|
|
375
|
-
}
|
|
376
|
-
willUpdate(changed) {
|
|
377
|
-
super.willUpdate(changed);
|
|
378
|
-
if (changed.has("searchValue") || changed.has("searchExpr") || changed.has("dataGrid")) this._pushSearchFields();
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Hand the declared fields to the controller.
|
|
382
|
-
*
|
|
383
|
-
* Only pushes when something was actually declared — an untouched element must
|
|
384
|
-
* not clobber `monoDataGrid({ searchExpr })` with an empty list. The
|
|
385
|
-
* stringified guard matters because `_applyControllerProps` re-applies
|
|
386
|
-
* `props.search` on every controller notify, and `setSearchExpr` re-runs the
|
|
387
|
-
* live query: without it, searching would loop.
|
|
388
|
-
*/
|
|
389
|
-
_pushSearchFields() {
|
|
390
|
-
const grid = this.dataGrid;
|
|
391
|
-
const setFields = grid?.setSearchValue ?? grid?.setSearchExpr;
|
|
392
|
-
if (!grid || typeof setFields !== "function") return;
|
|
393
|
-
const entries = mergeSearchFields(this);
|
|
394
|
-
if (!entries?.length) return;
|
|
395
|
-
const key = JSON.stringify(entries.map((e) => typeof e === "string" ? e : e.field));
|
|
396
|
-
if (key === this._pushedFields) return;
|
|
397
|
-
this._pushedFields = key;
|
|
398
|
-
setFields.call(grid, entries);
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Delegate focus to the inner `<input>` — `@query` resolves against
|
|
402
|
-
* `renderRoot`, so this reaches the input in BOTH builds (light: the host
|
|
403
|
-
* itself; shadow: the shadow root). Consumers (e.g. `<mono-dropdown-table>`
|
|
404
|
-
* auto-focusing its search region) can then just call `el.focus()` without
|
|
405
|
-
* having to know which build they hold or reach across a shadow boundary.
|
|
406
|
-
*/
|
|
407
|
-
focus(options) {
|
|
408
|
-
this._inputEl?.focus(options);
|
|
409
|
-
}
|
|
410
|
-
blur() {
|
|
411
|
-
this._inputEl?.blur();
|
|
412
|
-
}
|
|
413
|
-
/** Everything currently searched: committed chips plus the live input. */
|
|
414
|
-
_terms(extra) {
|
|
415
|
-
const out = [...this._chips];
|
|
416
|
-
if (extra) out.push(extra);
|
|
417
|
-
else if (this._value && !this.multiContext) out.push({ value: this._value });
|
|
418
|
-
return out;
|
|
419
|
-
}
|
|
420
|
-
_push(value) {
|
|
421
|
-
const grid = this.dataGrid;
|
|
422
|
-
if (!grid) return;
|
|
423
|
-
if (this.multiContext) {
|
|
424
|
-
grid.setSearchTerms?.(this._chips);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (typeof grid.setSearchTerms === "function") grid.setSearchTerms([{ value }].filter((t) => t.value));
|
|
428
|
-
else grid.setSearch(value);
|
|
429
|
-
}
|
|
430
|
-
/** Suggestion rows: an all-columns row, then one per registered `mono-table-th`. */
|
|
431
|
-
_suggestions() {
|
|
432
|
-
const grid = this.dataGrid;
|
|
433
|
-
if (!grid || typeof grid.registeredColumns !== "function") return [];
|
|
434
|
-
const cols = grid.registeredColumns().filter((c) => !!c.field).map((c) => ({
|
|
435
|
-
field: c.field,
|
|
436
|
-
caption: c.caption || c.field
|
|
437
|
-
}));
|
|
438
|
-
return [{ caption: this.allFieldsLabel }, ...cols];
|
|
439
|
-
}
|
|
440
|
-
_suggestionLabel(s) {
|
|
441
|
-
if (!s.field) return `${this.allFieldsLabel}: ${this._value}`;
|
|
442
|
-
return this.suggestionTemplate.replace("{caption}", s.caption).replace("{term}", this._value);
|
|
443
|
-
}
|
|
444
|
-
get _canSuggest() {
|
|
445
|
-
return this.suggestion && !this.disabled && !this.readonly && !!this._value;
|
|
446
|
-
}
|
|
447
|
-
_openPanel() {
|
|
448
|
-
if (isServer || !this._canSuggest) return;
|
|
449
|
-
this._closeMore();
|
|
450
|
-
this._closeFilter();
|
|
451
|
-
this._ensurePopup();
|
|
452
|
-
this._activeIndex = 0;
|
|
453
|
-
this._zone = "input";
|
|
454
|
-
this._open = true;
|
|
455
|
-
this._bindDocListener();
|
|
456
|
-
}
|
|
457
|
-
_closePanel() {
|
|
458
|
-
this._open = false;
|
|
459
|
-
this._zone = "input";
|
|
460
|
-
this._tabHeld = false;
|
|
461
|
-
this._teardownDocListener();
|
|
462
|
-
}
|
|
463
|
-
/** Accept the active suggestion: chip it (multiContext) or search it directly. */
|
|
464
|
-
_acceptActive() {
|
|
465
|
-
const s = this._suggestions()[this._activeIndex];
|
|
466
|
-
if (!s || !this._value) return;
|
|
467
|
-
const term = s.field ? {
|
|
468
|
-
value: this._value,
|
|
469
|
-
field: s.field
|
|
470
|
-
} : { value: this._value };
|
|
471
|
-
if (this.multiContext) {
|
|
472
|
-
this._chips = [...this._chips, term];
|
|
473
|
-
this._value = "";
|
|
474
|
-
if (this._inputEl) this._inputEl.value = "";
|
|
475
|
-
if (this._timer) clearTimeout(this._timer);
|
|
476
|
-
this.dataGrid?.setSearchTerms?.(this._chips);
|
|
477
|
-
} else {
|
|
478
|
-
if (this._timer) clearTimeout(this._timer);
|
|
479
|
-
this.dataGrid?.setSearchTerms?.([term]);
|
|
480
|
-
}
|
|
481
|
-
this._closePanel();
|
|
482
|
-
this._inputEl?.focus();
|
|
483
|
-
}
|
|
484
|
-
_removeChip(index) {
|
|
485
|
-
this._chips = this._chips.filter((_, i) => i !== index);
|
|
486
|
-
this.dataGrid?.setSearchTerms?.(this._chips);
|
|
487
|
-
if (!this._overflowChips.length) this._closeMore();
|
|
488
|
-
this._inputEl?.focus();
|
|
489
|
-
}
|
|
490
|
-
_detachChipsSwallow() {
|
|
491
|
-
if (!this._chipsSwallow) return;
|
|
492
|
-
this._chipsSwallow.strip.removeEventListener("click", this._chipsSwallow.fn, true);
|
|
493
|
-
this._chipsSwallow = null;
|
|
494
|
-
}
|
|
495
|
-
_handleInput(event) {
|
|
496
|
-
const value = event.currentTarget.value;
|
|
497
|
-
this._value = value;
|
|
498
|
-
if (value && this.suggestion) this._openPanel();
|
|
499
|
-
else if (!value) this._closePanel();
|
|
500
|
-
if (this._timer) clearTimeout(this._timer);
|
|
501
|
-
this._timer = setTimeout(() => this._push(value), Math.max(0, Number(this.debounce) || 0));
|
|
502
|
-
}
|
|
503
|
-
_ensurePopup() {
|
|
504
|
-
if (this._popup || isServer) return;
|
|
505
|
-
this._popup = new PopupPortalController(this, {
|
|
506
|
-
getPanel: () => this.renderRoot.querySelector(".mono-table-search-panel"),
|
|
507
|
-
getAnchor: () => this.renderRoot.querySelector(".mono-input-field") ?? this,
|
|
508
|
-
getStyleScope: () => this.closest(".mono-table, [mono-table]") ?? this.renderRoot.querySelector(".mono-table-search") ?? this,
|
|
509
|
-
isOpen: () => this._open,
|
|
510
|
-
side: () => "bottom",
|
|
511
|
-
align: () => "start",
|
|
512
|
-
offset: () => 4,
|
|
513
|
-
flip: () => true,
|
|
514
|
-
shift: () => true,
|
|
515
|
-
matchWidth: true
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
_bindDocListener() {
|
|
519
|
-
if (this._onDocPointer || isServer) return;
|
|
520
|
-
this._onDocPointer = (e) => {
|
|
521
|
-
const path = e.composedPath();
|
|
522
|
-
if (path.includes(this)) return;
|
|
523
|
-
if (this._popup?.containsInPath(path)) return;
|
|
524
|
-
this._closePanel();
|
|
525
|
-
};
|
|
526
|
-
document.addEventListener("pointerdown", this._onDocPointer, true);
|
|
527
|
-
}
|
|
528
|
-
_teardownDocListener() {
|
|
529
|
-
if (this._onDocPointer) document.removeEventListener("pointerdown", this._onDocPointer, true);
|
|
530
|
-
this._onDocPointer = void 0;
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Clearing is an explicit act, so it skips the debounce — the grid resets to
|
|
534
|
-
* the unfiltered rows immediately rather than after a dangling timer.
|
|
535
|
-
*/
|
|
536
|
-
_handleClear() {
|
|
537
|
-
if (this.disabled || this.readonly) return;
|
|
538
|
-
if (this._timer) clearTimeout(this._timer);
|
|
539
|
-
this._value = "";
|
|
540
|
-
if (this._inputEl) this._inputEl.value = "";
|
|
541
|
-
this._push("");
|
|
542
|
-
this._inputEl?.focus();
|
|
543
|
-
}
|
|
544
|
-
/** Mirrors `input-core`'s resolution: an explicit state wins, else error/success. */
|
|
545
|
-
get _resolvedValidationState() {
|
|
546
|
-
if (this.validationState && this.validationState !== "default") return this.validationState;
|
|
547
|
-
if (this.error || this.errorMessage) return "invalid";
|
|
548
|
-
if (this.success || this.successMessage) return "valid";
|
|
549
|
-
return "default";
|
|
550
|
-
}
|
|
551
|
-
_cls(base, key) {
|
|
552
|
-
return cssPart(this.cssClass, base, key);
|
|
553
|
-
}
|
|
554
|
-
/** Modifier classes shared by the wrapper and the field (see `input-core`). */
|
|
555
|
-
get _stateClasses() {
|
|
556
|
-
const hasSuffix = this.clearable && !!this._value || this._filterSlotted;
|
|
557
|
-
return [
|
|
558
|
-
this.size,
|
|
559
|
-
this.color,
|
|
560
|
-
this.variant,
|
|
561
|
-
this.disabled ? "disabled" : "",
|
|
562
|
-
this.readonly ? "readonly" : "",
|
|
563
|
-
this._resolvedValidationState !== "default" ? `is-${this._resolvedValidationState}` : "",
|
|
564
|
-
this.noIcon ? "" : "has-prefix",
|
|
565
|
-
hasSuffix ? "has-suffix" : "",
|
|
566
|
-
this._value ? "has-value" : ""
|
|
567
|
-
].filter(Boolean).join(" ");
|
|
568
|
-
}
|
|
569
|
-
get _wrapperClasses() {
|
|
570
|
-
return [
|
|
571
|
-
"mono-input",
|
|
572
|
-
"mono-table-search",
|
|
573
|
-
this._stateClasses,
|
|
574
|
-
this.cssClassName,
|
|
575
|
-
this.cssClass?.root
|
|
576
|
-
].filter(Boolean).join(" ");
|
|
577
|
-
}
|
|
578
|
-
get _fieldClasses() {
|
|
579
|
-
return [this._cls("mono-input-field", "field"), this._stateClasses].filter(Boolean).join(" ");
|
|
580
|
-
}
|
|
581
|
-
/** Internal icon — light: UnoCSS `.mono-icon`; shadow: inline SVG. */
|
|
582
|
-
renderIcon(_name) {
|
|
583
|
-
return html``;
|
|
584
|
-
}
|
|
585
|
-
_renderLabel() {
|
|
586
|
-
if (!this.label) return nothing;
|
|
587
|
-
return html`<label class=${this._cls("mono-input-label", "label")} mono-label>${this.label}</label>`;
|
|
588
|
-
}
|
|
589
|
-
_renderMessage() {
|
|
590
|
-
const base = this._cls("mono-input-message", "message");
|
|
591
|
-
const wrap = (state, text) => html`<div mono-message-wrap><div class=${`${base} ${state}`} mono-message=${state}>${text}</div></div>`;
|
|
592
|
-
if (this.validationMessage) return wrap(this._resolvedValidationState, this.validationMessage);
|
|
593
|
-
if (this.errorMessage) return wrap("invalid", this.errorMessage);
|
|
594
|
-
if (this.successMessage) return wrap("valid", this.successMessage);
|
|
595
|
-
if (this.helperText) return wrap("helper", this.helperText);
|
|
596
|
-
return nothing;
|
|
597
|
-
}
|
|
598
|
-
_renderClear() {
|
|
599
|
-
if (!(this.clearable && this._value && !this.disabled && !this.readonly)) return nothing;
|
|
600
|
-
return html`
|
|
601
|
-
<button
|
|
602
|
-
type="button"
|
|
603
|
-
class=${this._cls("mono-input-clear", "clear")}
|
|
604
|
-
mono-clear
|
|
605
|
-
aria-label="Clear search"
|
|
606
|
-
@click=${this._handleClear}
|
|
607
|
-
>
|
|
608
|
-
${this.renderIcon("close")}
|
|
609
|
-
</button>
|
|
610
|
-
`;
|
|
611
|
-
}
|
|
612
|
-
/** Sanitized inline-chip limit (>= 0) or `Infinity` when collapsing is off. */
|
|
613
|
-
get _maxChips() {
|
|
614
|
-
const n = Math.floor(Number(this.maxChips));
|
|
615
|
-
if (!Number.isFinite(n) || n < 0) return Infinity;
|
|
616
|
-
return n;
|
|
617
|
-
}
|
|
618
|
-
/** Chips beyond the inline limit — the ones the "See All" panel lists. */
|
|
619
|
-
get _overflowChips() {
|
|
620
|
-
const max = this._maxChips;
|
|
621
|
-
return max === Infinity ? [] : this._chips.slice(max);
|
|
622
|
-
}
|
|
623
|
-
/** Render one removable chip at its true index in `_chips`. */
|
|
624
|
-
_renderOneChip(t, index) {
|
|
625
|
-
return html`
|
|
626
|
-
<span class="mono-table-search-chip" mono-search-chip data-chip=${t.field ?? "*"}>
|
|
627
|
-
${t.field ? html`<span class="mono-table-search-chip-ctx" mono-search-chip-ctx>${this._captionOf(t.field)}:</span>` : nothing}
|
|
628
|
-
<span class="mono-table-search-chip-text" mono-search-chip-text>${t.value}</span>
|
|
629
|
-
<button
|
|
630
|
-
type="button"
|
|
631
|
-
class="mono-table-search-chip-x" mono-search-chip-x
|
|
632
|
-
aria-label=${`Remove ${t.value}`}
|
|
633
|
-
@click=${() => this._removeChip(index)}
|
|
634
|
-
>
|
|
635
|
-
${this.renderIcon("close")}
|
|
636
|
-
</button>
|
|
637
|
-
</span>
|
|
638
|
-
`;
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Committed chips, rendered inside the field ahead of the input. Only the
|
|
642
|
-
* first `maxChips` show inline; the rest collapse into a `${moreLabel}`
|
|
643
|
-
* trigger chip so a long filter list never stretches the input. Clicking it
|
|
644
|
-
* opens `_renderMorePanel`.
|
|
645
|
-
*/
|
|
646
|
-
_renderChips() {
|
|
647
|
-
if (!this.multiContext || !this._chips.length && !this._filterChip) return nothing;
|
|
648
|
-
const max = this._maxChips;
|
|
649
|
-
const inline = max === Infinity ? this._chips : this._chips.slice(0, max);
|
|
650
|
-
const overflow = this._overflowChips;
|
|
651
|
-
return html`
|
|
652
|
-
<span
|
|
653
|
-
class="mono-table-search-chips ${this._chipsDragging ? "is-grabbing" : ""}" mono-search-chips
|
|
654
|
-
@pointerdown=${this._onChipsPointerDown}
|
|
655
|
-
>
|
|
656
|
-
${this._filterChip ? this._renderFilterChip() : nothing}
|
|
657
|
-
${inline.map((t, i) => this._renderOneChip(t, i))}
|
|
658
|
-
${overflow.length ? html`
|
|
659
|
-
<button
|
|
660
|
-
type="button"
|
|
661
|
-
class="mono-table-search-chip mono-table-search-chip-more ${this._moreOpen ? "open" : ""}" mono-search-chip mono-search-chip-more
|
|
662
|
-
aria-haspopup="true"
|
|
663
|
-
aria-expanded=${this._moreOpen ? "true" : "false"}
|
|
664
|
-
@click=${(e) => {
|
|
665
|
-
e.stopPropagation();
|
|
666
|
-
this._toggleMore();
|
|
667
|
-
}}
|
|
668
|
-
>
|
|
669
|
-
<span class="mono-table-search-chip-text" mono-search-chip-text>${this.moreLabel}</span>
|
|
670
|
-
<span class="mono-table-search-chip-count" mono-search-chip-count>${overflow.length}</span>
|
|
671
|
-
</button>
|
|
672
|
-
` : nothing}
|
|
673
|
-
</span>
|
|
674
|
-
`;
|
|
675
|
-
}
|
|
676
|
-
_captionOf(field) {
|
|
677
|
-
return this._suggestions().find((s) => s.field === field)?.caption ?? field;
|
|
678
|
-
}
|
|
679
|
-
/** The overflow panel — lists the chips that did not fit inline. */
|
|
680
|
-
_renderMorePanel() {
|
|
681
|
-
const overflow = this._overflowChips;
|
|
682
|
-
if (!this._moreOpen || !overflow.length) return nothing;
|
|
683
|
-
const max = this._maxChips;
|
|
684
|
-
return html`
|
|
685
|
-
<div class="mono-table-search-more-panel" mono-search-more-panel role="dialog" aria-label=${this.moreLabel}>
|
|
686
|
-
${overflow.map((t, i) => this._renderOneChip(t, max + i))}
|
|
687
|
-
</div>
|
|
688
|
-
`;
|
|
689
|
-
}
|
|
690
|
-
_toggleMore() {
|
|
691
|
-
if (this._moreOpen) {
|
|
692
|
-
this._closeMore();
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
this._closePanel();
|
|
696
|
-
this._closeFilter();
|
|
697
|
-
this._moreOpen = true;
|
|
698
|
-
this._bindMoreDocListener();
|
|
699
|
-
}
|
|
700
|
-
_closeMore() {
|
|
701
|
-
if (!this._moreOpen) return;
|
|
702
|
-
this._moreOpen = false;
|
|
703
|
-
this._teardownMoreDocListener();
|
|
704
|
-
}
|
|
705
|
-
_bindMoreDocListener() {
|
|
706
|
-
if (this._onMoreDocPointer || isServer) return;
|
|
707
|
-
this._onMoreDocPointer = (e) => {
|
|
708
|
-
if (e.composedPath().includes(this)) return;
|
|
709
|
-
this._closeMore();
|
|
710
|
-
};
|
|
711
|
-
document.addEventListener("pointerdown", this._onMoreDocPointer, true);
|
|
712
|
-
}
|
|
713
|
-
_teardownMoreDocListener() {
|
|
714
|
-
if (this._onMoreDocPointer) document.removeEventListener("pointerdown", this._onMoreDocPointer, true);
|
|
715
|
-
this._onMoreDocPointer = void 0;
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* A `<mono-filter-builder slot="filter-builder">` joins the search: a
|
|
719
|
-
* chevron in the field toggles its panel, and its `mno-apply` either renders
|
|
720
|
-
* one filter chip (multi-context) or filters the grid directly.
|
|
721
|
-
*
|
|
722
|
-
* Projection differs per build: the light build can't use `<slot>` (it
|
|
723
|
-
* renders into the host, not a shadow root), so it captures the child and
|
|
724
|
-
* moves it into the `[data-mono-slot]` placeholder rendered by
|
|
725
|
-
* `_renderFilterSlotContent`; the shadow build overrides that hook to render
|
|
726
|
-
* a native `<slot>` and detects content via `_onFilterSlotChange`.
|
|
727
|
-
*/
|
|
728
|
-
_setFilterSlotted(value) {
|
|
729
|
-
this._filterSlotted = value;
|
|
730
|
-
}
|
|
731
|
-
_onFilterSlotChange(e) {
|
|
732
|
-
const slot = e.target;
|
|
733
|
-
this._setFilterSlotted(slot.assignedElements().length > 0);
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Inner content of the filter panel. Default = the light-build placeholder
|
|
737
|
-
* (`[data-mono-slot]`) the captured child is moved into; the shadow build
|
|
738
|
-
* overrides this to render a native `<slot name="filter-builder">`.
|
|
739
|
-
*/
|
|
740
|
-
_renderFilterSlotContent() {
|
|
741
|
-
return html`<div data-mono-slot="filter-builder"></div>`;
|
|
742
|
-
}
|
|
743
|
-
_toggleFilter() {
|
|
744
|
-
if (this._filterOpen) {
|
|
745
|
-
this._closeFilter();
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
this._closePanel();
|
|
749
|
-
this._closeMore();
|
|
750
|
-
this._ensureFilterPopup();
|
|
751
|
-
this._filterOpen = true;
|
|
752
|
-
this._bindFilterDocListener();
|
|
753
|
-
}
|
|
754
|
-
_closeFilter() {
|
|
755
|
-
if (!this._filterOpen) return;
|
|
756
|
-
this._filterOpen = false;
|
|
757
|
-
this._teardownFilterDocListener();
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* The filter panel is portaled (like the suggestion panel) so it floats over
|
|
761
|
-
* the rows instead of pushing them, and flips / shifts to stay on screen at
|
|
762
|
-
* a viewport edge.
|
|
763
|
-
*/
|
|
764
|
-
_ensureFilterPopup() {
|
|
765
|
-
if (this._filterPopup || isServer) return;
|
|
766
|
-
this._filterPopup = new PopupPortalController(this, {
|
|
767
|
-
getPanel: () => this.renderRoot.querySelector(".mono-table-search-filter"),
|
|
768
|
-
getAnchor: () => this.renderRoot.querySelector(".mono-input-field") ?? this,
|
|
769
|
-
getStyleScope: () => this.closest(".mono-table, [mono-table]") ?? this.renderRoot.querySelector(".mono-table-search") ?? this,
|
|
770
|
-
isOpen: () => this._filterOpen,
|
|
771
|
-
side: () => "bottom",
|
|
772
|
-
align: () => "start",
|
|
773
|
-
offset: () => 4,
|
|
774
|
-
flip: () => true,
|
|
775
|
-
shift: () => true,
|
|
776
|
-
constrainSize: () => true
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
/** Where the portaled filter panel lives now (host or body portal). */
|
|
780
|
-
_filterPanelRoot() {
|
|
781
|
-
return this._filterPopup?.panelRoot ?? null;
|
|
782
|
-
}
|
|
783
|
-
_bindFilterDocListener() {
|
|
784
|
-
if (this._onFilterDocPointer || isServer) return;
|
|
785
|
-
this._onFilterDocPointer = (e) => {
|
|
786
|
-
const path = e.composedPath();
|
|
787
|
-
if (path.includes(this)) return;
|
|
788
|
-
if (this._filterPopup?.containsInPath(path)) return;
|
|
789
|
-
this._closeFilter();
|
|
790
|
-
};
|
|
791
|
-
document.addEventListener("pointerdown", this._onFilterDocPointer, true);
|
|
792
|
-
}
|
|
793
|
-
_teardownFilterDocListener() {
|
|
794
|
-
if (this._onFilterDocPointer) document.removeEventListener("pointerdown", this._onFilterDocPointer, true);
|
|
795
|
-
this._onFilterDocPointer = void 0;
|
|
796
|
-
}
|
|
797
|
-
/** `mno-apply` from the slotted builder — filter the grid, chip it if multi-context. */
|
|
798
|
-
_onFilterApply(e) {
|
|
799
|
-
const detail = e.detail;
|
|
800
|
-
const arr = detail?.array;
|
|
801
|
-
const has = !!(Array.isArray(arr) && arr.length || detail?.string);
|
|
802
|
-
const grid = this.dataGrid;
|
|
803
|
-
if (grid?.setFilter) grid.setFilter(has ? arr : null);
|
|
804
|
-
this._filterChip = this.multiContext && has ? {
|
|
805
|
-
label: this.filterLabel,
|
|
806
|
-
string: detail?.string || ""
|
|
807
|
-
} : null;
|
|
808
|
-
}
|
|
809
|
-
/** `mno-clear` from the slotted builder — drop the filter and the chip. */
|
|
810
|
-
_onFilterClear() {
|
|
811
|
-
this.dataGrid?.setFilter?.(null);
|
|
812
|
-
this._filterChip = null;
|
|
813
|
-
}
|
|
814
|
-
/** Remove button on the filter chip — clear the grid filter, keep the builder's edits. */
|
|
815
|
-
_removeFilter() {
|
|
816
|
-
this.dataGrid?.setFilter?.(null);
|
|
817
|
-
this._filterChip = null;
|
|
818
|
-
this._inputEl?.focus();
|
|
819
|
-
}
|
|
820
|
-
/** The chevron suffix button — only when a filter-builder is slotted. */
|
|
821
|
-
_renderFilterToggle() {
|
|
822
|
-
if (!this._filterSlotted) return nothing;
|
|
823
|
-
return html`
|
|
824
|
-
<button
|
|
825
|
-
type="button"
|
|
826
|
-
class=${`mono-table-search-filter-toggle ${this._filterOpen ? "open" : ""} ${this._filterChip ? "active" : ""}`} mono-search-filter-toggle
|
|
827
|
-
?mono-open=${this._filterOpen}
|
|
828
|
-
?mono-active=${!!this._filterChip}
|
|
829
|
-
aria-haspopup="true"
|
|
830
|
-
aria-expanded=${this._filterOpen ? "true" : "false"}
|
|
831
|
-
title=${this.filterLabel}
|
|
832
|
-
@click=${(e) => {
|
|
833
|
-
e.stopPropagation();
|
|
834
|
-
this._toggleFilter();
|
|
835
|
-
}}
|
|
836
|
-
>
|
|
837
|
-
${this.renderIcon("chevron")}
|
|
838
|
-
</button>
|
|
839
|
-
`;
|
|
840
|
-
}
|
|
841
|
-
/** One removable chip representing the active built filter (multi-context only). */
|
|
842
|
-
_renderFilterChip() {
|
|
843
|
-
const chip = this._filterChip;
|
|
844
|
-
if (!chip) return html``;
|
|
845
|
-
return html`
|
|
846
|
-
<span
|
|
847
|
-
class="mono-table-search-chip mono-table-search-filter-chip" mono-search-chip mono-search-filter-chip
|
|
848
|
-
data-chip="filter"
|
|
849
|
-
title=${chip.string || chip.label}
|
|
850
|
-
>
|
|
851
|
-
<span class="mono-table-search-chip-text" mono-search-chip-text>${chip.label}</span>
|
|
852
|
-
<button
|
|
853
|
-
type="button"
|
|
854
|
-
class="mono-table-search-chip-x" mono-search-chip-x
|
|
855
|
-
aria-label=${`Remove ${chip.label}`}
|
|
856
|
-
@click=${() => this._removeFilter()}
|
|
857
|
-
>
|
|
858
|
-
${this.renderIcon("close")}
|
|
859
|
-
</button>
|
|
860
|
-
</span>
|
|
861
|
-
`;
|
|
862
|
-
}
|
|
863
|
-
/** The slotted filter-builder panel — hidden unless the chevron is toggled. */
|
|
864
|
-
_renderFilterSlot() {
|
|
865
|
-
return html`
|
|
866
|
-
<div
|
|
867
|
-
class="mono-table-search-filter ${this._filterOpen ? "open" : ""}" mono-search-filter
|
|
868
|
-
?mono-open=${this._filterOpen}
|
|
869
|
-
?hidden=${!this._filterOpen}
|
|
870
|
-
@mno-apply=${this._onFilterApply}
|
|
871
|
-
@mno-clear=${this._onFilterClear}
|
|
872
|
-
>
|
|
873
|
-
${this._renderFilterSlotContent()}
|
|
874
|
-
</div>
|
|
875
|
-
`;
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* The suggestion dropdown — portaled, so it escapes the table's overflow.
|
|
879
|
-
*
|
|
880
|
-
* The host div renders even with `suggestion` off (empty and `hidden`) rather
|
|
881
|
-
* than collapsing to `nothing`: once the popup controller has portaled it out
|
|
882
|
-
* of the render root, Lit can no longer remove it, so a conditional branch
|
|
883
|
-
* would strand the old panel and render a second one on the way back.
|
|
884
|
-
*/
|
|
885
|
-
_renderPanel() {
|
|
886
|
-
const list = this.suggestion ? this._suggestions() : [];
|
|
887
|
-
return html`
|
|
888
|
-
<div
|
|
889
|
-
class="mono-table-search-panel ${this._open ? "open" : ""} ${this._zone === "list" ? "zone-list" : ""}" mono-search-panel
|
|
890
|
-
?mono-open=${this._open}
|
|
891
|
-
?mono-zone-list=${this._zone === "list"}
|
|
892
|
-
role="listbox"
|
|
893
|
-
?hidden=${!this.suggestion}
|
|
894
|
-
aria-hidden=${this._open ? "false" : "true"}
|
|
895
|
-
>
|
|
896
|
-
${list.map((s, i) => html`
|
|
897
|
-
<button
|
|
898
|
-
type="button"
|
|
899
|
-
role="option"
|
|
900
|
-
aria-selected=${i === this._activeIndex ? "true" : "false"}
|
|
901
|
-
class="mono-table-search-option ${i === this._activeIndex ? "active" : ""}" mono-search-option
|
|
902
|
-
?mono-active=${i === this._activeIndex}
|
|
903
|
-
data-suggest=${s.field ?? "*"}
|
|
904
|
-
@mouseenter=${() => this._activeIndex = i}
|
|
905
|
-
@click=${() => {
|
|
906
|
-
this._activeIndex = i;
|
|
907
|
-
this._acceptActive();
|
|
908
|
-
}}
|
|
909
|
-
>
|
|
910
|
-
${this._suggestionLabel(s)}
|
|
911
|
-
</button>
|
|
912
|
-
`)}
|
|
913
|
-
</div>
|
|
914
|
-
`;
|
|
915
|
-
}
|
|
916
|
-
render() {
|
|
917
|
-
return html`
|
|
918
|
-
<div
|
|
919
|
-
class=${this._wrapperClasses}
|
|
920
|
-
style=${styleMap(buildSizeStyle(this))}
|
|
921
|
-
mono-input
|
|
922
|
-
mono-size=${this.size === "md" ? nothing : this.size}
|
|
923
|
-
mono-color=${this.color === "primary" ? nothing : this.color}
|
|
924
|
-
mono-variant=${this.variant === "outlined" ? nothing : this.variant}
|
|
925
|
-
mono-validation-state=${this._resolvedValidationState === "default" ? nothing : this._resolvedValidationState}
|
|
926
|
-
?mono-disabled=${this.disabled}
|
|
927
|
-
?mono-readonly=${this.readonly}
|
|
928
|
-
?mono-clearable=${this.clearable}
|
|
929
|
-
@keydown=${this._onKeydown}
|
|
930
|
-
@keyup=${this._onKeyup}
|
|
931
|
-
>
|
|
932
|
-
${this._renderLabel()}
|
|
933
|
-
<div class=${this._fieldClasses} mono-field>
|
|
934
|
-
${this.noIcon ? nothing : html`<span class=${this._cls("mono-input-prefix", "prefix")} mono-prefix aria-hidden="true"
|
|
935
|
-
>${this.renderIcon("search")}</span
|
|
936
|
-
>`}
|
|
937
|
-
${this._renderChips()}
|
|
938
|
-
<input
|
|
939
|
-
class=${`${this._cls("mono-input-native", "native")} ${this.size}`}
|
|
940
|
-
mono-native
|
|
941
|
-
type="search"
|
|
942
|
-
.value=${this._value}
|
|
943
|
-
name=${ifDefined(this.name || void 0)}
|
|
944
|
-
placeholder=${ifDefined(this.placeholder || void 0)}
|
|
945
|
-
autocomplete=${ifDefined(this.autocomplete || void 0)}
|
|
946
|
-
inputmode=${ifDefined(this.inputmode || void 0)}
|
|
947
|
-
minlength=${ifDefined(this.minLength)}
|
|
948
|
-
maxlength=${ifDefined(this.maxLength)}
|
|
949
|
-
?disabled=${this.disabled}
|
|
950
|
-
?readonly=${this.readonly}
|
|
951
|
-
?autofocus=${this.autofocus}
|
|
952
|
-
aria-label=${ifDefined(this.ariaLabelText || this.label || this.placeholder || void 0)}
|
|
953
|
-
aria-invalid=${this._resolvedValidationState === "invalid" ? "true" : "false"}
|
|
954
|
-
@input=${this._handleInput}
|
|
955
|
-
/>
|
|
956
|
-
${this._renderClear()}
|
|
957
|
-
${this._renderFilterToggle()}
|
|
958
|
-
</div>
|
|
959
|
-
${this._renderPanel()} ${this._renderMorePanel()} ${this._renderFilterSlot()} ${this._renderMessage()}
|
|
960
|
-
</div>
|
|
961
|
-
`;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
__decorate([property({ attribute: "search-value" })], MonoTableSearchCoreClass.prototype, "searchValue", void 0);
|
|
965
|
-
__decorate([property({ attribute: "search-expr" })], MonoTableSearchCoreClass.prototype, "searchExpr", void 0);
|
|
966
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "placeholder", void 0);
|
|
967
|
-
__decorate([property({
|
|
968
|
-
reflect: true,
|
|
969
|
-
converter: booleanStringConverter
|
|
970
|
-
})], MonoTableSearchCoreClass.prototype, "disabled", void 0);
|
|
971
|
-
__decorate([property({ converter: numberStringConverter })], MonoTableSearchCoreClass.prototype, "debounce", void 0);
|
|
972
|
-
__decorate([property({
|
|
973
|
-
attribute: "no-icon",
|
|
974
|
-
reflect: true,
|
|
975
|
-
converter: booleanStringConverter
|
|
976
|
-
})], MonoTableSearchCoreClass.prototype, "noIcon", void 0);
|
|
977
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "size", void 0);
|
|
978
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "color", void 0);
|
|
979
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "variant", void 0);
|
|
980
|
-
__decorate([property({
|
|
981
|
-
reflect: true,
|
|
982
|
-
converter: booleanStringConverter
|
|
983
|
-
})], MonoTableSearchCoreClass.prototype, "readonly", void 0);
|
|
984
|
-
__decorate([property({
|
|
985
|
-
reflect: true,
|
|
986
|
-
converter: booleanStringConverter
|
|
987
|
-
})], MonoTableSearchCoreClass.prototype, "clearable", void 0);
|
|
988
|
-
__decorate([property({
|
|
989
|
-
reflect: true,
|
|
990
|
-
converter: booleanStringConverter
|
|
991
|
-
})], MonoTableSearchCoreClass.prototype, "autofocus", void 0);
|
|
992
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "label", void 0);
|
|
993
|
-
__decorate([property({
|
|
994
|
-
type: String,
|
|
995
|
-
attribute: "helper-text"
|
|
996
|
-
})], MonoTableSearchCoreClass.prototype, "helperText", void 0);
|
|
997
|
-
__decorate([property({
|
|
998
|
-
type: String,
|
|
999
|
-
attribute: "validation-state"
|
|
1000
|
-
})], MonoTableSearchCoreClass.prototype, "validationState", void 0);
|
|
1001
|
-
__decorate([property({
|
|
1002
|
-
type: String,
|
|
1003
|
-
attribute: "validation-message"
|
|
1004
|
-
})], MonoTableSearchCoreClass.prototype, "validationMessage", void 0);
|
|
1005
|
-
__decorate([property({
|
|
1006
|
-
reflect: true,
|
|
1007
|
-
converter: booleanStringConverter
|
|
1008
|
-
})], MonoTableSearchCoreClass.prototype, "error", void 0);
|
|
1009
|
-
__decorate([property({
|
|
1010
|
-
type: String,
|
|
1011
|
-
attribute: "error-message"
|
|
1012
|
-
})], MonoTableSearchCoreClass.prototype, "errorMessage", void 0);
|
|
1013
|
-
__decorate([property({
|
|
1014
|
-
reflect: true,
|
|
1015
|
-
converter: booleanStringConverter
|
|
1016
|
-
})], MonoTableSearchCoreClass.prototype, "success", void 0);
|
|
1017
|
-
__decorate([property({
|
|
1018
|
-
type: String,
|
|
1019
|
-
attribute: "success-message"
|
|
1020
|
-
})], MonoTableSearchCoreClass.prototype, "successMessage", void 0);
|
|
1021
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "name", void 0);
|
|
1022
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "autocomplete", void 0);
|
|
1023
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "inputmode", void 0);
|
|
1024
|
-
__decorate([property({
|
|
1025
|
-
attribute: "min-length",
|
|
1026
|
-
converter: numberStringConverter
|
|
1027
|
-
})], MonoTableSearchCoreClass.prototype, "minLength", void 0);
|
|
1028
|
-
__decorate([property({
|
|
1029
|
-
attribute: "max-length",
|
|
1030
|
-
converter: numberStringConverter
|
|
1031
|
-
})], MonoTableSearchCoreClass.prototype, "maxLength", void 0);
|
|
1032
|
-
__decorate([property({
|
|
1033
|
-
type: String,
|
|
1034
|
-
attribute: "aria-label"
|
|
1035
|
-
})], MonoTableSearchCoreClass.prototype, "ariaLabelText", void 0);
|
|
1036
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "width", void 0);
|
|
1037
|
-
__decorate([property({ type: String })], MonoTableSearchCoreClass.prototype, "height", void 0);
|
|
1038
|
-
__decorate([property({
|
|
1039
|
-
type: String,
|
|
1040
|
-
attribute: "min-width"
|
|
1041
|
-
})], MonoTableSearchCoreClass.prototype, "minWidth", void 0);
|
|
1042
|
-
__decorate([property({
|
|
1043
|
-
type: String,
|
|
1044
|
-
attribute: "max-width"
|
|
1045
|
-
})], MonoTableSearchCoreClass.prototype, "maxWidth", void 0);
|
|
1046
|
-
__decorate([property({
|
|
1047
|
-
type: String,
|
|
1048
|
-
attribute: "min-height"
|
|
1049
|
-
})], MonoTableSearchCoreClass.prototype, "minHeight", void 0);
|
|
1050
|
-
__decorate([property({
|
|
1051
|
-
type: String,
|
|
1052
|
-
attribute: "max-height"
|
|
1053
|
-
})], MonoTableSearchCoreClass.prototype, "maxHeight", void 0);
|
|
1054
|
-
__decorate([property({ attribute: false })], MonoTableSearchCoreClass.prototype, "cssClass", void 0);
|
|
1055
|
-
__decorate([property({
|
|
1056
|
-
type: String,
|
|
1057
|
-
attribute: "css-class"
|
|
1058
|
-
})], MonoTableSearchCoreClass.prototype, "cssClassName", void 0);
|
|
1059
|
-
__decorate([property({
|
|
1060
|
-
reflect: true,
|
|
1061
|
-
converter: booleanStringConverter
|
|
1062
|
-
})], MonoTableSearchCoreClass.prototype, "suggestion", void 0);
|
|
1063
|
-
__decorate([property({
|
|
1064
|
-
attribute: "multi-context",
|
|
1065
|
-
reflect: true,
|
|
1066
|
-
converter: booleanStringConverter
|
|
1067
|
-
})], MonoTableSearchCoreClass.prototype, "multiContext", void 0);
|
|
1068
|
-
__decorate([property({ attribute: "suggestion-template" })], MonoTableSearchCoreClass.prototype, "suggestionTemplate", void 0);
|
|
1069
|
-
__decorate([property({ attribute: "all-fields-label" })], MonoTableSearchCoreClass.prototype, "allFieldsLabel", void 0);
|
|
1070
|
-
__decorate([property({
|
|
1071
|
-
attribute: "max-chips",
|
|
1072
|
-
converter: numberStringConverter
|
|
1073
|
-
})], MonoTableSearchCoreClass.prototype, "maxChips", void 0);
|
|
1074
|
-
__decorate([property({ attribute: "more-label" })], MonoTableSearchCoreClass.prototype, "moreLabel", void 0);
|
|
1075
|
-
__decorate([property({ attribute: "filter-label" })], MonoTableSearchCoreClass.prototype, "filterLabel", void 0);
|
|
1076
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_value", void 0);
|
|
1077
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_chips", void 0);
|
|
1078
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_open", void 0);
|
|
1079
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_activeIndex", void 0);
|
|
1080
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_zone", void 0);
|
|
1081
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_moreOpen", void 0);
|
|
1082
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_chipsDragging", void 0);
|
|
1083
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_filterSlotted", void 0);
|
|
1084
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_filterOpen", void 0);
|
|
1085
|
-
__decorate([state()], MonoTableSearchCoreClass.prototype, "_filterChip", void 0);
|
|
1086
|
-
__decorate([query(".mono-input-native")], MonoTableSearchCoreClass.prototype, "_inputEl", void 0);
|
|
1087
|
-
return MonoTableSearchCoreClass;
|
|
1088
|
-
};
|
|
1089
|
-
|
|
1090
|
-
//#endregion
|
|
1091
13
|
//#region src/components/table/mono-table-search.ts
|
|
1092
14
|
let MonoTableSearch = class MonoTableSearch extends MonoTableSearchCore(LitElement) {
|
|
1093
15
|
constructor(..._args) {
|
|
@@ -1129,319 +51,6 @@ let MonoTableSearch = class MonoTableSearch extends MonoTableSearchCore(LitEleme
|
|
|
1129
51
|
};
|
|
1130
52
|
MonoTableSearch = __decorate([customElement("mono-table-search")], MonoTableSearch);
|
|
1131
53
|
|
|
1132
|
-
//#endregion
|
|
1133
|
-
//#region src/components/table/mono-table-paging-core.ts
|
|
1134
|
-
/** Defaults the shadow build tests against before swapping in its inline SVG. */
|
|
1135
|
-
const DEFAULT_PREV_ICON = "i-mdi-chevron-left";
|
|
1136
|
-
const DEFAULT_NEXT_ICON = "i-mdi-chevron-right";
|
|
1137
|
-
/**
|
|
1138
|
-
* `MonoTablePagingCore` — render-mode-agnostic logic for `mono-table-paging`.
|
|
1139
|
-
*
|
|
1140
|
-
* - `type="standard"` (default): Prev / numbered / Next buttons wired to
|
|
1141
|
-
* `setPage`. The arrows are ICONS (`prev-icon` / `next-icon`, mdi chevrons by
|
|
1142
|
-
* default) rendered through `_renderIcon`, the same seam `mono-table-detail`
|
|
1143
|
-
* uses — they were text `‹ ›` glyphs, whose ink is a fraction of the em box and
|
|
1144
|
-
* sits off its centre, so they read as tiny and never optically centred next to
|
|
1145
|
-
* the digits. The `…` gap marker stays text: it has no centring problem and no
|
|
1146
|
-
* icon would say it better.
|
|
1147
|
-
* - `type="infinity-scroll"`: drives the controller's accumulator — scrolling the
|
|
1148
|
-
* nearest `.mono-table-scroll` container to the end auto-calls `loadNext()`.
|
|
1149
|
-
* - `type="virtual-scroll"`: same auto-loading, plus it computes a render window
|
|
1150
|
-
* from `scrollTop`/`rowHeight` and pushes it via `setVirtualWindow()` so the
|
|
1151
|
-
* consumer renders spacer rows + only the visible slice.
|
|
1152
|
-
*
|
|
1153
|
-
* Flat tables only — the controller refuses scroll modes on a grouped grid.
|
|
1154
|
-
*/
|
|
1155
|
-
/** Scroll containers known by structure — every spelling a consumer or the library writes. */
|
|
1156
|
-
const SCROLL_HOSTS = ".mono-table-scroll, [mono-table-scroll], [mono-dd-region=\"body\"], .mono-dropdown-table-region.body";
|
|
1157
|
-
const DD_PANEL = "[mono-dd-panel], .mono-dropdown-table-panel";
|
|
1158
|
-
const DD_BODY = "[mono-dd-region=\"body\"], .mono-dropdown-table-region.body";
|
|
1159
|
-
/**
|
|
1160
|
-
* Ancestors in the FLATTENED tree: a slotted node continues at its `<slot>`, and a shadow root's
|
|
1161
|
-
* top continues at its host. `closest()` stops at both, which hid the shadow dropdown-table's
|
|
1162
|
-
* panel from a pager slotted into it.
|
|
1163
|
-
*/
|
|
1164
|
-
function flatAncestors(start) {
|
|
1165
|
-
const out = [];
|
|
1166
|
-
let node = start;
|
|
1167
|
-
while (node) {
|
|
1168
|
-
const next = node.assignedSlot ?? node.parentElement ?? node.parentNode?.host ?? null;
|
|
1169
|
-
if (next) out.push(next);
|
|
1170
|
-
node = next;
|
|
1171
|
-
}
|
|
1172
|
-
return out;
|
|
1173
|
-
}
|
|
1174
|
-
const MonoTablePagingCore = (superClass) => {
|
|
1175
|
-
class MonoTablePagingCoreClass extends MonoTableControllerCore(superClass) {
|
|
1176
|
-
constructor(..._args) {
|
|
1177
|
-
super(..._args);
|
|
1178
|
-
this._propsSlot = "paging";
|
|
1179
|
-
this.type = "standard";
|
|
1180
|
-
this.siblings = 1;
|
|
1181
|
-
this.simple = false;
|
|
1182
|
-
this.rowHeight = 44;
|
|
1183
|
-
this.threshold = 200;
|
|
1184
|
-
this.overscan = 6;
|
|
1185
|
-
this.prevIcon = DEFAULT_PREV_ICON;
|
|
1186
|
-
this.nextIcon = DEFAULT_NEXT_ICON;
|
|
1187
|
-
this._scrollEl = null;
|
|
1188
|
-
this._onScrollBound = () => this._scheduleScroll();
|
|
1189
|
-
this._rafPending = false;
|
|
1190
|
-
this._ownsPageSize = false;
|
|
1191
|
-
this._retryPending = false;
|
|
1192
|
-
}
|
|
1193
|
-
connectedCallback() {
|
|
1194
|
-
super.connectedCallback();
|
|
1195
|
-
if (!isServer) this._configure();
|
|
1196
|
-
}
|
|
1197
|
-
disconnectedCallback() {
|
|
1198
|
-
this._detachScroll();
|
|
1199
|
-
if (this._scrollActive()) this.dataGrid?.setScrollPaging("off", { reload: false });
|
|
1200
|
-
super.disconnectedCallback();
|
|
1201
|
-
}
|
|
1202
|
-
willUpdate(changed) {
|
|
1203
|
-
super.willUpdate(changed);
|
|
1204
|
-
if (!isServer && (changed.has("type") || changed.has("dataGrid") || changed.has("size"))) this._configure();
|
|
1205
|
-
}
|
|
1206
|
-
/** Also recompute the virtual window whenever the grid notifies (new rows). */
|
|
1207
|
-
_subscribe() {
|
|
1208
|
-
this._off?.();
|
|
1209
|
-
this._off = this.dataGrid?.subscribe(() => {
|
|
1210
|
-
this.requestUpdate();
|
|
1211
|
-
if (this.type === "virtual-scroll") this._scheduleScroll();
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
_scrollActive() {
|
|
1215
|
-
return this.type === "infinity-scroll" || this.type === "virtual-scroll";
|
|
1216
|
-
}
|
|
1217
|
-
_modeOf() {
|
|
1218
|
-
return this.type === "infinity-scroll" ? "infinity" : this.type === "virtual-scroll" ? "virtual" : "off";
|
|
1219
|
-
}
|
|
1220
|
-
/** Apply mode + page-size override to the grid, sequentially (avoid racing loads). */
|
|
1221
|
-
async _applyGridConfig(grid) {
|
|
1222
|
-
await grid.setScrollPaging(this._modeOf());
|
|
1223
|
-
if (this.size != null && this.size > 0) {
|
|
1224
|
-
this._ownsPageSize = true;
|
|
1225
|
-
await grid.setPreferredPageSize(this.size);
|
|
1226
|
-
} else if (this._ownsPageSize) {
|
|
1227
|
-
this._ownsPageSize = false;
|
|
1228
|
-
await grid.setPreferredPageSize(null);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
_configure() {
|
|
1232
|
-
this._detachScroll();
|
|
1233
|
-
const grid = this.dataGrid;
|
|
1234
|
-
if (!grid) return;
|
|
1235
|
-
this._applyGridConfig(grid);
|
|
1236
|
-
if (!this._scrollActive()) return;
|
|
1237
|
-
this._attachScroll();
|
|
1238
|
-
}
|
|
1239
|
-
/** Resolve the scroll container and listen to it; the grid config is not touched. */
|
|
1240
|
-
_attachScroll() {
|
|
1241
|
-
this._scrollEl = this._resolveScrollEl();
|
|
1242
|
-
if (!this._scrollEl) {
|
|
1243
|
-
if (!this._retryPending && typeof requestAnimationFrame === "function") {
|
|
1244
|
-
this._retryPending = true;
|
|
1245
|
-
requestAnimationFrame(() => {
|
|
1246
|
-
this._retryPending = false;
|
|
1247
|
-
if (this.isConnected && !this._scrollEl && this._scrollActive()) this._attachScroll();
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
this._scrollEl.addEventListener("scroll", this._onScrollBound, { passive: true });
|
|
1253
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
1254
|
-
this._ro = new ResizeObserver(this._onScrollBound);
|
|
1255
|
-
this._ro.observe(this._scrollEl);
|
|
1256
|
-
}
|
|
1257
|
-
this._scheduleScroll();
|
|
1258
|
-
}
|
|
1259
|
-
_detachScroll() {
|
|
1260
|
-
this._scrollEl?.removeEventListener("scroll", this._onScrollBound);
|
|
1261
|
-
this._ro?.disconnect();
|
|
1262
|
-
this._ro = void 0;
|
|
1263
|
-
this._scrollEl = null;
|
|
1264
|
-
}
|
|
1265
|
-
/** The scroll container: explicit `scroll-target`, else nearest `.mono-table-scroll`. */
|
|
1266
|
-
_resolveScrollEl() {
|
|
1267
|
-
if (this.scrollTarget) {
|
|
1268
|
-
const el = (this.getRootNode?.() ?? document).querySelector?.(this.scrollTarget) ?? document.querySelector(this.scrollTarget);
|
|
1269
|
-
if (el) return el;
|
|
1270
|
-
}
|
|
1271
|
-
const ancestors = flatAncestors(this);
|
|
1272
|
-
const near = ancestors.find((el) => el.matches(SCROLL_HOSTS));
|
|
1273
|
-
if (near) return near;
|
|
1274
|
-
const panel = ancestors.find((el) => el.matches(DD_PANEL));
|
|
1275
|
-
const body = panel ? Array.from(panel.children).find((c) => c.matches(DD_BODY)) : void 0;
|
|
1276
|
-
if (body) return body;
|
|
1277
|
-
for (const el of ancestors) {
|
|
1278
|
-
if (el === document.body) break;
|
|
1279
|
-
if (/(auto|scroll)/.test(getComputedStyle(el).overflowY)) return el;
|
|
1280
|
-
}
|
|
1281
|
-
return null;
|
|
1282
|
-
}
|
|
1283
|
-
_scheduleScroll() {
|
|
1284
|
-
if (this._rafPending || isServer) return;
|
|
1285
|
-
this._rafPending = true;
|
|
1286
|
-
const run = () => {
|
|
1287
|
-
this._rafPending = false;
|
|
1288
|
-
this._onScroll();
|
|
1289
|
-
};
|
|
1290
|
-
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
1291
|
-
else run();
|
|
1292
|
-
}
|
|
1293
|
-
_onScroll() {
|
|
1294
|
-
const grid = this.dataGrid;
|
|
1295
|
-
const el = this._scrollEl;
|
|
1296
|
-
if (!grid || !el) return;
|
|
1297
|
-
if (el.clientHeight <= 0) return;
|
|
1298
|
-
if (this.type === "virtual-scroll") this._recomputeWindow();
|
|
1299
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight <= Math.max(0, this.threshold) && grid.hasMore && !grid.loading) grid.loadNext();
|
|
1300
|
-
}
|
|
1301
|
-
/** Compute the virtual window from scrollTop + rowHeight and push it to the grid. */
|
|
1302
|
-
_recomputeWindow() {
|
|
1303
|
-
const grid = this.dataGrid;
|
|
1304
|
-
const el = this._scrollEl;
|
|
1305
|
-
if (!grid || !el || this.type !== "virtual-scroll") return;
|
|
1306
|
-
const rh = Math.max(1, Number(this.rowHeight) || 44);
|
|
1307
|
-
const over = Math.max(0, Math.floor(Number(this.overscan) || 0));
|
|
1308
|
-
const loaded = grid.loadedCount;
|
|
1309
|
-
const first = Math.floor(el.scrollTop / rh);
|
|
1310
|
-
const last = Math.ceil((el.scrollTop + el.clientHeight) / rh);
|
|
1311
|
-
const stride = Math.max(1, over);
|
|
1312
|
-
let start = Math.floor(first / stride) * stride - over;
|
|
1313
|
-
let end = Math.ceil(last / stride) * stride + over;
|
|
1314
|
-
start = Math.max(0, start);
|
|
1315
|
-
end = Math.min(loaded, Math.max(start, end));
|
|
1316
|
-
const padTop = start * rh;
|
|
1317
|
-
const padBottom = Math.max(0, (loaded - end) * rh);
|
|
1318
|
-
grid.setVirtualWindow(start, end, padTop, padBottom);
|
|
1319
|
-
}
|
|
1320
|
-
_go(pageIndex) {
|
|
1321
|
-
this.dataGrid?.setPage(pageIndex);
|
|
1322
|
-
}
|
|
1323
|
-
/** Windowed list of 1-based page numbers with 'gap' markers for ellipses. */
|
|
1324
|
-
_pages(current1, total) {
|
|
1325
|
-
if (total <= 1) return total === 1 ? [1] : [];
|
|
1326
|
-
const sib = Math.max(0, Math.floor(Number(this.siblings) || 0));
|
|
1327
|
-
const set = /* @__PURE__ */ new Set([1, total]);
|
|
1328
|
-
for (let p = current1 - sib; p <= current1 + sib; p++) if (p >= 1 && p <= total) set.add(p);
|
|
1329
|
-
const sorted = [...set].sort((a, b) => a - b);
|
|
1330
|
-
const out = [];
|
|
1331
|
-
let prev = 0;
|
|
1332
|
-
for (const p of sorted) {
|
|
1333
|
-
if (p - prev > 1) out.push("gap");
|
|
1334
|
-
out.push(p);
|
|
1335
|
-
prev = p;
|
|
1336
|
-
}
|
|
1337
|
-
return out;
|
|
1338
|
-
}
|
|
1339
|
-
_renderScrollStatus() {
|
|
1340
|
-
const grid = this.dataGrid;
|
|
1341
|
-
const loading = grid?.loading ?? false;
|
|
1342
|
-
const hasMore = grid?.hasMore ?? false;
|
|
1343
|
-
return html`
|
|
1344
|
-
<div class="mono-table-pg mono-table-pg-scroll" mono-table-paging mono-pg-scroll data-type=${this.type}>
|
|
1345
|
-
${loading ? html`<span class="mono-table-pg-el" mono-pg-el>Loading more…</span>` : hasMore ? html`<button
|
|
1346
|
-
class="mono-table-pgb" mono-pgb
|
|
1347
|
-
?disabled=${!grid}
|
|
1348
|
-
@click=${() => void this.dataGrid?.loadNext()}
|
|
1349
|
-
>
|
|
1350
|
-
Load more
|
|
1351
|
-
</button>` : html`<span class="mono-table-pg-el" mono-pg-el>— end —</span>`}
|
|
1352
|
-
</div>
|
|
1353
|
-
`;
|
|
1354
|
-
}
|
|
1355
|
-
/**
|
|
1356
|
-
* One arrow — mdi chevron-left / chevron-right, INLINE, in both builds.
|
|
1357
|
-
*
|
|
1358
|
-
* Deliberately not a `i-mdi-*` utility class by default, which is what
|
|
1359
|
-
* `mono-table-detail` does. That class only paints if the CONSUMER's icon tooling
|
|
1360
|
-
* generated it, and a bundler scans the app's own sources — not this package under
|
|
1361
|
-
* `node_modules`. So a class emitted from here resolves to nothing unless every
|
|
1362
|
-
* consumer remembers to safelist it, and the arrows vanish instead of merely
|
|
1363
|
-
* looking wrong. An inline SVG owes the consumer nothing.
|
|
1364
|
-
*
|
|
1365
|
-
* It also fixes the centring these arrows never had: `‹` / `›` are text, so their
|
|
1366
|
-
* ink sits wherever the font's metrics put it inside the em box. An SVG's viewBox
|
|
1367
|
-
* IS its box, so the chevron is centred by construction.
|
|
1368
|
-
*
|
|
1369
|
-
* Setting `prev-icon` / `next-icon` opts back into a class — a consumer who names
|
|
1370
|
-
* one is telling us their stylesheet has it.
|
|
1371
|
-
*/
|
|
1372
|
-
_renderIcon(direction) {
|
|
1373
|
-
const isPrev = direction === "prev";
|
|
1374
|
-
const icon = isPrev ? this.prevIcon : this.nextIcon;
|
|
1375
|
-
if (isPrev ? icon !== "i-mdi-chevron-left" : icon !== "i-mdi-chevron-right") return html`<span class="mono-icon ${icon}" aria-hidden="true"></span>`;
|
|
1376
|
-
return html`
|
|
1377
|
-
<svg class="mono-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
1378
|
-
<path
|
|
1379
|
-
d=${isPrev ? "M15.41 16.58L10.83 12l4.58-4.59L14 6l-6 6l6 6z" : "M8.59 16.58L13.17 12L8.59 7.41L10 6l6 6l-6 6z"}
|
|
1380
|
-
/>
|
|
1381
|
-
</svg>
|
|
1382
|
-
`;
|
|
1383
|
-
}
|
|
1384
|
-
render() {
|
|
1385
|
-
if (this._scrollActive()) return this._renderScrollStatus();
|
|
1386
|
-
const grid = this.dataGrid;
|
|
1387
|
-
const loading = grid?.loading ?? false;
|
|
1388
|
-
const current = grid?.pageIndex ?? 0;
|
|
1389
|
-
const count = grid?.pageCount ?? 0;
|
|
1390
|
-
const atFirst = current <= 0;
|
|
1391
|
-
const atLast = count > 0 ? current >= count - 1 : true;
|
|
1392
|
-
return html`
|
|
1393
|
-
<div class="mono-table-pg" mono-table-paging>
|
|
1394
|
-
<button
|
|
1395
|
-
class="mono-table-pgb" mono-pgb
|
|
1396
|
-
?disabled=${atFirst || loading || !grid}
|
|
1397
|
-
@click=${() => this._go(current - 1)}
|
|
1398
|
-
aria-label="Previous page"
|
|
1399
|
-
>
|
|
1400
|
-
${this._renderIcon("prev")}
|
|
1401
|
-
</button>
|
|
1402
|
-
|
|
1403
|
-
${this.simple ? html`<span class="mono-table-pg-el" mono-pg-el>${current + 1} / ${Math.max(1, count)}</span>` : this._pages(current + 1, count).map((p) => p === "gap" ? html`<span class="mono-table-pg-el" mono-pg-el>…</span>` : html`<button
|
|
1404
|
-
class=${`mono-table-pgb${p === current + 1 ? " on" : ""}`} mono-pgb ?mono-on=${p === current + 1}
|
|
1405
|
-
?disabled=${loading}
|
|
1406
|
-
@click=${() => this._go(p - 1)}
|
|
1407
|
-
>
|
|
1408
|
-
${p}
|
|
1409
|
-
</button>`)}
|
|
1410
|
-
|
|
1411
|
-
<button
|
|
1412
|
-
class="mono-table-pgb" mono-pgb
|
|
1413
|
-
?disabled=${atLast || loading || !grid}
|
|
1414
|
-
@click=${() => this._go(current + 1)}
|
|
1415
|
-
aria-label="Next page"
|
|
1416
|
-
>
|
|
1417
|
-
${this._renderIcon("next")}
|
|
1418
|
-
</button>
|
|
1419
|
-
</div>
|
|
1420
|
-
`;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
__decorate([property({ reflect: true })], MonoTablePagingCoreClass.prototype, "type", void 0);
|
|
1424
|
-
__decorate([property({ converter: numberStringConverter })], MonoTablePagingCoreClass.prototype, "siblings", void 0);
|
|
1425
|
-
__decorate([property({
|
|
1426
|
-
reflect: true,
|
|
1427
|
-
converter: booleanStringConverter
|
|
1428
|
-
})], MonoTablePagingCoreClass.prototype, "simple", void 0);
|
|
1429
|
-
__decorate([property({
|
|
1430
|
-
attribute: "size",
|
|
1431
|
-
converter: numberStringConverter
|
|
1432
|
-
})], MonoTablePagingCoreClass.prototype, "size", void 0);
|
|
1433
|
-
__decorate([property({
|
|
1434
|
-
attribute: "row-height",
|
|
1435
|
-
converter: numberStringConverter
|
|
1436
|
-
})], MonoTablePagingCoreClass.prototype, "rowHeight", void 0);
|
|
1437
|
-
__decorate([property({ attribute: "scroll-target" })], MonoTablePagingCoreClass.prototype, "scrollTarget", void 0);
|
|
1438
|
-
__decorate([property({ converter: numberStringConverter })], MonoTablePagingCoreClass.prototype, "threshold", void 0);
|
|
1439
|
-
__decorate([property({ converter: numberStringConverter })], MonoTablePagingCoreClass.prototype, "overscan", void 0);
|
|
1440
|
-
__decorate([property({ attribute: "prev-icon" })], MonoTablePagingCoreClass.prototype, "prevIcon", void 0);
|
|
1441
|
-
__decorate([property({ attribute: "next-icon" })], MonoTablePagingCoreClass.prototype, "nextIcon", void 0);
|
|
1442
|
-
return MonoTablePagingCoreClass;
|
|
1443
|
-
};
|
|
1444
|
-
|
|
1445
54
|
//#endregion
|
|
1446
55
|
//#region src/components/table/mono-table-paging.ts
|
|
1447
56
|
let MonoTablePaging = class MonoTablePaging extends MonoTablePagingCore(LitElement) {
|
|
@@ -1451,122 +60,6 @@ let MonoTablePaging = class MonoTablePaging extends MonoTablePagingCore(LitEleme
|
|
|
1451
60
|
};
|
|
1452
61
|
MonoTablePaging = __decorate([customElement("mono-table-paging")], MonoTablePaging);
|
|
1453
62
|
|
|
1454
|
-
//#endregion
|
|
1455
|
-
//#region src/components/table/mono-table-paging-group-core.ts
|
|
1456
|
-
/**
|
|
1457
|
-
* `MonoTablePagingGroupCore` — render-mode-agnostic logic for
|
|
1458
|
-
* `mono-table-paging-group`: paginates the rows inside ONE group. Extends the
|
|
1459
|
-
* controller base with group-paging registration on connect / disconnect /
|
|
1460
|
-
* group-path change. No slots, no icons — light and shadow share `render()`.
|
|
1461
|
-
*/
|
|
1462
|
-
const MonoTablePagingGroupCore = (superClass) => {
|
|
1463
|
-
class MonoTablePagingGroupCoreClass extends MonoTableControllerCore(superClass) {
|
|
1464
|
-
constructor(..._args) {
|
|
1465
|
-
super(..._args);
|
|
1466
|
-
this._propsSlot = "pagingGroup";
|
|
1467
|
-
this.pageSize = 5;
|
|
1468
|
-
this.siblings = 1;
|
|
1469
|
-
this.simple = false;
|
|
1470
|
-
}
|
|
1471
|
-
/** The bound group's stable path key (drives every controller call). */
|
|
1472
|
-
get _path() {
|
|
1473
|
-
const g = this.group;
|
|
1474
|
-
return (typeof g === "string" ? g : g?.path) ?? "";
|
|
1475
|
-
}
|
|
1476
|
-
connectedCallback() {
|
|
1477
|
-
super.connectedCallback();
|
|
1478
|
-
this._register();
|
|
1479
|
-
}
|
|
1480
|
-
disconnectedCallback() {
|
|
1481
|
-
if (this._path) this.dataGrid?.clearGroupPaging(this._path);
|
|
1482
|
-
super.disconnectedCallback();
|
|
1483
|
-
}
|
|
1484
|
-
willUpdate(changed) {
|
|
1485
|
-
super.willUpdate(changed);
|
|
1486
|
-
const groupChanged = changed.has("group");
|
|
1487
|
-
const prev = groupChanged ? changed.get("group") : void 0;
|
|
1488
|
-
const prevPath = typeof prev === "string" ? prev : prev?.path;
|
|
1489
|
-
const pathChanged = groupChanged && prevPath !== this._path;
|
|
1490
|
-
if (changed.has("dataGrid") || changed.has("pageSize") || pathChanged) {
|
|
1491
|
-
if (pathChanged && prevPath) this.dataGrid?.clearGroupPaging(prevPath);
|
|
1492
|
-
this._register();
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
_register() {
|
|
1496
|
-
const path = this._path;
|
|
1497
|
-
if (path && this.dataGrid) this.dataGrid.setGroupPageSize(path, this.pageSize);
|
|
1498
|
-
}
|
|
1499
|
-
_go(pageIndex) {
|
|
1500
|
-
if (this._path) this.dataGrid?.setGroupPage(this._path, pageIndex);
|
|
1501
|
-
}
|
|
1502
|
-
/** Windowed list of 1-based page numbers with 'gap' markers for ellipses. */
|
|
1503
|
-
_pages(current1, total) {
|
|
1504
|
-
if (total <= 1) return total === 1 ? [1] : [];
|
|
1505
|
-
const sib = Math.max(0, Math.floor(Number(this.siblings) || 0));
|
|
1506
|
-
const set = /* @__PURE__ */ new Set([1, total]);
|
|
1507
|
-
for (let p = current1 - sib; p <= current1 + sib; p++) if (p >= 1 && p <= total) set.add(p);
|
|
1508
|
-
const sorted = [...set].sort((a, b) => a - b);
|
|
1509
|
-
const out = [];
|
|
1510
|
-
let prev = 0;
|
|
1511
|
-
for (const p of sorted) {
|
|
1512
|
-
if (p - prev > 1) out.push("gap");
|
|
1513
|
-
out.push(p);
|
|
1514
|
-
prev = p;
|
|
1515
|
-
}
|
|
1516
|
-
return out;
|
|
1517
|
-
}
|
|
1518
|
-
render() {
|
|
1519
|
-
const grid = this.dataGrid;
|
|
1520
|
-
if (!grid || !this._path) return nothing;
|
|
1521
|
-
const info = grid.groupPageInfo(this._path);
|
|
1522
|
-
const count = info.pageCount;
|
|
1523
|
-
if (count <= 1) return nothing;
|
|
1524
|
-
const current = info.pageIndex;
|
|
1525
|
-
const atFirst = current <= 0;
|
|
1526
|
-
const atLast = current >= count - 1;
|
|
1527
|
-
return html`
|
|
1528
|
-
<div class="mono-table-pg mono-table-pg-group" mono-table-paging mono-table-paging-group>
|
|
1529
|
-
<button
|
|
1530
|
-
class="mono-table-pgb" mono-pgb
|
|
1531
|
-
?disabled=${atFirst}
|
|
1532
|
-
@click=${() => this._go(current - 1)}
|
|
1533
|
-
aria-label="Previous rows"
|
|
1534
|
-
>
|
|
1535
|
-
‹
|
|
1536
|
-
</button>
|
|
1537
|
-
|
|
1538
|
-
${this.simple ? html`<span class="mono-table-pg-el" mono-pg-el>${current + 1} / ${count}</span>` : this._pages(current + 1, count).map((p) => p === "gap" ? html`<span class="mono-table-pg-el" mono-pg-el>…</span>` : html`<button
|
|
1539
|
-
class=${`mono-table-pgb${p === current + 1 ? " on" : ""}`} mono-pgb ?mono-on=${p === current + 1}
|
|
1540
|
-
@click=${() => this._go(p - 1)}
|
|
1541
|
-
>
|
|
1542
|
-
${p}
|
|
1543
|
-
</button>`)}
|
|
1544
|
-
|
|
1545
|
-
<button
|
|
1546
|
-
class="mono-table-pgb" mono-pgb
|
|
1547
|
-
?disabled=${atLast}
|
|
1548
|
-
@click=${() => this._go(current + 1)}
|
|
1549
|
-
aria-label="Next rows"
|
|
1550
|
-
>
|
|
1551
|
-
›
|
|
1552
|
-
</button>
|
|
1553
|
-
</div>
|
|
1554
|
-
`;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
__decorate([property({ attribute: false })], MonoTablePagingGroupCoreClass.prototype, "group", void 0);
|
|
1558
|
-
__decorate([property({
|
|
1559
|
-
attribute: "page-size",
|
|
1560
|
-
converter: numberStringConverter
|
|
1561
|
-
})], MonoTablePagingGroupCoreClass.prototype, "pageSize", void 0);
|
|
1562
|
-
__decorate([property({ converter: numberStringConverter })], MonoTablePagingGroupCoreClass.prototype, "siblings", void 0);
|
|
1563
|
-
__decorate([property({
|
|
1564
|
-
reflect: true,
|
|
1565
|
-
converter: booleanStringConverter
|
|
1566
|
-
})], MonoTablePagingGroupCoreClass.prototype, "simple", void 0);
|
|
1567
|
-
return MonoTablePagingGroupCoreClass;
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
63
|
//#endregion
|
|
1571
64
|
//#region src/components/table/mono-table-paging-group.ts
|
|
1572
65
|
let MonoTablePagingGroup = class MonoTablePagingGroup extends MonoTablePagingGroupCore(LitElement) {
|
|
@@ -1576,70 +69,6 @@ let MonoTablePagingGroup = class MonoTablePagingGroup extends MonoTablePagingGro
|
|
|
1576
69
|
};
|
|
1577
70
|
MonoTablePagingGroup = __decorate([customElement("mono-table-paging-group")], MonoTablePagingGroup);
|
|
1578
71
|
|
|
1579
|
-
//#endregion
|
|
1580
|
-
//#region src/components/table/mono-table-page-size-core.ts
|
|
1581
|
-
/**
|
|
1582
|
-
* `MonoTablePageSizeCore` — render-mode-agnostic logic for `mono-table-page-size`:
|
|
1583
|
-
* a native `<select>` of page-size options wired to the controller. No slots, no
|
|
1584
|
-
* icons — light and shadow share the full `render()`.
|
|
1585
|
-
*/
|
|
1586
|
-
const MonoTablePageSizeCore = (superClass) => {
|
|
1587
|
-
class MonoTablePageSizeCoreClass extends MonoTableControllerCore(superClass) {
|
|
1588
|
-
constructor(..._args) {
|
|
1589
|
-
super(..._args);
|
|
1590
|
-
this._propsSlot = "pageSize";
|
|
1591
|
-
this.sizes = [
|
|
1592
|
-
10,
|
|
1593
|
-
20,
|
|
1594
|
-
50,
|
|
1595
|
-
100
|
|
1596
|
-
];
|
|
1597
|
-
this.label = "";
|
|
1598
|
-
}
|
|
1599
|
-
get _options() {
|
|
1600
|
-
const raw = this.sizes;
|
|
1601
|
-
const list = Array.isArray(raw) ? raw : String(raw).split(",").map((s) => s.trim());
|
|
1602
|
-
const out = [];
|
|
1603
|
-
for (const v of list) if (typeof v === "string" && v.toLowerCase() === "all") out.push("all");
|
|
1604
|
-
else {
|
|
1605
|
-
const n = Number(v);
|
|
1606
|
-
if (Number.isFinite(n) && n > 0) out.push(n);
|
|
1607
|
-
}
|
|
1608
|
-
return out;
|
|
1609
|
-
}
|
|
1610
|
-
_handleChange(event) {
|
|
1611
|
-
const raw = event.currentTarget.value;
|
|
1612
|
-
if (raw === "all") {
|
|
1613
|
-
this.dataGrid?.setPageSize("all");
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
const value = Number(raw);
|
|
1617
|
-
if (Number.isFinite(value) && value > 0) this.dataGrid?.setPageSize(value);
|
|
1618
|
-
}
|
|
1619
|
-
render() {
|
|
1620
|
-
const current = this.dataGrid?.pageSize ?? 0;
|
|
1621
|
-
const all = this.dataGrid?.pageSizeAll ?? false;
|
|
1622
|
-
const loading = this.dataGrid?.loading ?? false;
|
|
1623
|
-
return html`
|
|
1624
|
-
<label class="mono-table-page-size" mono-table-page-size>
|
|
1625
|
-
${this.label ? html`<span>${this.label}</span>` : nothing}
|
|
1626
|
-
<select class="mono-table-sel" mono-sel ?disabled=${loading} @change=${this._handleChange}>
|
|
1627
|
-
${this._options.map((o) => {
|
|
1628
|
-
const isAll = o === "all";
|
|
1629
|
-
return html`<option value=${isAll ? "all" : o} ?selected=${isAll ? all : !all && o === current}>
|
|
1630
|
-
${isAll ? "All" : o}
|
|
1631
|
-
</option>`;
|
|
1632
|
-
})}
|
|
1633
|
-
</select>
|
|
1634
|
-
</label>
|
|
1635
|
-
`;
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
__decorate([property({ attribute: false })], MonoTablePageSizeCoreClass.prototype, "sizes", void 0);
|
|
1639
|
-
__decorate([property({ type: String })], MonoTablePageSizeCoreClass.prototype, "label", void 0);
|
|
1640
|
-
return MonoTablePageSizeCoreClass;
|
|
1641
|
-
};
|
|
1642
|
-
|
|
1643
72
|
//#endregion
|
|
1644
73
|
//#region src/components/table/mono-table-page-size.ts
|
|
1645
74
|
let MonoTablePageSize = class MonoTablePageSize extends MonoTablePageSizeCore(LitElement) {
|
|
@@ -1649,36 +78,6 @@ let MonoTablePageSize = class MonoTablePageSize extends MonoTablePageSizeCore(Li
|
|
|
1649
78
|
};
|
|
1650
79
|
MonoTablePageSize = __decorate([customElement("mono-table-page-size")], MonoTablePageSize);
|
|
1651
80
|
|
|
1652
|
-
//#endregion
|
|
1653
|
-
//#region src/components/table/mono-table-info-core.ts
|
|
1654
|
-
/**
|
|
1655
|
-
* `MonoTableInfoCore` — render-mode-agnostic logic for `mono-table-info`: a
|
|
1656
|
-
* read-only summary line (`Showing {from}–{to} of {total}`) driven by the
|
|
1657
|
-
* controller. No slots, no icons — light and shadow share the full `render()`.
|
|
1658
|
-
*/
|
|
1659
|
-
const MonoTableInfoCore = (superClass) => {
|
|
1660
|
-
class MonoTableInfoCoreClass extends MonoTableControllerCore(superClass) {
|
|
1661
|
-
constructor(..._args) {
|
|
1662
|
-
super(..._args);
|
|
1663
|
-
this._propsSlot = "info";
|
|
1664
|
-
this.template = "Showing {from}–{to} of {total}";
|
|
1665
|
-
}
|
|
1666
|
-
render() {
|
|
1667
|
-
const grid = this.dataGrid;
|
|
1668
|
-
const shown = grid?.items.length ?? 0;
|
|
1669
|
-
const pageIndex = grid?.pageIndex ?? 0;
|
|
1670
|
-
const pageSize = grid?.pageSize ?? 0;
|
|
1671
|
-
const total = grid?.totalCount ?? shown;
|
|
1672
|
-
const from = shown === 0 ? 0 : pageIndex * pageSize + 1;
|
|
1673
|
-
const to = shown === 0 ? 0 : from + shown - 1;
|
|
1674
|
-
const text = this.template.replace("{from}", String(from)).replace("{to}", String(to)).replace("{total}", String(total)).replace("{page}", String(pageIndex + 1)).replace("{pages}", String(grid?.pageCount ?? 1));
|
|
1675
|
-
return html`<span class="mono-table-info" mono-table-info>${text}</span>`;
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
__decorate([property({ type: String })], MonoTableInfoCoreClass.prototype, "template", void 0);
|
|
1679
|
-
return MonoTableInfoCoreClass;
|
|
1680
|
-
};
|
|
1681
|
-
|
|
1682
81
|
//#endregion
|
|
1683
82
|
//#region src/components/table/mono-table-info.ts
|
|
1684
83
|
let MonoTableInfo = class MonoTableInfo extends MonoTableInfoCore(LitElement) {
|
|
@@ -1688,481 +87,6 @@ let MonoTableInfo = class MonoTableInfo extends MonoTableInfoCore(LitElement) {
|
|
|
1688
87
|
};
|
|
1689
88
|
MonoTableInfo = __decorate([customElement("mono-table-info")], MonoTableInfo);
|
|
1690
89
|
|
|
1691
|
-
//#endregion
|
|
1692
|
-
//#region src/components/table/table-sort-utils.ts
|
|
1693
|
-
/**
|
|
1694
|
-
* Pure sort-cycle helpers shared by `mono-table-sort` and `mono-table-th` so the
|
|
1695
|
-
* two header controls behave identically. Kept dependency-free (no Lit/DOM) so
|
|
1696
|
-
* both cores can import them.
|
|
1697
|
-
*/
|
|
1698
|
-
/**
|
|
1699
|
-
* The next direction when a sortable header is clicked.
|
|
1700
|
-
* - default cycle: `null → asc → desc → null`
|
|
1701
|
-
* - `noClear`: `asc ↔ desc` (never returns to `null`)
|
|
1702
|
-
*/
|
|
1703
|
-
function nextOrder(current, noClear = false) {
|
|
1704
|
-
if (current === "asc") return "desc";
|
|
1705
|
-
if (current === "desc") return noClear ? "asc" : null;
|
|
1706
|
-
return "asc";
|
|
1707
|
-
}
|
|
1708
|
-
/** `aria-sort` value for the parent header cell. */
|
|
1709
|
-
function ariaSort(current) {
|
|
1710
|
-
return current === "asc" ? "ascending" : current === "desc" ? "descending" : "none";
|
|
1711
|
-
}
|
|
1712
|
-
/** `1 → '1st'`, `2 → '2nd'` … used in the sort button's tooltip. */
|
|
1713
|
-
function ordinal(n) {
|
|
1714
|
-
const rem100 = n % 100;
|
|
1715
|
-
if (rem100 >= 11 && rem100 <= 13) return `${n}th`;
|
|
1716
|
-
return `${n}${[
|
|
1717
|
-
"th",
|
|
1718
|
-
"st",
|
|
1719
|
-
"nd",
|
|
1720
|
-
"rd"
|
|
1721
|
-
][n % 10] ?? "th"}`;
|
|
1722
|
-
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Native tooltip text shown on hover — reflects the current sort direction and,
|
|
1725
|
-
* when more than one column is sorted, this column's precedence (which the
|
|
1726
|
-
* `<sup>` badge shows visually).
|
|
1727
|
-
*/
|
|
1728
|
-
function sortTitle(current, seq = 0, total = 0) {
|
|
1729
|
-
const base = current === "asc" ? "Sorted ascending" : current === "desc" ? "Sorted descending" : "Click to sort";
|
|
1730
|
-
if (!current || total < 2 || seq < 1) return base;
|
|
1731
|
-
return `${base} (${ordinal(seq)} of ${total})`;
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
//#endregion
|
|
1735
|
-
//#region src/components/table/table-menu-core.ts
|
|
1736
|
-
/**
|
|
1737
|
-
* `MonoTableMenuCore` — the header context-menu machinery shared by
|
|
1738
|
-
* `mono-table-th` and the standalone `mono-table-sort`.
|
|
1739
|
-
*
|
|
1740
|
-
* Right-click on the header cell ALWAYS opens the menu; its **Sort** row cascades
|
|
1741
|
-
* into an Ascending / Descending / Clear / Clear-all submenu, and picking a
|
|
1742
|
-
* direction there ACCUMULATES keys (the column's own arrow stays single-key).
|
|
1743
|
-
* Only `mono-table-th` adds a Header Filter row (and its value panel), so the
|
|
1744
|
-
* filter-specific parts deliberately stay in that core — this mixin owns just what
|
|
1745
|
-
* is genuinely common:
|
|
1746
|
-
*
|
|
1747
|
-
* - the menu + submenu `PopupPortalController`s,
|
|
1748
|
-
* - the `contextmenu` binding on the closest `th`/`td`,
|
|
1749
|
-
* - the document `pointerdown` / `Escape` dismissal, with a
|
|
1750
|
-
* `_menuKeepsPath()` hook subclasses extend for their own popups,
|
|
1751
|
-
* - the Sort row and submenu rendering.
|
|
1752
|
-
*
|
|
1753
|
-
* It only *declares* `field` / `caption` — both cores already define them as
|
|
1754
|
-
* reactive properties, and re-declaring would shadow them.
|
|
1755
|
-
*/
|
|
1756
|
-
const MonoTableMenuCore = (superClass) => {
|
|
1757
|
-
class MonoTableMenuCoreClass extends MonoTableControllerCore(superClass) {
|
|
1758
|
-
constructor(..._args) {
|
|
1759
|
-
super(..._args);
|
|
1760
|
-
this._menuOpen = false;
|
|
1761
|
-
this._sortMenuOpen = false;
|
|
1762
|
-
this._menuPanelEl = null;
|
|
1763
|
-
this._cell = null;
|
|
1764
|
-
}
|
|
1765
|
-
connectedCallback() {
|
|
1766
|
-
super.connectedCallback();
|
|
1767
|
-
if (!isServer) this.requestUpdate();
|
|
1768
|
-
}
|
|
1769
|
-
disconnectedCallback() {
|
|
1770
|
-
this.detachContextMenu();
|
|
1771
|
-
this.teardownDocListeners();
|
|
1772
|
-
super.disconnectedCallback();
|
|
1773
|
-
}
|
|
1774
|
-
/** This column's direction, or null when it isn't part of the sort. */
|
|
1775
|
-
get _sortDir() {
|
|
1776
|
-
const grid = this.dataGrid;
|
|
1777
|
-
if (!grid) return null;
|
|
1778
|
-
if (typeof grid.sortOf === "function") return grid.sortOf(this.field);
|
|
1779
|
-
return grid.sortField === this.field ? grid.sortOrder ?? null : null;
|
|
1780
|
-
}
|
|
1781
|
-
/** Resolved sort config. Subclasses supply the raw object. */
|
|
1782
|
-
resolveSort(raw) {
|
|
1783
|
-
return {
|
|
1784
|
-
enable: raw?.enable !== false,
|
|
1785
|
-
showIcon: raw?.showIcon !== false,
|
|
1786
|
-
noClear: !!raw?.noClear,
|
|
1787
|
-
disabled: !!raw?.disabled
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
/**
|
|
1791
|
-
* (Re)bind `contextmenu` on the closest header cell when `wanted`. Idempotent:
|
|
1792
|
-
* it no-ops while already bound to the same cell, so it is safe to call from
|
|
1793
|
-
* every `updated()`.
|
|
1794
|
-
*/
|
|
1795
|
-
bindContextMenu(wanted) {
|
|
1796
|
-
if (isServer) return;
|
|
1797
|
-
const cell = wanted ? this.closest("th, td") : null;
|
|
1798
|
-
if (cell === this._cell) return;
|
|
1799
|
-
this.detachContextMenu();
|
|
1800
|
-
this._cell = cell;
|
|
1801
|
-
if (!cell) return;
|
|
1802
|
-
this._ctxHandler ??= (e) => {
|
|
1803
|
-
e.stopImmediatePropagation();
|
|
1804
|
-
this.openMenu(e);
|
|
1805
|
-
};
|
|
1806
|
-
cell.addEventListener("contextmenu", this._ctxHandler);
|
|
1807
|
-
}
|
|
1808
|
-
detachContextMenu() {
|
|
1809
|
-
if (this._cell && this._ctxHandler) this._cell.removeEventListener("contextmenu", this._ctxHandler);
|
|
1810
|
-
this._cell = null;
|
|
1811
|
-
}
|
|
1812
|
-
/** Create the menu + submenu popups. Subclasses override to add their own. */
|
|
1813
|
-
ensurePopups() {
|
|
1814
|
-
if (this._menuPopup || isServer) return;
|
|
1815
|
-
const scope = () => this.closest(".mono-table, [mono-table]") ?? this;
|
|
1816
|
-
this._menuPopup = new PopupPortalController(this, {
|
|
1817
|
-
getPanel: () => {
|
|
1818
|
-
const el = this.renderRoot.querySelector(".mono-th-menu:not(.mono-th-submenu)");
|
|
1819
|
-
if (el) this._menuPanelEl = el;
|
|
1820
|
-
return this._menuPanelEl;
|
|
1821
|
-
},
|
|
1822
|
-
getAnchor: () => this._cell ?? this,
|
|
1823
|
-
getStyleScope: scope,
|
|
1824
|
-
isOpen: () => this._menuOpen,
|
|
1825
|
-
side: () => "bottom",
|
|
1826
|
-
align: () => "start",
|
|
1827
|
-
offset: () => 2,
|
|
1828
|
-
flip: () => true,
|
|
1829
|
-
shift: () => true
|
|
1830
|
-
});
|
|
1831
|
-
this._sortMenuPopup = new PopupPortalController(this, {
|
|
1832
|
-
getPanel: () => this.renderRoot.querySelector(".mono-th-submenu"),
|
|
1833
|
-
getAnchor: () => this._menuPanelEl?.querySelector("[data-menu=\"sort\"]") ?? this._cell ?? this,
|
|
1834
|
-
getStyleScope: scope,
|
|
1835
|
-
isOpen: () => this._sortMenuOpen,
|
|
1836
|
-
side: () => "right",
|
|
1837
|
-
align: () => "start",
|
|
1838
|
-
offset: () => 4,
|
|
1839
|
-
flip: () => true,
|
|
1840
|
-
shift: () => true
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
/** Open the header menu. Safe to wire straight to a click / contextmenu. */
|
|
1844
|
-
openMenu(e) {
|
|
1845
|
-
if (isServer) return;
|
|
1846
|
-
e?.preventDefault();
|
|
1847
|
-
e?.stopPropagation();
|
|
1848
|
-
this.ensurePopups();
|
|
1849
|
-
this._sortMenuOpen = false;
|
|
1850
|
-
this._menuOpen = true;
|
|
1851
|
-
this.bindDocListeners();
|
|
1852
|
-
}
|
|
1853
|
-
closeMenu() {
|
|
1854
|
-
this._menuOpen = false;
|
|
1855
|
-
this._sortMenuOpen = false;
|
|
1856
|
-
this.teardownDocListeners();
|
|
1857
|
-
}
|
|
1858
|
-
/**
|
|
1859
|
-
* Paths that must NOT dismiss the menu. Subclasses override to add their own
|
|
1860
|
-
* popups and trigger icons — without that, a trigger's own `pointerdown`
|
|
1861
|
-
* counts as "outside", closes the menu, and its click handler immediately
|
|
1862
|
-
* reopens it, so the toggle never shuts.
|
|
1863
|
-
*/
|
|
1864
|
-
_menuKeepsPath(path) {
|
|
1865
|
-
if (this._cell && path.includes(this._cell)) return true;
|
|
1866
|
-
if (this._menuPopup?.containsInPath(path)) return true;
|
|
1867
|
-
if (this._sortMenuPopup?.containsInPath(path)) return true;
|
|
1868
|
-
return false;
|
|
1869
|
-
}
|
|
1870
|
-
bindDocListeners() {
|
|
1871
|
-
if (this._onDocPointer || isServer) return;
|
|
1872
|
-
this._onDocPointer = (e) => {
|
|
1873
|
-
if (this._menuKeepsPath(e.composedPath())) return;
|
|
1874
|
-
this.closeMenu();
|
|
1875
|
-
};
|
|
1876
|
-
this._onDocKey = (e) => {
|
|
1877
|
-
if (e.key === "Escape") this.closeMenu();
|
|
1878
|
-
};
|
|
1879
|
-
document.addEventListener("pointerdown", this._onDocPointer, true);
|
|
1880
|
-
document.addEventListener("keydown", this._onDocKey, true);
|
|
1881
|
-
}
|
|
1882
|
-
teardownDocListeners() {
|
|
1883
|
-
if (this._onDocPointer) document.removeEventListener("pointerdown", this._onDocPointer, true);
|
|
1884
|
-
if (this._onDocKey) document.removeEventListener("keydown", this._onDocKey, true);
|
|
1885
|
-
this._onDocPointer = void 0;
|
|
1886
|
-
this._onDocKey = void 0;
|
|
1887
|
-
}
|
|
1888
|
-
_caretIcon() {
|
|
1889
|
-
return html`<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1890
|
-
<path d="M8.6 16.6 13.2 12 8.6 7.4 10 6l6 6-6 6z" />
|
|
1891
|
-
</svg>`;
|
|
1892
|
-
}
|
|
1893
|
-
_sortGlyph(dir) {
|
|
1894
|
-
if (dir === "asc") return html`<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1895
|
-
<path d="M11 22h2V8.414h5.414L12 2L5.586 8.414H11z" />
|
|
1896
|
-
</svg>`;
|
|
1897
|
-
if (dir === "desc") return html`<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1898
|
-
<path d="M13 2h-2v13.586H5.586L12 22l6.414-6.414H13z" />
|
|
1899
|
-
</svg>`;
|
|
1900
|
-
return html`<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1901
|
-
<path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
|
|
1902
|
-
</svg>`;
|
|
1903
|
-
}
|
|
1904
|
-
/** The "clear all sorting" glyph — a crossed-out circle. */
|
|
1905
|
-
_clearAllIcon() {
|
|
1906
|
-
return html`<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1907
|
-
<path
|
|
1908
|
-
d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20m0 2c1.85 0 3.55.63 4.9 1.69L5.69 16.9A7.9 7.9 0 0 1 4 12a8 8 0 0 1 8-8m0 16a7.9 7.9 0 0 1-4.9-1.69L18.31 7.1A7.9 7.9 0 0 1 20 12a8 8 0 0 1-8 8"
|
|
1909
|
-
/>
|
|
1910
|
-
</svg>`;
|
|
1911
|
-
}
|
|
1912
|
-
_checkIcon() {
|
|
1913
|
-
return html`<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" aria-hidden="true">
|
|
1914
|
-
<path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
|
1915
|
-
</svg>`;
|
|
1916
|
-
}
|
|
1917
|
-
/** The `Sort ›` row that cascades into {@link renderSortSubmenu}. */
|
|
1918
|
-
renderSortMenuItem() {
|
|
1919
|
-
return html`
|
|
1920
|
-
<button
|
|
1921
|
-
type="button"
|
|
1922
|
-
class="mono-th-menu-item" mono-th-menu-item
|
|
1923
|
-
role="menuitem"
|
|
1924
|
-
data-menu="sort"
|
|
1925
|
-
aria-haspopup="true"
|
|
1926
|
-
@click=${() => {
|
|
1927
|
-
this.ensurePopups();
|
|
1928
|
-
this._sortMenuOpen = true;
|
|
1929
|
-
}}
|
|
1930
|
-
>
|
|
1931
|
-
<span class="mono-th-menu-ic" mono-th-menu-ic>
|
|
1932
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" aria-hidden="true">
|
|
1933
|
-
<path
|
|
1934
|
-
d="M10.73 13.79c.29.28.75.28 1.04 0l2.75-2.65a.75.75 0 1 0-1.04-1.08L12 11.486V2.75a.75.75 0 0 0-1.5 0v8.736L9.02 10.06a.75.75 0 1 0-1.04 1.08zM5.28 2.22a.75.75 0 0 0-1.06 0L1.47 4.97a.75.75 0 0 0 1.06 1.06L4 4.56v8.69a.75.75 0 0 0 1.5 0V4.56l1.47 1.47a.75.75 0 0 0 1.06-1.06z"
|
|
1935
|
-
/>
|
|
1936
|
-
</svg>
|
|
1937
|
-
</span>
|
|
1938
|
-
<span class="mono-th-menu-label" mono-th-menu-label>Sort</span>
|
|
1939
|
-
<span class="mono-th-menu-caret" mono-th-menu-caret>${this._caretIcon()}</span>
|
|
1940
|
-
</button>
|
|
1941
|
-
`;
|
|
1942
|
-
}
|
|
1943
|
-
/**
|
|
1944
|
-
* Apply a direction from the submenu, then close everything. The menu is the
|
|
1945
|
-
* multi-key path — `{ multi: true }` appends an unsorted column (pick order is
|
|
1946
|
-
* precedence) and drops just this one on `null`, leaving the other keys alone.
|
|
1947
|
-
* `sticky` is what makes the grid remember the gesture, so the arrows append
|
|
1948
|
-
* too from here on (until nothing is sorted any more).
|
|
1949
|
-
*/
|
|
1950
|
-
async pickSort(order) {
|
|
1951
|
-
this.closeMenu();
|
|
1952
|
-
await this.dataGrid?.setSort(this.field, order, {
|
|
1953
|
-
multi: true,
|
|
1954
|
-
sticky: true
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
/** Empty the whole sort, however many keys it holds. */
|
|
1958
|
-
async clearAllSorts() {
|
|
1959
|
-
this.closeMenu();
|
|
1960
|
-
await this.dataGrid?.setSort(null);
|
|
1961
|
-
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Ascending / Descending / Clear, then a grid-level **Clear all sorting**.
|
|
1964
|
-
* `noClear` drops this column's Clear row — that config means the column never
|
|
1965
|
-
* returns to unsorted on its own — but not Clear all, which is not about one
|
|
1966
|
-
* column and stays reachable from every header.
|
|
1967
|
-
*/
|
|
1968
|
-
renderSortSubmenu(noClear = false) {
|
|
1969
|
-
const current = this._sortDir;
|
|
1970
|
-
const sortCount = this.dataGrid?.sorts?.length ?? 0;
|
|
1971
|
-
const row = (order, glyph, label) => {
|
|
1972
|
-
const active = order !== null && current === order;
|
|
1973
|
-
return html`
|
|
1974
|
-
<button
|
|
1975
|
-
type="button"
|
|
1976
|
-
class="mono-th-menu-item ${active ? "active" : ""}" mono-th-menu-item
|
|
1977
|
-
?mono-active=${active}
|
|
1978
|
-
role="menuitemradio"
|
|
1979
|
-
aria-checked=${active ? "true" : "false"}
|
|
1980
|
-
data-sort-pick=${glyph}
|
|
1981
|
-
@click=${() => this.pickSort(order)}
|
|
1982
|
-
>
|
|
1983
|
-
<span class="mono-th-menu-ic" mono-th-menu-ic>${this._sortGlyph(glyph)}</span>
|
|
1984
|
-
<span class="mono-th-menu-label" mono-th-menu-label>${label}</span>
|
|
1985
|
-
<span class="mono-th-menu-check" mono-th-menu-check>${active ? this._checkIcon() : nothing}</span>
|
|
1986
|
-
</button>
|
|
1987
|
-
`;
|
|
1988
|
-
};
|
|
1989
|
-
return html`
|
|
1990
|
-
<div
|
|
1991
|
-
class="mono-th-menu mono-th-submenu ${this._sortMenuOpen ? "open" : ""}" mono-th-menu mono-th-submenu
|
|
1992
|
-
?mono-open=${this._sortMenuOpen}
|
|
1993
|
-
role="menu"
|
|
1994
|
-
aria-label="Sort"
|
|
1995
|
-
>
|
|
1996
|
-
${row("asc", "asc", "Ascending")} ${row("desc", "desc", "Descending")}
|
|
1997
|
-
${noClear ? nothing : row(null, "clear", "Clear")}
|
|
1998
|
-
<div class="mono-th-menu-sep" mono-th-menu-sep role="separator"></div>
|
|
1999
|
-
<button
|
|
2000
|
-
type="button"
|
|
2001
|
-
class="mono-th-menu-item" mono-th-menu-item
|
|
2002
|
-
role="menuitem"
|
|
2003
|
-
data-sort-pick="clear-all"
|
|
2004
|
-
?disabled=${sortCount === 0}
|
|
2005
|
-
aria-disabled=${sortCount === 0 ? "true" : "false"}
|
|
2006
|
-
@click=${() => this.clearAllSorts()}
|
|
2007
|
-
>
|
|
2008
|
-
<span class="mono-th-menu-ic" mono-th-menu-ic>${this._clearAllIcon()}</span>
|
|
2009
|
-
<span class="mono-th-menu-label" mono-th-menu-label>Clear all sorting</span>
|
|
2010
|
-
</button>
|
|
2011
|
-
</div>
|
|
2012
|
-
`;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
__decorate([state()], MonoTableMenuCoreClass.prototype, "_menuOpen", void 0);
|
|
2016
|
-
__decorate([state()], MonoTableMenuCoreClass.prototype, "_sortMenuOpen", void 0);
|
|
2017
|
-
return MonoTableMenuCoreClass;
|
|
2018
|
-
};
|
|
2019
|
-
|
|
2020
|
-
//#endregion
|
|
2021
|
-
//#region src/components/table/mono-table-sort-core.ts
|
|
2022
|
-
/**
|
|
2023
|
-
* `MonoTableSortCore` — render-mode-agnostic logic for `mono-table-sort`: a
|
|
2024
|
-
* column-header sort control whose arrow cycles a SINGLE key `asc → desc → none`
|
|
2025
|
-
* (or `asc ↔ desc` with `no-clear`), while right-clicking the header cell opens
|
|
2026
|
-
* the `Sort ›` menu — the only place a multi-key sort can be built. The header
|
|
2027
|
-
* label is delegated to a `renderSlot('label')`
|
|
2028
|
-
* hook (light: a `[data-mono-slot]` placeholder filled with captured nodes;
|
|
2029
|
-
* shadow: native `<slot>`) and the up/down chevrons to `renderIcon('chevron')`
|
|
2030
|
-
* (light: `i-mdi-chevron-*`; shadow: inline SVG).
|
|
2031
|
-
*
|
|
2032
|
-
* `updated()` reflects the sort state onto the parent `<th>`/`<td>` via
|
|
2033
|
-
* `closest()` — guarded by `isServer` (lit) so it never runs during SSR.
|
|
2034
|
-
*/
|
|
2035
|
-
const MonoTableSortCore = (superClass) => {
|
|
2036
|
-
class MonoTableSortCoreClass extends MonoTableMenuCore(superClass) {
|
|
2037
|
-
constructor(...args) {
|
|
2038
|
-
super(...args);
|
|
2039
|
-
this._propsSlot = "sort";
|
|
2040
|
-
this.field = "";
|
|
2041
|
-
this.caption = "";
|
|
2042
|
-
this.disabled = false;
|
|
2043
|
-
this.enable = true;
|
|
2044
|
-
this.noClear = false;
|
|
2045
|
-
this.showIcon = true;
|
|
2046
|
-
defineHybridPropAliases(this, ["noClear", "showIcon"]);
|
|
2047
|
-
}
|
|
2048
|
-
updated(changed) {
|
|
2049
|
-
super.updated(changed);
|
|
2050
|
-
if (isServer) return;
|
|
2051
|
-
const cell = this.closest("th, td");
|
|
2052
|
-
if (cell) cell.setAttribute("aria-sort", ariaSort(this._current));
|
|
2053
|
-
this.bindContextMenu(this._sortable);
|
|
2054
|
-
}
|
|
2055
|
-
get _sortable() {
|
|
2056
|
-
return !!this.field && this.enable && !this.disabled && !!this.dataGrid;
|
|
2057
|
-
}
|
|
2058
|
-
/** This column's current direction, or null when it isn't part of the sort. */
|
|
2059
|
-
get _current() {
|
|
2060
|
-
const grid = this.dataGrid;
|
|
2061
|
-
if (!grid) return null;
|
|
2062
|
-
if (typeof grid.sortOf === "function") return grid.sortOf(this.field);
|
|
2063
|
-
return grid.sortField === this.field ? grid.sortOrder ?? null : null;
|
|
2064
|
-
}
|
|
2065
|
-
/** 1-based precedence of this column in the sort; `0` when unsorted. */
|
|
2066
|
-
get _seq() {
|
|
2067
|
-
return this.dataGrid?.sortIndex?.(this.field) ?? 0;
|
|
2068
|
-
}
|
|
2069
|
-
/** How many columns are sorted — the `<sup>` only shows when 2+. */
|
|
2070
|
-
get _sortCount() {
|
|
2071
|
-
return this.dataGrid?.sorts?.length ?? 0;
|
|
2072
|
-
}
|
|
2073
|
-
/**
|
|
2074
|
-
* The arrow (or, with `show-icon="false"`, the label). Single-key — this
|
|
2075
|
-
* column becomes the entire sort, collapsing any stack — UNLESS the user has
|
|
2076
|
-
* started combining from the right-click menu and something is still
|
|
2077
|
-
* sorted: then the arrow appends too, because a user who began a multi-key
|
|
2078
|
-
* sort means to keep building it (`sortCombining`). Cycling past `desc`
|
|
2079
|
-
* clears this column either way.
|
|
2080
|
-
*/
|
|
2081
|
-
_toggle() {
|
|
2082
|
-
if (!this._sortable) return;
|
|
2083
|
-
this.dataGrid?.setSort(this.field, nextOrder(this._current, this.noClear), { multi: !!this.dataGrid?.sortCombining?.() });
|
|
2084
|
-
}
|
|
2085
|
-
/** Per-region slot content — overridden per build. */
|
|
2086
|
-
renderSlot(_name) {
|
|
2087
|
-
return html``;
|
|
2088
|
-
}
|
|
2089
|
-
/** Internal icon — light: UnoCSS `.mono-icon`; shadow: inline SVG. */
|
|
2090
|
-
renderIcon(_name) {
|
|
2091
|
-
return html``;
|
|
2092
|
-
}
|
|
2093
|
-
_label() {
|
|
2094
|
-
return html`<span class="mono-table-sort-label" data-mono-slot="label"
|
|
2095
|
-
>${this.caption ? this.caption : this.renderSlot("label")}</span
|
|
2096
|
-
>`;
|
|
2097
|
-
}
|
|
2098
|
-
render() {
|
|
2099
|
-
if (!this._sortable) return this._label();
|
|
2100
|
-
const current = this._current;
|
|
2101
|
-
const seq = this._seq;
|
|
2102
|
-
const total = this._sortCount;
|
|
2103
|
-
const seqBadge = total > 1 && seq > 0 ? html`<sup class="mono-table-sort-seq" mono-sort-seq aria-hidden="true">${seq}</sup>` : nothing;
|
|
2104
|
-
const menus = html`
|
|
2105
|
-
<div class="mono-th-menu ${this._menuOpen ? "open" : ""}" mono-th-menu ?mono-open=${this._menuOpen} role="menu">
|
|
2106
|
-
${this.renderSortMenuItem()}
|
|
2107
|
-
</div>
|
|
2108
|
-
${this.renderSortSubmenu(this.noClear)}
|
|
2109
|
-
`;
|
|
2110
|
-
if (!this.showIcon) return html`
|
|
2111
|
-
<span class="mono-table-sort-head no-icon" mono-sort-head>
|
|
2112
|
-
<button
|
|
2113
|
-
type="button"
|
|
2114
|
-
class="mono-table-sort-btn label-trigger" mono-sort-btn
|
|
2115
|
-
data-dir=${current ?? "none"}
|
|
2116
|
-
title=${sortTitle(current, seq, total)}
|
|
2117
|
-
@click=${this._toggle}
|
|
2118
|
-
>
|
|
2119
|
-
${this._label()}${seqBadge}
|
|
2120
|
-
</button>
|
|
2121
|
-
</span>
|
|
2122
|
-
${menus}
|
|
2123
|
-
`;
|
|
2124
|
-
return html`
|
|
2125
|
-
<span class="mono-table-sort-head" mono-sort-head>
|
|
2126
|
-
${this._label()}
|
|
2127
|
-
<button
|
|
2128
|
-
type="button"
|
|
2129
|
-
class="mono-table-sort-btn" mono-sort-btn
|
|
2130
|
-
data-dir=${current ?? "none"}
|
|
2131
|
-
title=${sortTitle(current, seq, total)}
|
|
2132
|
-
aria-label=${`Sort by ${this.caption || this.field}`}
|
|
2133
|
-
@click=${this._toggle}
|
|
2134
|
-
>
|
|
2135
|
-
<span class="mono-table-sort-ind" mono-sort-ind aria-hidden="true">${this.renderIcon("chevron")}</span>
|
|
2136
|
-
${seqBadge}
|
|
2137
|
-
</button>
|
|
2138
|
-
</span>
|
|
2139
|
-
${menus}
|
|
2140
|
-
`;
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
__decorate([property({ type: String })], MonoTableSortCoreClass.prototype, "field", void 0);
|
|
2144
|
-
__decorate([property({ type: String })], MonoTableSortCoreClass.prototype, "caption", void 0);
|
|
2145
|
-
__decorate([property({
|
|
2146
|
-
reflect: true,
|
|
2147
|
-
converter: booleanStringConverter
|
|
2148
|
-
})], MonoTableSortCoreClass.prototype, "disabled", void 0);
|
|
2149
|
-
__decorate([property({
|
|
2150
|
-
reflect: true,
|
|
2151
|
-
converter: booleanStringConverter
|
|
2152
|
-
})], MonoTableSortCoreClass.prototype, "enable", void 0);
|
|
2153
|
-
__decorate([property({
|
|
2154
|
-
attribute: "no-clear",
|
|
2155
|
-
reflect: true,
|
|
2156
|
-
converter: booleanStringConverter
|
|
2157
|
-
})], MonoTableSortCoreClass.prototype, "noClear", void 0);
|
|
2158
|
-
__decorate([property({
|
|
2159
|
-
attribute: "show-icon",
|
|
2160
|
-
reflect: true,
|
|
2161
|
-
converter: booleanStringConverter
|
|
2162
|
-
})], MonoTableSortCoreClass.prototype, "showIcon", void 0);
|
|
2163
|
-
return MonoTableSortCoreClass;
|
|
2164
|
-
};
|
|
2165
|
-
|
|
2166
90
|
//#endregion
|
|
2167
91
|
//#region src/components/table/mono-table-sort.ts
|
|
2168
92
|
let MonoTableSort = class MonoTableSort extends MonoTableSortCore(LitElement) {
|
|
@@ -2204,803 +128,6 @@ let MonoTableSort = class MonoTableSort extends MonoTableSortCore(LitElement) {
|
|
|
2204
128
|
};
|
|
2205
129
|
MonoTableSort = __decorate([customElement("mono-table-sort")], MonoTableSort);
|
|
2206
130
|
|
|
2207
|
-
//#endregion
|
|
2208
|
-
//#region src/components/table/mono-table-th-core.ts
|
|
2209
|
-
/** Coerce a size prop to a CSS length: a number or bare numeric string → px; anything with a unit (`'10rem'`, `'40%'`) is used as-is. */
|
|
2210
|
-
function toCssSize(value) {
|
|
2211
|
-
return typeof value === "number" || /^\d+(\.\d+)?$/.test(String(value)) ? `${value}px` : String(value);
|
|
2212
|
-
}
|
|
2213
|
-
/**
|
|
2214
|
-
* `MonoTableThCore` — render-mode-agnostic logic for `mono-table-th`, the unified
|
|
2215
|
-
* column-header cell. It declares a column's `field`/`caption`, folds the sort
|
|
2216
|
-
* behavior into a single `:sort.prop` object (`{ order?, noClear?, disabled? }`),
|
|
2217
|
-
* marks a column editable (`editable`), and — with `header-filter` — adds a
|
|
2218
|
-
* DevExtreme-style **header filter**: a funnel icon (or the header's right-click
|
|
2219
|
-
* menu → "Header Filter") opens a panel of the column's distinct values (fetched
|
|
2220
|
-
* via the controller's `distinctValues`, i.e. an OData `$apply=groupby`) with
|
|
2221
|
-
* checkboxes; checking some + Apply calls `dataGrid.setColumnFilter(field, values)`.
|
|
2222
|
-
*
|
|
2223
|
-
* Gestures are fixed, not configurable, and both features split the same way: the
|
|
2224
|
-
* ICON (sort arrow / filter funnel) acts on ONE column, replacing whatever else
|
|
2225
|
-
* was sorted or filtered, while RIGHT-CLICK always opens the menu, whose `Sort ›`
|
|
2226
|
-
* / `Header Filter ›` rows combine columns. One carry-over: once the menu has
|
|
2227
|
-
* started a combination and something is still sorted / filtered, the icons
|
|
2228
|
-
* combine too — the grid remembers the gesture (`sortCombining` /
|
|
2229
|
-
* `columnFilterCombining`) until the last key / filter is gone.
|
|
2230
|
-
*
|
|
2231
|
-
* The standalone `mono-table-sort` element is unchanged and still supported.
|
|
2232
|
-
*/
|
|
2233
|
-
const MonoTableThCore = (superClass) => {
|
|
2234
|
-
class MonoTableThCoreClass extends MonoTableMenuCore(superClass) {
|
|
2235
|
-
constructor(...args) {
|
|
2236
|
-
super(...args);
|
|
2237
|
-
this._propsSlot = "th";
|
|
2238
|
-
this.field = "";
|
|
2239
|
-
this.caption = "";
|
|
2240
|
-
this.sort = false;
|
|
2241
|
-
this.editable = false;
|
|
2242
|
-
this.required = false;
|
|
2243
|
-
this.headerFilter = false;
|
|
2244
|
-
this.dateFilter = false;
|
|
2245
|
-
this._tree = [];
|
|
2246
|
-
this._expanded = /* @__PURE__ */ new Set();
|
|
2247
|
-
this._exclusiveWarned = false;
|
|
2248
|
-
this._lastLoaded = 0;
|
|
2249
|
-
this._sizeSig = [];
|
|
2250
|
-
this._seededSort = false;
|
|
2251
|
-
this._filterOpen = false;
|
|
2252
|
-
this._values = [];
|
|
2253
|
-
this._checked = /* @__PURE__ */ new Set();
|
|
2254
|
-
this._valuesLoading = false;
|
|
2255
|
-
this._valuesError = false;
|
|
2256
|
-
this._search = "";
|
|
2257
|
-
this._fromIcon = false;
|
|
2258
|
-
defineHybridPropAliases(this, [
|
|
2259
|
-
"editableTrigger",
|
|
2260
|
-
"minWidth",
|
|
2261
|
-
"maxWidth"
|
|
2262
|
-
]);
|
|
2263
|
-
}
|
|
2264
|
-
/** The sort config as an object: `true` → `{}`, `false` → `undefined`. */
|
|
2265
|
-
get _sortObj() {
|
|
2266
|
-
const s = this.sort;
|
|
2267
|
-
if (s === true) return {};
|
|
2268
|
-
if (!s || typeof s !== "object") return void 0;
|
|
2269
|
-
return s;
|
|
2270
|
-
}
|
|
2271
|
-
connectedCallback() {
|
|
2272
|
-
super.connectedCallback();
|
|
2273
|
-
this._register();
|
|
2274
|
-
if (!isServer) startMonoSizeAttributes();
|
|
2275
|
-
}
|
|
2276
|
-
disconnectedCallback() {
|
|
2277
|
-
this.dataGrid?.unregisterColumn(this);
|
|
2278
|
-
this._autoSize?.stop();
|
|
2279
|
-
this._sizeSig = [];
|
|
2280
|
-
super.disconnectedCallback();
|
|
2281
|
-
}
|
|
2282
|
-
willUpdate(changed) {
|
|
2283
|
-
super.willUpdate(changed);
|
|
2284
|
-
if (changed.has("dataGrid") || changed.has("field") || changed.has("sort")) this._register();
|
|
2285
|
-
}
|
|
2286
|
-
_register() {
|
|
2287
|
-
if (isServer || !this.dataGrid) return;
|
|
2288
|
-
this.dataGrid.registerColumn(this);
|
|
2289
|
-
this._seedSort();
|
|
2290
|
-
}
|
|
2291
|
-
/**
|
|
2292
|
-
* `width="auto"` → hand the column to the auto-sizer (re-run on every update: each controller
|
|
2293
|
-
* notify lands here). A fixed width → written as before, and an auto-sizer left over from an
|
|
2294
|
-
* earlier `'auto'` is stopped with its px cleared. The bounds go onto the cell either way —
|
|
2295
|
-
* the auto-sizer clamps with them, an auto-layout table honours them.
|
|
2296
|
-
*/
|
|
2297
|
-
_syncWidth(cell) {
|
|
2298
|
-
if (String(this.width ?? "").trim().toLowerCase() === "auto") {
|
|
2299
|
-
const fresh = !this._autoSize;
|
|
2300
|
-
const sizer = this._autoSize ??= new ColumnAutoSize(this);
|
|
2301
|
-
const grid = this.dataGrid;
|
|
2302
|
-
const loaded = grid?.loadedCount ?? 0;
|
|
2303
|
-
if (loaded < this._lastLoaded) sizer.resetFloor();
|
|
2304
|
-
this._lastLoaded = loaded;
|
|
2305
|
-
const sig = [
|
|
2306
|
-
this.minWidth,
|
|
2307
|
-
this.maxWidth,
|
|
2308
|
-
grid?.scrollMode,
|
|
2309
|
-
loaded,
|
|
2310
|
-
this.caption,
|
|
2311
|
-
cell.closest("table")
|
|
2312
|
-
];
|
|
2313
|
-
const moved = sig.length !== this._sizeSig.length || sig.some((v, i) => !Object.is(v, this._sizeSig[i]));
|
|
2314
|
-
if (fresh || moved) {
|
|
2315
|
-
this._sizeSig = sig;
|
|
2316
|
-
sizer.min = cssLengthToPx(this.minWidth ?? null, cell);
|
|
2317
|
-
sizer.max = cssLengthToPx(this.maxWidth ?? null, cell);
|
|
2318
|
-
sizer.growOnly = grid?.scrollMode === "virtual";
|
|
2319
|
-
sizer.schedule();
|
|
2320
|
-
}
|
|
2321
|
-
} else {
|
|
2322
|
-
if (this._autoSize) {
|
|
2323
|
-
this._autoSize.stop(true);
|
|
2324
|
-
this._autoSize = void 0;
|
|
2325
|
-
this._sizeSig = [];
|
|
2326
|
-
}
|
|
2327
|
-
if (this.width != null && this.width !== "") cell.style.width = toCssSize(this.width);
|
|
2328
|
-
}
|
|
2329
|
-
cell.style.minWidth = this.minWidth != null && this.minWidth !== "" ? toCssSize(this.minWidth) : "";
|
|
2330
|
-
cell.style.maxWidth = this.maxWidth != null && this.maxWidth !== "" ? toCssSize(this.maxWidth) : "";
|
|
2331
|
-
}
|
|
2332
|
-
/** Apply `sort.order` once, unless THIS column is already sorted (don't fight a user sort). */
|
|
2333
|
-
_seedSort() {
|
|
2334
|
-
if (this._seededSort) return;
|
|
2335
|
-
if (!this._sortObj) return;
|
|
2336
|
-
this._seededSort = true;
|
|
2337
|
-
const order = this._sortObj.order;
|
|
2338
|
-
if (!order || !this.field) return;
|
|
2339
|
-
if (this._current) return;
|
|
2340
|
-
this.dataGrid?.setSort(this.field, order, { multi: true });
|
|
2341
|
-
}
|
|
2342
|
-
updated(changed) {
|
|
2343
|
-
super.updated(changed);
|
|
2344
|
-
if (isServer) return;
|
|
2345
|
-
const cell = this.closest("th, td");
|
|
2346
|
-
if (cell) {
|
|
2347
|
-
cell.setAttribute("aria-sort", ariaSort(this._current));
|
|
2348
|
-
this._syncWidth(cell);
|
|
2349
|
-
if (this.height != null && this.height !== "") cell.style.height = toCssSize(this.height);
|
|
2350
|
-
cell.style.textAlign = this.align ?? "";
|
|
2351
|
-
}
|
|
2352
|
-
this._syncContextMenu();
|
|
2353
|
-
this.toggleAttribute("data-filtered", this._filtered);
|
|
2354
|
-
this._syncRowTrigger();
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* Hand the controller the `<table>` that holds this grid's rows so it can
|
|
2358
|
-
* bind the click / double-click that opens a row's inline editor. Every
|
|
2359
|
-
* header cell calls in; `bindRowTrigger` is idempotent, so they collapse to
|
|
2360
|
-
* one listener. Only a th that declares its own `editable-trigger` writes the
|
|
2361
|
-
* shared setting, leaving `monoDataGrid({ editableTrigger })` in charge
|
|
2362
|
-
* otherwise.
|
|
2363
|
-
*/
|
|
2364
|
-
_syncRowTrigger() {
|
|
2365
|
-
const grid = this.dataGrid;
|
|
2366
|
-
if (!grid?.bindRowTrigger) return;
|
|
2367
|
-
if (this.editableTrigger && grid.editableTrigger !== this.editableTrigger) grid.editableTrigger = this.editableTrigger;
|
|
2368
|
-
grid.bindRowTrigger(this.closest("table"));
|
|
2369
|
-
}
|
|
2370
|
-
/**
|
|
2371
|
-
* The header-filter config with every default resolved, so no call site has to
|
|
2372
|
-
* re-test `headerFilter`'s shape. `true` → all defaults; an object implies
|
|
2373
|
-
* `enable: true` unless it says otherwise (config that silently does nothing
|
|
2374
|
-
* would be worse than useless).
|
|
2375
|
-
*/
|
|
2376
|
-
get _hf() {
|
|
2377
|
-
const df = this._df;
|
|
2378
|
-
const raw = this.headerFilter;
|
|
2379
|
-
const cfg = raw && typeof raw === "object" ? raw : {};
|
|
2380
|
-
const hfEnable = raw && typeof raw === "object" ? cfg.enable !== false : !!raw;
|
|
2381
|
-
if (df) {
|
|
2382
|
-
if (hfEnable && !this._exclusiveWarned) {
|
|
2383
|
-
this._exclusiveWarned = true;
|
|
2384
|
-
console.warn(`[mono-table-th] "${this.field}" sets both headerFilter and dateFilter; they are exclusive — using dateFilter.`);
|
|
2385
|
-
}
|
|
2386
|
-
return {
|
|
2387
|
-
enable: true,
|
|
2388
|
-
title: df.title ?? `Filter: ${this.caption || this.field}`,
|
|
2389
|
-
showIcon: df.showIcon !== false,
|
|
2390
|
-
dataSourceOptions: df.dataSourceOptions,
|
|
2391
|
-
date: true
|
|
2392
|
-
};
|
|
2393
|
-
}
|
|
2394
|
-
return {
|
|
2395
|
-
enable: hfEnable,
|
|
2396
|
-
title: cfg.title ?? `Filter: ${this.caption || this.field}`,
|
|
2397
|
-
showIcon: cfg.showIcon !== false,
|
|
2398
|
-
dataSourceOptions: cfg.dataSourceOptions,
|
|
2399
|
-
date: false
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
/** The date-filter config when it is ON, with its defaults resolved; else `null`. */
|
|
2403
|
-
get _df() {
|
|
2404
|
-
const raw = this.dateFilter;
|
|
2405
|
-
const cfg = raw && typeof raw === "object" ? raw : {};
|
|
2406
|
-
if (!(raw && typeof raw === "object" ? cfg.enable !== false : !!raw)) return null;
|
|
2407
|
-
return {
|
|
2408
|
-
...cfg,
|
|
2409
|
-
depth: cfg.depth ?? "month",
|
|
2410
|
-
utc: cfg.utc === true
|
|
2411
|
-
};
|
|
2412
|
-
}
|
|
2413
|
-
/** The column's sort config with every default resolved. */
|
|
2414
|
-
get _sortCfg() {
|
|
2415
|
-
return this.resolveSort(this._sortObj);
|
|
2416
|
-
}
|
|
2417
|
-
/** Whether the sort arrow is drawn. Hidden → see `_sortHeader()`. */
|
|
2418
|
-
get _sortIcon() {
|
|
2419
|
-
return this._sortCfg.showIcon;
|
|
2420
|
-
}
|
|
2421
|
-
get _sortable() {
|
|
2422
|
-
const sort = this._sortObj;
|
|
2423
|
-
if (sort?.enable === false) return false;
|
|
2424
|
-
return !!this.field && !!sort && !sort.disabled && !!this.dataGrid;
|
|
2425
|
-
}
|
|
2426
|
-
/** This column's current direction, or null when it isn't part of the sort. */
|
|
2427
|
-
get _current() {
|
|
2428
|
-
const grid = this.dataGrid;
|
|
2429
|
-
if (!grid) return null;
|
|
2430
|
-
if (typeof grid.sortOf === "function") return grid.sortOf(this.field);
|
|
2431
|
-
return grid.sortField === this.field ? grid.sortOrder ?? null : null;
|
|
2432
|
-
}
|
|
2433
|
-
/**
|
|
2434
|
-
* True when THIS column currently has an active header filter. Read by
|
|
2435
|
-
* `updated()` (the `data-filtered` reflection, which drives the colour rules
|
|
2436
|
-
* in table.css) and by both builds' `renderIcon('funnel')`, which swaps the
|
|
2437
|
-
* outlined funnel for the filled one.
|
|
2438
|
-
*
|
|
2439
|
-
* Safe to read during `render()`: every subscribed element re-renders on the
|
|
2440
|
-
* controller's `notify()`, and `setColumnFilter` notifies BEFORE it reloads,
|
|
2441
|
-
* so the funnel flips with the click rather than with the response.
|
|
2442
|
-
*/
|
|
2443
|
-
get _filtered() {
|
|
2444
|
-
return this._hf.enable && (this.dataGrid?.columnFilter?.(this.field)?.length ?? 0) > 0;
|
|
2445
|
-
}
|
|
2446
|
-
/** 1-based precedence of this column in the sort; `0` when unsorted. */
|
|
2447
|
-
get _seq() {
|
|
2448
|
-
return this.dataGrid?.sortIndex?.(this.field) ?? 0;
|
|
2449
|
-
}
|
|
2450
|
-
/** How many columns are sorted — the `<sup>` only shows when 2+. */
|
|
2451
|
-
get _sortCount() {
|
|
2452
|
-
return this.dataGrid?.sorts?.length ?? 0;
|
|
2453
|
-
}
|
|
2454
|
-
/**
|
|
2455
|
-
* The arrow (or, with `showIcon: false`, the caption). Single-key — this
|
|
2456
|
-
* column becomes the entire sort, collapsing any stack — UNLESS the user has
|
|
2457
|
-
* started combining from the right-click menu and something is still
|
|
2458
|
-
* sorted: then the arrow appends too, because a user who began a multi-key
|
|
2459
|
-
* sort means to keep building it (`sortCombining`). Cycling past `desc`
|
|
2460
|
-
* clears this column either way.
|
|
2461
|
-
*/
|
|
2462
|
-
_toggle() {
|
|
2463
|
-
if (!this._sortable) return;
|
|
2464
|
-
this.dataGrid?.setSort(this.field, nextOrder(this._current, this._sortObj?.noClear), { multi: !!this.dataGrid?.sortCombining?.() });
|
|
2465
|
-
}
|
|
2466
|
-
/** Per-region slot content — overridden per build. */
|
|
2467
|
-
renderSlot(_name) {
|
|
2468
|
-
return html``;
|
|
2469
|
-
}
|
|
2470
|
-
/**
|
|
2471
|
-
* Internal icon — light: a UnoCSS `i-*` class; shadow: inline SVG (utility CSS
|
|
2472
|
-
* can't reach a shadow root). Both glyphs encode state the override reads off
|
|
2473
|
-
* `this`: `'chevron'` the sort direction (`_current`), `'funnel'` whether the
|
|
2474
|
-
* column is filtered (`_filtered`).
|
|
2475
|
-
*/
|
|
2476
|
-
renderIcon(_name) {
|
|
2477
|
-
return html``;
|
|
2478
|
-
}
|
|
2479
|
-
/**
|
|
2480
|
-
* The caption, plus the `required` marker when the column carries one.
|
|
2481
|
-
*
|
|
2482
|
-
* The marker sits in a WRAPPER around the label rather than inside it. Two
|
|
2483
|
-
* reasons, both load-bearing:
|
|
2484
|
-
*
|
|
2485
|
-
* · `[data-mono-slot="label"]` must stay the exact element it has always been —
|
|
2486
|
-
* the light build re-appends the consumer's captured nodes (and the Vue
|
|
2487
|
-
* anchors travelling with them) into it after every render. Rendering into
|
|
2488
|
-
* it as well would put Lit's own child part in the same range.
|
|
2489
|
-
* · The header lays out as three independent boxes — funnel, caption, sort
|
|
2490
|
-
* arrow — so a marker at that level drifts away from the text as soon as the
|
|
2491
|
-
* header wraps. An inline-flex wrapper around label + `*` cannot be split.
|
|
2492
|
-
*
|
|
2493
|
-
* The wrapper is emitted ONLY for a marked column, so every other header keeps
|
|
2494
|
-
* the DOM it had. Plain text, so both builds render it from here — no per-build
|
|
2495
|
-
* icon hook, which is the whole point of a `*` over a glyph.
|
|
2496
|
-
*/
|
|
2497
|
-
_label() {
|
|
2498
|
-
const label = html`<span class="mono-table-sort-label" data-mono-slot="label"
|
|
2499
|
-
>${this.caption ? this.caption : this.renderSlot("label")}</span
|
|
2500
|
-
>`;
|
|
2501
|
-
if (!this.required) return label;
|
|
2502
|
-
return html`<span class="mono-th-caption" mono-th-caption
|
|
2503
|
-
>${label}<sup class="mono-th-required" mono-th-required>*</sup></span
|
|
2504
|
-
>`;
|
|
2505
|
-
}
|
|
2506
|
-
/**
|
|
2507
|
-
* Label + sort arrow. Only the ARROW is a button — clicking the caption text
|
|
2508
|
-
* must not sort, so the label sits outside it. `_label()` keeps its
|
|
2509
|
-
* `[data-mono-slot="label"]` placeholder: the light build re-appends the
|
|
2510
|
-
* consumer's captured nodes (and their Vue anchors) into that exact element.
|
|
2511
|
-
*/
|
|
2512
|
-
_sortHeader() {
|
|
2513
|
-
const current = this._current;
|
|
2514
|
-
const seq = this._seq;
|
|
2515
|
-
const total = this._sortCount;
|
|
2516
|
-
const seqBadge = total > 1 && seq > 0 ? html`<sup class="mono-table-sort-seq" mono-sort-seq aria-hidden="true">${seq}</sup>` : nothing;
|
|
2517
|
-
if (!this._sortIcon) return html`
|
|
2518
|
-
<span class="mono-table-sort-head no-icon" mono-sort-head>
|
|
2519
|
-
<button
|
|
2520
|
-
type="button"
|
|
2521
|
-
class="mono-table-sort-btn label-trigger" mono-sort-btn
|
|
2522
|
-
data-dir=${current ?? "none"}
|
|
2523
|
-
title=${sortTitle(current, seq, total)}
|
|
2524
|
-
@click=${this._toggle}
|
|
2525
|
-
>
|
|
2526
|
-
${this._label()}${seqBadge}
|
|
2527
|
-
</button>
|
|
2528
|
-
</span>
|
|
2529
|
-
`;
|
|
2530
|
-
return html`
|
|
2531
|
-
<span class="mono-table-sort-head" mono-sort-head>
|
|
2532
|
-
${this._label()}
|
|
2533
|
-
<button
|
|
2534
|
-
type="button"
|
|
2535
|
-
class="mono-table-sort-btn" mono-sort-btn
|
|
2536
|
-
data-dir=${current ?? "none"}
|
|
2537
|
-
title=${sortTitle(current, seq, total)}
|
|
2538
|
-
aria-label=${`Sort by ${this.caption || this.field}`}
|
|
2539
|
-
data-shimmer-ignore
|
|
2540
|
-
@click=${this._toggle}
|
|
2541
|
-
>
|
|
2542
|
-
<span class="mono-table-sort-ind" mono-sort-ind aria-hidden="true">${this.renderIcon("chevron")}</span>
|
|
2543
|
-
${seqBadge}
|
|
2544
|
-
</button>
|
|
2545
|
-
</span>
|
|
2546
|
-
`;
|
|
2547
|
-
}
|
|
2548
|
-
render() {
|
|
2549
|
-
const hf = this._hf;
|
|
2550
|
-
const header = this._sortable ? this._sortHeader() : this._label();
|
|
2551
|
-
return html`${hf.enable && hf.showIcon ? this._renderFilterIcon() : nothing}${header}${this._wantsMenu ? this._renderMenus() : nothing}`;
|
|
2552
|
-
}
|
|
2553
|
-
/**
|
|
2554
|
-
* Whether right-click has anything to show. Sorting and the header filter each
|
|
2555
|
-
* contribute a section; a column with neither keeps the browser's own menu.
|
|
2556
|
-
*/
|
|
2557
|
-
get _wantsMenu() {
|
|
2558
|
-
return this._sortable || this._hf.enable;
|
|
2559
|
-
}
|
|
2560
|
-
/**
|
|
2561
|
-
* Bind `contextmenu` whenever the menu has a section to show. Unconditional now
|
|
2562
|
-
* that the gesture is fixed, which is also what keeps a header filter with
|
|
2563
|
-
* `showIcon: false` reachable — it used to need a warned-about fallback.
|
|
2564
|
-
*/
|
|
2565
|
-
_syncContextMenu() {
|
|
2566
|
-
if (isServer) return;
|
|
2567
|
-
this.bindContextMenu(this._wantsMenu);
|
|
2568
|
-
}
|
|
2569
|
-
ensurePopups() {
|
|
2570
|
-
if (this._filterPopup || isServer) return;
|
|
2571
|
-
super.ensurePopups();
|
|
2572
|
-
const scope = () => this.closest(".mono-table, [mono-table]") ?? this;
|
|
2573
|
-
this._filterPopup = new PopupPortalController(this, {
|
|
2574
|
-
getPanel: () => this.renderRoot.querySelector(".mono-th-filter"),
|
|
2575
|
-
getAnchor: () => this._fromIcon ? this.renderRoot.querySelector(".mono-th-filter-ind") ?? this : this._menuPanelEl?.querySelector("[data-menu=\"filter\"]") ?? this._cell ?? this,
|
|
2576
|
-
getStyleScope: scope,
|
|
2577
|
-
isOpen: () => this._filterOpen,
|
|
2578
|
-
side: () => this._fromIcon ? "bottom" : "right",
|
|
2579
|
-
align: () => "start",
|
|
2580
|
-
offset: () => 4,
|
|
2581
|
-
flip: () => true,
|
|
2582
|
-
shift: () => true
|
|
2583
|
-
});
|
|
2584
|
-
}
|
|
2585
|
-
openMenu(e) {
|
|
2586
|
-
this._fromIcon = false;
|
|
2587
|
-
this._filterOpen = false;
|
|
2588
|
-
super.openMenu(e);
|
|
2589
|
-
}
|
|
2590
|
-
closeMenu() {
|
|
2591
|
-
this._filterOpen = false;
|
|
2592
|
-
this._fromIcon = false;
|
|
2593
|
-
super.closeMenu();
|
|
2594
|
-
}
|
|
2595
|
-
/** Also keep the filter panel and the funnel from dismissing the menu. */
|
|
2596
|
-
_menuKeepsPath(path) {
|
|
2597
|
-
if (super._menuKeepsPath(path)) return true;
|
|
2598
|
-
if (this._filterPopup?.containsInPath(path)) return true;
|
|
2599
|
-
const icon = this.renderRoot.querySelector(".mono-th-filter-ind");
|
|
2600
|
-
return !!icon && path.includes(icon);
|
|
2601
|
-
}
|
|
2602
|
-
async _openFilter() {
|
|
2603
|
-
this._menuOpen = !this._fromIcon;
|
|
2604
|
-
this._filterOpen = true;
|
|
2605
|
-
this._search = "";
|
|
2606
|
-
this._expanded = /* @__PURE__ */ new Set();
|
|
2607
|
-
const current = this.dataGrid?.columnFilter?.(this.field) ?? [];
|
|
2608
|
-
this._checked = this._hf.date ? /* @__PURE__ */ new Set() : new Set(current.map((v) => String(v)));
|
|
2609
|
-
await this._loadValues();
|
|
2610
|
-
if (this._hf.date) this._checked = seedFromRanges(this._tree, current);
|
|
2611
|
-
}
|
|
2612
|
-
/**
|
|
2613
|
-
* Left-click on the funnel — open the value panel DIRECTLY, skipping the
|
|
2614
|
-
* `Header Filter ›` row that right-click goes through. A visible click
|
|
2615
|
-
* affordance shouldn't need two steps.
|
|
2616
|
-
*/
|
|
2617
|
-
async _openFromIcon(e) {
|
|
2618
|
-
if (isServer || !this._hf.enable) return;
|
|
2619
|
-
e.preventDefault();
|
|
2620
|
-
e.stopPropagation();
|
|
2621
|
-
if (this._filterOpen) {
|
|
2622
|
-
this.closeMenu();
|
|
2623
|
-
return;
|
|
2624
|
-
}
|
|
2625
|
-
this.ensurePopups();
|
|
2626
|
-
this._fromIcon = true;
|
|
2627
|
-
this.bindDocListeners();
|
|
2628
|
-
await this._openFilter();
|
|
2629
|
-
}
|
|
2630
|
-
_renderFilterIcon() {
|
|
2631
|
-
return html`
|
|
2632
|
-
<button
|
|
2633
|
-
type="button"
|
|
2634
|
-
class="mono-th-filter-ind" mono-th-filter-ind
|
|
2635
|
-
title=${this._hf.title}
|
|
2636
|
-
aria-label=${`Filter ${this.caption || this.field}`}
|
|
2637
|
-
data-shimmer-ignore
|
|
2638
|
-
@click=${this._openFromIcon}
|
|
2639
|
-
>
|
|
2640
|
-
${this.renderIcon("funnel")}
|
|
2641
|
-
</button>
|
|
2642
|
-
`;
|
|
2643
|
-
}
|
|
2644
|
-
/** (Re)fetch the column's distinct values (used on open and on Retry). */
|
|
2645
|
-
async _loadValues() {
|
|
2646
|
-
this._valuesError = false;
|
|
2647
|
-
this._valuesLoading = true;
|
|
2648
|
-
this._values = [];
|
|
2649
|
-
this._tree = [];
|
|
2650
|
-
try {
|
|
2651
|
-
this._values = await this.dataGrid?.distinctValues?.(this.field, this._hf.dataSourceOptions) ?? [];
|
|
2652
|
-
const df = this._df;
|
|
2653
|
-
if (df) this._tree = buildDateTree(this._values, {
|
|
2654
|
-
depth: df.depth,
|
|
2655
|
-
utc: df.utc,
|
|
2656
|
-
locale: df.locale
|
|
2657
|
-
});
|
|
2658
|
-
} catch {
|
|
2659
|
-
this._valuesError = true;
|
|
2660
|
-
} finally {
|
|
2661
|
-
this._valuesLoading = false;
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
_toggleNode(node, on) {
|
|
2665
|
-
this._checked = setChecked(node, on, this._checked, this._tree);
|
|
2666
|
-
}
|
|
2667
|
-
_toggleExpand(key) {
|
|
2668
|
-
const next = new Set(this._expanded);
|
|
2669
|
-
if (next.has(key)) next.delete(key);
|
|
2670
|
-
else next.add(key);
|
|
2671
|
-
this._expanded = next;
|
|
2672
|
-
}
|
|
2673
|
-
_renderDateNodes(nodes, level, inheritedOn = false) {
|
|
2674
|
-
return html`
|
|
2675
|
-
<ul class="mono-th-tree" mono-th-tree role=${level === 0 ? "tree" : "group"}>
|
|
2676
|
-
${nodes.map((n) => {
|
|
2677
|
-
const state = checkState(n, this._checked, void 0, inheritedOn);
|
|
2678
|
-
const open = this._expanded.has(n.key);
|
|
2679
|
-
const branch = n.children.length > 0;
|
|
2680
|
-
return html`
|
|
2681
|
-
<li role="treeitem" aria-expanded=${branch ? open ? "true" : "false" : nothing}>
|
|
2682
|
-
<div class="mono-th-tree-row" mono-th-tree-row data-level=${level} data-key=${n.key}>
|
|
2683
|
-
<button
|
|
2684
|
-
type="button"
|
|
2685
|
-
class="mono-th-tree-toggle ${open ? "open" : ""}" mono-th-tree-toggle
|
|
2686
|
-
?mono-open=${open}
|
|
2687
|
-
?hidden=${!branch}
|
|
2688
|
-
tabindex=${branch ? 0 : -1}
|
|
2689
|
-
aria-label=${open ? "Collapse" : "Expand"}
|
|
2690
|
-
@click=${() => this._toggleExpand(n.key)}
|
|
2691
|
-
>
|
|
2692
|
-
${this._caretIcon()}
|
|
2693
|
-
</button>
|
|
2694
|
-
<label class="mono-th-check" mono-th-check>
|
|
2695
|
-
<input
|
|
2696
|
-
type="checkbox"
|
|
2697
|
-
.checked=${state === "on"}
|
|
2698
|
-
.indeterminate=${state === "mixed"}
|
|
2699
|
-
@change=${(e) => this._toggleNode(n, e.target.checked)}
|
|
2700
|
-
/>
|
|
2701
|
-
<span class="mono-th-check-box" mono-th-check-box></span>
|
|
2702
|
-
<span class="mono-th-check-label" mono-th-check-label>${n.label}</span>
|
|
2703
|
-
<span class="mono-th-check-count" mono-th-check-count>${n.count}</span>
|
|
2704
|
-
</label>
|
|
2705
|
-
</div>
|
|
2706
|
-
${branch && open ? this._renderDateNodes(n.children, level + 1, state === "on") : nothing}
|
|
2707
|
-
</li>
|
|
2708
|
-
`;
|
|
2709
|
-
})}
|
|
2710
|
-
</ul>
|
|
2711
|
-
`;
|
|
2712
|
-
}
|
|
2713
|
-
_renderDateTree() {
|
|
2714
|
-
const roots = this._tree;
|
|
2715
|
-
const allOn = roots.length > 0 && roots.every((r) => checkState(r, this._checked) === "on");
|
|
2716
|
-
const anyOn = roots.some((r) => checkState(r, this._checked) !== "off");
|
|
2717
|
-
return html`
|
|
2718
|
-
<label class="mono-th-check mono-th-check-all" mono-th-check mono-th-check-all>
|
|
2719
|
-
<input
|
|
2720
|
-
type="checkbox"
|
|
2721
|
-
.checked=${allOn}
|
|
2722
|
-
.indeterminate=${!allOn && anyOn}
|
|
2723
|
-
@change=${(e) => this._checked = setAllChecked(roots, e.target.checked)}
|
|
2724
|
-
/>
|
|
2725
|
-
<span class="mono-th-check-box" mono-th-check-box></span>
|
|
2726
|
-
<span class="mono-th-check-label" mono-th-check-label>(Select all)</span>
|
|
2727
|
-
<span class="mono-th-check-count" mono-th-check-count>${roots.reduce((n, r) => n + r.count, 0)}</span>
|
|
2728
|
-
</label>
|
|
2729
|
-
${this._renderDateNodes(roots, 0)}
|
|
2730
|
-
`;
|
|
2731
|
-
}
|
|
2732
|
-
/**
|
|
2733
|
-
* Label for a distinct value. A column `map` relabels the list so it reads like
|
|
2734
|
-
* the table body — the checkbox still carries the RAW value, so
|
|
2735
|
-
* `setColumnFilter` keeps matching the real data.
|
|
2736
|
-
*/
|
|
2737
|
-
_display(value, map = this.dataGrid?.columnMap?.(this.field)) {
|
|
2738
|
-
if (map) try {
|
|
2739
|
-
const mapped = map(value, { [this.field]: value }, 0);
|
|
2740
|
-
if (mapped != null && mapped !== "") return String(mapped);
|
|
2741
|
-
} catch {}
|
|
2742
|
-
return value == null || value === "" ? "(Blanks)" : String(value);
|
|
2743
|
-
}
|
|
2744
|
-
/**
|
|
2745
|
-
* The figure shown beside `(Select all)`: the summed row count of the listed
|
|
2746
|
-
* values, so it lines up with each value's own count in the same column. Falls
|
|
2747
|
-
* back to the NUMBER of values when the source reports no counts (a consumer
|
|
2748
|
-
* `distinctValues` resolver may return bare values).
|
|
2749
|
-
*/
|
|
2750
|
-
_totalCount(list) {
|
|
2751
|
-
const sum = list.reduce((n, v) => n + (Number(v.count) || 0), 0);
|
|
2752
|
-
return sum > 0 ? sum : list.length;
|
|
2753
|
-
}
|
|
2754
|
-
_filteredValues() {
|
|
2755
|
-
const q = this._search.trim().toLowerCase();
|
|
2756
|
-
if (!q) return this._values;
|
|
2757
|
-
const map = this.dataGrid?.columnMap?.(this.field);
|
|
2758
|
-
return this._values.filter((v) => this._display(v.value, map).toLowerCase().includes(q));
|
|
2759
|
-
}
|
|
2760
|
-
_toggleValue(key, on) {
|
|
2761
|
-
const next = new Set(this._checked);
|
|
2762
|
-
if (on) next.add(key);
|
|
2763
|
-
else next.delete(key);
|
|
2764
|
-
this._checked = next;
|
|
2765
|
-
}
|
|
2766
|
-
_toggleSelectAll(on) {
|
|
2767
|
-
const next = new Set(this._checked);
|
|
2768
|
-
for (const v of this._filteredValues()) {
|
|
2769
|
-
const key = String(v.value);
|
|
2770
|
-
if (on) next.add(key);
|
|
2771
|
-
else next.delete(key);
|
|
2772
|
-
}
|
|
2773
|
-
this._checked = next;
|
|
2774
|
-
}
|
|
2775
|
-
/**
|
|
2776
|
-
* Which way this panel was reached decides whether the filter COMBINES with the
|
|
2777
|
-
* other columns — the same split the sort arrow and `Sort ›` menu use: the
|
|
2778
|
-
* menu combines, the funnel replaces. With one carry-over: once the user has
|
|
2779
|
-
* started combining from the menu and something is still filtered, the
|
|
2780
|
-
* funnel combines too (`columnFilterCombining`) — a user who began a
|
|
2781
|
-
* multi-column filter means to keep building it. Read it BEFORE
|
|
2782
|
-
* `closeMenu()`, which resets `_fromIcon`.
|
|
2783
|
-
*/
|
|
2784
|
-
get _filterMulti() {
|
|
2785
|
-
return !this._fromIcon || !!this.dataGrid?.columnFilterCombining?.();
|
|
2786
|
-
}
|
|
2787
|
-
async _apply() {
|
|
2788
|
-
if (this._hf.date) {
|
|
2789
|
-
const ranges = minimize(this._tree, this._checked);
|
|
2790
|
-
const multi = this._filterMulti;
|
|
2791
|
-
const sticky = !this._fromIcon;
|
|
2792
|
-
this.closeMenu();
|
|
2793
|
-
await this.dataGrid?.setColumnFilter?.(this.field, ranges, {
|
|
2794
|
-
multi,
|
|
2795
|
-
sticky
|
|
2796
|
-
});
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
const values = this._values.filter((v) => this._checked.has(String(v.value))).map((v) => v.value);
|
|
2800
|
-
const multi = this._filterMulti;
|
|
2801
|
-
const sticky = !this._fromIcon;
|
|
2802
|
-
this.closeMenu();
|
|
2803
|
-
await this.dataGrid?.setColumnFilter?.(this.field, values, {
|
|
2804
|
-
multi,
|
|
2805
|
-
sticky
|
|
2806
|
-
});
|
|
2807
|
-
}
|
|
2808
|
-
async _clear() {
|
|
2809
|
-
this._checked = /* @__PURE__ */ new Set();
|
|
2810
|
-
const multi = this._filterMulti;
|
|
2811
|
-
const sticky = !this._fromIcon;
|
|
2812
|
-
this.closeMenu();
|
|
2813
|
-
await this.dataGrid?.setColumnFilter?.(this.field, [], {
|
|
2814
|
-
multi,
|
|
2815
|
-
sticky
|
|
2816
|
-
});
|
|
2817
|
-
}
|
|
2818
|
-
/** Drop every column's header filter, however many are active. */
|
|
2819
|
-
async _clearAllFilters() {
|
|
2820
|
-
this._checked = /* @__PURE__ */ new Set();
|
|
2821
|
-
this.closeMenu();
|
|
2822
|
-
await this.dataGrid?.setColumnFilter?.(this.field, [], { multi: false });
|
|
2823
|
-
}
|
|
2824
|
-
_retryIcon() {
|
|
2825
|
-
return html`<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true">
|
|
2826
|
-
<path
|
|
2827
|
-
d="M17.65 6.35A8 8 0 1 0 19.73 14h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
|
|
2828
|
-
/>
|
|
2829
|
-
</svg>`;
|
|
2830
|
-
}
|
|
2831
|
-
/** Empty / error state with a Retry action below the message. */
|
|
2832
|
-
_renderMessage(text) {
|
|
2833
|
-
return html`
|
|
2834
|
-
<div class="mono-th-filter-msg" mono-th-filter-msg>
|
|
2835
|
-
<div class="mono-th-filter-msg-text" mono-th-filter-msg-text>${text}</div>
|
|
2836
|
-
<button type="button" class="mono-th-retry" mono-th-retry @click=${() => this._loadValues()}>
|
|
2837
|
-
${this._retryIcon()} Retry
|
|
2838
|
-
</button>
|
|
2839
|
-
</div>
|
|
2840
|
-
`;
|
|
2841
|
-
}
|
|
2842
|
-
/**
|
|
2843
|
-
* The header menu (+ its cascading panels). The funnel itself is rendered by
|
|
2844
|
-
* `_renderFilterIcon()` BEFORE the caption; this only carries the popups.
|
|
2845
|
-
*
|
|
2846
|
-
* One section per enabled feature, with a separator between them when a column
|
|
2847
|
-
* has both.
|
|
2848
|
-
*/
|
|
2849
|
-
_renderMenus() {
|
|
2850
|
-
const hf = this._hf;
|
|
2851
|
-
const sortSection = this._sortable;
|
|
2852
|
-
const filterSection = hf.enable;
|
|
2853
|
-
return html`
|
|
2854
|
-
<div class="mono-th-menu ${this._menuOpen ? "open" : ""}" mono-th-menu ?mono-open=${this._menuOpen} role="menu">
|
|
2855
|
-
${sortSection ? this.renderSortMenuItem() : nothing}
|
|
2856
|
-
${sortSection && filterSection ? html`<div class="mono-th-menu-sep" mono-th-menu-sep role="separator"></div>` : nothing}
|
|
2857
|
-
${filterSection ? html`
|
|
2858
|
-
<button
|
|
2859
|
-
type="button"
|
|
2860
|
-
class="mono-th-menu-item" mono-th-menu-item
|
|
2861
|
-
role="menuitem"
|
|
2862
|
-
data-menu="filter"
|
|
2863
|
-
aria-haspopup="true"
|
|
2864
|
-
@click=${() => this._openFilter()}
|
|
2865
|
-
>
|
|
2866
|
-
<span class="mono-th-menu-ic" mono-th-menu-ic>${this.renderIcon("funnel")}</span>
|
|
2867
|
-
<span class="mono-th-menu-label" mono-th-menu-label>${this._hf.date ? "Date Filter" : "Header Filter"}</span>
|
|
2868
|
-
<span class="mono-th-menu-caret" mono-th-menu-caret>${this._caretIcon()}</span>
|
|
2869
|
-
</button>
|
|
2870
|
-
` : nothing}
|
|
2871
|
-
</div>
|
|
2872
|
-
${sortSection ? this.renderSortSubmenu(this._sortCfg.noClear) : nothing}
|
|
2873
|
-
${hf.enable ? this._renderFilterPanel() : nothing}
|
|
2874
|
-
`;
|
|
2875
|
-
}
|
|
2876
|
-
_renderFilterPanel() {
|
|
2877
|
-
const open = this._filterOpen;
|
|
2878
|
-
const list = open ? this._filteredValues() : [];
|
|
2879
|
-
const map = open ? this.dataGrid?.columnMap?.(this.field) : void 0;
|
|
2880
|
-
const allChecked = list.length > 0 && list.every((v) => this._checked.has(String(v.value)));
|
|
2881
|
-
const multi = this._filterMulti;
|
|
2882
|
-
const filteredColumns = open ? this.dataGrid?.filteredColumns?.() ?? [] : [];
|
|
2883
|
-
const otherFiltered = filteredColumns.filter((f) => f !== this.field).length;
|
|
2884
|
-
const clearAllCount = otherFiltered + (filteredColumns.includes(this.field) ? 1 : 0);
|
|
2885
|
-
return html`
|
|
2886
|
-
<div
|
|
2887
|
-
class="mono-th-filter ${this._filterOpen ? "open" : ""} ${this._hf.date ? "is-date" : ""}" mono-th-filter
|
|
2888
|
-
?mono-open=${this._filterOpen}
|
|
2889
|
-
?mono-date=${!!this._hf.date}
|
|
2890
|
-
role="dialog"
|
|
2891
|
-
aria-label=${this._hf.date ? "Date filter" : "Header filter"}
|
|
2892
|
-
>
|
|
2893
|
-
<div class="mono-th-filter-head" mono-th-filter-head>${this._hf.title}</div>
|
|
2894
|
-
${this._hf.date ? nothing : html`
|
|
2895
|
-
<div class="mono-th-filter-search" mono-th-filter-search>
|
|
2896
|
-
<input
|
|
2897
|
-
type="text"
|
|
2898
|
-
class="mono-th-filter-input" mono-th-filter-input
|
|
2899
|
-
placeholder="Search…"
|
|
2900
|
-
.value=${this._search}
|
|
2901
|
-
@input=${(e) => this._search = e.target.value}
|
|
2902
|
-
/>
|
|
2903
|
-
</div>
|
|
2904
|
-
`}
|
|
2905
|
-
<div class="mono-th-filter-list" mono-th-filter-list>
|
|
2906
|
-
${!open ? nothing : this._valuesLoading ? html`<div class="mono-th-filter-msg" mono-th-filter-msg><span class="mono-th-filter-spin" mono-th-filter-spin></span>Loading…</div>` : this._valuesError ? this._renderMessage("Failed to load values.") : this._hf.date ? this._tree.length === 0 ? this._renderMessage("No values.") : this._renderDateTree() : list.length === 0 ? this._renderMessage("No values.") : html`
|
|
2907
|
-
<label class="mono-th-check mono-th-check-all" mono-th-check mono-th-check-all>
|
|
2908
|
-
<input
|
|
2909
|
-
type="checkbox"
|
|
2910
|
-
.checked=${allChecked}
|
|
2911
|
-
@change=${(e) => this._toggleSelectAll(e.target.checked)}
|
|
2912
|
-
/>
|
|
2913
|
-
<span class="mono-th-check-box" mono-th-check-box></span>
|
|
2914
|
-
<span class="mono-th-check-label" mono-th-check-label>(Select all)</span>
|
|
2915
|
-
<span class="mono-th-check-count" mono-th-check-count>${this._totalCount(list)}</span>
|
|
2916
|
-
</label>
|
|
2917
|
-
${list.map((v) => {
|
|
2918
|
-
const key = String(v.value);
|
|
2919
|
-
return html`
|
|
2920
|
-
<label class="mono-th-check" mono-th-check>
|
|
2921
|
-
<input
|
|
2922
|
-
type="checkbox"
|
|
2923
|
-
.checked=${this._checked.has(key)}
|
|
2924
|
-
@change=${(e) => this._toggleValue(key, e.target.checked)}
|
|
2925
|
-
/>
|
|
2926
|
-
<span class="mono-th-check-box" mono-th-check-box></span>
|
|
2927
|
-
<span class="mono-th-check-label" mono-th-check-label>${this._display(v.value, map)}</span>
|
|
2928
|
-
<span class="mono-th-check-count" mono-th-check-count>${v.count}</span>
|
|
2929
|
-
</label>
|
|
2930
|
-
`;
|
|
2931
|
-
})}
|
|
2932
|
-
`}
|
|
2933
|
-
</div>
|
|
2934
|
-
<div class="mono-th-filter-foot" mono-th-filter-foot>
|
|
2935
|
-
<button type="button" class="mono-th-filter-btn ghost" mono-th-filter-btn @click=${() => this._clear()}>
|
|
2936
|
-
Clear
|
|
2937
|
-
</button>
|
|
2938
|
-
${multi && otherFiltered > 0 ? html`
|
|
2939
|
-
<button
|
|
2940
|
-
type="button"
|
|
2941
|
-
class="mono-th-filter-btn ghost" mono-th-filter-btn
|
|
2942
|
-
data-action="clear-all"
|
|
2943
|
-
title="Drop the header filter on every column (${clearAllCount})"
|
|
2944
|
-
@click=${() => this._clearAllFilters()}
|
|
2945
|
-
>
|
|
2946
|
-
Clear all columns (${clearAllCount})
|
|
2947
|
-
</button>
|
|
2948
|
-
` : nothing}
|
|
2949
|
-
<span class="mono-th-filter-foot-sp" mono-th-filter-foot-sp></span>
|
|
2950
|
-
<button type="button" class="mono-th-filter-btn ghost" mono-th-filter-btn @click=${() => this.closeMenu()}>
|
|
2951
|
-
Cancel
|
|
2952
|
-
</button>
|
|
2953
|
-
<button type="button" class="mono-th-filter-btn primary" mono-th-filter-btn @click=${() => this._apply()}>
|
|
2954
|
-
Apply
|
|
2955
|
-
</button>
|
|
2956
|
-
</div>
|
|
2957
|
-
</div>
|
|
2958
|
-
`;
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
__decorate([property({ type: String })], MonoTableThCoreClass.prototype, "field", void 0);
|
|
2962
|
-
__decorate([property({ type: String })], MonoTableThCoreClass.prototype, "caption", void 0);
|
|
2963
|
-
__decorate([property({
|
|
2964
|
-
attribute: "sort",
|
|
2965
|
-
reflect: true,
|
|
2966
|
-
converter: booleanStringConverter
|
|
2967
|
-
})], MonoTableThCoreClass.prototype, "sort", void 0);
|
|
2968
|
-
__decorate([property({
|
|
2969
|
-
reflect: true,
|
|
2970
|
-
converter: booleanStringConverter
|
|
2971
|
-
})], MonoTableThCoreClass.prototype, "editable", void 0);
|
|
2972
|
-
__decorate([property({ attribute: "editable-trigger" })], MonoTableThCoreClass.prototype, "editableTrigger", void 0);
|
|
2973
|
-
__decorate([property({
|
|
2974
|
-
reflect: true,
|
|
2975
|
-
converter: booleanStringConverter
|
|
2976
|
-
})], MonoTableThCoreClass.prototype, "required", void 0);
|
|
2977
|
-
__decorate([property({
|
|
2978
|
-
attribute: "header-filter",
|
|
2979
|
-
reflect: true,
|
|
2980
|
-
converter: booleanStringConverter
|
|
2981
|
-
})], MonoTableThCoreClass.prototype, "headerFilter", void 0);
|
|
2982
|
-
__decorate([property({
|
|
2983
|
-
attribute: "date-filter",
|
|
2984
|
-
reflect: true,
|
|
2985
|
-
converter: booleanStringConverter
|
|
2986
|
-
})], MonoTableThCoreClass.prototype, "dateFilter", void 0);
|
|
2987
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_tree", void 0);
|
|
2988
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_expanded", void 0);
|
|
2989
|
-
__decorate([property()], MonoTableThCoreClass.prototype, "width", void 0);
|
|
2990
|
-
__decorate([property({ attribute: "min-width" })], MonoTableThCoreClass.prototype, "minWidth", void 0);
|
|
2991
|
-
__decorate([property({ attribute: "max-width" })], MonoTableThCoreClass.prototype, "maxWidth", void 0);
|
|
2992
|
-
__decorate([property({ reflect: true })], MonoTableThCoreClass.prototype, "align", void 0);
|
|
2993
|
-
__decorate([property()], MonoTableThCoreClass.prototype, "height", void 0);
|
|
2994
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_filterOpen", void 0);
|
|
2995
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_values", void 0);
|
|
2996
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_checked", void 0);
|
|
2997
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_valuesLoading", void 0);
|
|
2998
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_valuesError", void 0);
|
|
2999
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_search", void 0);
|
|
3000
|
-
__decorate([state()], MonoTableThCoreClass.prototype, "_fromIcon", void 0);
|
|
3001
|
-
return MonoTableThCoreClass;
|
|
3002
|
-
};
|
|
3003
|
-
|
|
3004
131
|
//#endregion
|
|
3005
132
|
//#region src/components/table/mono-table-th.ts
|
|
3006
133
|
let MonoTableTh = class MonoTableTh extends MonoTableThCore(LitElement) {
|
|
@@ -3046,495 +173,6 @@ let MonoTableTh = class MonoTableTh extends MonoTableThCore(LitElement) {
|
|
|
3046
173
|
};
|
|
3047
174
|
MonoTableTh = __decorate([customElement("mono-table-th")], MonoTableTh);
|
|
3048
175
|
|
|
3049
|
-
//#endregion
|
|
3050
|
-
//#region src/components/table/table-overlay.ts
|
|
3051
|
-
/**
|
|
3052
|
-
* The element an overlay covers: the `<table>` first, the scroll wrapper only as
|
|
3053
|
-
* a fallback.
|
|
3054
|
-
*
|
|
3055
|
-
* The order is the whole point. An absolutely positioned descendant of a SCROLL
|
|
3056
|
-
* CONTAINER scrolls with the content, so covering the wrapper pins the overlay to
|
|
3057
|
-
* the scroll origin at only the scrollport's size — scroll a wide or long table
|
|
3058
|
-
* and the overlay slides away, leaving live rows showing through beside it. The
|
|
3059
|
-
* table's box IS the content, so covering that keeps every column and row under
|
|
3060
|
-
* the overlay at any scroll offset.
|
|
3061
|
-
*/
|
|
3062
|
-
function overlayHost(el) {
|
|
3063
|
-
return el.closest("table") ?? el.closest(".mono-table-scroll, [mono-table-scroll]");
|
|
3064
|
-
}
|
|
3065
|
-
/**
|
|
3066
|
-
* The element whose height is reserved while an overlay is up — NEVER the
|
|
3067
|
-
* `<table>`.
|
|
3068
|
-
*
|
|
3069
|
-
* A `<table>` does not treat a height as "reserve this much space": CSS table
|
|
3070
|
-
* layout DISTRIBUTES any surplus over the rows. Freeze a table at the height it
|
|
3071
|
-
* had when it was full, then let the source clear its rows, and the surplus lands
|
|
3072
|
-
* on the only row left — the header — which stretches into a band hundreds of
|
|
3073
|
-
* pixels tall with its captions floating in the middle of it. That is the
|
|
3074
|
-
* opposite of the collapse this guard exists to prevent.
|
|
3075
|
-
*
|
|
3076
|
-
* A block box just reserves the space, so the hold goes on the scroll wrapper, or
|
|
3077
|
-
* failing that on the table's own parent. If the table has no block ancestor to
|
|
3078
|
-
* hold the space the hold is SKIPPED — a grid that briefly goes short is a
|
|
3079
|
-
* smaller problem than a deformed one.
|
|
3080
|
-
*/
|
|
3081
|
-
function overlaySizeHost(el) {
|
|
3082
|
-
const scroll = el.closest(".mono-table-scroll, [mono-table-scroll]");
|
|
3083
|
-
if (scroll) return scroll;
|
|
3084
|
-
const table = el.closest("table");
|
|
3085
|
-
if (!table) return overlayHost(el);
|
|
3086
|
-
const parent = table.parentElement;
|
|
3087
|
-
return parent && parent.tagName !== "TABLE" ? parent : null;
|
|
3088
|
-
}
|
|
3089
|
-
/**
|
|
3090
|
-
* Widest header row, counting `colspan` — the span a generated overlay cell
|
|
3091
|
-
* needs.
|
|
3092
|
-
*
|
|
3093
|
-
* Generated and spacer rows are excluded by `data-mono-stripe-skip` rather than
|
|
3094
|
-
* by class name: this has to skip the row the OTHER overlay may have built as
|
|
3095
|
-
* well as its own, and both already carry that attribute for the zebra count.
|
|
3096
|
-
*/
|
|
3097
|
-
function columnCount(table) {
|
|
3098
|
-
const widest = Array.from(table.querySelectorAll("thead tr")).reduce((max, tr) => {
|
|
3099
|
-
const span = Array.from(tr.children).reduce((sum, c) => sum + (c.colSpan || 1), 0);
|
|
3100
|
-
return Math.max(max, span);
|
|
3101
|
-
}, 0);
|
|
3102
|
-
if (widest) return widest;
|
|
3103
|
-
const first = table.querySelector("tbody tr:not([data-mono-stripe-skip])");
|
|
3104
|
-
return first ? Array.from(first.children).length : 0;
|
|
3105
|
-
}
|
|
3106
|
-
/**
|
|
3107
|
-
* Give an overlay a spec-valid home inside the table, and make that home carry
|
|
3108
|
-
* what the styling keys on.
|
|
3109
|
-
*
|
|
3110
|
-
* `<table>`'s content model permits only `caption` / `colgroup` / `thead` /
|
|
3111
|
-
* `tbody` / `tfoot`, and a row section permits only `<tr>` — so a bare custom
|
|
3112
|
-
* element in either is invalid HTML. Vue's compiler warns about it on every
|
|
3113
|
-
* consumer build, and the HTML PARSER foster-parents it out of the table: a
|
|
3114
|
-
* server-rendered `<mono-shadow-table-error>` written straight into `<tbody>`
|
|
3115
|
-
* has left the table before hydration starts, at which point it is not a row and
|
|
3116
|
-
* covers nothing.
|
|
3117
|
-
*
|
|
3118
|
-
* So the form the docs teach is the valid one — `<tr><td colspan>` written by the
|
|
3119
|
-
* consumer — and this function has two jobs:
|
|
3120
|
-
*
|
|
3121
|
-
* 1. **Adopt** a hand-written cell. A row the consumer wrote lacks the three
|
|
3122
|
-
* things the generated one has: the `rowClass` (which is the ONLY thing the
|
|
3123
|
-
* padding/border reset in `table.css` keys on — without it a docs-style
|
|
3124
|
-
* `td { padding }` puts a 14px gutter around the bar), `data-mono-stripe-skip`
|
|
3125
|
-
* (zebra parity), and a `colspan` that `syncRowSpan` keeps in step with the
|
|
3126
|
-
* columns. Asking a consumer to remember all three is a trap, so the element
|
|
3127
|
-
* stamps them on the row it finds itself in. The class is ADDED, never
|
|
3128
|
-
* replaced — a framework does not patch an attribute it did not bind, so what
|
|
3129
|
-
* is written here survives their re-renders, and what they wrote survives us.
|
|
3130
|
-
*
|
|
3131
|
-
* 2. **Generate** a row when the element is dropped bare into a table or a row
|
|
3132
|
-
* section anyway — the client-only fallback that makes an imperative
|
|
3133
|
-
* `body.appendChild(el)` keep working. Not the taught form, for the reasons
|
|
3134
|
-
* above.
|
|
3135
|
-
*
|
|
3136
|
-
* A `<caption>` host is left alone: it is valid HTML and its CSS is honoured as
|
|
3137
|
-
* written. Anything else (`undefined`) is not a table position at all.
|
|
3138
|
-
*/
|
|
3139
|
-
function ensureRowHost(el, rowClass) {
|
|
3140
|
-
const rowAttr = rowClass.replace(/^mono-table-/, "mono-");
|
|
3141
|
-
const parent = el.parentElement;
|
|
3142
|
-
if (!parent) return void 0;
|
|
3143
|
-
const tag = parent.tagName;
|
|
3144
|
-
if (tag === "TD" || tag === "TH") {
|
|
3145
|
-
const row = parent.parentElement;
|
|
3146
|
-
if (!row || row.tagName !== "TR") return void 0;
|
|
3147
|
-
row.classList.add(rowClass);
|
|
3148
|
-
row.setAttribute(rowAttr, "");
|
|
3149
|
-
row.setAttribute("data-mono-stripe-skip", "");
|
|
3150
|
-
return {
|
|
3151
|
-
row,
|
|
3152
|
-
generated: false
|
|
3153
|
-
};
|
|
3154
|
-
}
|
|
3155
|
-
if (tag !== "TABLE" && tag !== "TBODY" && tag !== "THEAD" && tag !== "TFOOT") return void 0;
|
|
3156
|
-
const table = el.closest("table");
|
|
3157
|
-
const row = document.createElement("tr");
|
|
3158
|
-
row.className = rowClass;
|
|
3159
|
-
row.setAttribute(rowAttr, "");
|
|
3160
|
-
row.setAttribute("data-mono-stripe-skip", "");
|
|
3161
|
-
const cell = document.createElement("td");
|
|
3162
|
-
const span = table ? columnCount(table) : 0;
|
|
3163
|
-
if (span > 0) cell.colSpan = span;
|
|
3164
|
-
const section = tag === "TABLE" ? table?.querySelector("tbody") ?? table?.appendChild(document.createElement("tbody")) : parent;
|
|
3165
|
-
cell.appendChild(el);
|
|
3166
|
-
row.appendChild(cell);
|
|
3167
|
-
section?.insertBefore(row, section.firstChild);
|
|
3168
|
-
return {
|
|
3169
|
-
row,
|
|
3170
|
-
generated: true
|
|
3171
|
-
};
|
|
3172
|
-
}
|
|
3173
|
-
/**
|
|
3174
|
-
* Drop a GENERATED row on disconnect. An empty `<tr>` left behind would still
|
|
3175
|
-
* draw a border and occupy a slot in the zebra parity count.
|
|
3176
|
-
*
|
|
3177
|
-
* An adopted row is never touched: it is the consumer's node, still referenced by
|
|
3178
|
-
* whatever rendered it, and removing it is exactly the "disrupt future
|
|
3179
|
-
* functionality" the Vue warning threatens.
|
|
3180
|
-
*/
|
|
3181
|
-
function releaseRowHost(el, host) {
|
|
3182
|
-
if (host?.generated && !host.row.contains(el)) host.row.remove();
|
|
3183
|
-
}
|
|
3184
|
-
/**
|
|
3185
|
-
* Re-widen the row's cell after a column change — generated or adopted.
|
|
3186
|
-
*
|
|
3187
|
-
* `ensureRowHost` sets `colspan` once on a generated cell, from the header as it
|
|
3188
|
-
* stood on connect; a hand-written cell has whatever the consumer typed, which
|
|
3189
|
-
* may be nothing. A grid whose columns are driven by data — a `v-for` over a
|
|
3190
|
-
* props snapshot, a column toggled off — changes that count later, and a stale
|
|
3191
|
-
* span leaves the row short of the table or overflowing it.
|
|
3192
|
-
* `mono-table-detail-core` recomputes for the same reason.
|
|
3193
|
-
*
|
|
3194
|
-
* Only writes when the number actually moved, so this is safe to call from every
|
|
3195
|
-
* `updated()` — and a consumer's correct `colspan` is never rewritten.
|
|
3196
|
-
*/
|
|
3197
|
-
function syncRowSpan(el, host) {
|
|
3198
|
-
if (!host) return;
|
|
3199
|
-
const table = el.closest("table");
|
|
3200
|
-
if (!table) return;
|
|
3201
|
-
const cell = el.closest("td, th") ?? host.row.firstElementChild;
|
|
3202
|
-
if (!cell) return;
|
|
3203
|
-
const span = columnCount(table);
|
|
3204
|
-
if (span > 0 && cell.colSpan !== span) cell.colSpan = span;
|
|
3205
|
-
}
|
|
3206
|
-
/**
|
|
3207
|
-
* Prefix for the per-overlay height reservations parked on the size host.
|
|
3208
|
-
*
|
|
3209
|
-
* Every overlay reserves room by writing ITS OWN variable here, and the host's
|
|
3210
|
-
* `min-height` is then the `max()` of whatever variables are present. A single
|
|
3211
|
-
* shared inline `min-height` cannot work once two overlays are in the same table
|
|
3212
|
-
* — which is the arrangement the docs teach, a spinner and an empty state in one
|
|
3213
|
-
* `<caption>`: the spinner's hide would clear the reservation the empty state had
|
|
3214
|
-
* just made, the scroll wrapper would collapse to a header, and the message would
|
|
3215
|
-
* be clipped out of existence by the wrapper's own `overflow`. Nothing about the
|
|
3216
|
-
* DOM would look wrong; the grid would simply be blank.
|
|
3217
|
-
*/
|
|
3218
|
-
const HOLD_PREFIX = "--mono-table-hold-";
|
|
3219
|
-
/** `max()` over every reservation currently parked on `el`, or `''` if none. */
|
|
3220
|
-
function holdExpression(el) {
|
|
3221
|
-
const names = [];
|
|
3222
|
-
for (let i = 0; i < el.style.length; i++) {
|
|
3223
|
-
const name = el.style.item(i);
|
|
3224
|
-
if (name.startsWith(HOLD_PREFIX)) names.push(name);
|
|
3225
|
-
}
|
|
3226
|
-
if (!names.length) return "";
|
|
3227
|
-
const terms = names.map((n) => `var(${n}, 0px)`);
|
|
3228
|
-
return terms.length === 1 ? terms[0] : `max(${terms.join(", ")})`;
|
|
3229
|
-
}
|
|
3230
|
-
/**
|
|
3231
|
-
* The hosts an overlay is currently holding.
|
|
3232
|
-
*
|
|
3233
|
-
* Remembered rather than re-derived, because the one moment they are most needed
|
|
3234
|
-
* is the one moment they cannot be found: `disconnectedCallback` runs AFTER the
|
|
3235
|
-
* element leaves the tree, so `closest('table')` returns null and an overlay
|
|
3236
|
-
* cleaning up on its way out would silently clean up nothing — leaving a
|
|
3237
|
-
* permanent reservation on the consumer's own scroll wrapper, i.e. a gap under
|
|
3238
|
-
* their header that nothing on the page explains.
|
|
3239
|
-
*/
|
|
3240
|
-
const applied = /* @__PURE__ */ new WeakMap();
|
|
3241
|
-
/**
|
|
3242
|
-
* Give back everything an overlay reserved, from the hosts it remembers.
|
|
3243
|
-
*
|
|
3244
|
-
* Safe to call on an element that never applied anything, and safe to call after
|
|
3245
|
-
* it has been removed from the DOM — which is the point.
|
|
3246
|
-
*/
|
|
3247
|
-
function releaseOverlayHost(el) {
|
|
3248
|
-
const last = applied.get(el);
|
|
3249
|
-
if (!last) return;
|
|
3250
|
-
if (last.sizeHost) {
|
|
3251
|
-
last.sizeHost.style.removeProperty(last.holdVar);
|
|
3252
|
-
last.sizeHost.style.minHeight = holdExpression(last.sizeHost);
|
|
3253
|
-
}
|
|
3254
|
-
el.style.minHeight = "";
|
|
3255
|
-
last.host.style.removeProperty(last.headVar);
|
|
3256
|
-
applied.delete(el);
|
|
3257
|
-
}
|
|
3258
|
-
/**
|
|
3259
|
-
* Make the host a positioning context, publish the header offset, reserve the
|
|
3260
|
-
* space and stretch the overlay — or undo all four.
|
|
3261
|
-
*
|
|
3262
|
-
* **The order is load-bearing, and each step feeds the next.** The header offset
|
|
3263
|
-
* goes first because a configured hold may be a function of it. The hold goes
|
|
3264
|
-
* next because the stretch measures the scroll region the hold has just resized —
|
|
3265
|
-
* do it the other way round and the overlay is stretched to the height the table
|
|
3266
|
-
* had before any room was made, which on an empty grid is a header-high strip.
|
|
3267
|
-
*/
|
|
3268
|
-
function applyOverlayHost(el, on, opts) {
|
|
3269
|
-
const host = overlayHost(el);
|
|
3270
|
-
if (!host) return;
|
|
3271
|
-
const sizeHost = overlaySizeHost(el);
|
|
3272
|
-
if (!on) {
|
|
3273
|
-
releaseOverlayHost(el);
|
|
3274
|
-
return;
|
|
3275
|
-
}
|
|
3276
|
-
if (getComputedStyle(host).position === "static") host.style.position = "relative";
|
|
3277
|
-
applied.set(el, {
|
|
3278
|
-
host,
|
|
3279
|
-
sizeHost,
|
|
3280
|
-
headVar: opts.headVar,
|
|
3281
|
-
holdVar: opts.holdVar
|
|
3282
|
-
});
|
|
3283
|
-
const head = opts.measureHead === "always" || !!host.closest("[mono-sticky-head], .mono-table-sticky-head") ? host.querySelector(":scope > thead")?.offsetHeight ?? 0 : 0;
|
|
3284
|
-
host.style.setProperty(opts.headVar, `${head}px`);
|
|
3285
|
-
if (sizeHost) {
|
|
3286
|
-
if (opts.hold === "measure") {
|
|
3287
|
-
if (!sizeHost.style.getPropertyValue(opts.holdVar)) sizeHost.style.setProperty(opts.holdVar, `${sizeHost.offsetHeight}px`);
|
|
3288
|
-
} else sizeHost.style.setProperty(opts.holdVar, typeof opts.hold === "function" ? opts.hold() : opts.hold);
|
|
3289
|
-
sizeHost.style.minHeight = holdExpression(sizeHost);
|
|
3290
|
-
}
|
|
3291
|
-
if (sizeHost && sizeHost !== host) el.style.minHeight = holdExpression(sizeHost);
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
//#endregion
|
|
3295
|
-
//#region src/components/table/mono-table-empty-core.ts
|
|
3296
|
-
/**
|
|
3297
|
-
* Defaults, so the element says something sensible with no props at all.
|
|
3298
|
-
*
|
|
3299
|
-
* A neutral question mark rather than a "broken"/"error" glyph: an empty grid is
|
|
3300
|
-
* usually a search that matched nothing, which is a normal outcome and not a
|
|
3301
|
-
* fault. The subtitle names the two things a user can actually do about it —
|
|
3302
|
-
* anything more specific would be wrong on most tables.
|
|
3303
|
-
*/
|
|
3304
|
-
const DEFAULT_ICON = "i-mdi-help-circle-outline";
|
|
3305
|
-
const DEFAULT_TITLE = "No Data found";
|
|
3306
|
-
const DEFAULT_SUBTITLE = "Try adjusting your search or filters.";
|
|
3307
|
-
/** Fallback when `--mono-table-empty-min-h` resolves to nothing. */
|
|
3308
|
-
const DEFAULT_MIN_HEIGHT = "12rem";
|
|
3309
|
-
/** Breathing room left under the message when its own height sets the reserve. */
|
|
3310
|
-
const TAIL_GAP = 16;
|
|
3311
|
-
/**
|
|
3312
|
-
* `MonoTableEmptyCore` — render-mode-agnostic logic for `mono-table-empty`, the
|
|
3313
|
-
* message shown over a grid that came back with no rows.
|
|
3314
|
-
*
|
|
3315
|
-
* It is `mono-table-loading`'s pair: same `<caption>` host, same overlay geometry,
|
|
3316
|
-
* same controller binding — one covers the grid while a query runs, this one
|
|
3317
|
-
* covers it when the query returned nothing. Everything they share lives in
|
|
3318
|
-
* `table-overlay.ts`.
|
|
3319
|
-
*
|
|
3320
|
-
* **When it shows.** With a controller bound: only once that controller has
|
|
3321
|
-
* settled at least once (`hasLoaded`) AND has no rows AND is not loading. The
|
|
3322
|
-
* `hasLoaded` gate is the load-bearing one — a fresh controller reads
|
|
3323
|
-
* `items: [], loading: false`, which is indistinguishable from "loaded and
|
|
3324
|
-
* genuinely empty", so without it every table would flash "No data" on mount
|
|
3325
|
-
* before its first request had even been made.
|
|
3326
|
-
*
|
|
3327
|
-
* With NO controller bound it shows unconditionally, because then the consumer is
|
|
3328
|
-
* driving it with `v-if` / `v-show` and an element that hid itself would simply
|
|
3329
|
-
* never appear.
|
|
3330
|
-
*
|
|
3331
|
-
* **What it shows.** `icon`, then `title`, then `subtitle`, then a reload button —
|
|
3332
|
-
* each omitted when its prop is empty. A `slot="body"` replaces all four, so a
|
|
3333
|
-
* consumer can put arbitrary markup (or a mono component) in the same box and
|
|
3334
|
-
* keep the placement and centring for free.
|
|
3335
|
-
*
|
|
3336
|
-
* SSR-safe: all DOM work is guarded behind `isServer`.
|
|
3337
|
-
*/
|
|
3338
|
-
const MonoTableEmptyCore = (superClass) => {
|
|
3339
|
-
class MonoTableEmptyCoreClass extends MonoTableControllerCore(superClass) {
|
|
3340
|
-
constructor(..._args) {
|
|
3341
|
-
super(..._args);
|
|
3342
|
-
this._propsSlot = "empty";
|
|
3343
|
-
this.icon = DEFAULT_ICON;
|
|
3344
|
-
this.title = DEFAULT_TITLE;
|
|
3345
|
-
this.subtitle = DEFAULT_SUBTITLE;
|
|
3346
|
-
this.reload = true;
|
|
3347
|
-
this.reloadLabel = "Reload";
|
|
3348
|
-
this._hasBodySlot = false;
|
|
3349
|
-
this._visible = false;
|
|
3350
|
-
}
|
|
3351
|
-
connectedCallback() {
|
|
3352
|
-
super.connectedCallback();
|
|
3353
|
-
if (isServer) return;
|
|
3354
|
-
this._rowHost = ensureRowHost(this, "mono-table-empty-row");
|
|
3355
|
-
const wasVisible = this._visible;
|
|
3356
|
-
this._sync();
|
|
3357
|
-
if (this._visible && wasVisible) this._apply();
|
|
3358
|
-
}
|
|
3359
|
-
disconnectedCallback() {
|
|
3360
|
-
releaseOverlayHost(this);
|
|
3361
|
-
releaseRowHost(this, this._rowHost);
|
|
3362
|
-
this._rowHost = void 0;
|
|
3363
|
-
super.disconnectedCallback();
|
|
3364
|
-
}
|
|
3365
|
-
/**
|
|
3366
|
-
* Re-read state on every notify. Replaces the base subscribe rather than
|
|
3367
|
-
* chaining it, the way `mono-table-loading` does: visibility is applied
|
|
3368
|
-
* imperatively, so waiting for Lit's async render would let a fast
|
|
3369
|
-
* load→empty→load sequence paint the message in between.
|
|
3370
|
-
*/
|
|
3371
|
-
_subscribe() {
|
|
3372
|
-
this._off?.();
|
|
3373
|
-
this._off = this.dataGrid?.subscribe(() => {
|
|
3374
|
-
this._sync();
|
|
3375
|
-
if (monoPendingActive(this)) this.requestUpdate();
|
|
3376
|
-
});
|
|
3377
|
-
if (!isServer) this._sync();
|
|
3378
|
-
}
|
|
3379
|
-
updated(changed) {
|
|
3380
|
-
super.updated?.(changed);
|
|
3381
|
-
if (isServer) return;
|
|
3382
|
-
if (changed.has("dataGrid")) this._sync();
|
|
3383
|
-
else if (this._visible) this._apply();
|
|
3384
|
-
}
|
|
3385
|
-
/** Whether the bound controller says "loaded, and there is nothing". */
|
|
3386
|
-
get _controllerEmpty() {
|
|
3387
|
-
const grid = this.dataGrid;
|
|
3388
|
-
if (!grid) return false;
|
|
3389
|
-
if (grid.loading) return false;
|
|
3390
|
-
if (grid.error) return false;
|
|
3391
|
-
if (!grid.hasLoaded) return false;
|
|
3392
|
-
return (grid.items?.length ?? 0) === 0;
|
|
3393
|
-
}
|
|
3394
|
-
/** Runs synchronously on every controller notify. */
|
|
3395
|
-
_sync() {
|
|
3396
|
-
if (isServer) return;
|
|
3397
|
-
const next = this.dataGrid ? this._controllerEmpty : true;
|
|
3398
|
-
if (next === this._visible && this.hasAttribute("data-mono-empty") === next) return;
|
|
3399
|
-
this._visible = next;
|
|
3400
|
-
this._apply();
|
|
3401
|
-
}
|
|
3402
|
-
/**
|
|
3403
|
-
* The room to reserve under the header.
|
|
3404
|
-
*
|
|
3405
|
-
* A CONFIGURED height, not a measured one: this overlay appears over a grid
|
|
3406
|
-
* that is already a header and nothing else, so there is no height left to
|
|
3407
|
-
* freeze. `--mono-table-empty-min-h` is the floor, read off the element so a
|
|
3408
|
-
* theme or a single field can retune it.
|
|
3409
|
-
*
|
|
3410
|
-
* The floor alone is not enough, though, because the message is `sticky`: a
|
|
3411
|
-
* sticky box cannot leave its containing block, so once the message is taller
|
|
3412
|
-
* than the room reserved for it the browser CLAMPS it back upwards — and it
|
|
3413
|
-
* creeps up until its first line is sitting on the column names again, which
|
|
3414
|
-
* is the exact thing the header offset exists to prevent. So the floor is
|
|
3415
|
-
* raised to whatever the message actually needs.
|
|
3416
|
-
*
|
|
3417
|
-
* Both measurements are taken from values the clamp does not affect: `top` is
|
|
3418
|
-
* the resolved `header + gap`, and `offsetHeight` is the message's own height
|
|
3419
|
-
* wherever it happens to be painted. Measuring its POSITION instead would be
|
|
3420
|
-
* circular — it is the clamped position we are sizing to avoid.
|
|
3421
|
-
*/
|
|
3422
|
-
_holdHeight() {
|
|
3423
|
-
if (this.height) return this.height;
|
|
3424
|
-
const floor = this.minHeight || getComputedStyle(this).getPropertyValue("--mono-table-empty-min-h").trim() || DEFAULT_MIN_HEIGHT;
|
|
3425
|
-
const box = this.renderRoot?.querySelector?.(".mono-table-empty-box");
|
|
3426
|
-
const top = box ? parseFloat(getComputedStyle(box).top) || 0 : 0;
|
|
3427
|
-
const needed = box ? Math.ceil(top + box.offsetHeight + TAIL_GAP) : 0;
|
|
3428
|
-
const grown = needed ? `max(${floor}, ${needed}px)` : floor;
|
|
3429
|
-
return this.maxHeight ? `min(${grown}, ${this.maxHeight})` : grown;
|
|
3430
|
-
}
|
|
3431
|
-
/** Reflect visibility, and reserve the room the message needs. */
|
|
3432
|
-
_apply() {
|
|
3433
|
-
this.toggleAttribute("data-mono-empty", this._visible);
|
|
3434
|
-
this.setAttribute("aria-hidden", this._visible ? "false" : "true");
|
|
3435
|
-
applyOverlayHost(this, this._visible, {
|
|
3436
|
-
headVar: "--mono-table-empty-head",
|
|
3437
|
-
holdVar: "--mono-table-hold-empty",
|
|
3438
|
-
hold: () => this._holdHeight(),
|
|
3439
|
-
measureHead: "always"
|
|
3440
|
-
});
|
|
3441
|
-
}
|
|
3442
|
-
/** Reload the bound controller. No-op without one — the button isn't rendered then. */
|
|
3443
|
-
_onReload() {
|
|
3444
|
-
const grid = this.dataGrid;
|
|
3445
|
-
if (!grid) return;
|
|
3446
|
-
dispatchMonoEvent(this, "reload", { grid });
|
|
3447
|
-
grid.reload().catch(() => {});
|
|
3448
|
-
}
|
|
3449
|
-
_reloadIcon() {
|
|
3450
|
-
return html`<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true">
|
|
3451
|
-
<path
|
|
3452
|
-
d="M17.65 6.35A8 8 0 1 0 19.73 14h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
|
|
3453
|
-
/>
|
|
3454
|
-
</svg>`;
|
|
3455
|
-
}
|
|
3456
|
-
/**
|
|
3457
|
-
* The icon, when the props are in play.
|
|
3458
|
-
*
|
|
3459
|
-
* Two shapes, one prop. An iconify class paints as a mask on an empty span, so
|
|
3460
|
-
* it must go on the `class` attribute; anything else — an emoji, a letter —
|
|
3461
|
-
* is just text and must go in the node. Rendering either one as the other
|
|
3462
|
-
* gives a blank box, which is why the branch exists rather than a single
|
|
3463
|
-
* "put it somewhere" line. The shadow build overrides this: a page-level
|
|
3464
|
-
* `i-*` class cannot reach a shadow root without help.
|
|
3465
|
-
*/
|
|
3466
|
-
_renderIcon() {
|
|
3467
|
-
const icon = this.icon?.trim();
|
|
3468
|
-
if (!icon) return nothing;
|
|
3469
|
-
if (isIconifyClass(icon)) return html`<span class=${`mono-table-empty-icon ${icon}`} mono-empty-icon aria-hidden="true"></span>`;
|
|
3470
|
-
return html`<span class="mono-table-empty-icon is-text" mono-empty-icon aria-hidden="true">${icon}</span>`;
|
|
3471
|
-
}
|
|
3472
|
-
/**
|
|
3473
|
-
* The `slot="body"` region — a `data-mono-slot` target in light, a real
|
|
3474
|
-
* `<slot>` in shadow.
|
|
3475
|
-
*
|
|
3476
|
-
* ALWAYS rendered, even when the props are in play. In the light build it is
|
|
3477
|
-
* the target `placeLightSlots` re-attaches the captured nodes into, and in the
|
|
3478
|
-
* shadow build a `<slot>` that is not in the tree has no assigned nodes — so
|
|
3479
|
-
* dropping it would make "is the body filled?" permanently false and the
|
|
3480
|
-
* override could never turn on.
|
|
3481
|
-
*/
|
|
3482
|
-
_renderBodySlot() {
|
|
3483
|
-
return html`<div class="mono-table-empty-body" mono-empty-body data-mono-slot="body"></div>`;
|
|
3484
|
-
}
|
|
3485
|
-
/** Icon, title, subtitle, reload — in that order, each omitted when unset. */
|
|
3486
|
-
_renderProps() {
|
|
3487
|
-
const showReload = this.reload && !!this.dataGrid;
|
|
3488
|
-
return html`
|
|
3489
|
-
${this._renderIcon()}
|
|
3490
|
-
${this.title ? html`<div class="mono-table-empty-title" mono-empty-title>${this.title}</div>` : nothing}
|
|
3491
|
-
${this.subtitle ? html`<div class="mono-table-empty-sub" mono-empty-sub>${this.subtitle}</div>` : nothing}
|
|
3492
|
-
${showReload ? html`<button
|
|
3493
|
-
type="button"
|
|
3494
|
-
class="mono-table-empty-reload" mono-empty-reload
|
|
3495
|
-
part="reload"
|
|
3496
|
-
@click=${() => this._onReload()}
|
|
3497
|
-
>
|
|
3498
|
-
${this._reloadIcon()}${this.reloadLabel}
|
|
3499
|
-
</button>` : nothing}
|
|
3500
|
-
`;
|
|
3501
|
-
}
|
|
3502
|
-
/**
|
|
3503
|
-
* `slot="body"` REPLACES the props rather than sitting beside them. Anyone who
|
|
3504
|
-
* fills it has said what the box should contain, and a stray title showing
|
|
3505
|
-
* above their own markup would be a bug they could not turn off without also
|
|
3506
|
-
* clearing a prop they never set.
|
|
3507
|
-
*/
|
|
3508
|
-
_renderContent() {
|
|
3509
|
-
return html`${this._hasBodySlot ? nothing : this._renderProps()}${this._renderBodySlot()}`;
|
|
3510
|
-
}
|
|
3511
|
-
render() {
|
|
3512
|
-
return html`<div class="mono-table-empty-box" mono-empty-box part="box" role="status">
|
|
3513
|
-
${this._renderContent()}
|
|
3514
|
-
</div>`;
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
__decorate([property({ type: String })], MonoTableEmptyCoreClass.prototype, "icon", void 0);
|
|
3518
|
-
__decorate([property({ type: String })], MonoTableEmptyCoreClass.prototype, "title", void 0);
|
|
3519
|
-
__decorate([property({ type: String })], MonoTableEmptyCoreClass.prototype, "subtitle", void 0);
|
|
3520
|
-
__decorate([property({ type: Boolean })], MonoTableEmptyCoreClass.prototype, "reload", void 0);
|
|
3521
|
-
__decorate([property({
|
|
3522
|
-
type: String,
|
|
3523
|
-
attribute: "reload-label"
|
|
3524
|
-
})], MonoTableEmptyCoreClass.prototype, "reloadLabel", void 0);
|
|
3525
|
-
__decorate([property({ type: String })], MonoTableEmptyCoreClass.prototype, "height", void 0);
|
|
3526
|
-
__decorate([property({
|
|
3527
|
-
type: String,
|
|
3528
|
-
attribute: "min-height"
|
|
3529
|
-
})], MonoTableEmptyCoreClass.prototype, "minHeight", void 0);
|
|
3530
|
-
__decorate([property({
|
|
3531
|
-
type: String,
|
|
3532
|
-
attribute: "max-height"
|
|
3533
|
-
})], MonoTableEmptyCoreClass.prototype, "maxHeight", void 0);
|
|
3534
|
-
__decorate([state()], MonoTableEmptyCoreClass.prototype, "_hasBodySlot", void 0);
|
|
3535
|
-
return MonoTableEmptyCoreClass;
|
|
3536
|
-
};
|
|
3537
|
-
|
|
3538
176
|
//#endregion
|
|
3539
177
|
//#region src/components/table/mono-table-empty.ts
|
|
3540
178
|
/** The one named region. Everything unnamed falls into the default bucket. */
|
|
@@ -3566,230 +204,6 @@ let MonoTableEmpty = class MonoTableEmpty extends MonoTableEmptyCore(LitElement)
|
|
|
3566
204
|
};
|
|
3567
205
|
MonoTableEmpty = __decorate([customElement("mono-table-empty")], MonoTableEmpty);
|
|
3568
206
|
|
|
3569
|
-
//#endregion
|
|
3570
|
-
//#region src/components/table/mono-table-error-core.ts
|
|
3571
|
-
/**
|
|
3572
|
-
* `MonoTableErrorCore` — render-mode-agnostic logic for `mono-table-error`, the
|
|
3573
|
-
* red bar shown under the header when a request fails.
|
|
3574
|
-
*
|
|
3575
|
-
* Unlike `mono-table-loading` and `mono-table-empty`, which paint OVER the grid,
|
|
3576
|
-
* this one is a real row in it, taking up honest height directly below the
|
|
3577
|
-
* header the way DevExtreme's error row does. The consumer writes that row —
|
|
3578
|
-
* `<tr><td colspan><mono-table-error/></td></tr>` — and the element ADOPTS it
|
|
3579
|
-
* (`ensureRowHost`): the row class the padding reset keys on, the zebra-skip
|
|
3580
|
-
* marker and a live `colspan` are stamped on, so the hand-written form needs
|
|
3581
|
-
* nothing but the two tags. Dropped bare into `<tbody>` it still wraps itself,
|
|
3582
|
-
* but that is invalid HTML — Vue warns, and it does not survive SSR — so it is
|
|
3583
|
-
* the fallback, not the taught form.
|
|
3584
|
-
*
|
|
3585
|
-
* **What it shows.** `message` if you set one — an explicit message is an
|
|
3586
|
-
* instruction, and it wins for as long as it is set. Otherwise the controller's
|
|
3587
|
-
* last caught error (`MonoTableController.error`), whose text comes from the
|
|
3588
|
-
* error itself rather than from a hardcoded string.
|
|
3589
|
-
*
|
|
3590
|
-
* **A failure clears the rows** (`behaviour="clear-list"`, the default): the
|
|
3591
|
-
* controller empties `items` when it records the error, so a rejected filter
|
|
3592
|
-
* never leaves the previous filter's rows under the bar. `"keep-list"` keeps
|
|
3593
|
-
* them. The setting is pushed to the controller (`setErrorBehaviour`), so it
|
|
3594
|
-
* holds whether or not this element is the one on screen.
|
|
3595
|
-
*
|
|
3596
|
-
* **Reload** (`reload`, default `true`): a ↻ beside the × that re-runs the
|
|
3597
|
-
* failed query (`table.reload()`). The bar stays until that succeeds.
|
|
3598
|
-
*
|
|
3599
|
-
* **Dismiss hides THIS element only.** The controller keeps its error, so
|
|
3600
|
-
* anything else bound to the same table still agrees that it happened. What
|
|
3601
|
-
* comes back is the next *failure*, not the next different message: the element
|
|
3602
|
-
* remembers the error object it dismissed, and the controller mints a fresh one
|
|
3603
|
-
* per failure, so an identical repeat error re-shows.
|
|
3604
|
-
*
|
|
3605
|
-
* **It stays in view.** The row is table-wide, and on a table wider than its
|
|
3606
|
-
* `.mono-table-scroll` that put the text at the far left and the ✕ / ↻ past the
|
|
3607
|
-
* right edge — invisible until you scrolled for them. So the bar inside the row
|
|
3608
|
-
* is `position: sticky; left: 0` at exactly the scrollport's width (`100cqw`,
|
|
3609
|
-
* the same container-query trick the loading spinner centres with — pure CSS in
|
|
3610
|
-
* `table.css`). Vertically it follows the HEADER: under `mono-table-sticky-head`
|
|
3611
|
-
* the row's cell is sticky too, `top` = the measured `<thead>` height, which this
|
|
3612
|
-
* element publishes as `--mono-table-error-head` on the `<table>` and keeps
|
|
3613
|
-
* fresh with a `ResizeObserver` (a banded two-row header, a caption that wraps).
|
|
3614
|
-
* A header that scrolls away takes the bar with it — it belongs to those rows.
|
|
3615
|
-
*
|
|
3616
|
-
* SSR-safe: all DOM work is guarded behind `isServer`.
|
|
3617
|
-
*/
|
|
3618
|
-
const MonoTableErrorCore = (superClass) => {
|
|
3619
|
-
class MonoTableErrorCoreClass extends MonoTableControllerCore(superClass) {
|
|
3620
|
-
constructor(..._args) {
|
|
3621
|
-
super(..._args);
|
|
3622
|
-
this._propsSlot = "error";
|
|
3623
|
-
this.message = "";
|
|
3624
|
-
this.dismissible = true;
|
|
3625
|
-
this.closeLabel = "Dismiss";
|
|
3626
|
-
this.behaviour = "clear-list";
|
|
3627
|
-
this.reload = true;
|
|
3628
|
-
this.reloadLabel = "Reload";
|
|
3629
|
-
this._dismissed = null;
|
|
3630
|
-
this._dismissedMessage = false;
|
|
3631
|
-
}
|
|
3632
|
-
connectedCallback() {
|
|
3633
|
-
super.connectedCallback();
|
|
3634
|
-
if (isServer) return;
|
|
3635
|
-
this._rowHost = ensureRowHost(this, "mono-table-error-row");
|
|
3636
|
-
if (this.hasUpdated) this._syncHead();
|
|
3637
|
-
}
|
|
3638
|
-
_subscribe() {
|
|
3639
|
-
super._subscribe();
|
|
3640
|
-
this._pushBehaviour();
|
|
3641
|
-
}
|
|
3642
|
-
/** The controller owns the clearing; this element only names the mode. */
|
|
3643
|
-
_pushBehaviour() {
|
|
3644
|
-
this.dataGrid?.setErrorBehaviour?.(this.behaviour === "keep-list" ? "keep-list" : "clear-list");
|
|
3645
|
-
}
|
|
3646
|
-
disconnectedCallback() {
|
|
3647
|
-
this._releaseHead();
|
|
3648
|
-
releaseRowHost(this, this._rowHost);
|
|
3649
|
-
this._rowHost = void 0;
|
|
3650
|
-
super.disconnectedCallback();
|
|
3651
|
-
}
|
|
3652
|
-
updated(changed) {
|
|
3653
|
-
super.updated?.(changed);
|
|
3654
|
-
if (isServer) return;
|
|
3655
|
-
syncRowSpan(this, this._rowHost);
|
|
3656
|
-
if (changed.has("message")) this._dismissedMessage = false;
|
|
3657
|
-
if (changed.has("behaviour")) this._pushBehaviour();
|
|
3658
|
-
this._syncHead();
|
|
3659
|
-
}
|
|
3660
|
-
/**
|
|
3661
|
-
* Publish the header's height for the sticky cell to sit under — only while
|
|
3662
|
-
* the bar is showing, and only under a frozen header (`mono-table-sticky-head`
|
|
3663
|
-
* on the table or its scroll wrapper). Anything else is a plain row.
|
|
3664
|
-
*
|
|
3665
|
-
* Written on the `<table>` rather than on this element: the sticky box is the
|
|
3666
|
-
* host `<td>`, which is an ANCESTOR, and a custom property only travels down.
|
|
3667
|
-
*/
|
|
3668
|
-
_syncHead() {
|
|
3669
|
-
const table = this.closest("table");
|
|
3670
|
-
if (!(!!this._text && !!table && !!this.closest(".mono-table-sticky-head, [mono-sticky-head]"))) {
|
|
3671
|
-
this._releaseHead();
|
|
3672
|
-
return;
|
|
3673
|
-
}
|
|
3674
|
-
const thead = table.querySelector(":scope > thead");
|
|
3675
|
-
if (this._headHost !== table) {
|
|
3676
|
-
this._releaseHead();
|
|
3677
|
-
this._headHost = table;
|
|
3678
|
-
if (thead && typeof ResizeObserver !== "undefined") {
|
|
3679
|
-
this._headObserver = new ResizeObserver(() => this._writeHead());
|
|
3680
|
-
this._headObserver.observe(thead);
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
this._writeHead();
|
|
3684
|
-
}
|
|
3685
|
-
_writeHead() {
|
|
3686
|
-
const table = this._headHost;
|
|
3687
|
-
if (!table) return;
|
|
3688
|
-
const next = `${table.querySelector(":scope > thead")?.offsetHeight ?? 0}px`;
|
|
3689
|
-
if (table.style.getPropertyValue("--mono-table-error-head") !== next) table.style.setProperty("--mono-table-error-head", next);
|
|
3690
|
-
}
|
|
3691
|
-
/** Undo `_syncHead` — from the remembered table, since `closest()` is empty after removal. */
|
|
3692
|
-
_releaseHead() {
|
|
3693
|
-
this._headObserver?.disconnect();
|
|
3694
|
-
this._headObserver = void 0;
|
|
3695
|
-
this._headHost?.style.removeProperty("--mono-table-error-head");
|
|
3696
|
-
this._headHost = void 0;
|
|
3697
|
-
}
|
|
3698
|
-
/** The controller error this element would show, if it has not been dismissed. */
|
|
3699
|
-
get _liveError() {
|
|
3700
|
-
const error = this.dataGrid?.error ?? null;
|
|
3701
|
-
if (!error) return null;
|
|
3702
|
-
return error === this._dismissed ? null : error;
|
|
3703
|
-
}
|
|
3704
|
-
/** The text to paint, or `''` for "show nothing". */
|
|
3705
|
-
get _text() {
|
|
3706
|
-
const manual = this.message?.trim();
|
|
3707
|
-
if (manual) return this._dismissedMessage ? "" : manual;
|
|
3708
|
-
return this._liveError?.message ?? "";
|
|
3709
|
-
}
|
|
3710
|
-
/**
|
|
3711
|
-
* What the server said beyond the headline (`MonoTableError.detail`), or
|
|
3712
|
-
* `''`. Only for a controller error — a manual `message` is the whole text.
|
|
3713
|
-
*/
|
|
3714
|
-
get _detail() {
|
|
3715
|
-
if (this.message?.trim()) return "";
|
|
3716
|
-
return this._liveError?.detail ?? "";
|
|
3717
|
-
}
|
|
3718
|
-
_onClose(event) {
|
|
3719
|
-
event.stopPropagation();
|
|
3720
|
-
if (this.message?.trim()) this._dismissedMessage = true;
|
|
3721
|
-
else this._dismissed = this.dataGrid?.error ?? null;
|
|
3722
|
-
dispatchMonoEvent(this, "close", {
|
|
3723
|
-
message: this._text,
|
|
3724
|
-
error: this.dataGrid?.error ?? null
|
|
3725
|
-
});
|
|
3726
|
-
}
|
|
3727
|
-
_onReload(event) {
|
|
3728
|
-
event.stopPropagation();
|
|
3729
|
-
const grid = this.dataGrid;
|
|
3730
|
-
dispatchMonoEvent(this, "reload", { error: grid?.error ?? null });
|
|
3731
|
-
grid?.reload().catch(() => {});
|
|
3732
|
-
}
|
|
3733
|
-
/**
|
|
3734
|
-
* Close ✕ / reload ↻. Light (default): the UnoCSS icon class. The shadow build overrides
|
|
3735
|
-
* this with inline SVG — a global `.i-mdi-close` rule cannot reach a shadow
|
|
3736
|
-
* root. Same split as `mono-modal` / `mono-drawer`.
|
|
3737
|
-
*/
|
|
3738
|
-
renderIcon(name) {
|
|
3739
|
-
return name === "refresh" ? html`<span class="mono-icon i-mdi-refresh" aria-hidden="true"></span>` : html`<span class="mono-icon i-mdi-close" aria-hidden="true"></span>`;
|
|
3740
|
-
}
|
|
3741
|
-
render() {
|
|
3742
|
-
const text = this._text;
|
|
3743
|
-
if (!text) return nothing;
|
|
3744
|
-
const detail = this._detail;
|
|
3745
|
-
return html`
|
|
3746
|
-
<div class="mono-table-error-bar" mono-error-bar part="bar" role="alert">
|
|
3747
|
-
<span class="mono-table-error-text" mono-error-text part="text" title=${detail || nothing}
|
|
3748
|
-
>${text}${detail ? html`<span class="mono-table-error-detail" mono-error-detail part="detail">${detail}</span>` : nothing}</span
|
|
3749
|
-
>
|
|
3750
|
-
<span class="mono-table-error-actions" mono-error-actions part="actions">
|
|
3751
|
-
${this.reload && this.dataGrid ? html`<button
|
|
3752
|
-
type="button"
|
|
3753
|
-
class="mono-table-error-btn mono-table-error-reload" mono-error-btn
|
|
3754
|
-
part="reload"
|
|
3755
|
-
aria-label=${this.reloadLabel}
|
|
3756
|
-
title=${this.reloadLabel}
|
|
3757
|
-
?disabled=${this.dataGrid.loading}
|
|
3758
|
-
@click=${(event) => this._onReload(event)}
|
|
3759
|
-
>
|
|
3760
|
-
${this.renderIcon("refresh")}
|
|
3761
|
-
</button>` : nothing}
|
|
3762
|
-
${this.dismissible ? html`<button
|
|
3763
|
-
type="button"
|
|
3764
|
-
class="mono-table-error-btn mono-table-error-close" mono-error-btn mono-error-close
|
|
3765
|
-
part="close"
|
|
3766
|
-
aria-label=${this.closeLabel}
|
|
3767
|
-
@click=${(event) => this._onClose(event)}
|
|
3768
|
-
>
|
|
3769
|
-
${this.renderIcon("close")}
|
|
3770
|
-
</button>` : nothing}
|
|
3771
|
-
</span>
|
|
3772
|
-
</div>
|
|
3773
|
-
`;
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
__decorate([property({ type: String })], MonoTableErrorCoreClass.prototype, "message", void 0);
|
|
3777
|
-
__decorate([property({ type: Boolean })], MonoTableErrorCoreClass.prototype, "dismissible", void 0);
|
|
3778
|
-
__decorate([property({
|
|
3779
|
-
type: String,
|
|
3780
|
-
attribute: "close-label"
|
|
3781
|
-
})], MonoTableErrorCoreClass.prototype, "closeLabel", void 0);
|
|
3782
|
-
__decorate([property({ reflect: true })], MonoTableErrorCoreClass.prototype, "behaviour", void 0);
|
|
3783
|
-
__decorate([property({ type: Boolean })], MonoTableErrorCoreClass.prototype, "reload", void 0);
|
|
3784
|
-
__decorate([property({
|
|
3785
|
-
type: String,
|
|
3786
|
-
attribute: "reload-label"
|
|
3787
|
-
})], MonoTableErrorCoreClass.prototype, "reloadLabel", void 0);
|
|
3788
|
-
__decorate([state()], MonoTableErrorCoreClass.prototype, "_dismissed", void 0);
|
|
3789
|
-
__decorate([state()], MonoTableErrorCoreClass.prototype, "_dismissedMessage", void 0);
|
|
3790
|
-
return MonoTableErrorCoreClass;
|
|
3791
|
-
};
|
|
3792
|
-
|
|
3793
207
|
//#endregion
|
|
3794
208
|
//#region src/components/table/mono-table-error.ts
|
|
3795
209
|
let MonoTableError = class MonoTableError extends MonoTableErrorCore(LitElement) {
|
|
@@ -3799,316 +213,6 @@ let MonoTableError = class MonoTableError extends MonoTableErrorCore(LitElement)
|
|
|
3799
213
|
};
|
|
3800
214
|
MonoTableError = __decorate([customElement("mono-table-error")], MonoTableError);
|
|
3801
215
|
|
|
3802
|
-
//#endregion
|
|
3803
|
-
//#region src/components/table/mono-table-loading-core.ts
|
|
3804
|
-
const SKELETON_FALLBACK = {
|
|
3805
|
-
cols: [
|
|
3806
|
-
25,
|
|
3807
|
-
25,
|
|
3808
|
-
25,
|
|
3809
|
-
25
|
|
3810
|
-
],
|
|
3811
|
-
rowH: 40,
|
|
3812
|
-
rows: 8
|
|
3813
|
-
};
|
|
3814
|
-
/**
|
|
3815
|
-
* `loading` is TRI-STATE, so the attribute can say "not set" as well as on / off: no attribute →
|
|
3816
|
-
* `null` (automatic, follow the controller); `loading` / `loading="true"` → `true`;
|
|
3817
|
-
* `loading="false"` → `false`. Never reflected — the element's own state is `data-mono-loading`.
|
|
3818
|
-
*/
|
|
3819
|
-
const loadingConverter = { fromAttribute(value) {
|
|
3820
|
-
if (value === null) return null;
|
|
3821
|
-
return String(value).trim().toLowerCase() !== "false";
|
|
3822
|
-
} };
|
|
3823
|
-
/**
|
|
3824
|
-
* `MonoTableLoadingCore` — render-mode-agnostic logic for `mono-table-loading`, a
|
|
3825
|
-
* drop-in spinner overlay shown over the table whenever the bound controller is
|
|
3826
|
-
* fetching (sort / search / paging / reload / save). Place it in the
|
|
3827
|
-
* `.mono-table-scroll` wrapper, or inside the `<table>` wrapped in a `<caption>`
|
|
3828
|
-
* (a bare custom-element child of `<table>` is invalid HTML — see the tag doc),
|
|
3829
|
-
* and bind the controller with `:data-grid.prop` — no `v-show` wiring.
|
|
3830
|
-
*
|
|
3831
|
-
* It reads `dataGrid.loading` **synchronously on every controller notify** (not in
|
|
3832
|
-
* an async render) so it can't miss a fast query whose `loading` flips back before
|
|
3833
|
-
* Lit re-renders, and holds the overlay for a small minimum duration
|
|
3834
|
-
* (`min-duration`, default 350ms) so the feedback is always perceptible. The
|
|
3835
|
-
* overlay itself is toggled by an imperative `data-loading` attribute + CSS — the
|
|
3836
|
-
* element never needs to re-render for the state to flip.
|
|
3837
|
-
*
|
|
3838
|
-
* "Just works" details, applied to the nearest overlay host — the enclosing
|
|
3839
|
-
* `.mono-table-scroll` if present, else the `<table>`:
|
|
3840
|
-
* - makes it a positioning context (`position: relative`) so the absolute overlay
|
|
3841
|
-
* covers it (the consumer adds nothing);
|
|
3842
|
-
* - holds the grid's height while active, so a source that momentarily clears its rows
|
|
3843
|
-
* mid-query can't collapse it to a thin strip. The hold goes on the nearest BLOCK box (the
|
|
3844
|
-
* scroll wrapper, else the table's parent) — never on the `<table>`, which would distribute
|
|
3845
|
-
* the height over its rows and stretch the header instead of reserving space.
|
|
3846
|
-
*
|
|
3847
|
-
* MANUAL mode: set `loading` (`:loading="isBusy"`) and the element follows THAT instead of the
|
|
3848
|
-
* controller — for a busy state the controller can't see (several requests, a save in a store,
|
|
3849
|
-
* a page with no controller at all). `true` shows, `false` hides, whatever the controller says;
|
|
3850
|
-
* `null` / `undefined` (or no attribute) hands it back to the controller. `min-duration` holds
|
|
3851
|
-
* for both sources.
|
|
3852
|
-
*
|
|
3853
|
-
* SSR-safe: all DOM/timer work is guarded behind `isServer`.
|
|
3854
|
-
*/
|
|
3855
|
-
const MonoTableLoadingCore = (superClass) => {
|
|
3856
|
-
class MonoTableLoadingCoreClass extends MonoTableControllerCore(superClass) {
|
|
3857
|
-
constructor(..._args) {
|
|
3858
|
-
super(..._args);
|
|
3859
|
-
this._propsSlot = "loading";
|
|
3860
|
-
this._pendingOn = false;
|
|
3861
|
-
this._skeleton = SKELETON_FALLBACK;
|
|
3862
|
-
this.minDuration = 350;
|
|
3863
|
-
this.loading = null;
|
|
3864
|
-
this._visible = false;
|
|
3865
|
-
this._shownAt = 0;
|
|
3866
|
-
}
|
|
3867
|
-
static {
|
|
3868
|
-
this.monoPendingMode = "custom";
|
|
3869
|
-
}
|
|
3870
|
-
static {
|
|
3871
|
-
this.monoPendingDraw = "phantom";
|
|
3872
|
-
}
|
|
3873
|
-
connectedCallback() {
|
|
3874
|
-
super.connectedCallback();
|
|
3875
|
-
if (!isServer) this._rowHost = ensureRowHost(this, "mono-table-loading-row");
|
|
3876
|
-
if (!isServer) this._watchAttr();
|
|
3877
|
-
if (!isServer) this._sync();
|
|
3878
|
-
}
|
|
3879
|
-
disconnectedCallback() {
|
|
3880
|
-
if (this._hideTimer) clearTimeout(this._hideTimer);
|
|
3881
|
-
this._hideTimer = void 0;
|
|
3882
|
-
this._attrObserver?.disconnect();
|
|
3883
|
-
this._attrObserver = void 0;
|
|
3884
|
-
releaseOverlayHost(this);
|
|
3885
|
-
releaseRowHost(this, this._rowHost);
|
|
3886
|
-
this._rowHost = void 0;
|
|
3887
|
-
super.disconnectedCallback();
|
|
3888
|
-
}
|
|
3889
|
-
/**
|
|
3890
|
-
* Honour a `data-loading` a CONSUMER sets, instead of fighting it.
|
|
3891
|
-
*
|
|
3892
|
-
* The attribute is documented as the CSS key, so a consumer whose busy state is wider than
|
|
3893
|
-
* one controller — a filter that runs several requests, a store flag held across a save —
|
|
3894
|
-
* reasonably drives it directly: `:data-loading="isBusy || null"`. Without this the two
|
|
3895
|
-
* sources stomp each other: the element's own `_apply(false)` REMOVES an attribute the
|
|
3896
|
-
* consumer set (their overlay vanishes mid-work, and their framework will not re-set an
|
|
3897
|
-
* attribute it still believes is there), and an externally set one runs NONE of the work
|
|
3898
|
-
* below — no height freeze, no measured header offset — because that hangs off the
|
|
3899
|
-
* controller's `loading`, not off the attribute.
|
|
3900
|
-
*
|
|
3901
|
-
* So the two sources are kept STRICTLY apart. `data-loading` belongs to the consumer and this
|
|
3902
|
-
* element never writes it; its own state goes on `data-mono-loading`, and the stylesheet shows
|
|
3903
|
-
* the overlay for either. Nothing has to work out who wrote what — which is the only version of
|
|
3904
|
-
* this that can be correct, because a MutationObserver callback is a MICROTASK: a
|
|
3905
|
-
* "`_writing` = true while I write" flag is already back to false by the time the callback
|
|
3906
|
-
* runs, so the element reads its OWN write as the consumer's and latches on forever.
|
|
3907
|
-
*/
|
|
3908
|
-
_watchAttr() {
|
|
3909
|
-
this._attrObserver = new MutationObserver(() => this._apply());
|
|
3910
|
-
this._attrObserver.observe(this, {
|
|
3911
|
-
attributes: true,
|
|
3912
|
-
attributeFilter: ["data-loading"]
|
|
3913
|
-
});
|
|
3914
|
-
this._apply();
|
|
3915
|
-
}
|
|
3916
|
-
/** Re-read state on every notify (chains the base subscribe re-bind). */
|
|
3917
|
-
_subscribe() {
|
|
3918
|
-
this._off?.();
|
|
3919
|
-
this._off = this.dataGrid?.subscribe(() => {
|
|
3920
|
-
this._sync();
|
|
3921
|
-
if (monoPendingActive(this)) this.requestUpdate();
|
|
3922
|
-
});
|
|
3923
|
-
if (!isServer) this._sync();
|
|
3924
|
-
}
|
|
3925
|
-
/**
|
|
3926
|
-
* Runs synchronously on every controller notify. Latches the overlay ON the
|
|
3927
|
-
* instant `loading` is observed true (defeating the async-render race) and
|
|
3928
|
-
* defers hiding until at least `minDuration` has elapsed.
|
|
3929
|
-
*/
|
|
3930
|
-
_sync() {
|
|
3931
|
-
if (isServer) return;
|
|
3932
|
-
if (monoPendingActive(this)) {
|
|
3933
|
-
if (this._hideTimer) clearTimeout(this._hideTimer);
|
|
3934
|
-
this._hideTimer = void 0;
|
|
3935
|
-
return;
|
|
3936
|
-
}
|
|
3937
|
-
if (this.loading != null ? !!this.loading : !!this.dataGrid?.loading) {
|
|
3938
|
-
if (this._hideTimer) {
|
|
3939
|
-
clearTimeout(this._hideTimer);
|
|
3940
|
-
this._hideTimer = void 0;
|
|
3941
|
-
}
|
|
3942
|
-
if (!this._visible) {
|
|
3943
|
-
this._visible = true;
|
|
3944
|
-
this._shownAt = Date.now();
|
|
3945
|
-
this._apply();
|
|
3946
|
-
}
|
|
3947
|
-
} else if (this._visible && !this._hideTimer) {
|
|
3948
|
-
const wait = Math.max(0, this.minDuration - (Date.now() - this._shownAt));
|
|
3949
|
-
this._hideTimer = setTimeout(() => {
|
|
3950
|
-
this._hideTimer = void 0;
|
|
3951
|
-
this._visible = false;
|
|
3952
|
-
this._apply();
|
|
3953
|
-
}, wait);
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
/**
|
|
3957
|
-
* Reflect visibility + hold/release the height, from BOTH sources.
|
|
3958
|
-
*
|
|
3959
|
-
* Writes only `data-mono-loading`, this element's own state. `data-loading` is the
|
|
3960
|
-
* consumer's to set and is read here, never written — see `_watchAttr`.
|
|
3961
|
-
*/
|
|
3962
|
-
_apply() {
|
|
3963
|
-
if (monoPendingActive(this)) {
|
|
3964
|
-
this.toggleAttribute("data-mono-loading", false);
|
|
3965
|
-
this.setAttribute("aria-hidden", "false");
|
|
3966
|
-
return;
|
|
3967
|
-
}
|
|
3968
|
-
const on = this._visible || this.hasAttribute("data-loading");
|
|
3969
|
-
this.toggleAttribute("data-mono-loading", this._visible);
|
|
3970
|
-
this.setAttribute("aria-hidden", on ? "false" : "true");
|
|
3971
|
-
applyOverlayHost(this, on, {
|
|
3972
|
-
headVar: "--mono-table-loading-head",
|
|
3973
|
-
holdVar: "--mono-table-hold-loading",
|
|
3974
|
-
hold: "measure"
|
|
3975
|
-
});
|
|
3976
|
-
}
|
|
3977
|
-
/** A manual `loading` change runs the same latch / min-duration path as a controller notify. */
|
|
3978
|
-
updated(changed) {
|
|
3979
|
-
super.updated(changed);
|
|
3980
|
-
const pending = monoPendingActive(this);
|
|
3981
|
-
if (pending !== this._pendingOn) {
|
|
3982
|
-
this._pendingOn = pending;
|
|
3983
|
-
this._applyPending(pending);
|
|
3984
|
-
} else if (pending) this._holdPending();
|
|
3985
|
-
if (changed.has("loading") && this.isConnected) this._sync();
|
|
3986
|
-
}
|
|
3987
|
-
/**
|
|
3988
|
-
* Measure the placeholder geometry right before the render that draws it. `update()` runs
|
|
3989
|
-
* after every `willUpdate` — including the skeleton mixin's, which is where `pending` is
|
|
3990
|
-
* resolved for this pass — and before `render()`, so the template sees fresh numbers
|
|
3991
|
-
* without a second update cycle.
|
|
3992
|
-
*/
|
|
3993
|
-
update(changed) {
|
|
3994
|
-
if (monoPendingActive(this)) this._measureSkeleton();
|
|
3995
|
-
super.update(changed);
|
|
3996
|
-
}
|
|
3997
|
-
/** Enter / leave the pending placeholder. */
|
|
3998
|
-
_applyPending(on) {
|
|
3999
|
-
if (on) {
|
|
4000
|
-
this.toggleAttribute("data-mono-pending", true);
|
|
4001
|
-
this._apply();
|
|
4002
|
-
this._holdPending();
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
this.removeAttribute("data-mono-pending");
|
|
4006
|
-
releaseOverlayHost(this);
|
|
4007
|
-
if (this._hideTimer) clearTimeout(this._hideTimer);
|
|
4008
|
-
this._hideTimer = void 0;
|
|
4009
|
-
this._visible = false;
|
|
4010
|
-
this._apply();
|
|
4011
|
-
this._sync();
|
|
4012
|
-
}
|
|
4013
|
-
/**
|
|
4014
|
-
* Reserve header + N rows on the size host (the spinner's own slot, so the two never
|
|
4015
|
-
* hold at once) and push the resolved phantom options onto the placeholder.
|
|
4016
|
-
*/
|
|
4017
|
-
/** Rows to draw: a configured phantom `count` (element / tag / global) wins over the page size. */
|
|
4018
|
-
_skeletonRows() {
|
|
4019
|
-
const count = Number(monoPhantomOptions(this).count);
|
|
4020
|
-
return Number.isFinite(count) && count > 0 ? Math.round(count) : this._skeleton.rows;
|
|
4021
|
-
}
|
|
4022
|
-
_holdPending() {
|
|
4023
|
-
const { rowH } = this._skeleton;
|
|
4024
|
-
const rows = this._skeletonRows();
|
|
4025
|
-
applyOverlayHost(this, true, {
|
|
4026
|
-
headVar: "--mono-table-loading-head",
|
|
4027
|
-
holdVar: "--mono-table-hold-loading",
|
|
4028
|
-
hold: () => {
|
|
4029
|
-
const host = overlayHost(this);
|
|
4030
|
-
return `${(host ? parseFloat(host.style.getPropertyValue("--mono-table-loading-head")) || 0 : 0) + rows * rowH}px`;
|
|
4031
|
-
},
|
|
4032
|
-
measureHead: "always"
|
|
4033
|
-
});
|
|
4034
|
-
monoApplyPhantomOptions(this, this.renderRoot.querySelector("phantom-ui[mono-skeleton]"));
|
|
4035
|
-
}
|
|
4036
|
-
/**
|
|
4037
|
-
* Mirror the header: one placeholder cell per `<th>`, at the header's column widths
|
|
4038
|
-
* (as percentages, so a resize needs no re-measure), rows as tall as a header row,
|
|
4039
|
-
* as many rows as the page size (capped — a "show all" page falls back to 8).
|
|
4040
|
-
* Cheap: N `offsetWidth` reads while pending. Every branch has a fallback, so a
|
|
4041
|
-
* header that has not laid out yet still gets an equal split.
|
|
4042
|
-
*/
|
|
4043
|
-
_measureSkeleton() {
|
|
4044
|
-
const table = this.closest("table");
|
|
4045
|
-
const grid = this.dataGrid;
|
|
4046
|
-
let cols = SKELETON_FALLBACK.cols;
|
|
4047
|
-
let rowH = SKELETON_FALLBACK.rowH;
|
|
4048
|
-
if (table) {
|
|
4049
|
-
const ths = Array.from(table.querySelectorAll(":scope > thead th"));
|
|
4050
|
-
const total = table.clientWidth;
|
|
4051
|
-
const widths = ths.map((th) => th.offsetWidth);
|
|
4052
|
-
const sum = widths.reduce((a, b) => a + b, 0);
|
|
4053
|
-
if (total > 0 && sum > 0) cols = widths.map((w) => Math.max(2, Math.round(w / total * 1e3) / 10));
|
|
4054
|
-
else {
|
|
4055
|
-
const n = columnCount(table) || grid?.props?.().th?.length || SKELETON_FALLBACK.cols.length;
|
|
4056
|
-
const each = Math.round(100 / n * 10) / 10;
|
|
4057
|
-
cols = Array.from({ length: n }, () => each);
|
|
4058
|
-
}
|
|
4059
|
-
const first = ths[0]?.offsetHeight ?? 0;
|
|
4060
|
-
if (first > 0) rowH = first;
|
|
4061
|
-
}
|
|
4062
|
-
const size = grid?.pageSize ?? 0;
|
|
4063
|
-
const rows = grid && !grid.pageSizeAll && size > 0 && size <= 50 ? size : SKELETON_FALLBACK.rows;
|
|
4064
|
-
const prev = this._skeleton;
|
|
4065
|
-
if (prev.rowH !== rowH || prev.rows !== rows || prev.cols.length !== cols.length || prev.cols.some((c, i) => c !== cols[i])) this._skeleton = {
|
|
4066
|
-
cols,
|
|
4067
|
-
rowH,
|
|
4068
|
-
rows
|
|
4069
|
-
};
|
|
4070
|
-
}
|
|
4071
|
-
/** The pending placeholder: one measured row, repeated by phantom's `count`. */
|
|
4072
|
-
_renderSkeleton() {
|
|
4073
|
-
const { cols, rowH } = this._skeleton;
|
|
4074
|
-
const rows = this._skeletonRows();
|
|
4075
|
-
return html`
|
|
4076
|
-
<div
|
|
4077
|
-
class="mono-table-skeleton" mono-table-skeleton
|
|
4078
|
-
style="padding-top: var(--mono-table-loading-head, 0px)"
|
|
4079
|
-
>
|
|
4080
|
-
<phantom-ui mono-skeleton loading count=${rows} count-gap="0" loading-label="Loading rows">
|
|
4081
|
-
<div class="mono-table-skeleton-row" mono-table-skeleton-row style=${`height:${rowH}px`}>
|
|
4082
|
-
${cols.map((pct) => html`<span
|
|
4083
|
-
class="mono-table-skeleton-cell" mono-table-skeleton-cell
|
|
4084
|
-
style=${`width:${pct}%`}
|
|
4085
|
-
><span class="mono-table-skeleton-bar" mono-table-skeleton-bar data-shimmer-no-children></span></span>`)}
|
|
4086
|
-
</div>
|
|
4087
|
-
</phantom-ui>
|
|
4088
|
-
</div>
|
|
4089
|
-
`;
|
|
4090
|
-
}
|
|
4091
|
-
render() {
|
|
4092
|
-
if (monoPendingActive(this)) return this._renderSkeleton();
|
|
4093
|
-
return html`<span
|
|
4094
|
-
class="mono-table-spinner" mono-loading-spinner
|
|
4095
|
-
part="spinner"
|
|
4096
|
-
role="status"
|
|
4097
|
-
aria-label="Loading"
|
|
4098
|
-
></span>`;
|
|
4099
|
-
}
|
|
4100
|
-
}
|
|
4101
|
-
__decorate([property({
|
|
4102
|
-
type: Number,
|
|
4103
|
-
attribute: "min-duration"
|
|
4104
|
-
})], MonoTableLoadingCoreClass.prototype, "minDuration", void 0);
|
|
4105
|
-
__decorate([property({
|
|
4106
|
-
attribute: "loading",
|
|
4107
|
-
converter: loadingConverter
|
|
4108
|
-
})], MonoTableLoadingCoreClass.prototype, "loading", void 0);
|
|
4109
|
-
return MonoTableLoadingCoreClass;
|
|
4110
|
-
};
|
|
4111
|
-
|
|
4112
216
|
//#endregion
|
|
4113
217
|
//#region src/components/table/mono-table-loading.ts
|
|
4114
218
|
let MonoTableLoading = class MonoTableLoading extends MonoTableLoadingCore(LitElement) {
|
|
@@ -4118,79 +222,6 @@ let MonoTableLoading = class MonoTableLoading extends MonoTableLoadingCore(LitEl
|
|
|
4118
222
|
};
|
|
4119
223
|
MonoTableLoading = __decorate([customElement("mono-table-loading")], MonoTableLoading);
|
|
4120
224
|
|
|
4121
|
-
//#endregion
|
|
4122
|
-
//#region src/components/table/mono-table-summary-core.ts
|
|
4123
|
-
/**
|
|
4124
|
-
* `MonoTableSummaryCore` — render-mode-agnostic logic for `mono-table-summary`: an
|
|
4125
|
-
* aggregate footer cell. The aggregate (type + formatting) is configured centrally
|
|
4126
|
-
* in `monoDataGrid(data, { summary: [...] })`; this element only names which
|
|
4127
|
-
* summary to show (`field`, optionally `type`/`name`) and renders the controller's
|
|
4128
|
-
* formatted result, re-rendering whenever the controller notifies.
|
|
4129
|
-
*
|
|
4130
|
-
* A `field` with no central spec falls back to a `sum` (the controller
|
|
4131
|
-
* auto-registers a default), so a bare `<mono-table-summary field="Price">` also
|
|
4132
|
-
* works with zero config.
|
|
4133
|
-
*
|
|
4134
|
-
* No slots, no icons — light and shadow share the full `render()`.
|
|
4135
|
-
*
|
|
4136
|
-
* SSR-safe: `dataGrid` is undefined on the server, so it renders an empty shell.
|
|
4137
|
-
*/
|
|
4138
|
-
const MonoTableSummaryCore = (superClass) => {
|
|
4139
|
-
class MonoTableSummaryCoreClass extends MonoTableControllerCore(superClass) {
|
|
4140
|
-
constructor(..._args) {
|
|
4141
|
-
super(..._args);
|
|
4142
|
-
this._propsSlot = "summary";
|
|
4143
|
-
}
|
|
4144
|
-
/**
|
|
4145
|
-
* Ensure the controller knows about this cell's summary.
|
|
4146
|
-
*
|
|
4147
|
-
* EVERY element registers, even when a matching spec already exists — the
|
|
4148
|
-
* controller dedupes by key and refcounts, so a centrally-declared spec (with
|
|
4149
|
-
* its formatting) is still never overwritten by a bare element. Registering
|
|
4150
|
-
* only the first element is what used to break a second cell on the same
|
|
4151
|
-
* field: when the first unmounted it removed the shared spec, and nothing
|
|
4152
|
-
* re-runs registration on the survivor, so it rendered empty forever.
|
|
4153
|
-
*/
|
|
4154
|
-
_ensureSpec() {
|
|
4155
|
-
const grid = this.dataGrid;
|
|
4156
|
-
if (!grid || !this.field) return;
|
|
4157
|
-
const wanted = {
|
|
4158
|
-
field: this.field,
|
|
4159
|
-
type: this.type ?? "sum",
|
|
4160
|
-
name: this.name
|
|
4161
|
-
};
|
|
4162
|
-
const current = this._ownSpec;
|
|
4163
|
-
if (current && current.field === wanted.field && current.type === wanted.type && current.name === wanted.name) return;
|
|
4164
|
-
if (current) grid.unregisterSummary(current);
|
|
4165
|
-
grid.registerSummary(wanted);
|
|
4166
|
-
this._ownSpec = wanted;
|
|
4167
|
-
}
|
|
4168
|
-
willUpdate(changed) {
|
|
4169
|
-
super.willUpdate?.(changed);
|
|
4170
|
-
if (changed.has("dataGrid") || changed.has("field") || changed.has("type") || changed.has("name")) this._ensureSpec();
|
|
4171
|
-
}
|
|
4172
|
-
connectedCallback() {
|
|
4173
|
-
super.connectedCallback();
|
|
4174
|
-
this._ensureSpec();
|
|
4175
|
-
}
|
|
4176
|
-
disconnectedCallback() {
|
|
4177
|
-
if (this._ownSpec) {
|
|
4178
|
-
this.dataGrid?.unregisterSummary(this._ownSpec);
|
|
4179
|
-
this._ownSpec = void 0;
|
|
4180
|
-
}
|
|
4181
|
-
super.disconnectedCallback();
|
|
4182
|
-
}
|
|
4183
|
-
render() {
|
|
4184
|
-
const text = this.field && this.dataGrid ? this.dataGrid.summaryText(this.field, this.type) : "";
|
|
4185
|
-
return html`<span class="mono-table-summary" mono-table-summary>${text}</span>`;
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
__decorate([property({ type: String })], MonoTableSummaryCoreClass.prototype, "field", void 0);
|
|
4189
|
-
__decorate([property({ type: String })], MonoTableSummaryCoreClass.prototype, "type", void 0);
|
|
4190
|
-
__decorate([property({ type: String })], MonoTableSummaryCoreClass.prototype, "name", void 0);
|
|
4191
|
-
return MonoTableSummaryCoreClass;
|
|
4192
|
-
};
|
|
4193
|
-
|
|
4194
225
|
//#endregion
|
|
4195
226
|
//#region src/components/table/mono-table-summary.ts
|
|
4196
227
|
let MonoTableSummary = class MonoTableSummary extends MonoTableSummaryCore(LitElement) {
|
|
@@ -4200,257 +231,6 @@ let MonoTableSummary = class MonoTableSummary extends MonoTableSummaryCore(LitEl
|
|
|
4200
231
|
};
|
|
4201
232
|
MonoTableSummary = __decorate([customElement("mono-table-summary")], MonoTableSummary);
|
|
4202
233
|
|
|
4203
|
-
//#endregion
|
|
4204
|
-
//#region src/components/table/mono-table-detail-core.ts
|
|
4205
|
-
/** Total column span of a row, so the panel cell can stretch the full width. */
|
|
4206
|
-
function colSpanOf(row) {
|
|
4207
|
-
let total = 0;
|
|
4208
|
-
for (const cell of Array.from(row.cells)) total += cell.colSpan || 1;
|
|
4209
|
-
return Math.max(1, total);
|
|
4210
|
-
}
|
|
4211
|
-
/**
|
|
4212
|
-
* `MonoTableDetailCore` — render-mode-agnostic logic for `mono-table-detail`, the
|
|
4213
|
-
* expand/collapse chevron for a table row.
|
|
4214
|
-
*
|
|
4215
|
-
* **Shape.** The element itself is *only the toggle*: put it in a `<td>` of the
|
|
4216
|
-
* row you want expandable and keep authoring the `<tr>` yourself. Everything
|
|
4217
|
-
* slotted into it becomes the **panel** — a `<tr class="mono-table-detail-row" mono-detail-row>`
|
|
4218
|
-
* with one `<td colspan="…">` that the element inserts into the `<tbody>`
|
|
4219
|
-
* immediately after its own row while open, and pulls back out when closed. The
|
|
4220
|
-
* `colspan` is recomputed from the parent row's cells on every attach, so it
|
|
4221
|
-
* always spans the whole grid.
|
|
4222
|
-
*
|
|
4223
|
-
* ```html
|
|
4224
|
-
* <tr :data-row-key="row.Id">
|
|
4225
|
-
* <td>
|
|
4226
|
-
* <mono-table-detail :control-table.prop="table">
|
|
4227
|
-
* <p>Notes: {{ row.Note }}</p>
|
|
4228
|
-
* </mono-table-detail>
|
|
4229
|
-
* </td>
|
|
4230
|
-
* <td>{{ row.Name }}</td>
|
|
4231
|
-
* </tr>
|
|
4232
|
-
* ```
|
|
4233
|
-
*
|
|
4234
|
-
* **Why the panel row is built imperatively.** Lit renders through a `<template>`,
|
|
4235
|
-
* and a bare `<tr>` in a non-table parsing context is dropped by the HTML parser —
|
|
4236
|
-
* so the row could never come out of `render()`. It is created with
|
|
4237
|
-
* `document.createElement` instead, which also means the SAME code path serves the
|
|
4238
|
-
* light and the shadow build: in both, the consumer's children are captured off
|
|
4239
|
-
* the host and re-parented into the panel cell (a shadow `<slot>` could not
|
|
4240
|
-
* project content to a row *outside* the host).
|
|
4241
|
-
*
|
|
4242
|
-
* **Content is real DOM the consumer owns**, captured through
|
|
4243
|
-
* `composables/light-slots`, so Vue interpolation (`{{ row.Note }}`), `v-if`
|
|
4244
|
-
* anchors, a whole nested `<table class="mono-table" mono-table>` with its own
|
|
4245
|
-
* `controlMonoTable`, and further nested `<mono-table-detail>` elements all work.
|
|
4246
|
-
* While closed, the captured nodes stay parked inside the (detached) panel cell
|
|
4247
|
-
* rather than orphaned — a captured node with a `null` parent is what crashes
|
|
4248
|
-
* Vue's next patch.
|
|
4249
|
-
*
|
|
4250
|
-
* **Accordion by default.** Opening a row closes the row that was open, scoped to
|
|
4251
|
-
* the bound controller — so a nested grid runs its own accordion without
|
|
4252
|
-
* disturbing the outer one. **`stay-open` is the exemption**: such a row is
|
|
4253
|
-
* skipped by the accordion *and* by `table.detail().collapseAll()`. It is not a
|
|
4254
|
-
* lock — its own chevron still closes it. Mark every row `stay-open` to get
|
|
4255
|
-
* independent panels back.
|
|
4256
|
-
*
|
|
4257
|
-
* SSR-safe: every DOM touch is behind `isServer`, so the server renders just the
|
|
4258
|
-
* collapsed toggle.
|
|
4259
|
-
*/
|
|
4260
|
-
const MonoTableDetailCore = (superClass) => {
|
|
4261
|
-
class MonoTableDetailCoreClass extends MonoTableControllerCore(superClass) {
|
|
4262
|
-
constructor(...args) {
|
|
4263
|
-
super(...args);
|
|
4264
|
-
this._propsSlot = "detail";
|
|
4265
|
-
this.icon = "i-mdi-chevron-right";
|
|
4266
|
-
this.iconExpanded = "i-mdi-chevron-down";
|
|
4267
|
-
this.open = false;
|
|
4268
|
-
this.stayOpen = false;
|
|
4269
|
-
this.disabled = false;
|
|
4270
|
-
this._slotsCaptured = false;
|
|
4271
|
-
this._buckets = /* @__PURE__ */ new Map();
|
|
4272
|
-
defineHybridPropAliases(this, ["iconExpanded", "stayOpen"]);
|
|
4273
|
-
}
|
|
4274
|
-
connectedCallback() {
|
|
4275
|
-
super.connectedCallback();
|
|
4276
|
-
if (isServer) return;
|
|
4277
|
-
this._captureSlots();
|
|
4278
|
-
this._syncRegistration();
|
|
4279
|
-
if (this.isConnected) this.performUpdate();
|
|
4280
|
-
this._refreshPanel();
|
|
4281
|
-
}
|
|
4282
|
-
disconnectedCallback() {
|
|
4283
|
-
this._panelRow?.parentNode?.removeChild(this._panelRow);
|
|
4284
|
-
this._registeredOn?.unregisterDetail?.(this);
|
|
4285
|
-
this._registeredOn = void 0;
|
|
4286
|
-
super.disconnectedCallback();
|
|
4287
|
-
}
|
|
4288
|
-
willUpdate(changed) {
|
|
4289
|
-
super.willUpdate(changed);
|
|
4290
|
-
if (changed.has("dataGrid")) this._syncRegistration();
|
|
4291
|
-
}
|
|
4292
|
-
updated(changed) {
|
|
4293
|
-
super.updated?.(changed);
|
|
4294
|
-
this._refreshPanel();
|
|
4295
|
-
}
|
|
4296
|
-
/**
|
|
4297
|
-
* Bring the panel in line with the current state: build it if needed, make
|
|
4298
|
-
* sure the consumer's captured nodes are in it, and attach/detach the row.
|
|
4299
|
-
* Idempotent, so the extra call from `connectedCallback` costs nothing.
|
|
4300
|
-
*/
|
|
4301
|
-
_refreshPanel() {
|
|
4302
|
-
if (isServer) return;
|
|
4303
|
-
this._ensurePanel();
|
|
4304
|
-
placeLightSlots(this._panelCell, this._buckets);
|
|
4305
|
-
this._syncPanel();
|
|
4306
|
-
}
|
|
4307
|
-
/**
|
|
4308
|
-
* Shared defaults from `props: { detail }`, minus `open`.
|
|
4309
|
-
*
|
|
4310
|
-
* Every other slot lets the controller win outright, but `open` is per-element
|
|
4311
|
-
* state that the user toggles: re-applying it on each update cycle would undo
|
|
4312
|
-
* the click a microtask later and the chevron would look broken. Open/close
|
|
4313
|
-
* from code goes through `table.detail()` instead.
|
|
4314
|
-
*/
|
|
4315
|
-
_applyControllerProps() {
|
|
4316
|
-
const patch = this.dataGrid?.props?.().detail;
|
|
4317
|
-
if (!patch) return;
|
|
4318
|
-
const { open: _open, ...rest } = patch;
|
|
4319
|
-
applyProps(this, rest);
|
|
4320
|
-
}
|
|
4321
|
-
_captureSlots() {
|
|
4322
|
-
if (this._slotsCaptured) return;
|
|
4323
|
-
this._slotsCaptured = true;
|
|
4324
|
-
this._buckets = captureLightSlots(this, { names: [] });
|
|
4325
|
-
}
|
|
4326
|
-
_syncRegistration() {
|
|
4327
|
-
const grid = this.dataGrid;
|
|
4328
|
-
if (this._registeredOn === grid) return;
|
|
4329
|
-
this._registeredOn?.unregisterDetail?.(this);
|
|
4330
|
-
this._registeredOn = grid;
|
|
4331
|
-
grid?.registerDetail?.(this);
|
|
4332
|
-
}
|
|
4333
|
-
/** The panel row + cell, created once and reused across every toggle. */
|
|
4334
|
-
_ensurePanel() {
|
|
4335
|
-
if (this._panelRow) return;
|
|
4336
|
-
const row = document.createElement("tr");
|
|
4337
|
-
row.className = "mono-table-detail-row";
|
|
4338
|
-
row.setAttribute("mono-detail-row", "");
|
|
4339
|
-
row.setAttribute("data-mono-detail-row", "");
|
|
4340
|
-
const cell = document.createElement("td");
|
|
4341
|
-
cell.className = "mono-table-detail-cell";
|
|
4342
|
-
const panel = document.createElement("div");
|
|
4343
|
-
panel.className = "mono-table-detail-panel";
|
|
4344
|
-
panel.setAttribute("data-mono-slot", "default");
|
|
4345
|
-
cell.appendChild(panel);
|
|
4346
|
-
row.appendChild(cell);
|
|
4347
|
-
this._panelRow = row;
|
|
4348
|
-
this._panelCell = cell;
|
|
4349
|
-
}
|
|
4350
|
-
/** The row this toggle belongs to (`null` outside a table). */
|
|
4351
|
-
get _row() {
|
|
4352
|
-
return this.closest("tr");
|
|
4353
|
-
}
|
|
4354
|
-
/** Attach the panel row after our own row while open; detach it while closed. */
|
|
4355
|
-
_syncPanel() {
|
|
4356
|
-
const row = this._panelRow;
|
|
4357
|
-
if (!row) return;
|
|
4358
|
-
const parent = this._row;
|
|
4359
|
-
const container = parent?.parentNode;
|
|
4360
|
-
if (!this.open || !parent || !container) {
|
|
4361
|
-
row.parentNode?.removeChild(row);
|
|
4362
|
-
return;
|
|
4363
|
-
}
|
|
4364
|
-
const cell = this._panelCell;
|
|
4365
|
-
const span = colSpanOf(parent);
|
|
4366
|
-
if (cell.colSpan !== span) cell.colSpan = span;
|
|
4367
|
-
if (!this._isPlaced(parent, row)) container.insertBefore(row, this._anchorAfter(parent));
|
|
4368
|
-
}
|
|
4369
|
-
/** Already sitting in this row's own run of detail rows? */
|
|
4370
|
-
_isPlaced(parent, row) {
|
|
4371
|
-
if (row.parentNode !== parent.parentNode) return false;
|
|
4372
|
-
let el = parent.nextElementSibling;
|
|
4373
|
-
while (el) {
|
|
4374
|
-
if (el === row) return true;
|
|
4375
|
-
if (!el.hasAttribute("data-mono-detail-row")) return false;
|
|
4376
|
-
el = el.nextElementSibling;
|
|
4377
|
-
}
|
|
4378
|
-
return false;
|
|
4379
|
-
}
|
|
4380
|
-
/** Insert point: after the row, past any detail rows a sibling toggle opened. */
|
|
4381
|
-
_anchorAfter(parent) {
|
|
4382
|
-
let last = parent;
|
|
4383
|
-
let el = parent.nextElementSibling;
|
|
4384
|
-
while (el && el.hasAttribute("data-mono-detail-row")) {
|
|
4385
|
-
last = el;
|
|
4386
|
-
el = el.nextElementSibling;
|
|
4387
|
-
}
|
|
4388
|
-
return last.nextSibling;
|
|
4389
|
-
}
|
|
4390
|
-
/**
|
|
4391
|
-
* Open, close, or flip the panel. Emits `toggle` (historically `mno-click`,
|
|
4392
|
-
* kept) and then `open` / `close`, exactly like a tap on the chevron.
|
|
4393
|
-
*/
|
|
4394
|
-
toggle(force, sourceEvent) {
|
|
4395
|
-
if (this.disabled) return;
|
|
4396
|
-
const next = force === void 0 ? !this.open : force;
|
|
4397
|
-
if (next === this.open) return;
|
|
4398
|
-
this.open = next;
|
|
4399
|
-
if (next) this.dataGrid?.detail?.().collapseOthers?.(this);
|
|
4400
|
-
const detail = {
|
|
4401
|
-
open: next,
|
|
4402
|
-
rowKey: this._row?.getAttribute("data-row-key") ?? null,
|
|
4403
|
-
sourceEvent
|
|
4404
|
-
};
|
|
4405
|
-
dispatchMonoEvent(this, "click", detail, { alias: "toggle" });
|
|
4406
|
-
dispatchMonoEvent(this, next ? "open" : "close", detail);
|
|
4407
|
-
}
|
|
4408
|
-
_handleClick(event) {
|
|
4409
|
-
event.stopPropagation();
|
|
4410
|
-
this.toggle(void 0, event);
|
|
4411
|
-
}
|
|
4412
|
-
/**
|
|
4413
|
-
* The chevron. The light build overrides nothing (global icon classes); the
|
|
4414
|
-
* shadow build swaps in inline SVG, which page-level utility CSS can't reach.
|
|
4415
|
-
*/
|
|
4416
|
-
_renderIcon() {
|
|
4417
|
-
const cls = this.open ? this.iconExpanded : this.icon;
|
|
4418
|
-
return html`<span class="mono-icon ${cls}" aria-hidden="true"></span>`;
|
|
4419
|
-
}
|
|
4420
|
-
render() {
|
|
4421
|
-
return html`
|
|
4422
|
-
<button
|
|
4423
|
-
type="button"
|
|
4424
|
-
class="mono-table-detail-toggle" mono-detail-toggle
|
|
4425
|
-
part="toggle"
|
|
4426
|
-
?disabled=${this.disabled}
|
|
4427
|
-
aria-expanded=${this.open ? "true" : "false"}
|
|
4428
|
-
aria-label=${this.label ?? "Toggle details"}
|
|
4429
|
-
@click=${this._handleClick}
|
|
4430
|
-
>
|
|
4431
|
-
${this._renderIcon()}
|
|
4432
|
-
</button>
|
|
4433
|
-
`;
|
|
4434
|
-
}
|
|
4435
|
-
}
|
|
4436
|
-
__decorate([property({ type: String })], MonoTableDetailCoreClass.prototype, "icon", void 0);
|
|
4437
|
-
__decorate([property({
|
|
4438
|
-
type: String,
|
|
4439
|
-
attribute: "icon-expanded"
|
|
4440
|
-
})], MonoTableDetailCoreClass.prototype, "iconExpanded", void 0);
|
|
4441
|
-
__decorate([property({
|
|
4442
|
-
converter: booleanStringConverter,
|
|
4443
|
-
reflect: true
|
|
4444
|
-
})], MonoTableDetailCoreClass.prototype, "open", void 0);
|
|
4445
|
-
__decorate([property({
|
|
4446
|
-
converter: booleanStringConverter,
|
|
4447
|
-
attribute: "stay-open"
|
|
4448
|
-
})], MonoTableDetailCoreClass.prototype, "stayOpen", void 0);
|
|
4449
|
-
__decorate([property({ converter: booleanStringConverter })], MonoTableDetailCoreClass.prototype, "disabled", void 0);
|
|
4450
|
-
__decorate([property({ type: String })], MonoTableDetailCoreClass.prototype, "label", void 0);
|
|
4451
|
-
return MonoTableDetailCoreClass;
|
|
4452
|
-
};
|
|
4453
|
-
|
|
4454
234
|
//#endregion
|
|
4455
235
|
//#region src/components/table/mono-table-detail.ts
|
|
4456
236
|
let MonoTableDetail = class MonoTableDetail extends MonoTableDetailCore(LitElement) {
|
|
@@ -4460,279 +240,6 @@ let MonoTableDetail = class MonoTableDetail extends MonoTableDetailCore(LitEleme
|
|
|
4460
240
|
};
|
|
4461
241
|
MonoTableDetail = __decorate([customElement("mono-table-detail")], MonoTableDetail);
|
|
4462
242
|
|
|
4463
|
-
//#endregion
|
|
4464
|
-
//#region src/components/table/mono-table-checkbox-core.ts
|
|
4465
|
-
/**
|
|
4466
|
-
* `MonoTableCheckboxCore` — render-mode-agnostic logic for `mono-table-checkbox`,
|
|
4467
|
-
* a checkbox that knows about the grid.
|
|
4468
|
-
*
|
|
4469
|
-
* Two jobs, chosen with `type`:
|
|
4470
|
-
* - **`type="single"`** (default) — one row's checkbox. Give it the row with
|
|
4471
|
-
* `:item.prop="row"`; it reads and writes `table.check()`.
|
|
4472
|
-
* - **`type="all"`** — the select-all. In `mode="all"` (default) checking it
|
|
4473
|
-
* drains the SOURCE in `chunk`-sized requests and selects every row the active
|
|
4474
|
-
* search/filter matches — including rows never fetched for display, which is the
|
|
4475
|
-
* point of the component. `mode="per-page"` selects the loaded page with no
|
|
4476
|
-
* request at all. **Every checkbox on the grid is disabled while a drain runs**
|
|
4477
|
-
* (the selection is still filling in), but only the select-all shows the spinner.
|
|
4478
|
-
*
|
|
4479
|
-
* ```html
|
|
4480
|
-
* <th><mono-table-checkbox type="all" :control-table.prop="table" key-value="Id" /></th>
|
|
4481
|
-
* <td><mono-table-checkbox :control-table.prop="table" :item.prop="row" /></td>
|
|
4482
|
-
* ```
|
|
4483
|
-
*
|
|
4484
|
-
* The `<th>` one canNOT render the `<td>` ones — the library never renders your
|
|
4485
|
-
* `<tbody>` — so both are declared; the row one needs only `:item.prop`.
|
|
4486
|
-
*
|
|
4487
|
-
* **It renders `mono-checkbox`'s markup and classes rather than embedding the
|
|
4488
|
-
* element**, so `size` / `color` / `disabled` / `label` behave identically and the
|
|
4489
|
-
* whole size × color matrix comes from `checkbox.css`. Same trade `mono-table-search`
|
|
4490
|
-
* makes with `mono-input` — and embedding a light `<mono-*>` inside another light
|
|
4491
|
-
* component would steal the parent's Lit-rendered children.
|
|
4492
|
-
*
|
|
4493
|
-
* SSR-safe: the controller is undefined on the server, so it renders an unchecked
|
|
4494
|
-
* shell and touches no DOM.
|
|
4495
|
-
*/
|
|
4496
|
-
const MonoTableCheckboxCore = (superClass) => {
|
|
4497
|
-
class MonoTableCheckboxCoreClass extends MonoTableControllerCore(superClass) {
|
|
4498
|
-
constructor(...args) {
|
|
4499
|
-
super(...args);
|
|
4500
|
-
this._propsSlot = "checkbox";
|
|
4501
|
-
this.type = "single";
|
|
4502
|
-
this.mode = "all";
|
|
4503
|
-
this.chunk = 100;
|
|
4504
|
-
this.size = "md";
|
|
4505
|
-
this.color = "primary";
|
|
4506
|
-
this.disabled = false;
|
|
4507
|
-
this.label = "";
|
|
4508
|
-
this.sublabel = "";
|
|
4509
|
-
this.cssClass = {};
|
|
4510
|
-
this.cssClassName = "";
|
|
4511
|
-
defineHybridPropAliases(this, [
|
|
4512
|
-
"keyValue",
|
|
4513
|
-
"ariaLabelText",
|
|
4514
|
-
"cssClass"
|
|
4515
|
-
]);
|
|
4516
|
-
defineHybridPropAlias(this, "description", "sublabel");
|
|
4517
|
-
}
|
|
4518
|
-
static get observedAttributes() {
|
|
4519
|
-
return [...super.observedAttributes ?? [], "description"];
|
|
4520
|
-
}
|
|
4521
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
4522
|
-
super.attributeChangedCallback(name, oldValue, newValue);
|
|
4523
|
-
if (name === "description" && oldValue !== newValue) this.sublabel = newValue ?? "";
|
|
4524
|
-
}
|
|
4525
|
-
/** `keyValue` as a list — an attribute may hold JSON or a comma list. */
|
|
4526
|
-
get _keys() {
|
|
4527
|
-
const raw = this.keyValue;
|
|
4528
|
-
if (raw == null || raw === "") return void 0;
|
|
4529
|
-
if (Array.isArray(raw)) return raw;
|
|
4530
|
-
const text = String(raw).trim();
|
|
4531
|
-
if (text.startsWith("[")) try {
|
|
4532
|
-
const parsed = JSON.parse(text);
|
|
4533
|
-
if (Array.isArray(parsed)) return parsed;
|
|
4534
|
-
} catch {}
|
|
4535
|
-
return text.includes(",") ? text.split(",").map((s) => s.trim()).filter(Boolean) : [text];
|
|
4536
|
-
}
|
|
4537
|
-
/**
|
|
4538
|
-
* Push this element's config onto the shared selection state, so declaring
|
|
4539
|
-
* `key-value` once (on the select-all, or in `props.checkbox`) covers every
|
|
4540
|
-
* row checkbox. Undefined values are skipped by `configure`.
|
|
4541
|
-
*/
|
|
4542
|
-
_syncConfig() {
|
|
4543
|
-
this.dataGrid?.check?.().configure({
|
|
4544
|
-
keyValue: this._keys,
|
|
4545
|
-
mode: this.mode,
|
|
4546
|
-
chunk: this.chunk
|
|
4547
|
-
});
|
|
4548
|
-
}
|
|
4549
|
-
update(changed) {
|
|
4550
|
-
this._syncConfig();
|
|
4551
|
-
super.update(changed);
|
|
4552
|
-
}
|
|
4553
|
-
updated(changed) {
|
|
4554
|
-
super.updated?.(changed);
|
|
4555
|
-
if (isServer || this.type !== "single") return;
|
|
4556
|
-
const row = this.closest("tr");
|
|
4557
|
-
row?.classList.toggle("mono-table-row-selected", this._checked);
|
|
4558
|
-
if (this._checked) row?.setAttribute("mono-selected", "");
|
|
4559
|
-
else row?.removeAttribute("mono-selected");
|
|
4560
|
-
}
|
|
4561
|
-
/**
|
|
4562
|
-
* All this box draws from the controller: ticked, mixed (select-all), and the drain's
|
|
4563
|
-
* `pending`. A notify that moves none of them — another row's tick, a load flag, a summary —
|
|
4564
|
-
* no longer re-renders every row's checkbox (see the base's `_stateKey`).
|
|
4565
|
-
*/
|
|
4566
|
-
_stateKey() {
|
|
4567
|
-
return this.type === "all" ? [
|
|
4568
|
-
this.type,
|
|
4569
|
-
this._checked,
|
|
4570
|
-
this._indeterminate,
|
|
4571
|
-
this._pending
|
|
4572
|
-
] : [
|
|
4573
|
-
this.type,
|
|
4574
|
-
this._checked,
|
|
4575
|
-
this._pending,
|
|
4576
|
-
this.item
|
|
4577
|
-
];
|
|
4578
|
-
}
|
|
4579
|
-
/** Whether this box is ticked right now. */
|
|
4580
|
-
get _checked() {
|
|
4581
|
-
const check = this.dataGrid?.check?.();
|
|
4582
|
-
if (!check) return false;
|
|
4583
|
-
if (this.type === "all") return check.allChecked;
|
|
4584
|
-
return this.item == null ? false : check.isChecked(this.item);
|
|
4585
|
-
}
|
|
4586
|
-
/** The select-all shows mixed while only some rows are ticked. */
|
|
4587
|
-
get _indeterminate() {
|
|
4588
|
-
if (this.type !== "all") return false;
|
|
4589
|
-
return this.dataGrid?.check?.().someChecked ?? false;
|
|
4590
|
-
}
|
|
4591
|
-
/**
|
|
4592
|
-
* A `mode="all"` drain is running.
|
|
4593
|
-
*
|
|
4594
|
-
* **Every** checkbox bound to the grid goes inert while it lands, not just
|
|
4595
|
-
* the select-all that started it: the selection is still filling in, so a row
|
|
4596
|
-
* ticked mid-drain would be overwritten (or fight) the rows arriving behind
|
|
4597
|
-
* it. They all re-render because each one subscribes to the controller, and
|
|
4598
|
-
* the drain notifies on entry and exit.
|
|
4599
|
-
*/
|
|
4600
|
-
get _pending() {
|
|
4601
|
-
return this.dataGrid?.check?.().pending ?? false;
|
|
4602
|
-
}
|
|
4603
|
-
/**
|
|
4604
|
-
* Whether THIS box shows the spinner. Only the select-all does: it is the one
|
|
4605
|
-
* the user acted on, and eight rows spinning in sympathy reads as eight
|
|
4606
|
-
* separate operations rather than one. The rows still go disabled — they are
|
|
4607
|
-
* just quietly inert.
|
|
4608
|
-
*/
|
|
4609
|
-
get _loading() {
|
|
4610
|
-
return this.type === "all" && this._pending;
|
|
4611
|
-
}
|
|
4612
|
-
get _isDisabled() {
|
|
4613
|
-
return this.disabled || this._pending;
|
|
4614
|
-
}
|
|
4615
|
-
_handleChange(event) {
|
|
4616
|
-
const input = event.target;
|
|
4617
|
-
const checked = input.checked;
|
|
4618
|
-
const check = this.dataGrid?.check?.();
|
|
4619
|
-
if (!check) {
|
|
4620
|
-
input.checked = false;
|
|
4621
|
-
return;
|
|
4622
|
-
}
|
|
4623
|
-
if (this.type === "all") {
|
|
4624
|
-
if (!checked) check.clear();
|
|
4625
|
-
else if (this.mode === "per-page") check.selectPage();
|
|
4626
|
-
else check.selectAll();
|
|
4627
|
-
} else if (this.item != null) check.toggle(this.item, checked);
|
|
4628
|
-
const actual = check.pending ? checked : this._checked;
|
|
4629
|
-
if (input.checked !== actual) input.checked = actual;
|
|
4630
|
-
dispatchMonoEvent(this, "change", {
|
|
4631
|
-
checked: actual,
|
|
4632
|
-
item: this.type === "all" ? null : this.item ?? null,
|
|
4633
|
-
count: check.count()
|
|
4634
|
-
}, { sourceEvent: event });
|
|
4635
|
-
this.requestUpdate();
|
|
4636
|
-
}
|
|
4637
|
-
/** `mono-checkbox`'s class recipe, so one stylesheet drives both elements. */
|
|
4638
|
-
_cls(base, key) {
|
|
4639
|
-
const extra = this.cssClass?.[key];
|
|
4640
|
-
return extra ? `${base} ${extra}` : base;
|
|
4641
|
-
}
|
|
4642
|
-
get _wrapperClasses() {
|
|
4643
|
-
return [
|
|
4644
|
-
"mono-checkbox",
|
|
4645
|
-
"mono-table-checkbox",
|
|
4646
|
-
this.color,
|
|
4647
|
-
this._loading ? "is-loading" : "",
|
|
4648
|
-
this._isDisabled ? "disabled" : "",
|
|
4649
|
-
this._checked ? "mono-checkbox-checked" : "",
|
|
4650
|
-
this._indeterminate ? "mono-checkbox-indeterminate" : "",
|
|
4651
|
-
this.cssClassName,
|
|
4652
|
-
this.cssClass?.root
|
|
4653
|
-
].filter(Boolean).join(" ");
|
|
4654
|
-
}
|
|
4655
|
-
_renderLabelBlock() {
|
|
4656
|
-
if (!this.label && !this.sublabel) return nothing;
|
|
4657
|
-
return html`
|
|
4658
|
-
<span class=${this._cls("mono-checkbox-label", "label")} mono-label>
|
|
4659
|
-
${this.label ? html`<span class=${this._cls("mono-checkbox-label-text", "labelText")} mono-label-text>${this.label}</span>` : nothing}
|
|
4660
|
-
${this.sublabel ? html`<span class=${this._cls("mono-checkbox-description", "description")} mono-description>${this.sublabel}</span>` : nothing}
|
|
4661
|
-
</span>
|
|
4662
|
-
`;
|
|
4663
|
-
}
|
|
4664
|
-
/**
|
|
4665
|
-
* The mid-drain spinner. Light uses the global icon utility class; the shadow
|
|
4666
|
-
* build overrides this with inline SVG, which page-level CSS can't reach.
|
|
4667
|
-
*/
|
|
4668
|
-
_renderLoadingIcon() {
|
|
4669
|
-
return html`<span class="mono-icon i-mdi-loading mono-table-checkbox-spinner" aria-hidden="true"></span>`;
|
|
4670
|
-
}
|
|
4671
|
-
render() {
|
|
4672
|
-
const checked = this._checked;
|
|
4673
|
-
const indeterminate = this._indeterminate;
|
|
4674
|
-
const loading = this._loading;
|
|
4675
|
-
const boxClasses = [
|
|
4676
|
-
this._cls("mono-checkbox-box", "box"),
|
|
4677
|
-
this.size,
|
|
4678
|
-
loading ? "has-custom-icon has-custom-indeterminate-icon" : ""
|
|
4679
|
-
].filter(Boolean).join(" ");
|
|
4680
|
-
const ariaLabel = this.ariaLabelText || this.label || (this.type === "all" ? "Select all rows" : "Select row");
|
|
4681
|
-
return html`
|
|
4682
|
-
<label
|
|
4683
|
-
class=${this._wrapperClasses}
|
|
4684
|
-
mono-checkbox
|
|
4685
|
-
mono-size=${this.size === "md" ? nothing : this.size}
|
|
4686
|
-
mono-color=${this.color === "primary" ? nothing : this.color}
|
|
4687
|
-
?mono-checked=${checked}
|
|
4688
|
-
?mono-indeterminate=${indeterminate}
|
|
4689
|
-
?mono-disabled=${this._isDisabled}
|
|
4690
|
-
?mono-loading=${loading}
|
|
4691
|
-
>
|
|
4692
|
-
<input
|
|
4693
|
-
class=${this._cls("mono-checkbox-input", "input")}
|
|
4694
|
-
mono-input
|
|
4695
|
-
type="checkbox"
|
|
4696
|
-
.checked=${checked}
|
|
4697
|
-
.indeterminate=${indeterminate}
|
|
4698
|
-
?disabled=${this._isDisabled}
|
|
4699
|
-
aria-checked=${indeterminate ? "mixed" : String(checked)}
|
|
4700
|
-
aria-label=${ifDefined(ariaLabel)}
|
|
4701
|
-
aria-busy=${this._pending ? "true" : "false"}
|
|
4702
|
-
@change=${this._handleChange}
|
|
4703
|
-
/>
|
|
4704
|
-
|
|
4705
|
-
<span
|
|
4706
|
-
class=${boxClasses}
|
|
4707
|
-
mono-box
|
|
4708
|
-
?mono-custom-icon=${loading}
|
|
4709
|
-
?mono-custom-indeterminate-icon=${loading}
|
|
4710
|
-
aria-hidden="true"
|
|
4711
|
-
>
|
|
4712
|
-
${loading ? this._renderLoadingIcon() : nothing}
|
|
4713
|
-
</span>
|
|
4714
|
-
|
|
4715
|
-
${this._renderLabelBlock()}
|
|
4716
|
-
</label>
|
|
4717
|
-
`;
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "type", void 0);
|
|
4721
|
-
__decorate([property({ attribute: false })], MonoTableCheckboxCoreClass.prototype, "item", void 0);
|
|
4722
|
-
__decorate([property({ attribute: "key-value" })], MonoTableCheckboxCoreClass.prototype, "keyValue", void 0);
|
|
4723
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "mode", void 0);
|
|
4724
|
-
__decorate([property({ type: Number })], MonoTableCheckboxCoreClass.prototype, "chunk", void 0);
|
|
4725
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "size", void 0);
|
|
4726
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "color", void 0);
|
|
4727
|
-
__decorate([property({ converter: booleanStringConverter })], MonoTableCheckboxCoreClass.prototype, "disabled", void 0);
|
|
4728
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "label", void 0);
|
|
4729
|
-
__decorate([property({ type: String })], MonoTableCheckboxCoreClass.prototype, "sublabel", void 0);
|
|
4730
|
-
__decorate([property({ attribute: "aria-label-text" })], MonoTableCheckboxCoreClass.prototype, "ariaLabelText", void 0);
|
|
4731
|
-
__decorate([property({ attribute: false })], MonoTableCheckboxCoreClass.prototype, "cssClass", void 0);
|
|
4732
|
-
__decorate([property({ attribute: "css-class" })], MonoTableCheckboxCoreClass.prototype, "cssClassName", void 0);
|
|
4733
|
-
return MonoTableCheckboxCoreClass;
|
|
4734
|
-
};
|
|
4735
|
-
|
|
4736
243
|
//#endregion
|
|
4737
244
|
//#region src/components/table/mono-table-checkbox.ts
|
|
4738
245
|
let MonoTableCheckbox = class MonoTableCheckbox extends MonoTableCheckboxCore(LitElement) {
|