@mono-lit/helper 0.0.9 → 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.
Files changed (161) hide show
  1. package/dist/accordion-core-DaNyPWRr.js +277 -0
  2. package/dist/alert-core-DWtHbX_-.js +134 -0
  3. package/dist/breadcrumb-list-core-Dh68T8Cf.js +747 -0
  4. package/dist/{mono-button-CLkHg3WW.js → button-core-DerXg84Q.js} +3 -276
  5. package/dist/button-dropdown-core-DKYEOyjy.js +654 -0
  6. package/dist/button-utils-D5-66M02.js +63 -0
  7. package/dist/card-utils-l80qAmrN.js +312 -0
  8. package/dist/chart-core-DV7hD3ZY.js +801 -0
  9. package/dist/checkbox-utils-DKdrhAzF.js +337 -0
  10. package/dist/chip-utils-DpRrd7Zj.js +403 -0
  11. package/dist/{data-source-controller-C6C4ej_M.js → data-source-controller-DS_JLrmS.js} +1 -1
  12. package/dist/{data-source-options-fxT5QIX4.js → data-source-options-DJUNU26C.js} +6 -6
  13. package/dist/{data-source-read-BL266IW0.js → data-source-read-uKJpRAzx.js} +2 -2
  14. package/dist/date-core-CyuSyvvG.js +734 -0
  15. package/dist/drawer-core-DbFWvNln.js +759 -0
  16. package/dist/dropdown-core-BCH1pK14.js +511 -0
  17. package/dist/dropdown-table-core-SfHUJVFq.js +1342 -0
  18. package/dist/export.js +1 -1
  19. package/dist/{field-path-BabiMrrA.js → field-path-DK2VBWHl.js} +1 -1
  20. package/dist/field-tooltip.css_raw-DVblekCl.js +5 -0
  21. package/dist/{field-validation.css_raw-Cx6SGrfP.js → field-validation.css_raw-BU7C6173.js} +1 -170
  22. package/dist/file-upload-core-Cz_dBQMx.js +624 -0
  23. package/dist/filter-builder-core-DyWjbBP7.js +381 -0
  24. package/dist/{filter-builder-B6Y2mmbM.js → filter-builder-vbqIuPhX.js} +1 -1
  25. package/dist/{filter-eval-ChDSaAUH.js → filter-eval-CqeBnMYh.js} +1 -1
  26. package/dist/{filter-odata-Bi27XUpQ.js → filter-odata-GsczYKNC.js} +1 -1
  27. package/dist/{hydration-flush-BaE9gN3C.js → hydration-flush-Volt9X7Q.js} +2 -1071
  28. package/dist/index.js +11 -11
  29. package/dist/{index.node-DTqDBca7.js → index.node-Csy9zrr1.js} +4 -4
  30. package/dist/index.node.js +11 -11
  31. package/dist/input-utils-BglY-k0H.js +1145 -0
  32. package/dist/menu-core-DkAYuCZ2.js +831 -0
  33. package/dist/modal-core-CSBSPN_m.js +844 -0
  34. package/dist/mono-button-DVvZ03P-.js +280 -0
  35. package/dist/{mono-button.shadow-DqZMpcqY.js → mono-button.shadow-BANxMGln.js} +6 -594
  36. package/dist/{mono-data-chart-NKPdAgVq.js → mono-data-chart-ChF4J9RT.js} +6 -6
  37. package/dist/{mono-data-dropdown-B2iRpJuI.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-llJCX9MC.js → mono-data-grid-DJkp96N-.js} +144 -46
  39. package/dist/{mono-event-BPku9HpS.js → mono-event-CAZf0F_k.js} +14 -9
  40. package/dist/{mono-form-controller-BrA10erm.js → mono-form-controller-DxZLvyg6.js} +2 -1
  41. package/dist/mono-table-checkbox-core-BKOMx8R5.js +5288 -0
  42. package/dist/nav-core-B5qU2dzn.js +225 -0
  43. package/dist/{normalize-error-D93O4q8g.js → normalize-error-BEcd1tmS.js} +2 -2
  44. package/dist/otp-core-DXg3vFLh.js +758 -0
  45. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  46. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  47. package/dist/select-utils-BgArYHk8.js +2140 -0
  48. package/dist/sidebar-core-CD6txDo3.js +583 -0
  49. package/dist/slider-core-vcWr321-.js +1014 -0
  50. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  51. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  52. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  55. package/dist/src/components/form/form-types.d.ts +9 -3
  56. package/dist/src/components/input/input-core.d.ts +1 -1
  57. package/dist/src/components/input/input-types.d.ts +1 -1
  58. package/dist/src/components/otp/otp-core.d.ts +1 -1
  59. package/dist/src/components/select/select-core.d.ts +1 -1
  60. package/dist/src/components/select/select-types.d.ts +1 -1
  61. package/dist/src/components/slider/slider-core.d.ts +1 -1
  62. package/dist/src/components/slider/slider-types.d.ts +1 -1
  63. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  64. package/dist/src/components/table/index.d.ts +5 -4
  65. package/dist/src/components/table/index.shadow.d.ts +5 -4
  66. package/dist/src/components/table/mono-data-grid.d.ts +22 -3
  67. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  68. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  69. package/dist/src/components/table/table-types.d.ts +74 -4
  70. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  71. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  72. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  73. package/dist/src/composables/mono-event.d.ts +7 -1
  74. package/dist/src/entries/index.d.ts +8 -6
  75. package/dist/src/entries/index.node.d.ts +8 -6
  76. package/dist/src/search/index.d.ts +1 -1
  77. package/dist/src/utils/group-count.d.ts +36 -0
  78. package/dist/switch-utils-CTZ4id2V.js +338 -0
  79. package/dist/tabs-core-r5-B8k_N.js +317 -0
  80. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  81. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  82. package/dist/ui/accordion.js +2 -274
  83. package/dist/ui/alert.js +2 -131
  84. package/dist/ui/breadcrumb.js +4 -745
  85. package/dist/ui/button-dropdown.js +4 -652
  86. package/dist/ui/button.js +2 -63
  87. package/dist/ui/card.js +2 -310
  88. package/dist/ui/chart.js +4 -800
  89. package/dist/ui/checkbox.js +4 -335
  90. package/dist/ui/chip.js +4 -402
  91. package/dist/ui/date.js +3 -732
  92. package/dist/ui/drawer.js +3 -757
  93. package/dist/ui/dropdown-table.js +5 -1341
  94. package/dist/ui/dropdown.js +4 -509
  95. package/dist/ui/file-upload.js +3 -622
  96. package/dist/ui/filter.js +5 -380
  97. package/dist/ui/form.js +1 -1
  98. package/dist/ui/index.css +118 -1
  99. package/dist/ui/input.js +3 -1141
  100. package/dist/ui/menu.js +4 -828
  101. package/dist/ui/modal.js +4 -843
  102. package/dist/ui/nav.js +4 -223
  103. package/dist/ui/otp.js +4 -756
  104. package/dist/ui/radio.js +4 -317
  105. package/dist/ui/rich-text-editor.js +4 -1278
  106. package/dist/ui/select.js +5 -2138
  107. package/dist/ui/shadow/accordion.js +4 -273
  108. package/dist/ui/shadow/alert.js +4 -130
  109. package/dist/ui/shadow/breadcrumb.js +5 -744
  110. package/dist/ui/shadow/button-dropdown.js +6 -652
  111. package/dist/ui/shadow/button.js +3 -63
  112. package/dist/ui/shadow/card.js +4 -309
  113. package/dist/ui/shadow/chart.js +6 -1576
  114. package/dist/ui/shadow/checkbox.js +4 -334
  115. package/dist/ui/shadow/chip.js +4 -396
  116. package/dist/ui/shadow/date.js +7 -731
  117. package/dist/ui/shadow/drawer.js +4 -756
  118. package/dist/ui/shadow/dropdown-table.js +8 -1762
  119. package/dist/ui/shadow/dropdown.js +4 -508
  120. package/dist/ui/shadow/file-upload.js +4 -620
  121. package/dist/ui/shadow/filter.js +7 -379
  122. package/dist/ui/shadow/input.js +6 -1140
  123. package/dist/ui/shadow/menu.js +5 -828
  124. package/dist/ui/shadow/modal.js +6 -1063
  125. package/dist/ui/shadow/nav.js +5 -223
  126. package/dist/ui/shadow/otp.js +6 -754
  127. package/dist/ui/shadow/radio.js +4 -316
  128. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  129. package/dist/ui/shadow/select.js +6 -2136
  130. package/dist/ui/shadow/sidebar.js +5 -581
  131. package/dist/ui/shadow/slider.js +5 -1010
  132. package/dist/ui/shadow/switch.js +4 -335
  133. package/dist/ui/shadow/table.js +10 -4501
  134. package/dist/ui/shadow/tabs.js +6 -315
  135. package/dist/ui/shadow/tag-input.js +7 -3057
  136. package/dist/ui/shadow/textarea.js +6 -817
  137. package/dist/ui/sidebar.js +3 -581
  138. package/dist/ui/slider.js +4 -1012
  139. package/dist/ui/switch.js +4 -336
  140. package/dist/ui/table.js +9 -4502
  141. package/dist/ui/tabs.js +4 -315
  142. package/dist/ui/tag-input.js +5 -3058
  143. package/dist/ui/textarea.js +4 -819
  144. package/dist/vue.d.ts +3 -2
  145. package/package.json +3 -3
  146. package/dist/chip-strip-bNfLn52Z.js +0 -215
  147. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  148. package/dist/data-source-options-BM4r4PRw.js +0 -558
  149. package/dist/data-source-read-HyBCRg1t.js +0 -242
  150. package/dist/excel-BXzr3uI1.js +0 -784
  151. package/dist/export-ixRdCS2y.js +0 -2388
  152. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  153. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  154. package/dist/filter-builder-C0T3pMx2.js +0 -208
  155. package/dist/form-control-core-BbiACMtL.js +0 -226
  156. package/dist/icon-ClxnYhBG.js +0 -19
  157. package/dist/import-Dc4eXC02.js +0 -612
  158. package/dist/light-slots-_no0r2Z-.js +0 -246
  159. package/dist/markdown-uGlE_OfJ.js +0 -20191
  160. package/dist/mono-data-grid-e9ZVBlNN.js +0 -4240
  161. 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-BabiMrrA.js";
