@mono-lit/helper 0.0.9 → 0.1.0

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 (164) 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/nuxt.js +1 -1
  45. package/dist/otp-core-DXg3vFLh.js +758 -0
  46. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  47. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  48. package/dist/select-utils-BgArYHk8.js +2140 -0
  49. package/dist/sidebar-core-CD6txDo3.js +583 -0
  50. package/dist/slider-core-vcWr321-.js +1014 -0
  51. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  52. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  55. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  56. package/dist/src/components/form/form-types.d.ts +9 -3
  57. package/dist/src/components/input/input-core.d.ts +1 -1
  58. package/dist/src/components/input/input-types.d.ts +1 -1
  59. package/dist/src/components/otp/otp-core.d.ts +1 -1
  60. package/dist/src/components/select/select-core.d.ts +1 -1
  61. package/dist/src/components/select/select-types.d.ts +1 -1
  62. package/dist/src/components/slider/slider-core.d.ts +1 -1
  63. package/dist/src/components/slider/slider-types.d.ts +1 -1
  64. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  65. package/dist/src/components/table/index.d.ts +5 -4
  66. package/dist/src/components/table/index.shadow.d.ts +5 -4
  67. package/dist/src/components/table/mono-data-grid.d.ts +22 -3
  68. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  69. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  70. package/dist/src/components/table/table-types.d.ts +74 -4
  71. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  72. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  73. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  74. package/dist/src/composables/mono-event.d.ts +7 -1
  75. package/dist/src/entries/index.d.ts +8 -6
  76. package/dist/src/entries/index.node.d.ts +8 -6
  77. package/dist/src/search/index.d.ts +1 -1
  78. package/dist/src/utils/group-count.d.ts +36 -0
  79. package/dist/switch-utils-CTZ4id2V.js +338 -0
  80. package/dist/tabs-core-r5-B8k_N.js +317 -0
  81. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  82. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  83. package/dist/ui/accordion.js +2 -274
  84. package/dist/ui/alert.js +2 -131
  85. package/dist/ui/breadcrumb.js +4 -745
  86. package/dist/ui/button-dropdown.js +4 -652
  87. package/dist/ui/button.js +2 -63
  88. package/dist/ui/card.js +2 -310
  89. package/dist/ui/chart.js +4 -800
  90. package/dist/ui/checkbox.js +4 -335
  91. package/dist/ui/chip.js +4 -402
  92. package/dist/ui/date.js +3 -732
  93. package/dist/ui/drawer.js +3 -757
  94. package/dist/ui/dropdown-table.js +5 -1341
  95. package/dist/ui/dropdown.js +4 -509
  96. package/dist/ui/file-upload.js +3 -622
  97. package/dist/ui/filter.js +5 -380
  98. package/dist/ui/form.js +1 -1
  99. package/dist/ui/index.css +118 -1
  100. package/dist/ui/input.js +3 -1141
  101. package/dist/ui/menu.js +4 -828
  102. package/dist/ui/modal.js +4 -843
  103. package/dist/ui/nav.js +4 -223
  104. package/dist/ui/otp.js +4 -756
  105. package/dist/ui/radio.js +4 -317
  106. package/dist/ui/rich-text-editor.js +4 -1278
  107. package/dist/ui/select.js +5 -2138
  108. package/dist/ui/shadow/accordion.js +4 -273
  109. package/dist/ui/shadow/alert.js +4 -130
  110. package/dist/ui/shadow/breadcrumb.js +5 -744
  111. package/dist/ui/shadow/button-dropdown.js +6 -652
  112. package/dist/ui/shadow/button.js +3 -63
  113. package/dist/ui/shadow/card.js +4 -309
  114. package/dist/ui/shadow/chart.js +6 -1576
  115. package/dist/ui/shadow/checkbox.js +4 -334
  116. package/dist/ui/shadow/chip.js +4 -396
  117. package/dist/ui/shadow/date.js +7 -731
  118. package/dist/ui/shadow/drawer.js +4 -756
  119. package/dist/ui/shadow/dropdown-table.js +8 -1762
  120. package/dist/ui/shadow/dropdown.js +4 -508
  121. package/dist/ui/shadow/file-upload.js +4 -620
  122. package/dist/ui/shadow/filter.js +7 -379
  123. package/dist/ui/shadow/input.js +6 -1140
  124. package/dist/ui/shadow/menu.js +5 -828
  125. package/dist/ui/shadow/modal.js +6 -1063
  126. package/dist/ui/shadow/nav.js +5 -223
  127. package/dist/ui/shadow/otp.js +6 -754
  128. package/dist/ui/shadow/radio.js +4 -316
  129. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  130. package/dist/ui/shadow/select.js +6 -2136
  131. package/dist/ui/shadow/sidebar.js +5 -581
  132. package/dist/ui/shadow/slider.js +5 -1010
  133. package/dist/ui/shadow/switch.js +4 -335
  134. package/dist/ui/shadow/table.js +10 -4501
  135. package/dist/ui/shadow/tabs.js +6 -315
  136. package/dist/ui/shadow/tag-input.js +7 -3057
  137. package/dist/ui/shadow/textarea.js +6 -817
  138. package/dist/ui/sidebar.js +3 -581
  139. package/dist/ui/slider.js +4 -1012
  140. package/dist/ui/switch.js +4 -336
  141. package/dist/ui/table.js +9 -4502
  142. package/dist/ui/tabs.js +4 -315
  143. package/dist/ui/tag-input.js +5 -3058
  144. package/dist/ui/textarea.js +4 -819
  145. package/dist/{vite-CivRCJaf.js → vite-CfW7z2wn.js} +21 -14
  146. package/dist/vite.js +1 -1
  147. package/dist/vue.d.ts +3 -2
  148. package/package.json +10 -11
  149. package/dist/chip-strip-bNfLn52Z.js +0 -215
  150. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  151. package/dist/data-source-options-BM4r4PRw.js +0 -558
  152. package/dist/data-source-read-HyBCRg1t.js +0 -242
  153. package/dist/excel-BXzr3uI1.js +0 -784
  154. package/dist/export-ixRdCS2y.js +0 -2388
  155. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  156. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  157. package/dist/filter-builder-C0T3pMx2.js +0 -208
  158. package/dist/form-control-core-BbiACMtL.js +0 -226
  159. package/dist/icon-ClxnYhBG.js +0 -19
  160. package/dist/import-Dc4eXC02.js +0 -612
  161. package/dist/light-slots-_no0r2Z-.js +0 -246
  162. package/dist/markdown-uGlE_OfJ.js +0 -20191
  163. package/dist/mono-data-grid-e9ZVBlNN.js +0 -4240
  164. package/dist/normalize-error-BmLbgANw.js +0 -1048