2
- import { a as buildGroups, c as isGroupNode, o as collectGroupPaths, s as flattenLeaves } from "../data-source-read-BL266IW0.js";
3
- import { A as resolveSearchEntries, D as isWildcardPattern, O as normalizeSearchExpr, c as resolveErrorMessages, h as mergeSearchFields, 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-D93O4q8g.js";
4
- import { h as monoArraySource } from "../data-source-options-fxT5QIX4.js";
5
- import { a as seedFromRanges, i as minimize, n as buildDateTree, o as setAllChecked, r as checkState, s as setChecked, t as monoDataGrid } from "../mono-data-grid-llJCX9MC.js";
6
- import { _ as monoHostChildren, b as monoPhantomOptions, d as buildSizeStyle, h as monoApplyPhantomOptions, l as ColumnAutoSize, s as startMonoSizeAttributes, u as cssLengthToPx, v as monoPendingActive, y as monoPendingGrace } from "../mono-ui-kvLVIf1V.js";
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 { a as defineHybridPropAliases, i as defineHybridPropAlias, o as numberStringConverter, r as booleanStringConverter, t as __decorate } from "../decorate-DbYFpi8R.js";
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 PopupPortalController } from "../popup-portal-CINuVjNY.js";
14
- import { n as cssPart } from "../css-class-B6Tc_MAx.js";
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) {