@@ -0,0 +1,2140 @@
1
+ import { t as getFieldValue } from "./field-path-DK2VBWHl.js";
2
+ import { a as errorTitle, f as MonoSourceSearch, i as errorText, n as describeError, x as searchRowPredicate, y as resolveSearchFields } from "./normalize-error-BEcd1tmS.js";
3
+ import { n as unwrapReactive } from "./reactive-Dtd-esHO.js";
4
+ import { d as buildSizeStyle, f as toCssSize } from "./mono-ui-kvLVIf1V.js";
5
+ import { a as defineHybridPropAliases, n as arrayHasChanged, r as booleanStringConverter, t as __decorate } from "./decorate-DbYFpi8R.js";
6
+ import { t as dispatchMonoEvent } from "./mono-event-CAZf0F_k.js";
7
+ import { t as LateSlotWatcher } from "./light-slots-B5Ck0t_E.js";
8
+ import { a as rateLimitHasChanged } from "./rate-limit-e9Jlpl5l.js";
9
+ import { t as PopupPortalController } from "./popup-portal-CINuVjNY.js";
10
+ import { c as spinnerIcon, s as refreshIcon } from "./field-icons-t8rXRCKs.js";
11
+ import { t as MonoFormControlCore } from "./form-control-core-CRZPZ1ru.js";
12
+ import { t as FieldTooltipController } from "./field-tooltip-Csk4s2ve.js";
13
+ import { t as FieldValidationController } from "./field-validation-CFmUmuDx.js";
14
+ import { t as DataSourceController } from "./data-source-controller-DS_JLrmS.js";
15
+ import { 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";
19
+
20
+ //#region src/components/select/select-core.ts
21
+ /**
22
+ * `MonoSelectCore` — render-mode-agnostic logic for `mono-select` (props, hybrid
23
+ * aliases, value/model sync, DataSource paging, search, grouping, keyboard nav,
24
+ * the popup controller, and the full `render()`). SSR-safe: every `document` /
25
+ * `window` / focus access is `isServer`-guarded.
26
+ *
27
+ * Each build supplies `createRenderRoot()` + `static styles`, the slot strategy
28
+ * (light captures children into `data-mono-slot` placeholders; shadow uses native
29
+ * `<slot>` + a `firstUpdated` scan), and the `_slotOutlet` / `renderIcon` hooks.
30
+ * The shared `_has*SlotState` `@state` fields back both strategies.
31
+ */
32
+ const MonoSelectCore = (superClass) => {
33
+ class MonoSelectCoreClass extends MonoFormControlCore(superClass) {
34
+ constructor(...args) {
35
+ super(...args);
36
+ this.size = "md";
37
+ this.color = "primary";
38
+ this.variant = "outlined";
39
+ this.labelPosition = "top";
40
+ this.validationPosition = "bottom";
41
+ this.modelValue = null;
42
+ this.value = null;
43
+ this.name = "";
44
+ this.label = "";
45
+ this.placeholder = "Select option";
46
+ this.helperText = "";
47
+ this.tooltip = null;
48
+ this._tooltip = new FieldTooltipController(this, () => this.tooltip);
49
+ this._validation = new FieldValidationController(this, () => ({
50
+ position: this.validationPosition,
51
+ ...this._validationNotice ?? {
52
+ state: void 0,
53
+ text: void 0
54
+ },
55
+ suppressed: this._open
56
+ }), () => this.renderRoot?.querySelector?.("[mono-select] > [mono-trigger]"));
57
+ this.validationState = "default";
58
+ this.validationMessage = "";
59
+ this.errorMessage = "";
60
+ this.successMessage = "";
61
+ this.disabled = false;
62
+ this.readonly = false;
63
+ this.required = false;
64
+ this.clearable = false;
65
+ this.items = [];
66
+ this.dataSource = null;
67
+ this.immediate = true;
68
+ this.pageSize = 10;
69
+ this.dropdownHeight = "";
70
+ this.dropdownMaxHeight = "";
71
+ this.flip = true;
72
+ this.shift = true;
73
+ this.keyValue = "";
74
+ this.displayValue = "";
75
+ this.displayGroup = [];
76
+ this.groupKey = "key";
77
+ this.groupItems = "items";
78
+ this.group = false;
79
+ this.groupSticky = false;
80
+ this.searchable = false;
81
+ this.stayOpen = false;
82
+ this.searchValue = "";
83
+ this.searchOperation = "contains";
84
+ this.searchDebounce = 300;
85
+ this.searchPlaceholder = "";
86
+ this.loadingText = "Loading…";
87
+ this.retryLabel = "Retry";
88
+ this.loading = false;
89
+ this.loadingError = null;
90
+ this.cssClass = {};
91
+ this.cssClassName = "";
92
+ this._open = false;
93
+ this._query = "";
94
+ this._searchActive = false;
95
+ this._activeIndex = -1;
96
+ this._cursorPending = false;
97
+ this._sourceSearch = new MonoSourceSearch();
98
+ this._searchVersion = 0;
99
+ this._scrollAutoMaxHeight = "";
100
+ this._hasLabelSlotState = false;
101
+ this._hasHelperSlotState = false;
102
+ this._hasPrefixSlotState = false;
103
+ this._hasSuffixSlotState = false;
104
+ this._hasListSlotState = false;
105
+ this._popup = new PopupPortalController(this, {
106
+ getPanel: () => this.renderRoot.querySelector(".mono-select-dropdown"),
107
+ getAnchor: () => this.renderRoot.querySelector(".mono-select-trigger"),
108
+ getStyleScope: () => this.renderRoot.querySelector(".mono-select"),
109
+ isOpen: () => this._open,
110
+ matchWidth: () => !toCssSize(this.dropdown?.width),
111
+ offset: () => 6,
112
+ flip: () => this.flip,
113
+ shift: () => this.shift,
114
+ constrainSize: () => true
115
+ });
116
+ this._selectId = `mono-select-${Math.random().toString(36).slice(2)}`;
117
+ this._messageId = `${this._selectId}-message`;
118
+ this._listboxId = `${this._selectId}-listbox`;
119
+ this._lateListSlot = new LateSlotWatcher(this, "list", () => this._onLateListSlot());
120
+ this._docListenersBound = false;
121
+ this._itemsEpoch = 0;
122
+ this._handleDropdownScroll = (event) => {
123
+ if (this._loadMoreMode !== "scroll") return;
124
+ if (this._ds.loadingMore || this._ds.atLastPage) return;
125
+ const el = event.currentTarget;
126
+ if (!el) return;
127
+ if (el.scrollHeight - el.scrollTop - el.clientHeight <= 24) this._ds.loadMore();
128
+ };
129
+ this._appliedQuery = "";
130
+ this._appFailure = null;
131
+ this._retry = (event) => {
132
+ event.preventDefault();
133
+ event.stopPropagation();
134
+ const error = this._failure;
135
+ if (this.disabled || this._busy || !error) return;
136
+ const fromApp = !!this._appFailure;
137
+ dispatchMonoEvent(this, "retry", {
138
+ error,
139
+ source: fromApp ? "app" : "dataSource"
140
+ });
141
+ if (fromApp || !this.dataSource || !this._ds.error) return;
142
+ const clearSearch = !!this._appliedQuery && this._serverSearch();
143
+ this._ds.retry(clearSearch ? () => this._pushSearch("") : void 0);
144
+ };
145
+ this._handleDocumentClick = (event) => {
146
+ if (!this._open || this.stayOpen) return;
147
+ const path = event.composedPath();
148
+ if (path.includes(this) || this._popup.containsInPath(path)) return;
149
+ this._close();
150
+ };
151
+ this._handleDocumentFocusIn = (event) => {
152
+ if (!this._open || this.stayOpen) return;
153
+ const path = event.composedPath();
154
+ if (path.includes(this) || this._popup.containsInPath(path)) return;
155
+ this._close();
156
+ };
157
+ this._handleDocumentKeydown = (event) => {
158
+ if (!this._open) return;
159
+ if (event.key === "Escape") {
160
+ if (this._popup.inLayerAbove(event.composedPath())) return;
161
+ event.preventDefault();
162
+ this._close();
163
+ this._triggerEl?.focus();
164
+ }
165
+ };
166
+ this._publishedEntries = [];
167
+ this._onListSlotClick = (event) => {
168
+ if (this._inert) return;
169
+ const target = event.target;
170
+ const item = target ? this._entryFromNode(target)?.item : void 0;
171
+ if (!item || item.disabled) return;
172
+ this._selectItem(item, event);
173
+ };
174
+ defineHybridPropAliases(this, [
175
+ "labelPosition",
176
+ "validationPosition",
177
+ "modelValue",
178
+ "loadingText",
179
+ "loadingError",
180
+ "helperText",
181
+ "validationState",
182
+ "validationMessage",
183
+ "errorMessage",
184
+ "successMessage",
185
+ "ariaLabelText",
186
+ "cssClass",
187
+ "keyValue",
188
+ "displayValue",
189
+ "displayGroup",
190
+ "groupKey",
191
+ "groupItems",
192
+ "groupSticky",
193
+ "searchValue",
194
+ "searchOperation",
195
+ "searchDebounce",
196
+ "searchPlaceholder",
197
+ "dataSource",
198
+ "loadMore",
199
+ "pageSize",
200
+ "dropdownHeight",
201
+ "dropdownMaxHeight",
202
+ "minWidth",
203
+ "maxWidth",
204
+ "minHeight",
205
+ "maxHeight",
206
+ "stayOpen"
207
+ ]);
208
+ /**
209
+ * Makes these Vue usages work:
210
+ *
211
+ * <mono-select :cssClass="{}" />
212
+ * <mono-select :css-class="{}" />
213
+ * <mono-select :cssclass="{}" />
214
+ */
215
+ Object.defineProperty(this, "css-class", {
216
+ get: () => this.cssClass,
217
+ set: (value) => {
218
+ this._setCssClass(value);
219
+ },
220
+ configurable: true,
221
+ enumerable: false
222
+ });
223
+ Object.defineProperty(this, "cssclass", {
224
+ get: () => this.cssClass,
225
+ set: (value) => {
226
+ this._setCssClass(value);
227
+ },
228
+ configurable: true,
229
+ enumerable: false
230
+ });
231
+ Object.defineProperty(this, "ariaLabel", {
232
+ get: () => this.ariaLabelText,
233
+ set: (value) => {
234
+ this.ariaLabelText = value == null ? void 0 : String(value);
235
+ },
236
+ configurable: true,
237
+ enumerable: false
238
+ });
239
+ Object.defineProperty(this, "aria-label", {
240
+ get: () => this.ariaLabelText,
241
+ set: (value) => {
242
+ this.ariaLabelText = value == null ? void 0 : String(value);
243
+ },
244
+ configurable: true,
245
+ enumerable: false
246
+ });
247
+ Object.defineProperty(this, "arialabel", {
248
+ get: () => this.ariaLabelText,
249
+ set: (value) => {
250
+ this.ariaLabelText = value == null ? void 0 : String(value);
251
+ },
252
+ configurable: true,
253
+ enumerable: false
254
+ });
255
+ this._ds = new DataSourceController(this, {
256
+ getDataSource: () => this.dataSource,
257
+ getItems: () => this.items,
258
+ getImmediate: () => this.immediate,
259
+ getLoadMoreMode: () => this._loadMoreMode,
260
+ getPageSize: () => this._pageSize
261
+ });
262
+ }
263
+ static {
264
+ this.monoPendingAuto = "data";
265
+ }
266
+ _monoPendingReady() {
267
+ return !this.dataSource || !this._ds.loading;
268
+ }
269
+ static get observedAttributes() {
270
+ return [
271
+ ...super.observedAttributes ?? [],
272
+ "modelvalue",
273
+ "loadingtext",
274
+ "loadingerror",
275
+ "model-value",
276
+ "helpertext",
277
+ "validationstate",
278
+ "validationmessage",
279
+ "errormessage",
280
+ "successmessage",
281
+ "arialabeltext",
282
+ "arialabel",
283
+ "css-class",
284
+ "cssclass",
285
+ "keyvalue",
286
+ "displayvalue",
287
+ "display-value",
288
+ "stayopen"
289
+ ];
290
+ }
291
+ attributeChangedCallback(name, oldValue, newValue) {
292
+ super.attributeChangedCallback(name, oldValue, newValue);
293
+ if (oldValue === newValue) return;
294
+ if (name === "modelvalue" || name === "model-value") {
295
+ this.modelValue = this._toSelectValue(newValue);
296
+ return;
297
+ }
298
+ if (name === "loadingtext") {
299
+ this.loadingText = newValue ?? "Loading…";
300
+ return;
301
+ }
302
+ if (name === "loadingerror") {
303
+ this.loadingError = newValue;
304
+ return;
305
+ }
306
+ if (name === "helpertext") {
307
+ this.helperText = newValue ?? "";
308
+ return;
309
+ }
310
+ if (name === "validationstate") {
311
+ this.validationState = newValue ?? "default";
312
+ return;
313
+ }
314
+ if (name === "validationmessage") {
315
+ this.validationMessage = newValue ?? "";
316
+ return;
317
+ }
318
+ if (name === "errormessage") {
319
+ this.errorMessage = newValue ?? "";
320
+ return;
321
+ }
322
+ if (name === "successmessage") {
323
+ this.successMessage = newValue ?? "";
324
+ return;
325
+ }
326
+ if (name === "arialabeltext" || name === "arialabel") {
327
+ this.ariaLabelText = newValue ?? void 0;
328
+ return;
329
+ }
330
+ if (name === "keyvalue") {
331
+ this.keyValue = newValue ?? "";
332
+ return;
333
+ }
334
+ if (name === "displayvalue" || name === "display-value") {
335
+ if (typeof this.displayValue === "function") return;
336
+ const next = newValue ?? "";
337
+ if (this._looksLikeSerializedFunction(next)) return;
338
+ this.displayValue = next;
339
+ return;
340
+ }
341
+ if (name === "stayopen") {
342
+ this.stayOpen = this._toBoolean(newValue);
343
+ return;
344
+ }
345
+ if (name === "css-class" || name === "cssclass") this._setCssClass(newValue);
346
+ }
347
+ /** The validation message and its state — the same precedence the message row uses. */
348
+ get _validationNotice() {
349
+ if (this.validationMessage) return {
350
+ state: this._resolvedValidationState,
351
+ text: this.validationMessage
352
+ };
353
+ if (this.errorMessage) return {
354
+ state: "invalid",
355
+ text: this.errorMessage
356
+ };
357
+ if (this.successMessage) return {
358
+ state: "valid",
359
+ text: this.successMessage
360
+ };
361
+ return null;
362
+ }
363
+ /** The message that moved to the icon / float bubble, kept for assistive tech only. */
364
+ _renderMovedMessage() {
365
+ const notice = this._validationNotice;
366
+ return html`<div
367
+ class=${`${this._cls("mono-select-message", "message")} ${notice.state}`}
368
+ mono-message=${notice.state}
369
+ mono-sr-only
370
+ role=${notice.state === "invalid" ? "alert" : nothing}
371
+ >${notice.text}</div>`;
372
+ }
373
+ /**
374
+ * The scroll body lives inside the dropdown panel, which the portal
375
+ * controller relocates into a `<body>` portal while open — query it through
376
+ * `panelRoot` (the portal when adopted, else the host render root).
377
+ */
378
+ get _dropdownBodyEl() {
379
+ return this._popup.panelRoot.querySelector(".mono-select-dropdown-body");
380
+ }
381
+ /**
382
+ * Where the consumer's list wrapper is parked — through the SAME portal-aware root as the panel,
383
+ * since while the dropdown is open it lives in a body portal a host query would miss.
384
+ */
385
+ get _listSlotTarget() {
386
+ return this._popup.panelRoot.querySelector("[data-mono-slot=\"list\"]");
387
+ }
388
+ /** A late wrapper arrived. Overridden per build — one adopts it, the other rescans. */
389
+ _onLateListSlot() {}
390
+ connectedCallback() {
391
+ super.connectedCallback();
392
+ this._lateListSlot.start();
393
+ if (isServer) return;
394
+ if (this._open) this._bindDocumentListeners();
395
+ }
396
+ /**
397
+ * Outside-dismiss listeners, bound on OPEN rather than on connect.
398
+ *
399
+ * All three handlers begin with `if (!this._open) return`, so binding them only
400
+ * while open is behaviour-preserving — the cost they carried was pure event
401
+ * DISPATCH. Every mounted select used to hold three `document` listeners, two of
402
+ * them capture-phase, so a grid with one select per row put 600 handlers (400 in
403
+ * capture) in the path of every click, focus move and keystroke on the page.
404
+ * Measured on the 200-row fixture: 600 listeners before, 0 at rest after.
405
+ *
406
+ * Same change, and the same reasoning, as the popup viewport listeners in
407
+ * `popup-portal.ts` — which this file was explicitly the remaining example of.
408
+ */
409
+ _bindDocumentListeners() {
410
+ if (this._docListenersBound) return;
411
+ this._docListenersBound = true;
412
+ document.addEventListener("click", this._handleDocumentClick, true);
413
+ document.addEventListener("focusin", this._handleDocumentFocusIn, true);
414
+ document.addEventListener("keydown", this._handleDocumentKeydown);
415
+ }
416
+ _unbindDocumentListeners() {
417
+ if (!this._docListenersBound) return;
418
+ this._docListenersBound = false;
419
+ document.removeEventListener("click", this._handleDocumentClick, true);
420
+ document.removeEventListener("focusin", this._handleDocumentFocusIn, true);
421
+ document.removeEventListener("keydown", this._handleDocumentKeydown);
422
+ }
423
+ disconnectedCallback() {
424
+ if (this._searchTimer) {
425
+ clearTimeout(this._searchTimer);
426
+ this._searchTimer = void 0;
427
+ }
428
+ this._sourceSearch.clear(this.dataSource);
429
+ this._lateListSlot.stop();
430
+ this._stopWatchingListChrome();
431
+ if (!isServer) this._unbindDocumentListeners();
432
+ super.disconnectedCallback();
433
+ }
434
+ willUpdate(changed) {
435
+ this._itemsEpoch++;
436
+ for (const key of [
437
+ "disabled",
438
+ "loading",
439
+ "readonly",
440
+ "required",
441
+ "clearable",
442
+ "immediate",
443
+ "group",
444
+ "groupSticky",
445
+ "searchable",
446
+ "stayOpen"
447
+ ]) {
448
+ const v = this[key];
449
+ if (typeof v !== "boolean") this[key] = this._toBoolean(v);
450
+ }
451
+ if (changed.has("loadingError")) {
452
+ const v = this.loadingError;
453
+ this._appFailure = v == null || v === false || v === "" ? null : describeError(v === true ? void 0 : v);
454
+ }
455
+ if (this._open && this._dsInert) {
456
+ this._open = false;
457
+ this._dropQuery();
458
+ }
459
+ if (changed.has("modelValue") && !this._isSameValue(this.value, this.modelValue)) this.value = unwrapReactive(this.modelValue);
460
+ if (changed.has("value") && !this._isSameValue(this.modelValue, this.value)) this.modelValue = unwrapReactive(this.value);
461
+ if (this.group && (changed.has("dataSource") || changed.has("displayGroup") || changed.has("group"))) {
462
+ const fields = this._groupSortFields();
463
+ if (fields.length) this.dataSource?.sort?.(fields);
464
+ }
465
+ if (changed.has("dataSource")) {
466
+ this._ds.bind(this.dataSource);
467
+ this._sourceSearch.bind(this.dataSource);
468
+ }
469
+ if (changed.has("searchValue") || changed.has("searchOperation") || changed.has("displayValue") || changed.has("keyValue") || changed.has("dataSource") || changed.has("items")) this._searchVersion++;
470
+ if (changed.has("immediate")) this._ds.maybeImmediateLoad();
471
+ if (changed.has("loadMore")) this._ds.onLoadMoreModeChange();
472
+ if (changed.has("items") || changed.has("loadMore") || changed.has("pageSize")) this._ds.resetVisible();
473
+ if (changed.has("_open")) {
474
+ this._cursorPending = this._open;
475
+ if (!this._open) this._activeIndex = -1;
476
+ }
477
+ if (this._cursorPending && this._open && this._filteredItems.length) {
478
+ this._activeIndex = this._initialActiveIndex();
479
+ this._cursorPending = false;
480
+ }
481
+ }
482
+ updated(changed) {
483
+ super.updated(changed);
484
+ this._syncListEntries();
485
+ if (isServer) return;
486
+ if (changed.has("_open") && this._open && this.searchable) this._searchFieldEl?.focus();
487
+ if (changed.has("_activeIndex") && this._open && this._activeIndex >= 0) (this._popup.panelRoot?.querySelectorAll?.(".mono-select-item")?.[this._activeIndex])?.scrollIntoView?.({ block: "nearest" });
488
+ this._updateScrollAutoHeight();
489
+ if (this._open) this._bindDocumentListeners();
490
+ else this._unbindDocumentListeners();
491
+ }
492
+ /**
493
+ * Scroll-mode ergonomics: a short page (e.g. 5 items) won't overflow the
494
+ * default dropdown height, so there's nothing to scroll and the next page
495
+ * could never be triggered. Rather than eagerly fetching another page on
496
+ * open, cap the list height to the loaded rows minus a sliver — enough to
497
+ * show a scrollbar so the user scrolls to load more. No height is forced
498
+ * when the consumer set `dropdown-height`/`dropdown-max-height`, when the
499
+ * last page is reached, or when the rows already overflow.
500
+ */
501
+ _updateScrollAutoHeight() {
502
+ const userSetHeight = this._toCssLength(this.dropdownHeight) != null || this._toCssLength(this.dropdownMaxHeight) != null || toCssSize(this.dropdown?.height) != null || toCssSize(this.dropdown?.maxHeight) != null;
503
+ if (!(this._loadMoreMode === "scroll" && this._open && !this._ds.atLastPage && !userSetHeight && this._visibleItems.length > 0)) {
504
+ this._setScrollAutoMaxHeight("");
505
+ return;
506
+ }
507
+ const el = this._dropdownBodyEl;
508
+ const rowH = (el?.querySelector(".mono-select-item"))?.offsetHeight ?? 0;
509
+ if (!el || !rowH) return;
510
+ const CAP_ROWS = 7;
511
+ const rows = this._visibleItems.length;
512
+ let visible = Math.min(rows, CAP_ROWS);
513
+ if (rows <= CAP_ROWS) visible -= .5;
514
+ this._setScrollAutoMaxHeight(`${Math.round(visible * rowH)}px`);
515
+ }
516
+ _setScrollAutoMaxHeight(value) {
517
+ if (value === this._scrollAutoMaxHeight) return;
518
+ queueMicrotask(() => {
519
+ this._scrollAutoMaxHeight = value;
520
+ });
521
+ }
522
+ _setCssClass(value) {
523
+ if (value == null) {
524
+ this.cssClass = {};
525
+ this.cssClassName = "";
526
+ return;
527
+ }
528
+ if (typeof value === "object") {
529
+ this.cssClass = value;
530
+ return;
531
+ }
532
+ if (typeof value === "string") {
533
+ const trimmed = value.trim();
534
+ if (!trimmed) {
535
+ this.cssClass = {};
536
+ this.cssClassName = "";
537
+ return;
538
+ }
539
+ /**
540
+ * Optional HTML object support:
541
+ * <mono-select css-class='{"root":"...", "trigger":"..."}'>
542
+ */
543
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) try {
544
+ this.cssClass = JSON.parse(trimmed);
545
+ return;
546
+ } catch {}
547
+ this.cssClassName = trimmed;
548
+ }
549
+ }
550
+ _toSelectValue(value) {
551
+ if (value === void 0 || value === null) return null;
552
+ if (typeof value !== "string") return value;
553
+ const trimmed = value.trim();
554
+ if (!trimmed) return null;
555
+ if (/^-?\d+(?:\.\d+)?$/.test(trimmed) || trimmed === "true" || trimmed === "false" || trimmed === "null" || trimmed[0] === "{" && trimmed[trimmed.length - 1] === "}" || trimmed[0] === "[" && trimmed[trimmed.length - 1] === "]" || trimmed[0] === "\"" && trimmed[trimmed.length - 1] === "\"") try {
556
+ return JSON.parse(trimmed);
557
+ } catch {}
558
+ return trimmed;
559
+ }
560
+ /**
561
+ * Wrap bare primitives so every row is an object, and pass objects through.
562
+ *
563
+ * The `.map()` used to be unconditional, which made this an ALLOCATION on the
564
+ * overwhelmingly common path (an array of objects, where the map is an identity
565
+ * function). It is read through `_normalizedItems` / `_visibleItems` / `_leafItems`
566
+ * several times per render, so at one select per table row it was the single
567
+ * hottest allocation in the component. The scan below is O(n) reads with no
568
+ * allocation, and returns the SAME array when nothing needs wrapping.
569
+ *
570
+ * Returning the input array is safe: every caller only reads
571
+ * (`find`/`filter`/`map`/`findIndex`/`length`), and `resolveSearchFields` takes its
572
+ * rows as `readonly unknown[]`. It also makes item identity STABLE across calls for
573
+ * primitive lists, which is the safe direction — see the note on
574
+ * `_initialActiveIndex`, which explains why it deliberately does not rely on that
575
+ * identity holding.
576
+ */
577
+ _normalizeItems(items) {
578
+ if (Array.isArray(items)) {
579
+ let needsWrap = false;
580
+ for (const item of items) if (typeof item !== "object" || item === null) {
581
+ needsWrap = true;
582
+ break;
583
+ }
584
+ if (!needsWrap) return items;
585
+ return items.map((item) => {
586
+ if (typeof item === "object" && item !== null) return item;
587
+ return { value: item };
588
+ });
589
+ }
590
+ if (typeof items === "string") try {
591
+ const parsed = JSON.parse(items);
592
+ return this._normalizeItems(parsed);
593
+ } catch {
594
+ return [];
595
+ }
596
+ return [];
597
+ }
598
+ /**
599
+ * Read `field` off an option row — PATH-AWARE, like every other field name in
600
+ * the library.
601
+ *
602
+ * A plain `field in item` was the old test, and it silently produced
603
+ * `undefined` for any nested accessor: an option list grouped by a navigation
604
+ * property (`$apply=groupby((PostBudget/Nama))` returns
605
+ * `{ PostBudget: { Nama: … } }`) has no top-level `"PostBudget.Nama"` key, so
606
+ * every option resolved to no value at all — selectable in appearance, inert in
607
+ * practice. `searchExpr`, table columns and summaries have always accepted
608
+ * these paths; a select's `key-value` / `display-value` now do too.
609
+ *
610
+ * The LITERAL key is still tried first, so a row that genuinely carries a
611
+ * dotted PROPERTY NAME keeps resolving exactly as it did — only a key that is
612
+ * absent falls through to being read as a path, which previously could only
613
+ * have yielded `undefined` anyway. Nothing that worked before changes.
614
+ */
615
+ _readField(item, field) {
616
+ if (field in item) return item[field];
617
+ return getFieldValue(item, field);
618
+ }
619
+ _resolveItemValue(item) {
620
+ if (item == null) return item;
621
+ if (typeof item !== "object") return item;
622
+ if (this.keyValue) return this._readField(item, this.keyValue);
623
+ return item;
624
+ }
625
+ _resolveItemDisplay(item) {
626
+ if (item == null) return "";
627
+ if (typeof this.displayValue === "function") return String(this.displayValue(item));
628
+ if (typeof this.displayValue === "string" && this.displayValue) {
629
+ if (typeof item !== "object") return "";
630
+ const value = this._readField(item, this.displayValue);
631
+ return value == null ? "" : String(value);
632
+ }
633
+ if (typeof item === "object" && "label" in item) {
634
+ const label = item.label;
635
+ return label == null ? "" : String(label);
636
+ }
637
+ if (typeof item === "object" && "value" in item) {
638
+ const value = item.value;
639
+ return value == null ? "" : String(value);
640
+ }
641
+ return String(item);
642
+ }
643
+ _looksLikeSerializedFunction(value) {
644
+ const trimmed = value.trim();
645
+ if (!trimmed) return false;
646
+ return /^(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(trimmed);
647
+ }
648
+ /**
649
+ * Value equality. With no `key-value` the whole item IS the value, so this is
650
+ * identity — with one correction that is load-bearing under Vue.
651
+ *
652
+ * The model round-trips through the consumer's `ref`, which hands the item back
653
+ * wrapped in a **reactive Proxy**; `items` normally stays raw, so `proxy === row`
654
+ * is false and the selected row stops resolving (blank field, no highlight).
655
+ * Comparing the raw targets restores it. Still identity, not deep equality:
656
+ * two distinct rows with equal contents remain different values.
657
+ */
658
+ _isSameValue(a, b) {
659
+ if (a === b) return true;
660
+ if (a == null || b == null) return false;
661
+ const rawA = unwrapReactive(a);
662
+ const rawB = unwrapReactive(b);
663
+ if (rawA === rawB) return true;
664
+ if (typeof rawA !== "object" || typeof rawB !== "object") return false;
665
+ return false;
666
+ }
667
+ _cls(base, key) {
668
+ const extra = this.cssClass?.[key];
669
+ return extra ? `${base} ${extra}` : base;
670
+ }
671
+ _toCssLength(value) {
672
+ if (value == null || value === "") return void 0;
673
+ if (typeof value === "number") return `${value}px`;
674
+ const trimmed = String(value).trim();
675
+ if (!trimmed) return void 0;
676
+ return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
677
+ }
678
+ /**
679
+ * The WIDTH half of `dropdown`, on the panel.
680
+ *
681
+ * Split from the heights below because this component's popup is two elements: the panel owns
682
+ * the box and the border, its body does the scrolling. That split is also why select needs no
683
+ * `--_…-panel-max-h` custom property the way tag-input does — the popup controller's own cap
684
+ * (`--mono-popup-avail-h`) lives on the panel and the author's on the body, so the two writers
685
+ * never meet on one declaration.
686
+ *
687
+ * The panel is positioned `left: 0; right: 0` against the field, and a width on top of that is
688
+ * the over-constrained case, which CSS resolves by dropping `right` — so the panel keeps its
689
+ * left edge and takes this size.
690
+ */
691
+ get _dropdownPanelStyle() {
692
+ const style = {};
693
+ const panel = this.dropdown;
694
+ const width = toCssSize(panel?.width);
695
+ if (width) style.width = width;
696
+ const minWidth = toCssSize(panel?.minWidth);
697
+ if (minWidth) style["min-width"] = minWidth;
698
+ const maxWidth = toCssSize(panel?.maxWidth);
699
+ if (maxWidth) style["max-width"] = maxWidth;
700
+ return style;
701
+ }
702
+ /** The HEIGHT half of `dropdown`, on the scrolling body — see `_dropdownPanelStyle`. */
703
+ get _dropdownBodyStyle() {
704
+ const style = {};
705
+ const panel = this.dropdown;
706
+ const minHeight = toCssSize(panel?.minHeight);
707
+ if (minHeight) style.minHeight = minHeight;
708
+ const height = toCssSize(panel?.height) ?? this._toCssLength(this.dropdownHeight);
709
+ if (height) style.height = height;
710
+ const maxHeight = toCssSize(panel?.maxHeight) ?? this._toCssLength(this.dropdownMaxHeight);
711
+ if (maxHeight) style.maxHeight = maxHeight;
712
+ else if (height) style.maxHeight = height;
713
+ else if (this._scrollAutoMaxHeight) style.maxHeight = this._scrollAutoMaxHeight;
714
+ return style;
715
+ }
716
+ get _normalizedItems() {
717
+ if (this._normalizedMemo?.epoch !== this._itemsEpoch) this._normalizedMemo = {
718
+ epoch: this._itemsEpoch,
719
+ items: this._normalizeItems(this._ds.sourceItems)
720
+ };
721
+ return this._normalizedMemo.items;
722
+ }
723
+ /** Sanitised chunk size for array-mode paging (>= 1). */
724
+ get _pageSize() {
725
+ const n = Math.floor(Number(this.pageSize));
726
+ return Number.isFinite(n) && n > 0 ? n : 10;
727
+ }
728
+ /**
729
+ * The effective load-more mode. Off when unset/false; otherwise 'button' only
730
+ * for the explicit `load-more="button"`, and 'scroll' for everything else that
731
+ * enables it (bare attribute, empty string, `true`, or `"scroll"`). Scroll is
732
+ * the default so enabling load-more "just works" without picking a mode.
733
+ */
734
+ get _loadMoreMode() {
735
+ const v = this.loadMore;
736
+ if (v === void 0 || v === null || v === false) return void 0;
737
+ return v === "button" ? "button" : "scroll";
738
+ }
739
+ /**
740
+ * The rows to actually render — the controller's visible slice, normalized.
741
+ * (Array load-more reveals a chunk; DataSource mode accumulates pages.)
742
+ */
743
+ get _visibleItems() {
744
+ if (this._visibleMemo?.epoch !== this._itemsEpoch) this._visibleMemo = {
745
+ epoch: this._itemsEpoch,
746
+ items: this._normalizeItems(this._ds.visibleItems)
747
+ };
748
+ return this._visibleMemo.items;
749
+ }
750
+ get _selectedItem() {
751
+ return this._leafItems.find((item) => this._isSameValue(this._resolveItemValue(item), this.value));
752
+ }
753
+ /** Grouping is active when at least one display-group accessor is given. */
754
+ get _grouped() {
755
+ return Array.isArray(this.displayGroup) && this.displayGroup.length > 0;
756
+ }
757
+ /** A node is a group when its `groupItems` field holds an array. */
758
+ _isGroupNode(x) {
759
+ return !!x && typeof x === "object" && Array.isArray(x[this.groupItems]);
760
+ }
761
+ /** Descend `groupItems[0]` to the first leaf row — the group's representative. */
762
+ _firstLeaf(node) {
763
+ let cur = node;
764
+ let guard = 0;
765
+ while (this._isGroupNode(cur) && guard++ < 50) cur = cur[this.groupItems]?.[0];
766
+ return cur ?? void 0;
767
+ }
768
+ /** Resolve a group header's label from `displayGroup[level]` over a leaf row. */
769
+ _resolveGroupLabel(node, level) {
770
+ const accessor = this.displayGroup[level];
771
+ const leaf = this._firstLeaf(node);
772
+ if (typeof accessor === "function") return String(accessor(leaf ?? {}) ?? "");
773
+ if (typeof accessor === "string" && leaf && accessor in leaf) return String(leaf[accessor] ?? "");
774
+ const key = node[this.groupKey];
775
+ return key == null ? "" : String(key);
776
+ }
777
+ /** The string `display-group` entries — usable as server group/sort fields. */
778
+ _groupSortFields() {
779
+ return (this.displayGroup ?? []).filter((a) => typeof a === "string");
780
+ }
781
+ /** A `display-group` level's key for a row (string → field, function → call). */
782
+ _groupKeyOf(row, level) {
783
+ const accessor = this.displayGroup[level];
784
+ if (typeof accessor === "function") return accessor(row);
785
+ if (typeof accessor === "string") return row[accessor];
786
+ }
787
+ /**
788
+ * `group` mode: bucket flat rows into the nested `{ [groupKey], [groupItems] }`
789
+ * shape client-side, by `display-group` level. Buckets keep first-appearance
790
+ * order, so each group is contiguous and grows in place as more pages load.
791
+ */
792
+ _buildClientTree(rows) {
793
+ const build = (items, level) => {
794
+ if (level >= this.displayGroup.length) return items;
795
+ const buckets = /* @__PURE__ */ new Map();
796
+ for (const row of items) {
797
+ const key = this._groupKeyOf(row, level);
798
+ const ks = String(key);
799
+ let bucket = buckets.get(ks);
800
+ if (!bucket) buckets.set(ks, bucket = {
801
+ key,
802
+ rows: []
803
+ });
804
+ bucket.rows.push(row);
805
+ }
806
+ return [...buckets.values()].map((b) => ({
807
+ [this.groupKey]: b.key,
808
+ [this.groupItems]: build(b.rows, level + 1)
809
+ }));
810
+ };
811
+ return build(rows, 0);
812
+ }
813
+ /** The nested group nodes to render — built client-side in `group` mode. */
814
+ _groupNodes() {
815
+ if (this.group) {
816
+ const leaves = this._visibleItems.filter((it) => this._searchMatches(it));
817
+ return this._buildClientTree(leaves);
818
+ }
819
+ return this._ds.visibleItems ?? [];
820
+ }
821
+ /** Every leaf row — for value/display lookup. */
822
+ get _leafItems() {
823
+ if (this._leafMemo?.epoch === this._itemsEpoch) return this._leafMemo.items;
824
+ const items = this._computeLeafItems();
825
+ this._leafMemo = {
826
+ epoch: this._itemsEpoch,
827
+ items
828
+ };
829
+ return items;
830
+ }
831
+ _computeLeafItems() {
832
+ if (!this._grouped) return this._normalizedItems;
833
+ if (this.group) return [...this._ds.sourceItems];
834
+ const out = [];
835
+ const walk = (nodes) => {
836
+ for (const n of nodes) if (this._isGroupNode(n)) walk(n[this.groupItems]);
837
+ else if (n != null) out.push(n);
838
+ };
839
+ walk(this._ds.sourceItems ?? []);
840
+ return out;
841
+ }
842
+ /**
843
+ * Flatten the visible group tree into ordered header / item render rows.
844
+ * Leaves are search-filtered (client mode); empty groups are dropped.
845
+ */
846
+ _groupRows() {
847
+ const filterLeaves = !this.group;
848
+ const build = (nodes, level, parentKey) => {
849
+ const rows = [];
850
+ nodes.forEach((node, i) => {
851
+ if (!this._isGroupNode(node)) {
852
+ const item = node;
853
+ if (item == null) return;
854
+ if (filterLeaves && !this._searchMatches(item)) return;
855
+ rows.push({
856
+ kind: "item",
857
+ item,
858
+ index: -1
859
+ });
860
+ return;
861
+ }
862
+ const rec = node;
863
+ const path = `${parentKey}/${String(rec[this.groupKey])}#${i}`;
864
+ const children = build(rec[this.groupItems] ?? [], level + 1, path);
865
+ if (!children.length) return;
866
+ rows.push({
867
+ kind: "group",
868
+ level,
869
+ label: this._resolveGroupLabel(rec, level),
870
+ key: `g:${level}:${path}`
871
+ });
872
+ rows.push(...children);
873
+ });
874
+ return rows;
875
+ };
876
+ let idx = 0;
877
+ return build(this._groupNodes(), 0, "").map((r) => r.kind === "item" ? {
878
+ ...r,
879
+ index: idx++
880
+ } : r);
881
+ }
882
+ /** Whether `search-value` names anything explicitly (vs falling back). */
883
+ get _hasSearchValue() {
884
+ const sv = this.searchValue;
885
+ if (Array.isArray(sv)) return sv.length > 0;
886
+ return typeof sv === "string" && !!sv.trim();
887
+ }
888
+ /**
889
+ * The natural fields searched alongside the `'*'` default when `search-value` is
890
+ * empty — the safety net, not the whole list.
891
+ *
892
+ * `'*'` resolves against loaded rows, so on a bound DataSource it expands to
893
+ * NOTHING until the first page lands (nothing loads on open, and a query can
894
+ * fire inside the debounce) — these keep that first query working. They are also
895
+ * exempt from the remote string-only rule, so a numeric `key-value` stays
896
+ * searchable. `display-value` contributes only when it's a string; a function
897
+ * builds text that no field expression can reach, which is why `_searchMatches`
898
+ * keeps matching the rendered display separately.
899
+ */
900
+ _fallbackSearchFields() {
901
+ return [typeof this.displayValue === "string" ? this.displayValue : void 0, this.keyValue];
902
+ }
903
+ /**
904
+ * A cheap signature of the rows' SHAPE, for the memo keys below.
905
+ *
906
+ * Row count alone isn't enough once `'*'` is in play: the resolved field list
907
+ * depends on the rows' keys, and a remote source paging from one full page to
908
+ * the next keeps the same length while the shape can differ. Sampling the first
909
+ * and last row keeps this O(1) while catching that case.
910
+ */
911
+ _rowsShapeKey(rows) {
912
+ const keysOf = (row) => row && typeof row === "object" ? Object.keys(row).join(",") : typeof row;
913
+ if (!rows.length) return "0";
914
+ return `${rows.length}:${keysOf(rows[0])}|${keysOf(rows[rows.length - 1])}`;
915
+ }
916
+ /**
917
+ * `search-value` resolved to concrete entries — `*` patterns expanded against
918
+ * the loaded rows. Memoized: a pattern samples up to 20 rows, so resolving per
919
+ * item (this feeds `_searchMatches`) would be O(items × fields) per keystroke.
920
+ *
921
+ * With no `search-value` this defaults to `'*'` (every top-level field) plus the
922
+ * natural fallbacks — see {@link _fallbackSearchFields}.
923
+ */
924
+ _searchEntries(remote) {
925
+ const rows = this._normalizedItems;
926
+ const key = `${this._searchVersion} ${remote ? 1 : 0} ${this._rowsShapeKey(rows)}`;
927
+ if (this._entriesCache?.key === key) return this._entriesCache.entries;
928
+ const entries = resolveSearchFields({
929
+ searchValue: this.searchValue,
930
+ fallbackFields: this._fallbackSearchFields(),
931
+ rows,
932
+ remote,
933
+ defaultToWildcard: true
934
+ });
935
+ this._entriesCache = {
936
+ key,
937
+ entries
938
+ };
939
+ return entries;
940
+ }
941
+ /** The compiled client-side predicate for the current query (memoized). */
942
+ _searchPredicate() {
943
+ const query = this._query.trim();
944
+ const operation = this.searchOperation || "contains";
945
+ const rows = this._normalizedItems;
946
+ const key = `${this._searchVersion} ${operation} ${this._rowsShapeKey(rows)} ${query}`;
947
+ if (this._predicateCache?.key === key) return this._predicateCache.pred;
948
+ const pred = searchRowPredicate(this._searchEntries(false), query, operation);
949
+ this._predicateCache = {
950
+ key,
951
+ pred
952
+ };
953
+ return pred;
954
+ }
955
+ /** A bound DataSource is searched on the server; a plain array, client-side. */
956
+ _serverSearch() {
957
+ return !!this.dataSource;
958
+ }
959
+ /** Client-side match test (server mode already filtered → always true). */
960
+ _searchMatches(item) {
961
+ if (this._serverSearch()) return true;
962
+ if (!this._searchActive) return true;
963
+ const q = this._query.trim().toLowerCase();
964
+ if (!q) return true;
965
+ if (this._hasSearchValue) {
966
+ const pred = this._searchPredicate();
967
+ return pred ? pred(item) : true;
968
+ }
969
+ const display = this._resolveItemDisplay(item).toLowerCase();
970
+ const val = this._resolveItemValue(item);
971
+ const valStr = val == null ? "" : String(val).toLowerCase();
972
+ const pred = this._searchPredicate();
973
+ return display.includes(q) || valStr.includes(q) || (pred ? pred(item) : false);
974
+ }
975
+ _onSearchInput(event) {
976
+ if (this._inert) return;
977
+ this._query = event.target.value;
978
+ this._searchActive = true;
979
+ this._open = true;
980
+ this._activeIndex = 0;
981
+ this._cursorPending = false;
982
+ this.requestUpdate();
983
+ if (this._searchTimer) clearTimeout(this._searchTimer);
984
+ if (!this._serverSearch()) {
985
+ this._searchTimer = void 0;
986
+ return;
987
+ }
988
+ const wait = Math.max(0, Number(this.searchDebounce) || 0);
989
+ this._searchTimer = setTimeout(() => void this._applySearch(this._query), wait);
990
+ }
991
+ /** Run the search: server query (debounced) or just re-render for client filter. */
992
+ async _applySearch(query) {
993
+ if (!this._serverSearch()) {
994
+ this.requestUpdate();
995
+ return;
996
+ }
997
+ await this._pushSearch(query).catch((err) => {
998
+ this._ds.setError(err);
999
+ });
1000
+ }
1001
+ /** Push `query` onto the source and reload it; rejects when the load fails. */
1002
+ _pushSearch(query) {
1003
+ this._appliedQuery = query;
1004
+ return this._sourceSearch.apply(this.dataSource, {
1005
+ query,
1006
+ searchValue: this.searchValue,
1007
+ fallbackFields: this._fallbackSearchFields(),
1008
+ rows: this._normalizedItems,
1009
+ remote: true,
1010
+ defaultToWildcard: true,
1011
+ operation: this.searchOperation || "contains"
1012
+ });
1013
+ }
1014
+ /** Forget the typed query without touching the source. */
1015
+ _dropQuery() {
1016
+ if (this._searchTimer) {
1017
+ clearTimeout(this._searchTimer);
1018
+ this._searchTimer = void 0;
1019
+ }
1020
+ this._query = "";
1021
+ this._searchActive = false;
1022
+ this._activeIndex = -1;
1023
+ }
1024
+ /** Loading: the app says so (`loading`), or the bound source's first load / retry. */
1025
+ get _busy() {
1026
+ return this.loading || !!this.dataSource && this._ds.busy;
1027
+ }
1028
+ /** The failure to paint: the app's `loading-error`, else the source's own; none while busy. */
1029
+ get _failure() {
1030
+ if (this._busy) return null;
1031
+ return this._appFailure ?? (this.dataSource ? this._ds.error : null);
1032
+ }
1033
+ /** Enter / Space on the focused field retries a failure. */
1034
+ _retryKey(event) {
1035
+ if (event.key !== "Enter" && event.key !== " ") return false;
1036
+ if (!this._failure || this.disabled) return false;
1037
+ this._retry(event);
1038
+ return true;
1039
+ }
1040
+ /** Loading, or failed: the field refuses every gesture but ↻. */
1041
+ get _dsInert() {
1042
+ return this._busy || !!this._failure;
1043
+ }
1044
+ /** Every interaction is off: disabled, read-only, or the source isn't usable. */
1045
+ get _inert() {
1046
+ return this.disabled || this.readonly || this._dsInert;
1047
+ }
1048
+ /** Clear the typed query (and the source query, on close / select). */
1049
+ _resetSearch() {
1050
+ if (this._searchTimer) {
1051
+ clearTimeout(this._searchTimer);
1052
+ this._searchTimer = void 0;
1053
+ }
1054
+ const had = this._searchActive && !!this._query;
1055
+ this._query = "";
1056
+ this._searchActive = false;
1057
+ this._activeIndex = -1;
1058
+ if (had && this._serverSearch()) this._applySearch("");
1059
+ }
1060
+ /** The label shown in the field while not actively typing a query. */
1061
+ get _displayText() {
1062
+ const item = this._selectedItem;
1063
+ return item ? this._resolveItemDisplay(item) : "";
1064
+ }
1065
+ /** Placeholder for the search field — a search hint while open, else the prompt. */
1066
+ get _fieldPlaceholder() {
1067
+ if (this._busy) return this.loadingText;
1068
+ if (this._failure) return errorText(this._failure);
1069
+ if ((this._open || this._searchActive) && this.searchPlaceholder) return this.searchPlaceholder;
1070
+ return this.placeholder;
1071
+ }
1072
+ /**
1073
+ * The selectable rows in render order — drives keyboard nav (`_activeIndex`)
1074
+ * for the searchable combobox. Mirrors what `_renderItems` paints.
1075
+ */
1076
+ /**
1077
+ * Where the keyboard cursor goes when the list opens: the row matching the
1078
+ * current value, else the first row.
1079
+ *
1080
+ * Deliberately runs the SAME predicate that paints `.selected` in
1081
+ * `_renderOption`, over `_filteredItems` — the very list ↑/↓ and Enter index
1082
+ * into — so the cursor is guaranteed to land on the row drawn as selected.
1083
+ * Going via `_selectedItem` + `indexOf` would not work: that searches
1084
+ * `_leafItems`, an unfiltered/unpaged array whose `_normalizeItems` wraps
1085
+ * primitives in fresh objects per call, so identity would not hold. Grouping
1086
+ * needs no special case — `_groupRows()` numbers leaf rows only, which is
1087
+ * exactly what `_filteredItems` reconstructs.
1088
+ *
1089
+ * `-1` when the list is empty; `0` when the selected row is not in it (a page
1090
+ * the user hasn't scrolled to, or a server query that didn't return it), which
1091
+ * is the behaviour this component has always had.
1092
+ */
1093
+ _initialActiveIndex() {
1094
+ const items = this._filteredItems;
1095
+ if (!items.length) return -1;
1096
+ const index = items.findIndex((item) => this._isSameValue(this._resolveItemValue(item), this.value));
1097
+ return index >= 0 ? index : 0;
1098
+ }
1099
+ get _filteredItems() {
1100
+ if (this._grouped) return this._groupRows().filter((r) => r.kind === "item").map((r) => r.item);
1101
+ return this._visibleItems.filter((item) => this._searchMatches(item));
1102
+ }
1103
+ get _hasValue() {
1104
+ return this.value !== null && this.value !== void 0 && this.value !== "";
1105
+ }
1106
+ get _hasLabelSlot() {
1107
+ return this._hasLabelSlotState;
1108
+ }
1109
+ get _hasHelperSlot() {
1110
+ return this._hasHelperSlotState;
1111
+ }
1112
+ get _hasPrefixSlot() {
1113
+ return this._hasPrefixSlotState;
1114
+ }
1115
+ get _hasSuffixSlot() {
1116
+ return this._hasSuffixSlotState;
1117
+ }
1118
+ get _resolvedValidationState() {
1119
+ if (this.validationState && this.validationState !== "default") return this.validationState;
1120
+ if (this.errorMessage) return "invalid";
1121
+ if (this.successMessage) return "valid";
1122
+ return "default";
1123
+ }
1124
+ get _wrapperClasses() {
1125
+ return [
1126
+ "mono-select",
1127
+ this.size,
1128
+ this.color,
1129
+ this.variant,
1130
+ this._open ? "open" : "",
1131
+ this.disabled ? "disabled" : "",
1132
+ this.readonly ? "readonly" : "",
1133
+ this._resolvedValidationState !== "default" ? `is-${this._resolvedValidationState}` : "",
1134
+ this._hasValue ? "has-value" : "",
1135
+ this._hasPrefixSlot ? "has-prefix" : "",
1136
+ this._hasSuffixSlot ? "has-suffix" : "",
1137
+ this.cssClassName,
1138
+ this.cssClass?.root
1139
+ ].filter(Boolean).join(" ");
1140
+ }
1141
+ get _triggerClasses() {
1142
+ return [
1143
+ this._cls("mono-select-trigger", "trigger"),
1144
+ this.size,
1145
+ this.color,
1146
+ this.variant,
1147
+ this.disabled ? "disabled" : "",
1148
+ this.readonly ? "readonly" : ""
1149
+ ].filter(Boolean).join(" ");
1150
+ }
1151
+ _createModelDetail(args) {
1152
+ return {
1153
+ modelValue: args.modelValue,
1154
+ currentValue: args.modelValue,
1155
+ oldValue: args.oldValue,
1156
+ value: args.modelValue,
1157
+ selectedItem: this._leafItems.find((item) => this._isSameValue(this._resolveItemValue(item), args.modelValue)),
1158
+ sourceEvent: args.sourceEvent
1159
+ };
1160
+ }
1161
+ _emitChange(detail) {
1162
+ dispatchMonoEvent(this, "change", detail);
1163
+ }
1164
+ _emitClear(detail) {
1165
+ dispatchMonoEvent(this, "clear", detail);
1166
+ }
1167
+ _toggleOpen(event) {
1168
+ event?.stopPropagation();
1169
+ if (this._inert) return;
1170
+ if (this._open) this._close();
1171
+ else this._open = true;
1172
+ }
1173
+ _close() {
1174
+ this._open = false;
1175
+ this._resetSearch();
1176
+ }
1177
+ _handleTriggerKeydown(event) {
1178
+ if (this._retryKey(event)) return;
1179
+ if (this._inert) return;
1180
+ if (!this._open) {
1181
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
1182
+ event.preventDefault();
1183
+ this._open = true;
1184
+ return;
1185
+ }
1186
+ if (event.key === "Escape") {
1187
+ event.preventDefault();
1188
+ this._close();
1189
+ }
1190
+ return;
1191
+ }
1192
+ this._handleListKeydown(event);
1193
+ }
1194
+ /**
1195
+ * ↑/↓/Enter/Esc against the rendered list, shared by the trigger and the
1196
+ * search field so both modes navigate identically.
1197
+ */
1198
+ _handleListKeydown(event) {
1199
+ const items = this._filteredItems;
1200
+ if (event.key === "ArrowDown") {
1201
+ event.preventDefault();
1202
+ this._open = true;
1203
+ this._activeIndex = items.length ? Math.min(this._activeIndex + 1, items.length - 1) : -1;
1204
+ return true;
1205
+ }
1206
+ if (event.key === "ArrowUp") {
1207
+ event.preventDefault();
1208
+ this._activeIndex = items.length ? Math.max(this._activeIndex - 1, 0) : -1;
1209
+ return true;
1210
+ }
1211
+ if (event.key === "Enter") {
1212
+ const active = this._activeIndex >= 0 ? items[this._activeIndex] : void 0;
1213
+ if (!active) return false;
1214
+ event.preventDefault();
1215
+ if (!active.disabled) this._selectItem(active, event);
1216
+ return true;
1217
+ }
1218
+ if (event.key === "Escape") {
1219
+ event.preventDefault();
1220
+ this._close();
1221
+ return true;
1222
+ }
1223
+ return false;
1224
+ }
1225
+ /**
1226
+ * Focusing the field selects the shown label so the first keystroke replaces
1227
+ * it (type to filter). It does NOT open the dropdown: a Tab into the field is
1228
+ * not a request to see the list — typing, ↓, a click in the field or the
1229
+ * chevron are.
1230
+ */
1231
+ _handleSearchFocus(event) {
1232
+ if (this._inert) return;
1233
+ event.target.select();
1234
+ }
1235
+ /**
1236
+ * Clicking anywhere in a SEARCHABLE field opens it and focuses the input.
1237
+ * Opens only — a click in a text input while the list is open is caret
1238
+ * placement or text selection, never a toggle; the chevron, Escape and an
1239
+ * outside click close. (The plain trigger is a button that toggles instead:
1240
+ * nothing to type there, so a second click means "close".)
1241
+ */
1242
+ _handleFieldClick(event) {
1243
+ event.stopPropagation();
1244
+ if (this._inert) return;
1245
+ this._open = true;
1246
+ this._searchFieldEl?.focus();
1247
+ }
1248
+ /** Keyboard nav for the search field: arrows move, Enter selects, Esc closes. */
1249
+ _handleSearchKeydown(event) {
1250
+ if (this._retryKey(event)) return;
1251
+ if (this._inert) return;
1252
+ this._handleListKeydown(event);
1253
+ }
1254
+ _selectItem(item, event) {
1255
+ event?.preventDefault();
1256
+ event?.stopPropagation();
1257
+ if (this._inert || item.disabled) return;
1258
+ const oldValue = this.modelValue;
1259
+ const nextValue = this._resolveItemValue(item);
1260
+ this.value = nextValue;
1261
+ this.modelValue = nextValue;
1262
+ this._open = false;
1263
+ this._resetSearch();
1264
+ const detail = this._createModelDetail({
1265
+ modelValue: nextValue,
1266
+ oldValue,
1267
+ sourceEvent: event
1268
+ });
1269
+ this._emitChange(detail);
1270
+ if (this.searchable) this.updateComplete.then(() => this._searchFieldEl?.select());
1271
+ else this._triggerEl?.focus();
1272
+ }
1273
+ _clear(event) {
1274
+ event.preventDefault();
1275
+ event.stopPropagation();
1276
+ if (this._inert) return;
1277
+ const oldValue = this.modelValue;
1278
+ const nextValue = null;
1279
+ this.value = nextValue;
1280
+ this.modelValue = nextValue;
1281
+ this._open = false;
1282
+ this._resetSearch();
1283
+ const detail = this._createModelDetail({
1284
+ modelValue: nextValue,
1285
+ oldValue,
1286
+ sourceEvent: event
1287
+ });
1288
+ this._emitChange(detail);
1289
+ this._emitClear(detail);
1290
+ if (this.searchable) this._searchFieldEl?.focus();
1291
+ }
1292
+ _renderLabel() {
1293
+ const hasContent = !!this.label || this._hasLabelSlot;
1294
+ if (!hasContent && !this._slotsAlwaysRender) return nothing;
1295
+ return html`
1296
+ <label
1297
+ class=${this._cls("mono-select-label", "label")}
1298
+ mono-label
1299
+ for=${this._selectId}
1300
+ ?mono-empty=${!hasContent}
1301
+ >
1302
+ ${this._slotOutlet("label", this.label)}
1303
+
1304
+ ${this.required ? html`
1305
+ <span class=${this._cls("mono-select-required", "required")} mono-required-mark>
1306
+ *
1307
+ </span>
1308
+ ` : nothing}
1309
+ ${this._tooltip.render()}
1310
+ </label>
1311
+ `;
1312
+ }
1313
+ _renderValue() {
1314
+ if (this._dsInert) {
1315
+ const err = this._failure;
1316
+ return html`
1317
+ <span
1318
+ class=${`${this._cls("mono-select-value", "value")} ${this._cls("mono-select-placeholder", "placeholder")}`}
1319
+ mono-value
1320
+ mono-placeholder
1321
+ ?mono-ds-error-text=${!!err}
1322
+ title=${ifDefined(err ? errorTitle(err) : void 0)}
1323
+ >
1324
+ ${err ? errorText(err) : this.loadingText}
1325
+ </span>
1326
+ `;
1327
+ }
1328
+ if (this._hasValue && this._selectedItem) return html`
1329
+ <span class=${this._cls("mono-select-value", "value")} mono-value>
1330
+ ${this._hasPrefixSlot || this._slotsAlwaysRender ? this._slotOutlet("prefix") : nothing}
1331
+
1332
+ <span>${this._resolveItemDisplay(this._selectedItem)}</span>
1333
+
1334
+ ${this._hasSuffixSlot || this._slotsAlwaysRender ? this._slotOutlet("suffix") : nothing}
1335
+ </span>
1336
+ `;
1337
+ return html`
1338
+ <span
1339
+ class=${`${this._cls("mono-select-value", "value")} ${this._cls("mono-select-placeholder", "placeholder")}`}
1340
+ mono-value
1341
+ mono-placeholder
1342
+ >
1343
+ ${this.placeholder}
1344
+ </span>
1345
+ `;
1346
+ }
1347
+ /** A single selectable option button. `index` drives the keyboard-active highlight. */
1348
+ _renderOption(item, index = -1) {
1349
+ const selected = this._isSameValue(this._resolveItemValue(item), this.value);
1350
+ const active = index >= 0 && index === this._activeIndex;
1351
+ const itemClass = [
1352
+ this._cls("mono-select-item", "item"),
1353
+ active ? `active ${this.cssClass?.itemActive ?? ""}` : "",
1354
+ selected ? `selected ${this.cssClass?.itemSelected ?? ""}` : "",
1355
+ item.disabled ? `disabled ${this.cssClass?.itemDisabled ?? ""}` : ""
1356
+ ].filter(Boolean).join(" ");
1357
+ return html`
1358
+ <button
1359
+ type="button"
1360
+ role="option"
1361
+ class=${itemClass}
1362
+ mono-item
1363
+ ?mono-active=${active}
1364
+ ?mono-selected=${selected}
1365
+ ?mono-disabled=${!!item.disabled}
1366
+ aria-selected=${selected ? "true" : "false"}
1367
+ ?disabled=${item.disabled}
1368
+ @mousedown=${(event) => event.preventDefault()}
1369
+ @click=${(event) => this._selectItem(item, event)}
1370
+ >
1371
+ ${this._resolveItemDisplay(item)}
1372
+ </button>
1373
+ `;
1374
+ }
1375
+ /**
1376
+ * The flat entry list published to `form.items()[key].list`.
1377
+ *
1378
+ * The SAME sequence the component renders — group headers interleaved with their leaves — so a
1379
+ * consumer looping it produces one node per row, in order, with nothing to pair up by hand.
1380
+ *
1381
+ * `key` is `item[keyValue]` via `_resolveItemValue`, the same value selection is keyed by, so
1382
+ * the feature introduces no second notion of a key.
1383
+ */
1384
+ _listEntries() {
1385
+ const rowEntry = (item, level, index) => ({
1386
+ type: "row",
1387
+ key: String(this._resolveItemValue(item) ?? ""),
1388
+ item,
1389
+ level,
1390
+ selected: this._isSameValue(this._resolveItemValue(item), this.value),
1391
+ active: index >= 0 && index === this._activeIndex
1392
+ });
1393
+ if (this._grouped) {
1394
+ const rows = this._groupRows();
1395
+ const leavesFrom = (start, level) => {
1396
+ const out = [];
1397
+ for (let i = start + 1; i < rows.length; i++) {
1398
+ const row = rows[i];
1399
+ if (row.kind === "group") {
1400
+ if (row.level <= level) break;
1401
+ continue;
1402
+ }
1403
+ out.push(row.item);
1404
+ }
1405
+ return out;
1406
+ };
1407
+ let rowLevel = 0;
1408
+ return rows.map((row, i) => {
1409
+ if (row.kind === "group") {
1410
+ rowLevel = row.level + 1;
1411
+ return {
1412
+ type: "group",
1413
+ key: row.key,
1414
+ label: row.label,
1415
+ level: row.level,
1416
+ items: leavesFrom(i, row.level)
1417
+ };
1418
+ }
1419
+ return rowEntry(row.item, rowLevel, row.index);
1420
+ });
1421
+ }
1422
+ return this._filteredItems.map((item, index) => rowEntry(item, 0, index));
1423
+ }
1424
+ /**
1425
+ * Hand the form the current entries — but only a NEW array when they actually moved.
1426
+ *
1427
+ * This runs from `updated()`, so it fires on every render; the controller notifies on a changed
1428
+ * list, and that notify re-renders this control. Returning the same array reference is what keeps
1429
+ * that from looping forever. Identity of `item` counts as a change: a reloaded DataSource hands
1430
+ * back fresh row objects under the same keys, and a consumer reading `e.item.Nama` must see them.
1431
+ */
1432
+ _syncListEntries() {
1433
+ if (!this.dataForm || !this.keyForm) return;
1434
+ const next = this._listEntries();
1435
+ const prev = this._publishedEntries;
1436
+ let same = prev.length === next.length;
1437
+ if (same) for (let i = 0; i < next.length; i++) {
1438
+ const a = prev[i];
1439
+ const b = next[i];
1440
+ if (a.type !== b.type || a.key !== b.key || a.item !== b.item || a.selected !== b.selected || a.active !== b.active) {
1441
+ same = false;
1442
+ break;
1443
+ }
1444
+ }
1445
+ if (same) return;
1446
+ this._publishedEntries = next;
1447
+ this._publishList(next);
1448
+ }
1449
+ /**
1450
+ * Where the consumer's `slot="list"` content goes — overridden per build.
1451
+ *
1452
+ * mono places that content as ONE block and never reaches inside it. Moving a `v-for`'s children
1453
+ * is not survivable: Vue inserts a new row with `parent.insertBefore(node, anchor)` where the
1454
+ * anchor is the existing node at that position, so relocating one makes the consumer's next
1455
+ * insert throw `NotFoundError`. Hence one block, and hence `data-mono-item-key` — with the rows
1456
+ * out of reach, a delegated click is the only way back to the item.
1457
+ */
1458
+ renderListSlot() {
1459
+ return html``;
1460
+ }
1461
+ /**
1462
+ * The entry a click landed on — by POSITION, so a consumer writes a plain `v-for` and nothing else.
1463
+ *
1464
+ * mono cannot read a node's item off the node: it published the list, but the DOM was built by
1465
+ * someone else. What it CAN do is count. The wrapper's Nth element child is the Nth published
1466
+ * entry, because that is the contract the slot already states — one flat loop, one node per entry,
1467
+ * in order — and here that contract is CHECKED rather than assumed: if the two lengths disagree,
1468
+ * this refuses the click instead of picking the wrong row.
1469
+ *
1470
+ * Position is resolved at CLICK time, never cached. That is what makes it safe with no observer
1471
+ * and no re-stamping: by the time a human clicks, the framework has long finished patching, so
1472
+ * there is no window where a stale pairing could be consulted.
1473
+ *
1474
+ * `data-mono-item-key` still wins when present — the escape hatch for a wrapper that cannot emit
1475
+ * exactly one element per entry (a sticky header of your own, a row that renders as a fragment).
1476
+ */
1477
+ /**
1478
+ * The consumer's `slot="list"` wrapper, wherever the build keeps it.
1479
+ *
1480
+ * The shadow build never moves it — it stays a light child of the host and is PROJECTED — so
1481
+ * that is the default. The light build moves it into the panel and overrides this with the node
1482
+ * it captured, which is also findable while the panel is closed and the wrapper parked.
1483
+ */
1484
+ get _listSlotWrapper() {
1485
+ return this._lateListSlot.find();
1486
+ }
1487
+ /**
1488
+ * Re-decorate whenever the consumer re-renders their rows.
1489
+ *
1490
+ * `_syncListChrome()` runs from `updated()`, but a framework patches the wrapper on ITS next
1491
+ * tick — so a row the search box just revealed would be born undecorated and stay that way until
1492
+ * something else made mono render. This closes that gap.
1493
+ *
1494
+ * `childList` on the WRAPPER, which is exactly the churn {@link LateSlotWatcher} refuses on the
1495
+ * host: there the consumer's row traffic is noise, here it IS the signal. No `subtree`, so
1496
+ * mono's own attribute writes cannot re-trigger it.
1497
+ */
1498
+ _watchListChrome() {
1499
+ if (typeof MutationObserver === "undefined") return;
1500
+ const wrapper = this._listSlotWrapper;
1501
+ if (!wrapper || wrapper === this._observedListWrapper) return;
1502
+ this._listChromeObserver?.disconnect();
1503
+ this._observedListWrapper = wrapper;
1504
+ this._listChromeObserver = new MutationObserver(() => this._syncListChrome());
1505
+ this._listChromeObserver.observe(wrapper, { childList: true });
1506
+ }
1507
+ _stopWatchingListChrome() {
1508
+ this._listChromeObserver?.disconnect();
1509
+ this._listChromeObserver = void 0;
1510
+ this._observedListWrapper = void 0;
1511
+ }
1512
+ /**
1513
+ * Decorate the lines the consumer rendered — the half of this feature that keeps the SLOT about
1514
+ * markup and mono about behaviour.
1515
+ *
1516
+ * Your template says what a line LOOKS like; the state it is in is stamped here, as `data-*`
1517
+ * attributes, so nothing in that template has to bind `e.selected` or `e.active` to say so.
1518
+ *
1519
+ * Where the tag input also injects a checkbox, this stops at the stamp: a select carries no
1520
+ * checkbox and no select-all, so there is no chrome to put anywhere.
1521
+ *
1522
+ * Three rules keep this safe beside a framework:
1523
+ * · children are ANNOTATED only, never moved, reordered or removed — every `insertBefore`
1524
+ * anchor a `v-for` patches against stays valid;
1525
+ * · `data-*` only, never `class` — `class` is the consumer's binding and would be clobbered;
1526
+ * · nothing is stamped unless the lines and the published entries match 1:1.
1527
+ */
1528
+ _syncListChrome() {
1529
+ if (!this._hasListSlotState) return;
1530
+ const wrapper = this._listSlotWrapper;
1531
+ if (!wrapper) return;
1532
+ const children = Array.from(wrapper.children);
1533
+ const entries = this._publishedEntries;
1534
+ if (children.length !== entries.length) return;
1535
+ for (let i = 0; i < children.length; i++) {
1536
+ const node = children[i];
1537
+ const entry = entries[i];
1538
+ node.setAttribute("data-mono-type", entry.type);
1539
+ node.setAttribute("data-mono-level", String(entry.level ?? 0));
1540
+ node.setAttribute("data-mono-key", entry.key);
1541
+ if (entry.type === "group") continue;
1542
+ node.toggleAttribute("data-mono-selected", !!entry.selected);
1543
+ node.toggleAttribute("data-mono-active", !!entry.active);
1544
+ }
1545
+ }
1546
+ _entryFromNode(node) {
1547
+ const keyed = node.closest?.("[data-mono-item-key]");
1548
+ if (keyed) {
1549
+ const key = keyed.getAttribute("data-mono-item-key");
1550
+ return this._publishedEntries.find((e) => e.type === "row" && e.key === key);
1551
+ }
1552
+ const wrapper = node.closest?.("[slot=\"list\"]");
1553
+ if (!wrapper) return void 0;
1554
+ let row = node;
1555
+ while (row && row.parentElement !== wrapper) row = row.parentElement;
1556
+ if (!row) return void 0;
1557
+ const children = Array.from(wrapper.children);
1558
+ if (children.length !== this._publishedEntries.length) {
1559
+ console.warn(`[mono-select] slot="list" rendered ${children.length} element(s) for ${this._publishedEntries.length} option(s), so a click cannot be paired with its row. Render exactly one element per entry, or put data-mono-item-key="<entry.key>" on each row.`);
1560
+ return;
1561
+ }
1562
+ const entry = this._publishedEntries[children.indexOf(row)];
1563
+ return entry?.type === "row" ? entry : void 0;
1564
+ }
1565
+ _emptyRow() {
1566
+ const text = this.dataSource && this._ds.loading ? this.loadingText : this._query ? "No matches" : "No items";
1567
+ return html`<div class=${this._cls("mono-select-item disabled", "itemDisabled")} mono-empty-row>${text}</div>`;
1568
+ }
1569
+ _renderItems() {
1570
+ if (this._hasListSlotState) return html`
1571
+ ${this._filteredItems.length ? nothing : this._emptyRow()}
1572
+ ${this.renderListSlot()}
1573
+ `;
1574
+ if (this._grouped) {
1575
+ const rows = this._groupRows();
1576
+ if (!rows.some((r) => r.kind === "item")) return this._emptyRow();
1577
+ return html`
1578
+ ${rows.map((row) => row.kind === "group" ? html`<div
1579
+ class=${`${this._cls("mono-select-group", "group")}${this.groupSticky ? " sticky" : ""}`}
1580
+ mono-group
1581
+ mono-level=${row.level}
1582
+ ?mono-sticky=${this.groupSticky}
1583
+ data-level=${row.level}
1584
+ role="presentation"
1585
+ >
1586
+ ${row.label}
1587
+ </div>` : this._renderOption(row.item, row.index))}
1588
+ `;
1589
+ }
1590
+ const items = this._visibleItems.filter((item) => this._searchMatches(item));
1591
+ if (!items.length) return this._emptyRow();
1592
+ return html`${items.map((item, index) => this._renderOption(item, index))}`;
1593
+ }
1594
+ _renderLoadMore() {
1595
+ if (!this._loadMoreMode) return nothing;
1596
+ if (!this._normalizedItems.length || this._ds.atLastPage) return nothing;
1597
+ if (this._ds.loadingMore) return html`
1598
+ <div class=${`${this._cls("mono-select-load-more", "loadMore")} loading`} mono-load-more mono-loading>
1599
+ Loading…
1600
+ </div>
1601
+ `;
1602
+ if (this._loadMoreMode !== "button") return nothing;
1603
+ return html`
1604
+ <button
1605
+ type="button"
1606
+ class=${this._cls("mono-select-load-more", "loadMore")}
1607
+ mono-load-more
1608
+ @click=${(event) => {
1609
+ event.preventDefault();
1610
+ event.stopPropagation();
1611
+ this._ds.loadMore();
1612
+ }}
1613
+ >
1614
+ Load more
1615
+ </button>
1616
+ `;
1617
+ }
1618
+ _renderHelper() {
1619
+ const base = this._cls("mono-select-message", "message");
1620
+ if (this._validation.moved) return html`${this._renderHelperText()}${this._renderMovedMessage()}`;
1621
+ if (this.validationMessage) {
1622
+ const state = this._resolvedValidationState;
1623
+ return html`
1624
+ <div class=${`${base} ${state}`} mono-message=${state} role=${state === "invalid" ? "alert" : nothing}>
1625
+ ${this.validationMessage}
1626
+ </div>
1627
+ `;
1628
+ }
1629
+ if (this.errorMessage) return html`
1630
+ <div class=${`${base} invalid`} mono-message="invalid" role="alert">
1631
+ ${this.errorMessage}
1632
+ </div>
1633
+ `;
1634
+ if (this.successMessage) return html`
1635
+ <div class=${`${base} valid`} mono-message="valid">
1636
+ ${this.successMessage}
1637
+ </div>
1638
+ `;
1639
+ return this._renderHelperText();
1640
+ }
1641
+ _renderHelperText() {
1642
+ const base = this._cls("mono-select-message", "message");
1643
+ if (this.helperText || this._hasHelperSlot || this._slotsAlwaysRender) return html`
1644
+ <div
1645
+ class=${`${base} helper`}
1646
+ mono-message="helper"
1647
+ ?mono-empty=${!this.helperText && !this._hasHelperSlot}
1648
+ >
1649
+ ${this._slotOutlet("helper", this.helperText)}
1650
+ </div>
1651
+ `;
1652
+ return nothing;
1653
+ }
1654
+ /** Shared clear (×) / chevron action. `toggleArrow` lets the chevron close an
1655
+ * already-open dropdown in the searchable field (the plain button toggles itself).
1656
+ *
1657
+ * ONE glyph, matching `<mono-tag-input>` and `<mono-dropdown-table>`: with a
1658
+ * clearable value the corner shows `✕` alone — clearing is the useful action
1659
+ * then, and the chevron would only say what the value already says. Without a
1660
+ * value (or `clearable` off) the chevron is alone. `disabled` / `readonly`
1661
+ * show NEITHER: the field refuses every gesture, so a chevron would promise an
1662
+ * open it never delivers. The actions span still renders so the gutter — and
1663
+ * the text beside it — stays put when the state toggles.
1664
+ *
1665
+ * THE VALUE WINS over the open state. On a searchable field the chevron is the
1666
+ * only mouse way to CLOSE — the body OPENS but never closes there (a click in a
1667
+ * text input is caret placement, not a toggle) — and it was once kept beside
1668
+ * `✕` for exactly that reason. The rule now is the simpler one: while there is
1669
+ * something to clear there is no chevron, open or closed; a searchable field
1670
+ * with a value closes by picking, Escape or an outside click. `role="button"`
1671
+ * on a span, not a `<button>`: the plain trigger is itself a button and buttons
1672
+ * do not nest. `mousedown.preventDefault` keeps focus where it is (the search
1673
+ * input); `_toggleOpen` stops propagation so the plain trigger does not toggle
1674
+ * a second time. */
1675
+ _renderActions() {
1676
+ if (this._busy) return html`
1677
+ <span class=${this._cls("mono-select-actions", "actions")} mono-actions>
1678
+ <span mono-ds-spinner role="status" aria-label=${this.loadingText}>${spinnerIcon()}</span>
1679
+ </span>
1680
+ `;
1681
+ if (this._failure && !this.disabled) return html`
1682
+ <span class=${this._cls("mono-select-actions", "actions")} mono-actions>
1683
+ <span
1684
+ role="button"
1685
+ tabindex="-1"
1686
+ mono-ds-retry
1687
+ aria-label=${this.retryLabel}
1688
+ title=${this.retryLabel}
1689
+ @mousedown=${(event) => event.preventDefault()}
1690
+ @click=${this._retry}
1691
+ >
1692
+ ${refreshIcon()}
1693
+ </span>
1694
+ </span>
1695
+ `;
1696
+ const inert = this._inert;
1697
+ const canClear = this.clearable && this._hasValue && !inert;
1698
+ const showArrow = !inert && !canClear;
1699
+ return html`
1700
+ <span class=${this._cls("mono-select-actions", "actions")} mono-actions>
1701
+ ${canClear ? html`
1702
+ <span
1703
+ role="button"
1704
+ tabindex="-1"
1705
+ class=${this._cls("mono-select-clear", "clear")}
1706
+ mono-clear
1707
+ aria-label="Clear select"
1708
+ @mousedown=${(event) => event.preventDefault()}
1709
+ @click=${this._clear}
1710
+ >
1711
+ ${this.renderIcon("close")}
1712
+ </span>
1713
+ ` : nothing}
1714
+ ${showArrow ? html`
1715
+ <span
1716
+ role="button"
1717
+ tabindex="-1"
1718
+ class=${this._cls("mono-select-arrow", "arrow")}
1719
+ mono-arrow
1720
+ aria-label="Toggle options"
1721
+ aria-expanded=${this._open ? "true" : "false"}
1722
+ @mousedown=${(event) => event.preventDefault()}
1723
+ @click=${this._toggleOpen}
1724
+ >
1725
+ ${this.renderIcon("chevron")}
1726
+ </span>
1727
+ ` : nothing}
1728
+ </span>
1729
+ `;
1730
+ }
1731
+ /** Plain trigger: a button showing the selected value (non-searchable). */
1732
+ _renderButtonTrigger(ariaLabel, describedBy) {
1733
+ return html`
1734
+ <button
1735
+ id=${this._selectId}
1736
+ type="button"
1737
+ class=${this._triggerClasses}
1738
+ mono-trigger
1739
+ ?disabled=${this.disabled}
1740
+ aria-haspopup="listbox"
1741
+ aria-expanded=${this._open ? "true" : "false"}
1742
+ aria-controls=${this._listboxId}
1743
+ aria-label=${ifDefined(ariaLabel)}
1744
+ aria-describedby=${ifDefined(describedBy)}
1745
+ @click=${this._toggleOpen}
1746
+ @keydown=${this._handleTriggerKeydown}
1747
+ >
1748
+ ${this._renderValue()}
1749
+ ${this._validation.renderIcon()}
1750
+ ${this._renderActions()}
1751
+ </button>
1752
+ `;
1753
+ }
1754
+ /** Searchable trigger: the field itself is a search input (combobox). */
1755
+ _renderSearchTrigger(ariaLabel, describedBy) {
1756
+ return html`
1757
+ <div class=${this._triggerClasses} mono-trigger @click=${this._handleFieldClick}>
1758
+ <input
1759
+ id=${this._selectId}
1760
+ class=${this._cls("mono-select-search-field", "searchField")}
1761
+ mono-search-field
1762
+ type="text"
1763
+ role="combobox"
1764
+ autocomplete="off"
1765
+ aria-haspopup="listbox"
1766
+ aria-expanded=${this._open ? "true" : "false"}
1767
+ aria-controls=${this._listboxId}
1768
+ aria-label=${ifDefined(ariaLabel)}
1769
+ aria-describedby=${ifDefined(describedBy)}
1770
+ .value=${this._dsInert ? "" : this._searchActive ? this._query : this._displayText}
1771
+ placeholder=${this._fieldPlaceholder}
1772
+ ?disabled=${this.disabled}
1773
+ ?readonly=${this.readonly || this._dsInert}
1774
+ @input=${this._onSearchInput}
1775
+ @focus=${this._handleSearchFocus}
1776
+ @keydown=${this._handleSearchKeydown}
1777
+ />
1778
+ ${this._validation.renderIcon()}
1779
+ ${this._renderActions()}
1780
+ </div>
1781
+ `;
1782
+ }
1783
+ /** Inline sizing applied to the root wrapper. */
1784
+ _sizeStyle() {
1785
+ return buildSizeStyle(this);
1786
+ }
1787
+ render() {
1788
+ const describedBy = this.validationMessage || this.errorMessage || this.successMessage || this.helperText || this._hasHelperSlot ? this._messageId : void 0;
1789
+ const ariaLabel = this.ariaLabelText || this.label || this.placeholder || void 0;
1790
+ const state = this._resolvedValidationState;
1791
+ return html`
1792
+ <div
1793
+ class=${this._wrapperClasses}
1794
+ style=${styleMap(this._sizeStyle())}
1795
+ mono-select
1796
+ mono-size=${this.size === "md" ? nothing : this.size}
1797
+ mono-color=${this.color === "primary" ? nothing : this.color}
1798
+ mono-variant=${this.variant === "outlined" ? nothing : this.variant}
1799
+ mono-label-position=${this.labelPosition === "top" ? nothing : this.labelPosition}
1800
+ mono-validation-position=${this.validationPosition === "bottom" ? nothing : this.validationPosition}
1801
+ mono-validation-state=${state === "default" ? nothing : state}
1802
+ ?mono-open=${this._open}
1803
+ ?mono-disabled=${this.disabled}
1804
+ ?mono-readonly=${this.readonly}
1805
+ ?mono-required=${this.required}
1806
+ ?mono-clearable=${this.clearable}
1807
+ ?mono-searchable=${this.searchable}
1808
+ ?mono-ds-loading=${this._busy}
1809
+ ?mono-ds-error=${!!this._failure}
1810
+ aria-busy=${this._busy ? "true" : nothing}
1811
+ >
1812
+ ${this._renderLabel()}
1813
+
1814
+ ${this.searchable ? this._renderSearchTrigger(ariaLabel, describedBy) : this._renderButtonTrigger(ariaLabel, describedBy)}
1815
+
1816
+ <div
1817
+ id=${this._listboxId}
1818
+ role="listbox"
1819
+ class=${this._cls("mono-select-dropdown", "dropdown")}
1820
+ mono-dropdown
1821
+ style=${styleMap(this._dropdownPanelStyle)}
1822
+ >
1823
+ <div
1824
+ class=${this._cls("mono-select-dropdown-body", "dropdownBody")}
1825
+ mono-dropdown-body
1826
+ style=${styleMap(this._dropdownBodyStyle)}
1827
+ @scroll=${this._handleDropdownScroll}
1828
+ >
1829
+ ${this._open ? this._renderItems() : nothing}
1830
+ ${this._open ? this._renderLoadMore() : nothing}
1831
+ </div>
1832
+ </div>
1833
+
1834
+ <div id=${this._messageId} class=${this.cssClass?.messageWrap ?? ""} mono-message-wrap>
1835
+ ${this._renderHelper()}
1836
+ </div>
1837
+
1838
+ ${this._validation.renderFloat()}
1839
+ </div>
1840
+ `;
1841
+ }
1842
+ focus(options) {
1843
+ if (this.searchable) this._searchFieldEl?.focus(options);
1844
+ else this._triggerEl?.focus(options);
1845
+ }
1846
+ blur() {
1847
+ if (this.searchable) this._searchFieldEl?.blur();
1848
+ else this._triggerEl?.blur();
1849
+ }
1850
+ /** Whether the dropdown panel is currently open. */
1851
+ get isOpen() {
1852
+ return this._open;
1853
+ }
1854
+ open() {
1855
+ if (this._inert) return;
1856
+ this._open = true;
1857
+ }
1858
+ close() {
1859
+ this._close();
1860
+ }
1861
+ toggle() {
1862
+ if (this._inert) return;
1863
+ if (this._open) this._close();
1864
+ else this._open = true;
1865
+ }
1866
+ /** Coerce a possibly-stringy SSR value to a real boolean (matches
1867
+ * `booleanStringConverter`: a bare / empty attribute means present → true). */
1868
+ _toBoolean(value) {
1869
+ if (typeof value === "boolean") return value;
1870
+ if (value == null) return false;
1871
+ const s = String(value).toLowerCase().trim();
1872
+ return s === "" || s === "true";
1873
+ }
1874
+ /** Whether the slot regions render even when empty. Light: no (omit empty
1875
+ * regions). Shadow: yes — the native `<slot>`s must exist to project DSD
1876
+ * content + be scanned (hidden via `[mono-empty]` when empty). */
1877
+ get _slotsAlwaysRender() {
1878
+ return false;
1879
+ }
1880
+ _hasSlot(name) {
1881
+ return name === "label" ? this._hasLabelSlotState : name === "helper" ? this._hasHelperSlotState : name === "prefix" ? this._hasPrefixSlotState : name === "suffix" ? this._hasSuffixSlotState : this._hasListSlotState;
1882
+ }
1883
+ _setSlotState(name, has) {
1884
+ if (name === "label") this._hasLabelSlotState = has;
1885
+ else if (name === "helper") this._hasHelperSlotState = has;
1886
+ else if (name === "prefix") this._hasPrefixSlotState = has;
1887
+ else if (name === "suffix") this._hasSuffixSlotState = has;
1888
+ else this._hasListSlotState = has;
1889
+ }
1890
+ /**
1891
+ * Slot outlet. Light build (default): a `data-mono-slot` placeholder the
1892
+ * captured light-DOM nodes are re-parented into when present, else the prop
1893
+ * `fallback`. Shadow build overrides this with a native `<slot name>` that
1894
+ * carries the fallback as native slot content.
1895
+ */
1896
+ _slotOutlet(name, fallback = nothing) {
1897
+ return this._hasSlot(name) ? html`<span data-mono-slot=${name}></span>` : html`${fallback}`;
1898
+ }
1899
+ /**
1900
+ * Icon hook. Light build (default): the global `.mono-icon` / `i-mdi-*` UnoCSS
1901
+ * icon. Shadow overrides with inline SVG (UnoCSS can't reach a shadow root).
1902
+ */
1903
+ renderIcon(name) {
1904
+ return html`<span class=${`mono-icon ${name === "close" ? "i-mdi-close" : "i-mdi-chevron-down"}`} mono-icon aria-hidden="true"></span>`;
1905
+ }
1906
+ }
1907
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "size", void 0);
1908
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "color", void 0);
1909
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "variant", void 0);
1910
+ __decorate([property({
1911
+ type: String,
1912
+ attribute: "label-position"
1913
+ })], MonoSelectCoreClass.prototype, "labelPosition", void 0);
1914
+ __decorate([property({
1915
+ type: String,
1916
+ attribute: "validation-position"
1917
+ })], MonoSelectCoreClass.prototype, "validationPosition", void 0);
1918
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "modelValue", void 0);
1919
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "value", void 0);
1920
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "name", void 0);
1921
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "label", void 0);
1922
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "placeholder", void 0);
1923
+ __decorate([property({
1924
+ type: String,
1925
+ attribute: "helper-text"
1926
+ })], MonoSelectCoreClass.prototype, "helperText", void 0);
1927
+ __decorate([property()], MonoSelectCoreClass.prototype, "tooltip", void 0);
1928
+ __decorate([property({
1929
+ type: String,
1930
+ attribute: "validation-state"
1931
+ })], MonoSelectCoreClass.prototype, "validationState", void 0);
1932
+ __decorate([property({
1933
+ type: String,
1934
+ attribute: "validation-message"
1935
+ })], MonoSelectCoreClass.prototype, "validationMessage", void 0);
1936
+ __decorate([property({
1937
+ type: String,
1938
+ attribute: "error-message"
1939
+ })], MonoSelectCoreClass.prototype, "errorMessage", void 0);
1940
+ __decorate([property({
1941
+ type: String,
1942
+ attribute: "success-message"
1943
+ })], MonoSelectCoreClass.prototype, "successMessage", void 0);
1944
+ __decorate([property({
1945
+ type: String,
1946
+ attribute: "aria-label"
1947
+ })], MonoSelectCoreClass.prototype, "ariaLabelText", void 0);
1948
+ __decorate([property({
1949
+ reflect: true,
1950
+ converter: booleanStringConverter
1951
+ })], MonoSelectCoreClass.prototype, "disabled", void 0);
1952
+ __decorate([property({
1953
+ reflect: true,
1954
+ converter: booleanStringConverter
1955
+ })], MonoSelectCoreClass.prototype, "readonly", void 0);
1956
+ __decorate([property({
1957
+ reflect: true,
1958
+ converter: booleanStringConverter
1959
+ })], MonoSelectCoreClass.prototype, "required", void 0);
1960
+ __decorate([property({
1961
+ reflect: true,
1962
+ converter: booleanStringConverter
1963
+ })], MonoSelectCoreClass.prototype, "clearable", void 0);
1964
+ __decorate([property({
1965
+ attribute: false,
1966
+ hasChanged: arrayHasChanged
1967
+ })], MonoSelectCoreClass.prototype, "items", void 0);
1968
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "dataSource", void 0);
1969
+ __decorate([property({
1970
+ attribute: "immediate",
1971
+ reflect: true,
1972
+ converter: booleanStringConverter
1973
+ })], MonoSelectCoreClass.prototype, "immediate", void 0);
1974
+ __decorate([property({
1975
+ attribute: "load-more",
1976
+ reflect: true
1977
+ })], MonoSelectCoreClass.prototype, "loadMore", void 0);
1978
+ __decorate([property({
1979
+ attribute: "page-size",
1980
+ reflect: true,
1981
+ type: Number
1982
+ })], MonoSelectCoreClass.prototype, "pageSize", void 0);
1983
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "dropdown", void 0);
1984
+ __decorate([property({
1985
+ attribute: "dropdown-height",
1986
+ reflect: true
1987
+ })], MonoSelectCoreClass.prototype, "dropdownHeight", void 0);
1988
+ __decorate([property({
1989
+ attribute: "dropdown-max-height",
1990
+ reflect: true
1991
+ })], MonoSelectCoreClass.prototype, "dropdownMaxHeight", void 0);
1992
+ __decorate([property({ converter: booleanStringConverter })], MonoSelectCoreClass.prototype, "flip", void 0);
1993
+ __decorate([property({ converter: booleanStringConverter })], MonoSelectCoreClass.prototype, "shift", void 0);
1994
+ __decorate([property({
1995
+ type: String,
1996
+ attribute: "key-value",
1997
+ reflect: true
1998
+ })], MonoSelectCoreClass.prototype, "keyValue", void 0);
1999
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "displayValue", void 0);
2000
+ __decorate([property({
2001
+ attribute: false,
2002
+ hasChanged: arrayHasChanged
2003
+ })], MonoSelectCoreClass.prototype, "displayGroup", void 0);
2004
+ __decorate([property({
2005
+ type: String,
2006
+ attribute: "group-key",
2007
+ reflect: true
2008
+ })], MonoSelectCoreClass.prototype, "groupKey", void 0);
2009
+ __decorate([property({
2010
+ type: String,
2011
+ attribute: "group-items",
2012
+ reflect: true
2013
+ })], MonoSelectCoreClass.prototype, "groupItems", void 0);
2014
+ __decorate([property({
2015
+ attribute: "group",
2016
+ reflect: true,
2017
+ converter: booleanStringConverter
2018
+ })], MonoSelectCoreClass.prototype, "group", void 0);
2019
+ __decorate([property({
2020
+ attribute: "group-sticky",
2021
+ reflect: true,
2022
+ converter: booleanStringConverter
2023
+ })], MonoSelectCoreClass.prototype, "groupSticky", void 0);
2024
+ __decorate([property({
2025
+ attribute: "searchable",
2026
+ reflect: true,
2027
+ converter: booleanStringConverter
2028
+ })], MonoSelectCoreClass.prototype, "searchable", void 0);
2029
+ __decorate([property({
2030
+ attribute: "stay-open",
2031
+ reflect: true,
2032
+ converter: booleanStringConverter
2033
+ })], MonoSelectCoreClass.prototype, "stayOpen", void 0);
2034
+ __decorate([property({
2035
+ attribute: "search-value",
2036
+ hasChanged: arrayHasChanged
2037
+ })], MonoSelectCoreClass.prototype, "searchValue", void 0);
2038
+ __decorate([property({
2039
+ type: String,
2040
+ attribute: "search-operation"
2041
+ })], MonoSelectCoreClass.prototype, "searchOperation", void 0);
2042
+ __decorate([property({
2043
+ attribute: "search-debounce",
2044
+ type: Number
2045
+ })], MonoSelectCoreClass.prototype, "searchDebounce", void 0);
2046
+ __decorate([property({
2047
+ type: String,
2048
+ attribute: "search-placeholder"
2049
+ })], MonoSelectCoreClass.prototype, "searchPlaceholder", void 0);
2050
+ __decorate([property({
2051
+ type: String,
2052
+ attribute: "loading-text"
2053
+ })], MonoSelectCoreClass.prototype, "loadingText", void 0);
2054
+ __decorate([property({
2055
+ type: String,
2056
+ attribute: "retry-label"
2057
+ })], MonoSelectCoreClass.prototype, "retryLabel", void 0);
2058
+ __decorate([property({ converter: booleanStringConverter })], MonoSelectCoreClass.prototype, "loading", void 0);
2059
+ __decorate([property({ attribute: "loading-error" })], MonoSelectCoreClass.prototype, "loadingError", void 0);
2060
+ __decorate([property({
2061
+ attribute: false,
2062
+ hasChanged: rateLimitHasChanged
2063
+ })], MonoSelectCoreClass.prototype, "cssClass", void 0);
2064
+ __decorate([property({ attribute: false })], MonoSelectCoreClass.prototype, "cssClassName", void 0);
2065
+ __decorate([state()], MonoSelectCoreClass.prototype, "_open", void 0);
2066
+ __decorate([state()], MonoSelectCoreClass.prototype, "_query", void 0);
2067
+ __decorate([state()], MonoSelectCoreClass.prototype, "_searchActive", void 0);
2068
+ __decorate([state()], MonoSelectCoreClass.prototype, "_activeIndex", void 0);
2069
+ __decorate([state()], MonoSelectCoreClass.prototype, "_scrollAutoMaxHeight", void 0);
2070
+ __decorate([state()], MonoSelectCoreClass.prototype, "_hasLabelSlotState", void 0);
2071
+ __decorate([state()], MonoSelectCoreClass.prototype, "_hasHelperSlotState", void 0);
2072
+ __decorate([state()], MonoSelectCoreClass.prototype, "_hasPrefixSlotState", void 0);
2073
+ __decorate([state()], MonoSelectCoreClass.prototype, "_hasSuffixSlotState", void 0);
2074
+ __decorate([state()], MonoSelectCoreClass.prototype, "_hasListSlotState", void 0);
2075
+ __decorate([query(".mono-select-search-field")], MonoSelectCoreClass.prototype, "_searchFieldEl", void 0);
2076
+ __decorate([query(".mono-select-trigger")], MonoSelectCoreClass.prototype, "_triggerEl", void 0);
2077
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "width", void 0);
2078
+ __decorate([property({ type: String })], MonoSelectCoreClass.prototype, "height", void 0);
2079
+ __decorate([property({
2080
+ type: String,
2081
+ attribute: "min-width"
2082
+ })], MonoSelectCoreClass.prototype, "minWidth", void 0);
2083
+ __decorate([property({
2084
+ type: String,
2085
+ attribute: "max-width"
2086
+ })], MonoSelectCoreClass.prototype, "maxWidth", void 0);
2087
+ __decorate([property({
2088
+ type: String,
2089
+ attribute: "min-height"
2090
+ })], MonoSelectCoreClass.prototype, "minHeight", void 0);
2091
+ __decorate([property({
2092
+ type: String,
2093
+ attribute: "max-height"
2094
+ })], MonoSelectCoreClass.prototype, "maxHeight", void 0);
2095
+ return MonoSelectCoreClass;
2096
+ };
2097
+
2098
+ //#endregion
2099
+ //#region src/components/select/select-utils.ts
2100
+ function validateSelectProps(props) {
2101
+ const validSizes = [
2102
+ "xs",
2103
+ "sm",
2104
+ "md",
2105
+ "lg",
2106
+ "xl",
2107
+ "xxl"
2108
+ ];
2109
+ const validColors = [
2110
+ "primary",
2111
+ "secondary",
2112
+ "success",
2113
+ "danger",
2114
+ "warning",
2115
+ "info",
2116
+ "teal",
2117
+ "purple",
2118
+ "neutral",
2119
+ "dark"
2120
+ ];
2121
+ const validVariants = [
2122
+ "outlined",
2123
+ "filled",
2124
+ "underlined"
2125
+ ];
2126
+ const validStates = [
2127
+ "default",
2128
+ "valid",
2129
+ "invalid",
2130
+ "warning"
2131
+ ];
2132
+ if (props.size && !validSizes.includes(props.size)) return false;
2133
+ if (props.color && !validColors.includes(props.color)) return false;
2134
+ if (props.variant && !validVariants.includes(props.variant)) return false;
2135
+ if (props.validationState && !validStates.includes(props.validationState)) return false;
2136
+ return true;
2137
+ }
2138
+
2139
+ //#endregion
2140
+ export { MonoSelectCore as n, validateSelectProps as t };