@mono-lit/helper 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/accordion-core-DaNyPWRr.js +277 -0
  2. package/dist/alert-core-DWtHbX_-.js +134 -0
  3. package/dist/breadcrumb-list-core-Dh68T8Cf.js +747 -0
  4. package/dist/{mono-button-CLkHg3WW.js → button-core-DerXg84Q.js} +3 -276
  5. package/dist/button-dropdown-core-DKYEOyjy.js +654 -0
  6. package/dist/button-utils-D5-66M02.js +63 -0
  7. package/dist/card-utils-l80qAmrN.js +312 -0
  8. package/dist/chart-core-DV7hD3ZY.js +801 -0
  9. package/dist/checkbox-utils-DKdrhAzF.js +337 -0
  10. package/dist/chip-utils-DpRrd7Zj.js +403 -0
  11. package/dist/{data-source-controller-C6C4ej_M.js → data-source-controller-DS_JLrmS.js} +1 -1
  12. package/dist/{data-source-options-fxT5QIX4.js → data-source-options-DJUNU26C.js} +6 -6
  13. package/dist/{data-source-read-BL266IW0.js → data-source-read-uKJpRAzx.js} +2 -2
  14. package/dist/date-core-CyuSyvvG.js +734 -0
  15. package/dist/drawer-core-DbFWvNln.js +759 -0
  16. package/dist/dropdown-core-BCH1pK14.js +511 -0
  17. package/dist/dropdown-table-core-SfHUJVFq.js +1342 -0
  18. package/dist/export.js +1 -1
  19. package/dist/{field-path-BabiMrrA.js → field-path-DK2VBWHl.js} +1 -1
  20. package/dist/field-tooltip.css_raw-DVblekCl.js +5 -0
  21. package/dist/{field-validation.css_raw-Cx6SGrfP.js → field-validation.css_raw-BU7C6173.js} +1 -170
  22. package/dist/file-upload-core-Cz_dBQMx.js +624 -0
  23. package/dist/filter-builder-core-DyWjbBP7.js +381 -0
  24. package/dist/{filter-builder-B6Y2mmbM.js → filter-builder-vbqIuPhX.js} +1 -1
  25. package/dist/{filter-eval-ChDSaAUH.js → filter-eval-CqeBnMYh.js} +1 -1
  26. package/dist/{filter-odata-Bi27XUpQ.js → filter-odata-GsczYKNC.js} +1 -1
  27. package/dist/{hydration-flush-BaE9gN3C.js → hydration-flush-Volt9X7Q.js} +2 -1071
  28. package/dist/index.js +11 -11
  29. package/dist/{index.node-CLqE8F8P.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-DUXo_jxd.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-Bb00QM72.js → mono-data-grid-DJkp96N-.js} +346 -51
  39. package/dist/{mono-event-BPku9HpS.js → mono-event-CAZf0F_k.js} +14 -9
  40. package/dist/{mono-form-controller-BrA10erm.js → mono-form-controller-DxZLvyg6.js} +2 -1
  41. package/dist/mono-table-checkbox-core-BKOMx8R5.js +5288 -0
  42. package/dist/nav-core-B5qU2dzn.js +225 -0
  43. package/dist/{normalize-error-D93O4q8g.js → normalize-error-BEcd1tmS.js} +2 -2
  44. package/dist/otp-core-DXg3vFLh.js +758 -0
  45. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  46. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  47. package/dist/select-utils-BgArYHk8.js +2140 -0
  48. package/dist/sidebar-core-CD6txDo3.js +583 -0
  49. package/dist/slider-core-vcWr321-.js +1014 -0
  50. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  51. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  52. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  55. package/dist/src/components/form/form-types.d.ts +9 -3
  56. package/dist/src/components/input/input-core.d.ts +1 -1
  57. package/dist/src/components/input/input-types.d.ts +1 -1
  58. package/dist/src/components/otp/otp-core.d.ts +1 -1
  59. package/dist/src/components/select/select-core.d.ts +1 -1
  60. package/dist/src/components/select/select-types.d.ts +1 -1
  61. package/dist/src/components/slider/slider-core.d.ts +1 -1
  62. package/dist/src/components/slider/slider-types.d.ts +1 -1
  63. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  64. package/dist/src/components/table/index.d.ts +5 -4
  65. package/dist/src/components/table/index.shadow.d.ts +5 -4
  66. package/dist/src/components/table/mono-data-grid.d.ts +58 -3
  67. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  68. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  69. package/dist/src/components/table/table-types.d.ts +74 -4
  70. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  71. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  72. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  73. package/dist/src/composables/mono-event.d.ts +7 -1
  74. package/dist/src/entries/index.d.ts +8 -6
  75. package/dist/src/entries/index.node.d.ts +8 -6
  76. package/dist/src/search/index.d.ts +1 -1
  77. package/dist/src/utils/group-count.d.ts +36 -0
  78. package/dist/switch-utils-CTZ4id2V.js +338 -0
  79. package/dist/tabs-core-r5-B8k_N.js +317 -0
  80. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  81. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  82. package/dist/ui/accordion.js +2 -274
  83. package/dist/ui/alert.js +2 -131
  84. package/dist/ui/breadcrumb.js +4 -745
  85. package/dist/ui/button-dropdown.js +4 -652
  86. package/dist/ui/button.js +2 -63
  87. package/dist/ui/card.js +2 -310
  88. package/dist/ui/chart.js +4 -800
  89. package/dist/ui/checkbox.js +4 -335
  90. package/dist/ui/chip.js +4 -402
  91. package/dist/ui/date.js +3 -732
  92. package/dist/ui/drawer.js +3 -757
  93. package/dist/ui/dropdown-table.js +5 -1341
  94. package/dist/ui/dropdown.js +4 -509
  95. package/dist/ui/file-upload.js +3 -622
  96. package/dist/ui/filter.js +5 -380
  97. package/dist/ui/form.js +1 -1
  98. package/dist/ui/index.css +130 -5
  99. package/dist/ui/input.js +3 -1141
  100. package/dist/ui/menu.js +4 -828
  101. package/dist/ui/modal.js +4 -843
  102. package/dist/ui/nav.js +4 -223
  103. package/dist/ui/otp.js +4 -756
  104. package/dist/ui/radio.js +4 -317
  105. package/dist/ui/rich-text-editor.js +4 -1278
  106. package/dist/ui/select.js +5 -2138
  107. package/dist/ui/shadow/accordion.js +4 -273
  108. package/dist/ui/shadow/alert.js +4 -130
  109. package/dist/ui/shadow/breadcrumb.js +5 -744
  110. package/dist/ui/shadow/button-dropdown.js +6 -652
  111. package/dist/ui/shadow/button.js +3 -63
  112. package/dist/ui/shadow/card.js +4 -309
  113. package/dist/ui/shadow/chart.js +6 -1576
  114. package/dist/ui/shadow/checkbox.js +4 -334
  115. package/dist/ui/shadow/chip.js +4 -396
  116. package/dist/ui/shadow/date.js +7 -731
  117. package/dist/ui/shadow/drawer.js +4 -756
  118. package/dist/ui/shadow/dropdown-table.js +8 -1762
  119. package/dist/ui/shadow/dropdown.js +4 -508
  120. package/dist/ui/shadow/file-upload.js +4 -620
  121. package/dist/ui/shadow/filter.js +7 -379
  122. package/dist/ui/shadow/input.js +6 -1140
  123. package/dist/ui/shadow/menu.js +5 -828
  124. package/dist/ui/shadow/modal.js +6 -1063
  125. package/dist/ui/shadow/nav.js +5 -223
  126. package/dist/ui/shadow/otp.js +6 -754
  127. package/dist/ui/shadow/radio.js +4 -316
  128. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  129. package/dist/ui/shadow/select.js +6 -2136
  130. package/dist/ui/shadow/sidebar.js +5 -581
  131. package/dist/ui/shadow/slider.js +5 -1010
  132. package/dist/ui/shadow/switch.js +4 -335
  133. package/dist/ui/shadow/table.js +10 -4501
  134. package/dist/ui/shadow/tabs.js +6 -315
  135. package/dist/ui/shadow/tag-input.js +7 -3057
  136. package/dist/ui/shadow/textarea.js +6 -817
  137. package/dist/ui/sidebar.js +3 -581
  138. package/dist/ui/slider.js +4 -1012
  139. package/dist/ui/switch.js +4 -336
  140. package/dist/ui/table.js +9 -4502
  141. package/dist/ui/tabs.js +4 -315
  142. package/dist/ui/tag-input.js +5 -3058
  143. package/dist/ui/textarea.js +4 -819
  144. package/dist/vue.d.ts +3 -2
  145. package/package.json +3 -3
  146. package/dist/chip-strip-bNfLn52Z.js +0 -215
  147. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  148. package/dist/data-source-options-BM4r4PRw.js +0 -558
  149. package/dist/data-source-read-HyBCRg1t.js +0 -242
  150. package/dist/excel-BXzr3uI1.js +0 -784
  151. package/dist/export-ixRdCS2y.js +0 -2388
  152. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  153. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  154. package/dist/filter-builder-C0T3pMx2.js +0 -208
  155. package/dist/form-control-core-BbiACMtL.js +0 -226
  156. package/dist/icon-ClxnYhBG.js +0 -19
  157. package/dist/import-Dc4eXC02.js +0 -612
  158. package/dist/light-slots-_no0r2Z-.js +0 -246
  159. package/dist/markdown-uGlE_OfJ.js +0 -20191
  160. package/dist/mono-data-grid-Bkr-e1-i.js +0 -4043
  161. package/dist/normalize-error-BmLbgANw.js +0 -1048
@@ -0,0 +1,3060 @@
1
+ import { r as readAllRows } from "./data-source-read-uKJpRAzx.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, r as unwrapReactiveDeep } 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 { t as PopupPortalController } from "./popup-portal-CINuVjNY.js";
9
+ import { c as spinnerIcon, i as chevronIcon, n as caretIcon, s as refreshIcon } from "./field-icons-t8rXRCKs.js";
10
+ import { t as MonoFormControlCore } from "./form-control-core-CRZPZ1ru.js";
11
+ import { t as FieldTooltipController } from "./field-tooltip-Csk4s2ve.js";
12
+ import { t as FieldValidationController } from "./field-validation-CFmUmuDx.js";
13
+ import { t as DataSourceController } from "./data-source-controller-DS_JLrmS.js";
14
+ import { n as visibleChipCap, r as ChipStripController, t as resolveChipLimit } from "./chip-limits-CFABydIL.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/tag-input/tag-input-core.ts
21
+ /**
22
+ * What inside the consumer's `slot="select-all"` content keeps its own click. A press on one of
23
+ * these is the consumer's action — a help button, a link — and must not also tick every row.
24
+ */
25
+ const SELECT_ALL_SLOT_INTERACTIVE = "button, a[href], input, select, textarea, label, [contenteditable], [data-mono-no-toggle]";
26
+ const numberStringConverter = {
27
+ fromAttribute(value) {
28
+ if (value === null || value === "") return void 0;
29
+ const parsed = Number(value);
30
+ return Number.isFinite(parsed) ? parsed : void 0;
31
+ },
32
+ toAttribute(value) {
33
+ if (value === void 0 || value === null) return null;
34
+ return String(value);
35
+ }
36
+ };
37
+ /**
38
+ * `MonoTagInputCore` — render-mode-agnostic logic for `mono-tag-input` (props,
39
+ * hybrid aliases, array value/model sync, DataSource paging, search, grouping,
40
+ * keyboard, tag chips, the popup controller, and the full `render()`). SSR-safe:
41
+ * every `document`/`window`/focus access is `isServer`-guarded. Each build
42
+ * supplies `createRenderRoot()` + `static styles`, the slot strategy (light
43
+ * captures children into `data-mono-slot` placeholders; shadow uses native
44
+ * `<slot>`), and the `_slotOutlet` / `renderIcon` hooks. Mirrors `select-core`.
45
+ */
46
+ const MonoTagInputCore = (superClass) => {
47
+ class MonoTagInputCoreClass extends MonoFormControlCore(superClass) {
48
+ constructor(...args) {
49
+ super(...args);
50
+ this.size = "md";
51
+ this.color = "primary";
52
+ this.variant = "outlined";
53
+ this.labelPosition = "top";
54
+ this.validationPosition = "bottom";
55
+ this.chip = {};
56
+ this.modelValue = [];
57
+ this.value = [];
58
+ this.name = "";
59
+ this.label = "";
60
+ this.placeholder = "Add tag...";
61
+ this.loadingText = "Loading…";
62
+ this.retryLabel = "Retry";
63
+ this.loading = false;
64
+ this.loadingError = null;
65
+ this.helperText = "";
66
+ this.tooltip = null;
67
+ this._tooltip = new FieldTooltipController(this, () => this.tooltip);
68
+ this._validation = new FieldValidationController(this, () => ({
69
+ position: this.validationPosition,
70
+ ...this._validationNotice ?? {
71
+ state: void 0,
72
+ text: void 0
73
+ },
74
+ suppressed: this._open || this._moreOpen
75
+ }), () => this.renderRoot?.querySelector?.("[mono-tag-input] > [mono-field]"));
76
+ this.validationState = "default";
77
+ this.validationMessage = "";
78
+ this.errorMessage = "";
79
+ this.successMessage = "";
80
+ this.disabled = false;
81
+ this.readonly = false;
82
+ this.required = false;
83
+ this.clearable = false;
84
+ this.allowCustom = true;
85
+ this.checkable = false;
86
+ this.selectAll = true;
87
+ this.selectAllLabel = "All";
88
+ this.groupSelectAll = true;
89
+ this.items = [];
90
+ this.dataSource = null;
91
+ this.immediate = true;
92
+ this.pageSize = 10;
93
+ this.dropdownHeight = "";
94
+ this.dropdownMaxHeight = "";
95
+ this.flip = true;
96
+ this.shift = true;
97
+ this.keyValue = "";
98
+ this.displayValue = "";
99
+ this.displayGroup = [];
100
+ this.groupKey = "key";
101
+ this.groupItems = "items";
102
+ this.group = false;
103
+ this.groupSticky = false;
104
+ this.searchable = true;
105
+ this.stayOpen = false;
106
+ this.searchValue = "";
107
+ this.searchOperation = "contains";
108
+ this.searchDebounce = 300;
109
+ this.cssClass = {};
110
+ this.cssClassName = "";
111
+ this._inputValue = "";
112
+ this._open = false;
113
+ this._activeIndex = -1;
114
+ this._cursorPending = false;
115
+ this._moreOpen = false;
116
+ this._selectAllPending = false;
117
+ this._scrollAutoMaxHeight = "";
118
+ this._hasLabelSlotState = false;
119
+ this._hasHelperSlotState = false;
120
+ this._chipStrip = new ChipStripController(this, {
121
+ strip: () => this.renderRoot?.querySelector(".mono-tag-input-chip-strip"),
122
+ enabled: () => this._chipBehaviour === "inline"
123
+ });
124
+ this._popup = new PopupPortalController(this, {
125
+ getPanel: () => this.renderRoot.querySelector(".mono-tag-input-dropdown"),
126
+ getAnchor: () => this.renderRoot.querySelector(".mono-tag-input-field"),
127
+ getStyleScope: () => this.renderRoot.querySelector(".mono-tag-input"),
128
+ isOpen: () => this._open,
129
+ matchWidth: () => !toCssSize(this.dropdown?.width),
130
+ offset: () => 6,
131
+ flip: () => this.flip,
132
+ shift: () => this.shift,
133
+ constrainSize: () => true
134
+ });
135
+ this._morePopup = new PopupPortalController(this, {
136
+ getPanel: () => this.renderRoot.querySelector(".mono-tag-input-more-panel"),
137
+ getAnchor: () => this.renderRoot.querySelector(".mono-tag-input-field"),
138
+ getStyleScope: () => this.renderRoot.querySelector(".mono-tag-input"),
139
+ isOpen: () => this._moreOpen,
140
+ matchWidth: () => true,
141
+ offset: () => 6,
142
+ flip: () => true,
143
+ shift: () => true,
144
+ constrainSize: () => true
145
+ });
146
+ this._inputId = `mono-tag-input-${Math.random().toString(36).slice(2)}`;
147
+ this._messageId = `${this._inputId}-message`;
148
+ this._listboxId = `${this._inputId}-listbox`;
149
+ this._lateListSlot = new LateSlotWatcher(this, "list", () => this._onLateListSlot());
150
+ this._lateSelectAllSlot = new LateSlotWatcher(this, "select-all", () => this._onLateSelectAllSlot());
151
+ this._docListenersBound = false;
152
+ this._filteredMemo = null;
153
+ this._filteredMemoOn = false;
154
+ this._selectedSetFor = null;
155
+ this._selectedSetLen = -1;
156
+ this._selectedSetMemo = /* @__PURE__ */ new Set();
157
+ this._sourceSearch = new MonoSourceSearch();
158
+ this._searchVersion = 0;
159
+ this._appliedQuery = "";
160
+ this._appFailure = null;
161
+ this._retry = (event) => {
162
+ event.preventDefault();
163
+ event.stopPropagation();
164
+ const error = this._failure;
165
+ if (this.disabled || this._busy || !error) return;
166
+ const fromApp = !!this._appFailure;
167
+ dispatchMonoEvent(this, "retry", {
168
+ error,
169
+ source: fromApp ? "app" : "dataSource"
170
+ });
171
+ if (fromApp || !this.dataSource || !this._ds.error) return;
172
+ const clearSearch = !!this._appliedQuery && this._serverSearch();
173
+ this._ds.retry(clearSearch ? () => this._pushSearch("") : void 0);
174
+ };
175
+ this._selectedItemCache = /* @__PURE__ */ new Map();
176
+ this._leafIndexKey = [];
177
+ this._leafIndexMemo = /* @__PURE__ */ new Map();
178
+ this._handleDocumentClick = (event) => {
179
+ if (this.stayOpen) return;
180
+ if (!this._open && !this._moreOpen) return;
181
+ const path = event.composedPath();
182
+ if (path.includes(this) || this._popup.containsInPath(path) || this._morePopup.containsInPath(path)) return;
183
+ this._close();
184
+ this._moreOpen = false;
185
+ };
186
+ this._handleDocumentFocusIn = (event) => {
187
+ if (this.stayOpen) return;
188
+ if (!this._open && !this._moreOpen) return;
189
+ const path = event.composedPath();
190
+ if (path.includes(this) || this._popup.containsInPath(path) || this._morePopup.containsInPath(path)) return;
191
+ this._close();
192
+ this._moreOpen = false;
193
+ };
194
+ this._handleDocumentKeydown = (event) => {
195
+ if (!this._open && !this._moreOpen) return;
196
+ if (event.key === "Escape") {
197
+ const path = event.composedPath();
198
+ if (this._popup.inLayerAbove(path) || this._morePopup.inLayerAbove(path)) return;
199
+ event.preventDefault();
200
+ this._close();
201
+ this._moreOpen = false;
202
+ }
203
+ };
204
+ this._renderedChipCount = 0;
205
+ this._measuredChipCount = 0;
206
+ this._handleDropdownScroll = (event) => {
207
+ if (this._loadMoreMode !== "scroll") return;
208
+ if (this._ds.loadingMore || this._ds.atLastPage) return;
209
+ const el = event.currentTarget;
210
+ if (!el) return;
211
+ if (el.scrollHeight - el.scrollTop - el.clientHeight <= 24) this._ds.loadMore();
212
+ };
213
+ this._hasListSlotState = false;
214
+ this._hasSelectAllSlotState = false;
215
+ this._publishedEntries = [];
216
+ this._onListSlotClick = (event) => {
217
+ if (this._inert) return;
218
+ const target = event.target;
219
+ const entry = target ? this._entryFromNode(target) : void 0;
220
+ if (!entry) return;
221
+ if (entry.type === "group") {
222
+ if (!this.checkable || !this.groupSelectAll) return;
223
+ const usable = (entry.items ?? []).filter((item) => this._isSelectable(item));
224
+ if (!usable.length) return;
225
+ event.preventDefault();
226
+ this._toggleMany(usable, event);
227
+ return;
228
+ }
229
+ const item = entry.item;
230
+ if (!item || !this._isSelectable(item)) return;
231
+ event.preventDefault();
232
+ this._toggleValue(this._resolveItemValue(item), event);
233
+ };
234
+ defineHybridPropAliases(this, [
235
+ "labelPosition",
236
+ "validationPosition",
237
+ "modelValue",
238
+ "loadingText",
239
+ "loadingError",
240
+ "helperText",
241
+ "validationState",
242
+ "validationMessage",
243
+ "errorMessage",
244
+ "successMessage",
245
+ "allowCustom",
246
+ "maxVisible",
247
+ "minVisible",
248
+ "ariaLabelText",
249
+ "cssClass",
250
+ "keyValue",
251
+ "displayValue",
252
+ "displayGroup",
253
+ "groupKey",
254
+ "groupItems",
255
+ "groupSticky",
256
+ "groupSelectAll",
257
+ "selectAll",
258
+ "selectAllLabel",
259
+ "searchValue",
260
+ "searchOperation",
261
+ "searchDebounce",
262
+ "dataSource",
263
+ "loadMore",
264
+ "pageSize",
265
+ "dropdownHeight",
266
+ "dropdownMaxHeight",
267
+ "minWidth",
268
+ "maxWidth",
269
+ "minHeight",
270
+ "maxHeight",
271
+ "stayOpen"
272
+ ]);
273
+ /**
274
+ * Vue support:
275
+ *
276
+ * <mono-tag-input :cssClass="{}" />
277
+ * <mono-tag-input :css-class="{}" />
278
+ * <mono-tag-input :cssclass="{}" />
279
+ */
280
+ Object.defineProperty(this, "css-class", {
281
+ get: () => this.cssClass,
282
+ set: (value) => {
283
+ this._setCssClass(value);
284
+ },
285
+ configurable: true,
286
+ enumerable: false
287
+ });
288
+ Object.defineProperty(this, "cssclass", {
289
+ get: () => this.cssClass,
290
+ set: (value) => {
291
+ this._setCssClass(value);
292
+ },
293
+ configurable: true,
294
+ enumerable: false
295
+ });
296
+ Object.defineProperty(this, "ariaLabel", {
297
+ get: () => this.ariaLabelText,
298
+ set: (value) => {
299
+ this.ariaLabelText = value == null ? void 0 : String(value);
300
+ },
301
+ configurable: true,
302
+ enumerable: false
303
+ });
304
+ Object.defineProperty(this, "aria-label", {
305
+ get: () => this.ariaLabelText,
306
+ set: (value) => {
307
+ this.ariaLabelText = value == null ? void 0 : String(value);
308
+ },
309
+ configurable: true,
310
+ enumerable: false
311
+ });
312
+ Object.defineProperty(this, "arialabel", {
313
+ get: () => this.ariaLabelText,
314
+ set: (value) => {
315
+ this.ariaLabelText = value == null ? void 0 : String(value);
316
+ },
317
+ configurable: true,
318
+ enumerable: false
319
+ });
320
+ this._ds = new DataSourceController(this, {
321
+ getDataSource: () => this.dataSource,
322
+ getItems: () => this.items,
323
+ getImmediate: () => this.immediate,
324
+ getLoadMoreMode: () => this._loadMoreMode,
325
+ getPageSize: () => this._pageSize
326
+ });
327
+ }
328
+ static {
329
+ this.monoPendingAuto = "data";
330
+ }
331
+ _monoPendingReady() {
332
+ return !this.dataSource || !this._ds.loading;
333
+ }
334
+ static get observedAttributes() {
335
+ return [
336
+ ...super.observedAttributes ?? [],
337
+ "modelvalue",
338
+ "loadingtext",
339
+ "loadingerror",
340
+ "helpertext",
341
+ "validationstate",
342
+ "validationmessage",
343
+ "errormessage",
344
+ "successmessage",
345
+ "allowcustom",
346
+ "maxvisible",
347
+ "minvisible",
348
+ "arialabeltext",
349
+ "arialabel",
350
+ "css-class",
351
+ "cssclass",
352
+ "chip",
353
+ "keyvalue",
354
+ "displayvalue",
355
+ "display-value",
356
+ "stayopen"
357
+ ];
358
+ }
359
+ attributeChangedCallback(name, oldValue, newValue) {
360
+ super.attributeChangedCallback(name, oldValue, newValue);
361
+ if (oldValue === newValue) return;
362
+ if (name === "modelvalue") {
363
+ this.modelValue = this._normalizeValue(newValue);
364
+ return;
365
+ }
366
+ if (name === "loadingtext") {
367
+ this.loadingText = newValue ?? "Loading…";
368
+ return;
369
+ }
370
+ if (name === "loadingerror") {
371
+ this.loadingError = newValue;
372
+ return;
373
+ }
374
+ if (name === "helpertext") {
375
+ this.helperText = newValue ?? "";
376
+ return;
377
+ }
378
+ if (name === "validationstate") {
379
+ this.validationState = newValue ?? "default";
380
+ return;
381
+ }
382
+ if (name === "validationmessage") {
383
+ this.validationMessage = newValue ?? "";
384
+ return;
385
+ }
386
+ if (name === "errormessage") {
387
+ this.errorMessage = newValue ?? "";
388
+ return;
389
+ }
390
+ if (name === "successmessage") {
391
+ this.successMessage = newValue ?? "";
392
+ return;
393
+ }
394
+ if (name === "allowcustom") {
395
+ this.allowCustom = this._toBoolean(newValue);
396
+ return;
397
+ }
398
+ if (name === "maxvisible") {
399
+ this.maxVisible = this._toOptionalNumber(newValue);
400
+ return;
401
+ }
402
+ if (name === "minvisible") {
403
+ this.minVisible = this._toOptionalNumber(newValue);
404
+ return;
405
+ }
406
+ if (name === "arialabeltext" || name === "arialabel") {
407
+ this.ariaLabelText = newValue ?? void 0;
408
+ return;
409
+ }
410
+ if (name === "keyvalue") {
411
+ this.keyValue = newValue ?? "";
412
+ return;
413
+ }
414
+ if (name === "displayvalue" || name === "display-value") {
415
+ if (typeof this.displayValue === "function") return;
416
+ const next = newValue ?? "";
417
+ if (this._looksLikeSerializedFunction(next)) return;
418
+ this.displayValue = next;
419
+ return;
420
+ }
421
+ if (name === "stayopen") {
422
+ this.stayOpen = this._toBoolean(newValue);
423
+ return;
424
+ }
425
+ if (name === "css-class" || name === "cssclass") {
426
+ this._setCssClass(newValue);
427
+ return;
428
+ }
429
+ if (name === "chip") this._setChip(newValue);
430
+ }
431
+ /** The validation message and its state — the same precedence the message row uses. */
432
+ get _validationNotice() {
433
+ if (this.validationMessage) return {
434
+ state: this._resolvedValidationState,
435
+ text: this.validationMessage
436
+ };
437
+ if (this.errorMessage) return {
438
+ state: "invalid",
439
+ text: this.errorMessage
440
+ };
441
+ if (this.successMessage) return {
442
+ state: "valid",
443
+ text: this.successMessage
444
+ };
445
+ return null;
446
+ }
447
+ /** The message that moved to the icon / float bubble, kept for assistive tech only. */
448
+ _renderMovedMessage() {
449
+ const notice = this._validationNotice;
450
+ return html`<div
451
+ class=${`${this._cls("mono-tag-input-message", "message")} ${notice.state}`}
452
+ mono-message=${notice.state}
453
+ mono-sr-only
454
+ role=${notice.state === "invalid" ? "alert" : nothing}
455
+ >${notice.text}</div>`;
456
+ }
457
+ /**
458
+ * The dropdown panel is relocated into a `<body>` portal while open — query
459
+ * it through `panelRoot` (the portal when adopted, else the host render root).
460
+ */
461
+ get _dropdownEl() {
462
+ return this._popup.panelRoot.querySelector(".mono-tag-input-dropdown");
463
+ }
464
+ /**
465
+ * Where the consumer's list wrapper is parked, through the SAME portal-aware root as the panel
466
+ * itself — while the dropdown is open it lives in a body portal, so a host query would miss it.
467
+ */
468
+ get _listSlotTarget() {
469
+ return this._popup.panelRoot.querySelector("[data-mono-slot=\"list\"]");
470
+ }
471
+ /** Where the consumer's `slot="select-all"` content goes — inside the "All" row, portal-aware. */
472
+ get _selectAllSlotTarget() {
473
+ return this._popup.panelRoot.querySelector("[data-mono-slot=\"select-all\"]");
474
+ }
475
+ /** A late wrapper arrived. Overridden per build — one adopts it, the other rescans. */
476
+ _onLateListSlot() {}
477
+ /** A late select-all wrapper arrived. Overridden per build, like `_onLateListSlot`. */
478
+ _onLateSelectAllSlot() {}
479
+ connectedCallback() {
480
+ super.connectedCallback();
481
+ this._lateListSlot.start();
482
+ this._lateSelectAllSlot.start();
483
+ if (isServer) return;
484
+ if (this._open || this._moreOpen) this._bindDocumentListeners();
485
+ }
486
+ disconnectedCallback() {
487
+ if (this._searchTimer) {
488
+ clearTimeout(this._searchTimer);
489
+ this._searchTimer = void 0;
490
+ }
491
+ this._sourceSearch.clear(this.dataSource);
492
+ this._lateListSlot.stop();
493
+ this._lateSelectAllSlot.stop();
494
+ this._stopWatchingListChrome();
495
+ if (!isServer) this._unbindDocumentListeners();
496
+ super.disconnectedCallback();
497
+ }
498
+ /**
499
+ * Outside-dismiss listeners, bound while a panel is OPEN (the list or the "+N more") rather
500
+ * than on connect — select's rule, same reasoning: all three handlers bail when both panels are
501
+ * closed, so the only cost they carried at rest was dispatch, three `document` listeners per
502
+ * field (two capture-phase) in the path of every click, focus move and keystroke.
503
+ */
504
+ _bindDocumentListeners() {
505
+ if (this._docListenersBound) return;
506
+ this._docListenersBound = true;
507
+ document.addEventListener("click", this._handleDocumentClick, true);
508
+ document.addEventListener("focusin", this._handleDocumentFocusIn, true);
509
+ document.addEventListener("keydown", this._handleDocumentKeydown);
510
+ }
511
+ _unbindDocumentListeners() {
512
+ if (!this._docListenersBound) return;
513
+ this._docListenersBound = false;
514
+ document.removeEventListener("click", this._handleDocumentClick, true);
515
+ document.removeEventListener("focusin", this._handleDocumentFocusIn, true);
516
+ document.removeEventListener("keydown", this._handleDocumentKeydown);
517
+ }
518
+ willUpdate(changed) {
519
+ this._filteredMemo = null;
520
+ this._filteredMemoOn = true;
521
+ for (const key of [
522
+ "disabled",
523
+ "loading",
524
+ "readonly",
525
+ "required",
526
+ "clearable",
527
+ "allowCustom",
528
+ "checkable",
529
+ "immediate",
530
+ "group",
531
+ "groupSticky",
532
+ "searchable",
533
+ "stayOpen",
534
+ "selectAll",
535
+ "groupSelectAll"
536
+ ]) {
537
+ const v = this[key];
538
+ if (typeof v !== "boolean") this[key] = this._toBoolean(v);
539
+ }
540
+ if (changed.has("loadingError")) {
541
+ const v = this.loadingError;
542
+ this._appFailure = v == null || v === false || v === "" ? null : describeError(v === true ? void 0 : v);
543
+ }
544
+ if (this._open && this._dsInert) {
545
+ this._open = false;
546
+ this._dropQuery();
547
+ }
548
+ if (changed.has("modelValue") && !this._arrayEqual(this.value, this.modelValue)) this.value = [...unwrapReactiveDeep(this.modelValue)];
549
+ if (changed.has("value") && !this._arrayEqual(this.modelValue, this.value)) this.modelValue = [...unwrapReactiveDeep(this.value)];
550
+ if (this.group && (changed.has("dataSource") || changed.has("displayGroup") || changed.has("group"))) {
551
+ const fields = this._groupSortFields();
552
+ if (fields.length) this.dataSource?.sort?.(fields);
553
+ }
554
+ if (changed.has("dataSource")) {
555
+ this._ds.bind(this.dataSource);
556
+ this._sourceSearch.bind(this.dataSource);
557
+ }
558
+ if (changed.has("searchValue") || changed.has("searchOperation") || changed.has("displayValue") || changed.has("keyValue") || changed.has("dataSource") || changed.has("items")) this._searchVersion++;
559
+ if (changed.has("immediate")) this._ds.maybeImmediateLoad();
560
+ if (changed.has("loadMore")) this._ds.onLoadMoreModeChange();
561
+ if (changed.has("items") || changed.has("loadMore") || changed.has("pageSize")) this._ds.resetVisible();
562
+ }
563
+ updated(changed) {
564
+ super.updated(changed);
565
+ this._syncListEntries();
566
+ if (isServer) {
567
+ this._endFilteredMemo();
568
+ return;
569
+ }
570
+ if (this._open || this._moreOpen) this._bindDocumentListeners();
571
+ else this._unbindDocumentListeners();
572
+ this._rememberSelected(this._selectedValues);
573
+ if (changed.has("_open")) {
574
+ this._cursorPending = this._open;
575
+ if (!this._open) this._activeIndex = -1;
576
+ }
577
+ if (this._moreOpen && !this._overflowChips.length) this._moreOpen = false;
578
+ if (this._cursorPending && this._open && this._filteredItems.length) {
579
+ this._activeIndex = this._initialActiveIndex();
580
+ this._cursorPending = false;
581
+ }
582
+ if (changed.has("_activeIndex") && this._open && this._activeIndex >= 0) (this._popup.panelRoot?.querySelectorAll?.(".mono-tag-input-item")?.[this._activeIndex])?.scrollIntoView?.({ block: "nearest" });
583
+ this._updateScrollAutoHeight();
584
+ if (changed.has("chip") || changed.has("size") || changed.has("disabled") || changed.has("readonly") || this._renderedChipCount !== this._measuredChipCount) {
585
+ this._measuredChipCount = this._renderedChipCount;
586
+ this._chipStrip.invalidate();
587
+ }
588
+ this._endFilteredMemo();
589
+ }
590
+ _endFilteredMemo() {
591
+ this._filteredMemoOn = false;
592
+ this._filteredMemo = null;
593
+ }
594
+ _setCssClass(value) {
595
+ if (value == null) {
596
+ this.cssClass = {};
597
+ this.cssClassName = "";
598
+ return;
599
+ }
600
+ if (typeof value === "object") {
601
+ this.cssClass = value;
602
+ return;
603
+ }
604
+ if (typeof value === "string") {
605
+ const trimmed = value.trim();
606
+ if (!trimmed) {
607
+ this.cssClass = {};
608
+ this.cssClassName = "";
609
+ return;
610
+ }
611
+ /**
612
+ * Optional HTML object support:
613
+ *
614
+ * <mono-tag-input css-class='{"root":"...", "field":"..."}'></mono-tag-input>
615
+ */
616
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) try {
617
+ this.cssClass = JSON.parse(trimmed);
618
+ return;
619
+ } catch {}
620
+ this.cssClassName = trimmed;
621
+ }
622
+ }
623
+ /**
624
+ * `chip` is an object prop, so the real binding is `:chip.prop="{…}"`. This
625
+ * covers the two ways a *string* can arrive: a hand-written JSON attribute
626
+ * (`chip='{"size":"md"}'`, incl. DSD/SSR), and Vue's `String(value)` mirror of
627
+ * a plain `:chip="{…}"` binding — which yields `"[object Object]"` and must be
628
+ * ignored, or it would wipe the property set moments later.
629
+ */
630
+ _setChip(value) {
631
+ if (value == null) {
632
+ this.chip = {};
633
+ return;
634
+ }
635
+ if (typeof value === "object") {
636
+ this.chip = value;
637
+ return;
638
+ }
639
+ if (typeof value !== "string") return;
640
+ const trimmed = value.trim();
641
+ if (!trimmed) {
642
+ this.chip = {};
643
+ return;
644
+ }
645
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return;
646
+ try {
647
+ this.chip = JSON.parse(trimmed);
648
+ } catch {}
649
+ }
650
+ _toBoolean(value) {
651
+ if (typeof value === "boolean") return value;
652
+ if (typeof value === "string") {
653
+ const normalized = value.toLowerCase().trim();
654
+ return normalized === "" || normalized === "true";
655
+ }
656
+ return Boolean(value);
657
+ }
658
+ _toOptionalNumber(value) {
659
+ if (value === void 0 || value === null || value === "") return void 0;
660
+ const parsed = Number(value);
661
+ return Number.isFinite(parsed) ? parsed : void 0;
662
+ }
663
+ _normalizeValue(value) {
664
+ if (Array.isArray(value)) return value.filter((item) => item !== null && item !== void 0);
665
+ if (typeof value === "string") {
666
+ const trimmed = value.trim();
667
+ if (!trimmed) return [];
668
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) try {
669
+ return this._normalizeValue(JSON.parse(trimmed));
670
+ } catch {
671
+ return [];
672
+ }
673
+ return trimmed.split(",").map((item) => item.trim()).filter(Boolean);
674
+ }
675
+ return [];
676
+ }
677
+ _normalizeItems(items) {
678
+ if (Array.isArray(items)) return items.map((item) => {
679
+ if (typeof item === "object" && item !== null) return item;
680
+ return { value: item };
681
+ });
682
+ if (typeof items === "string") try {
683
+ const parsed = JSON.parse(items);
684
+ return this._normalizeItems(parsed);
685
+ } catch {
686
+ return [];
687
+ }
688
+ return [];
689
+ }
690
+ _resolveItemValue(item) {
691
+ if (item == null) return item;
692
+ if (typeof item !== "object") return item;
693
+ if (this.keyValue && this.keyValue in item) return item[this.keyValue];
694
+ if (!this.keyValue) {
695
+ if ("value" in item) return item.value;
696
+ return item;
697
+ }
698
+ }
699
+ /** Whether `item` resolves to a value that can actually be stored (see above). */
700
+ _isSelectable(item) {
701
+ if (item == null || item.disabled) return false;
702
+ return this._resolveItemValue(item) !== void 0;
703
+ }
704
+ _resolveItemDisplay(item) {
705
+ if (item == null) return "";
706
+ if (typeof this.displayValue === "function") return String(this.displayValue(item));
707
+ if (typeof this.displayValue === "string" && this.displayValue) {
708
+ if (typeof item === "object" && this.displayValue in item) return String(item[this.displayValue]);
709
+ return "";
710
+ }
711
+ if (typeof item === "object" && "label" in item) {
712
+ const label = item.label;
713
+ return label == null ? "" : String(label);
714
+ }
715
+ if (typeof item === "object" && "value" in item) {
716
+ const value = item.value;
717
+ return value == null ? "" : String(value);
718
+ }
719
+ return String(item);
720
+ }
721
+ _looksLikeSerializedFunction(value) {
722
+ const trimmed = value.trim();
723
+ if (!trimmed) return false;
724
+ return /^(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(trimmed);
725
+ }
726
+ /**
727
+ * Value equality. With no `key-value` the whole item IS the value, so this is
728
+ * identity — with one correction that is load-bearing under Vue.
729
+ *
730
+ * The model round-trips through the consumer's `ref`, which hands each tag back
731
+ * wrapped in a **reactive Proxy**; `items` normally stays raw, so `proxy === row`
732
+ * is false, an already-selected option reads as unselected, and clicking it
733
+ * again APPENDS a duplicate instead of toggling it off. Comparing the raw
734
+ * targets restores it. Still identity, not deep equality: two distinct rows with
735
+ * equal contents remain different values.
736
+ */
737
+ _isSameValue(a, b) {
738
+ if (a === b) return true;
739
+ if (a == null || b == null) return false;
740
+ const rawA = unwrapReactive(a);
741
+ const rawB = unwrapReactive(b);
742
+ if (rawA === rawB) return true;
743
+ if (typeof rawA !== "object" || typeof rawB !== "object") return false;
744
+ return false;
745
+ }
746
+ _arrayEqual(a, b) {
747
+ if (a.length !== b.length) return false;
748
+ return a.every((item, index) => this._isSameValue(item, b[index]));
749
+ }
750
+ _cls(base, key) {
751
+ const extra = this.cssClass?.[key];
752
+ return extra ? `${base} ${extra}` : base;
753
+ }
754
+ /** Full normalized source (for resolving chip labels by value). */
755
+ get _normalizedItems() {
756
+ return this._normalizeItems(this._ds.sourceItems);
757
+ }
758
+ /** Normalized rows actually available to show (paged slice in array mode). */
759
+ get _visibleItems() {
760
+ return this._normalizeItems(this._ds.visibleItems);
761
+ }
762
+ /** Effective load-more mode (scroll is the default when enabled). */
763
+ get _loadMoreMode() {
764
+ const v = this.loadMore;
765
+ if (v === void 0 || v === null || v === false) return void 0;
766
+ return v === "button" ? "button" : "scroll";
767
+ }
768
+ /** Sanitised chunk size for array-mode paging (>= 1). */
769
+ get _pageSize() {
770
+ const n = Math.floor(Number(this.pageSize));
771
+ return Number.isFinite(n) && n > 0 ? n : 10;
772
+ }
773
+ get _selectedValues() {
774
+ return this.value ?? [];
775
+ }
776
+ get _hasValue() {
777
+ return this._selectedValues.length > 0;
778
+ }
779
+ get _hasLabelSlot() {
780
+ return this._hasLabelSlotState;
781
+ }
782
+ get _hasHelperSlot() {
783
+ return this._hasHelperSlotState;
784
+ }
785
+ get _resolvedValidationState() {
786
+ if (this.validationState && this.validationState !== "default") return this.validationState;
787
+ if (this.errorMessage) return "invalid";
788
+ if (this.successMessage) return "valid";
789
+ return "default";
790
+ }
791
+ _isSelected(item) {
792
+ return this._selectedSet.has(unwrapReactive(this._resolveItemValue(item)));
793
+ }
794
+ /**
795
+ * The selection as a Set of RAW values — `_isSameValue`'s rule (identity after
796
+ * `unwrapReactive`) as an O(1) lookup.
797
+ *
798
+ * `_isSelected` runs per row, from the option render, the list entries, the group
799
+ * headers and the "All" row — a `.some()` over the selection there made every render
800
+ * O(rows × selected), which after a select-all of a thousand rows is millions of
801
+ * comparisons per keystroke. Rebuilt when `value` is replaced (or resized in place).
802
+ */
803
+ get _selectedSet() {
804
+ const values = this._selectedValues;
805
+ if (values !== this._selectedSetFor || values.length !== this._selectedSetLen) {
806
+ this._selectedSetFor = values;
807
+ this._selectedSetLen = values.length;
808
+ this._selectedSetMemo = new Set(values.map((v) => unwrapReactive(v)));
809
+ }
810
+ return this._selectedSetMemo;
811
+ }
812
+ /** Whether `search-value` names anything explicitly (vs falling back). */
813
+ get _hasSearchValue() {
814
+ const sv = this.searchValue;
815
+ if (Array.isArray(sv)) return sv.length > 0;
816
+ return typeof sv === "string" && !!sv.trim();
817
+ }
818
+ /**
819
+ * The natural fields searched alongside the `'*'` default when `search-value` is
820
+ * empty — the safety net, not the whole list.
821
+ *
822
+ * `'*'` resolves against loaded rows, so on a bound DataSource it expands to
823
+ * NOTHING until the first page lands (nothing loads on open, and a query can
824
+ * fire inside the debounce) — these keep that first query working. They are also
825
+ * exempt from the remote string-only rule, so a numeric `key-value` stays
826
+ * searchable. `display-value` contributes only when it's a string; a function
827
+ * builds text that no field expression can reach, which is why `_searchMatches`
828
+ * keeps matching the rendered display separately.
829
+ */
830
+ _fallbackSearchFields() {
831
+ return [typeof this.displayValue === "string" ? this.displayValue : void 0, this.keyValue];
832
+ }
833
+ /**
834
+ * A cheap signature of the rows' SHAPE, for the memo keys below.
835
+ *
836
+ * Row count alone isn't enough once `'*'` is in play: the resolved field list
837
+ * depends on the rows' keys, and a remote source paging from one full page to
838
+ * the next keeps the same length while the shape can differ. Sampling the first
839
+ * and last row keeps this O(1) while catching that case.
840
+ */
841
+ _rowsShapeKey(rows) {
842
+ const keysOf = (row) => row && typeof row === "object" ? Object.keys(row).join(",") : typeof row;
843
+ if (!rows.length) return "0";
844
+ return `${rows.length}:${keysOf(rows[0])}|${keysOf(rows[rows.length - 1])}`;
845
+ }
846
+ /**
847
+ * `search-value` resolved to concrete entries — `*` patterns expanded against
848
+ * the loaded rows. Memoized: a pattern samples up to 20 rows, so resolving per
849
+ * item (this feeds `_searchMatches`) would be O(items × fields) per keystroke.
850
+ *
851
+ * With no `search-value` this defaults to `'*'` (every top-level field) plus the
852
+ * natural fallbacks — see {@link _fallbackSearchFields}.
853
+ */
854
+ _searchEntries(remote) {
855
+ const rows = this._normalizedItems;
856
+ const key = `${this._searchVersion} ${remote ? 1 : 0} ${this._rowsShapeKey(rows)}`;
857
+ if (this._entriesCache?.key === key) return this._entriesCache.entries;
858
+ const entries = resolveSearchFields({
859
+ searchValue: this.searchValue,
860
+ fallbackFields: this._fallbackSearchFields(),
861
+ rows,
862
+ remote,
863
+ defaultToWildcard: true
864
+ });
865
+ this._entriesCache = {
866
+ key,
867
+ entries
868
+ };
869
+ return entries;
870
+ }
871
+ /** The compiled client-side predicate for the current query (memoized). */
872
+ _searchPredicate() {
873
+ const query = this._inputValue.trim();
874
+ const operation = this.searchOperation || "contains";
875
+ const rows = this._normalizedItems;
876
+ const key = `${this._searchVersion} ${operation} ${this._rowsShapeKey(rows)} ${query}`;
877
+ if (this._predicateCache?.key === key) return this._predicateCache.pred;
878
+ const pred = searchRowPredicate(this._searchEntries(false), query, operation);
879
+ this._predicateCache = {
880
+ key,
881
+ pred
882
+ };
883
+ return pred;
884
+ }
885
+ /** A bound DataSource is searched on the server; a plain array, client-side. */
886
+ _serverSearch() {
887
+ return !!this.dataSource;
888
+ }
889
+ /** Run the server search, reset to page 0. A failure becomes the field's error. */
890
+ async _applySearch(query) {
891
+ if (!this._serverSearch()) return;
892
+ await this._pushSearch(query).catch((err) => {
893
+ this._ds.setError(err);
894
+ });
895
+ }
896
+ /** Push `query` onto the source and reload it; rejects when the load fails. */
897
+ _pushSearch(query) {
898
+ this._appliedQuery = query;
899
+ return this._sourceSearch.apply(this.dataSource, {
900
+ query,
901
+ searchValue: this.searchValue,
902
+ fallbackFields: this._fallbackSearchFields(),
903
+ rows: this._normalizedItems,
904
+ remote: true,
905
+ defaultToWildcard: true,
906
+ operation: this.searchOperation || "contains"
907
+ });
908
+ }
909
+ /** Forget the typed query without touching the source. */
910
+ _dropQuery() {
911
+ if (this._searchTimer) {
912
+ clearTimeout(this._searchTimer);
913
+ this._searchTimer = void 0;
914
+ }
915
+ this._inputValue = "";
916
+ this._activeIndex = -1;
917
+ }
918
+ /** Loading: the app says so (`loading`), or the bound source's first load / retry. */
919
+ get _busy() {
920
+ return this.loading || !!this.dataSource && this._ds.busy;
921
+ }
922
+ /** The failure to paint: the app's `loading-error`, else the source's own; none while busy. */
923
+ get _failure() {
924
+ if (this._busy) return null;
925
+ return this._appFailure ?? (this.dataSource ? this._ds.error : null);
926
+ }
927
+ /** Enter / Space on the focused field retries a failure. */
928
+ _retryKey(event) {
929
+ if (event.key !== "Enter" && event.key !== " ") return false;
930
+ if (!this._failure || this.disabled) return false;
931
+ this._retry(event);
932
+ return true;
933
+ }
934
+ /** Loading, or failed: the field refuses every gesture but ↻. */
935
+ get _dsInert() {
936
+ return this._busy || !!this._failure;
937
+ }
938
+ /** Every interaction is off: disabled, read-only, or the source isn't usable. */
939
+ get _inert() {
940
+ return this.disabled || this.readonly || this._dsInert;
941
+ }
942
+ /**
943
+ * Clear the typed query — the visible one AND the one applied to the source.
944
+ *
945
+ * These are two independent tracks, and with a bound DataSource only the second
946
+ * one actually filters anything: `_searchMatches` short-circuits to `true`
947
+ * because the rows arrive pre-filtered. So clearing `_inputValue` on its own
948
+ * looks like it worked and changes nothing, which is exactly how a query that
949
+ * is no longer visible anywhere kept filtering the list.
950
+ *
951
+ * Cancelling the debounce FIRST is load-bearing: a timer armed before the reset
952
+ * fires afterwards and re-applies the stale query.
953
+ */
954
+ _resetSearch() {
955
+ if (this._searchTimer) {
956
+ clearTimeout(this._searchTimer);
957
+ this._searchTimer = void 0;
958
+ }
959
+ const had = !!this._inputValue;
960
+ this._inputValue = "";
961
+ this._activeIndex = -1;
962
+ if (had && this._serverSearch()) this._applySearch("");
963
+ }
964
+ /**
965
+ * The single way the panel closes. Every exit resets the search, so reopening
966
+ * never shows a list filtered by a query the box no longer holds.
967
+ */
968
+ _close() {
969
+ this._open = false;
970
+ this._resetSearch();
971
+ this._pruneSelectedCache();
972
+ }
973
+ /**
974
+ * Drop remembered rows no current value needs, once the cache has outgrown the selection — it
975
+ * only ever grew (every value ever picked, every row a select-all drained). On close, with
976
+ * slack, so toggling a few tags never thrashes it.
977
+ */
978
+ _pruneSelectedCache() {
979
+ const values = this._selectedValues;
980
+ if (this._selectedItemCache.size <= values.length * 2 + 200) return;
981
+ const keep = new Set(values.map((v) => this._cacheKey(v)).filter((k) => k !== void 0));
982
+ for (const k of this._selectedItemCache.keys()) if (!keep.has(k)) this._selectedItemCache.delete(k);
983
+ }
984
+ /** Debounce a server search for the current input text. */
985
+ _scheduleSearch() {
986
+ if (!this.searchable || !this._serverSearch()) return;
987
+ if (this._searchTimer) clearTimeout(this._searchTimer);
988
+ const wait = Math.max(0, Number(this.searchDebounce) || 0);
989
+ this._searchTimer = setTimeout(() => void this._applySearch(this._inputValue), wait);
990
+ }
991
+ /** Whether an option matches the current typed query. */
992
+ _searchMatches(item) {
993
+ if (!this.searchable) return true;
994
+ if (this._serverSearch()) return true;
995
+ const query = this._inputValue.trim().toLowerCase();
996
+ if (!query) return true;
997
+ if (this._hasSearchValue) {
998
+ const pred = this._searchPredicate();
999
+ return pred ? pred(item) : true;
1000
+ }
1001
+ const itemValue = this._resolveItemValue(item);
1002
+ const display = this._resolveItemDisplay(item).toLowerCase();
1003
+ const valueStr = itemValue == null ? "" : String(itemValue).toLowerCase();
1004
+ const desc = typeof item === "object" && item && typeof item.description === "string" ? item.description.toLowerCase() : "";
1005
+ const pred = this._searchPredicate();
1006
+ return display.includes(query) || valueStr.includes(query) || desc.includes(query) || (pred ? pred(item) : false);
1007
+ }
1008
+ /**
1009
+ * Grouping is active when `display-group` names at least one level — OR when the
1010
+ * source already loaded PRE-GROUPED `{ key, items }` nodes.
1011
+ *
1012
+ * The second half is what makes "feed pre-grouped data directly" actually work.
1013
+ * Without it, a devextreme DataSource carrying `group:` loaded its group nodes and,
1014
+ * because `displayGroup` was empty, they were treated as ordinary OPTIONS: the
1015
+ * headers became rows, `keyValue` was absent on them so they resolved to
1016
+ * `undefined`, and selecting one wrote `undefined` into the value.
1017
+ *
1018
+ * `group` mode is excluded: there the source is deliberately FLAT and
1019
+ * `display-group` is what buckets it client-side.
1020
+ */
1021
+ get _grouped() {
1022
+ if (Array.isArray(this.displayGroup) && this.displayGroup.length > 0) return true;
1023
+ if (this.group) return false;
1024
+ const src = this._ds.sourceItems ?? [];
1025
+ return src.length > 0 && src.some((n) => this._isGroupNode(n));
1026
+ }
1027
+ /** A node is a group when its `groupItems` field holds an array. */
1028
+ _isGroupNode(x) {
1029
+ return !!x && typeof x === "object" && Array.isArray(x[this.groupItems]);
1030
+ }
1031
+ /** Descend `groupItems[0]` to the first leaf row — the group's representative. */
1032
+ _firstLeaf(node) {
1033
+ let cur = node;
1034
+ let guard = 0;
1035
+ while (this._isGroupNode(cur) && guard++ < 50) cur = cur[this.groupItems]?.[0];
1036
+ return cur ?? void 0;
1037
+ }
1038
+ /** Resolve a group header's label from `displayGroup[level]` over a leaf row. */
1039
+ _resolveGroupLabel(node, level) {
1040
+ const accessor = this.displayGroup[level];
1041
+ const leaf = this._firstLeaf(node);
1042
+ if (typeof accessor === "function") return String(accessor(leaf ?? {}) ?? "");
1043
+ if (typeof accessor === "string" && leaf && accessor in leaf) return String(leaf[accessor] ?? "");
1044
+ const key = node[this.groupKey];
1045
+ return key == null ? "" : String(key);
1046
+ }
1047
+ /** The string `display-group` entries — usable as server group/sort fields. */
1048
+ _groupSortFields() {
1049
+ return (this.displayGroup ?? []).filter((a) => typeof a === "string");
1050
+ }
1051
+ /** A `display-group` level's key for a row (string → field, function → call). */
1052
+ _groupKeyOf(row, level) {
1053
+ const accessor = this.displayGroup[level];
1054
+ if (typeof accessor === "function") return accessor(row);
1055
+ if (typeof accessor === "string") return row[accessor];
1056
+ }
1057
+ /** `group` mode: bucket flat rows into `{ [groupKey], [groupItems] }` by level. */
1058
+ _buildClientTree(rows) {
1059
+ const build = (items, level) => {
1060
+ if (level >= this.displayGroup.length) return items;
1061
+ const buckets = /* @__PURE__ */ new Map();
1062
+ for (const row of items) {
1063
+ const key = this._groupKeyOf(row, level);
1064
+ const ks = String(key);
1065
+ let bucket = buckets.get(ks);
1066
+ if (!bucket) buckets.set(ks, bucket = {
1067
+ key,
1068
+ rows: []
1069
+ });
1070
+ bucket.rows.push(row);
1071
+ }
1072
+ return [...buckets.values()].map((b) => ({
1073
+ [this.groupKey]: b.key,
1074
+ [this.groupItems]: build(b.rows, level + 1)
1075
+ }));
1076
+ };
1077
+ return build(rows, 0);
1078
+ }
1079
+ /** Every leaf row (all loaded) — for chip-label / value lookup. */
1080
+ get _leafItems() {
1081
+ if (!this._grouped) return this._normalizedItems;
1082
+ if (this.group) return this._normalizeItems(this._ds.sourceItems);
1083
+ const out = [];
1084
+ const walk = (nodes) => {
1085
+ for (const n of nodes) if (this._isGroupNode(n)) walk(n[this.groupItems]);
1086
+ else if (n != null) out.push(n);
1087
+ };
1088
+ walk(this._ds.sourceItems ?? []);
1089
+ return out;
1090
+ }
1091
+ /** The nested group nodes to render — built client-side in `group` mode. */
1092
+ _groupNodes() {
1093
+ if (this.group) {
1094
+ const leaves = this._visibleItems.filter((it) => this._searchMatches(it));
1095
+ return this._buildClientTree(leaves);
1096
+ }
1097
+ return this._ds.visibleItems ?? [];
1098
+ }
1099
+ /**
1100
+ * Flatten the visible group tree into ordered render rows (headers + items).
1101
+ * `index` is the item's position among rendered leaves (drives `_activeIndex`
1102
+ * and keyboard nav). Leaves are search-filtered; empty groups are dropped.
1103
+ */
1104
+ _groupRows() {
1105
+ const filterLeaves = !this.group;
1106
+ const build = (nodes, level, parentKey) => {
1107
+ const rows = [];
1108
+ nodes.forEach((node, i) => {
1109
+ if (!this._isGroupNode(node)) {
1110
+ const item = node;
1111
+ if (item == null) return;
1112
+ if (filterLeaves && !this._searchMatches(item)) return;
1113
+ rows.push({
1114
+ kind: "item",
1115
+ item,
1116
+ index: -1,
1117
+ level
1118
+ });
1119
+ return;
1120
+ }
1121
+ const rec = node;
1122
+ const path = `${parentKey}/${String(rec[this.groupKey])}#${i}`;
1123
+ const children = build(rec[this.groupItems] ?? [], level + 1, path);
1124
+ if (!children.length) return;
1125
+ rows.push({
1126
+ kind: "group",
1127
+ level,
1128
+ label: this._resolveGroupLabel(rec, level),
1129
+ key: `g:${level}:${path}`,
1130
+ items: children.filter((r) => r.kind === "item").map((r) => r.item)
1131
+ });
1132
+ rows.push(...children);
1133
+ });
1134
+ return rows;
1135
+ };
1136
+ let idx = 0;
1137
+ return build(this._groupNodes(), 0, "").map((r) => r.kind === "item" ? {
1138
+ ...r,
1139
+ index: idx++
1140
+ } : r);
1141
+ }
1142
+ /**
1143
+ * Where the keyboard cursor goes when the list opens: the row holding the most
1144
+ * recently picked tag, else the first row.
1145
+ *
1146
+ * "Most recent" is the TAIL of the value array — every add appends and nothing
1147
+ * sorts, so removing and re-adding a tag correctly moves it to the end. The
1148
+ * `Backspace` handler already leans on that same invariant.
1149
+ *
1150
+ * Selected rows stay in the suggestion list (see `_renderOption`), so this can
1151
+ * actually find them; the predicate is the one that paints `.selected`, so the
1152
+ * cursor lands on the row drawn as selected. `0` when the tag is not in the
1153
+ * loaded page — the behaviour this component has always had.
1154
+ */
1155
+ _initialActiveIndex() {
1156
+ const items = this._filteredItems;
1157
+ if (!items.length) return -1;
1158
+ const values = this._selectedValues;
1159
+ const last = values.length ? values[values.length - 1] : void 0;
1160
+ if (last === void 0) return 0;
1161
+ const index = items.findIndex((item) => this._isSameValue(this._resolveItemValue(item), last));
1162
+ return index >= 0 ? index : 0;
1163
+ }
1164
+ get _filteredItems() {
1165
+ if (this._filteredMemoOn && this._filteredMemo) return this._filteredMemo;
1166
+ const out = this._grouped ? this._groupRows().filter((r) => r.kind === "item").map((r) => r.item) : this._visibleItems.filter((item) => this._searchMatches(item));
1167
+ if (this._filteredMemoOn) this._filteredMemo = out;
1168
+ return out;
1169
+ }
1170
+ /**
1171
+ * The selection limits in force: `chip.max` / `chip.min` pin, the element's
1172
+ * `max` / `min` are the fallback (the rule `chip.color` follows). Undefined =
1173
+ * unlimited / no floor.
1174
+ */
1175
+ get _selectionLimits() {
1176
+ const chip = this._chipProps;
1177
+ return {
1178
+ max: resolveChipLimit(chip, "max", this.max),
1179
+ min: resolveChipLimit(chip, "min", this.min)
1180
+ };
1181
+ }
1182
+ /** Room left under `max`, or Infinity without one. */
1183
+ get _room() {
1184
+ const max = this._selectionLimits.max;
1185
+ return max === void 0 ? Infinity : Math.max(0, max - this._selectedValues.length);
1186
+ }
1187
+ get _canAddMore() {
1188
+ return this._room > 0;
1189
+ }
1190
+ /** The selection sits above its floor, so one more removal is allowed. */
1191
+ get _canRemoveMore() {
1192
+ const min = this._selectionLimits.min;
1193
+ return min === void 0 || this._selectedValues.length > min;
1194
+ }
1195
+ /**
1196
+ * A bulk deselect keeps the FIRST `min` values (selection order) — "remove
1197
+ * these" then means "down to the floor", the most a user gesture may do.
1198
+ */
1199
+ _keepFloor(next) {
1200
+ const min = this._selectionLimits.min ?? 0;
1201
+ if (next.length >= min) return next;
1202
+ const kept = [...next];
1203
+ for (const v of this._selectedValues) {
1204
+ if (kept.length >= min) break;
1205
+ if (!kept.some((k) => this._isSameValue(k, v))) kept.push(v);
1206
+ }
1207
+ return this._selectedValues.filter((v) => kept.some((k) => this._isSameValue(k, v)));
1208
+ }
1209
+ get _wrapperClasses() {
1210
+ return [
1211
+ "mono-tag-input",
1212
+ this.size,
1213
+ this.color,
1214
+ this.variant,
1215
+ this._open ? "open" : "",
1216
+ this._moreOpen ? "more-open" : "",
1217
+ this.disabled ? "disabled" : "",
1218
+ this.readonly ? "readonly" : "",
1219
+ this._resolvedValidationState !== "default" ? `is-${this._resolvedValidationState}` : "",
1220
+ this._hasValue ? "has-value" : "",
1221
+ this.checkable ? "checkable" : "",
1222
+ this.cssClassName,
1223
+ this.cssClass?.root
1224
+ ].filter(Boolean).join(" ");
1225
+ }
1226
+ /**
1227
+ * Whether the clear button renders. A getter because BOTH the field class list
1228
+ * and `render()` need it: the button is pinned to the top-right corner, and the
1229
+ * gutter that keeps wrapped chips from running under it is reserved by
1230
+ * `.has-clear` on the field.
1231
+ */
1232
+ get _showClear() {
1233
+ return this.clearable && this._hasValue && !this._inert && !((this._selectionLimits.min ?? 0) > 0);
1234
+ }
1235
+ /** The input's placeholder: the source's state while it isn't usable, else the prompt. */
1236
+ get _inputPlaceholder() {
1237
+ if (this._busy) return this.loadingText;
1238
+ if (this._failure) return errorText(this._failure);
1239
+ return this._hasValue ? void 0 : this.placeholder;
1240
+ }
1241
+ /** Full error text (detail first) on hover, since the placeholder may truncate. */
1242
+ get _dsErrorTitle() {
1243
+ const err = this._failure;
1244
+ return err ? errorTitle(err) : void 0;
1245
+ }
1246
+ get _fieldClasses() {
1247
+ return [
1248
+ this._cls("mono-tag-input-field", "field"),
1249
+ this.size,
1250
+ this.color,
1251
+ this.variant,
1252
+ this.disabled ? "disabled" : "",
1253
+ this.readonly ? "readonly" : "",
1254
+ this._showClear ? "has-clear" : "",
1255
+ "has-actions",
1256
+ this._chipBehaviour === "inline" ? "is-inline" : "",
1257
+ this._inputValue ? "is-typing" : ""
1258
+ ].filter(Boolean).join(" ");
1259
+ }
1260
+ _cacheKey(value) {
1261
+ if (value == null || typeof value === "object") return void 0;
1262
+ return String(value);
1263
+ }
1264
+ /**
1265
+ * Remember the rows behind `values` while they are still loaded.
1266
+ *
1267
+ * Called from `_setValue`, i.e. at the moment of selection — the row is in the
1268
+ * list by definition right then, which is why this needs no request. Values
1269
+ * that cannot be resolved yet are skipped, not cached as misses, so a later
1270
+ * load can still fill them in.
1271
+ */
1272
+ _rememberSelected(values) {
1273
+ if (!this.keyValue) return;
1274
+ for (const value of values) {
1275
+ const key = this._cacheKey(value);
1276
+ if (key === void 0 || this._selectedItemCache.has(key)) continue;
1277
+ const item = this._findLoadedItem(value);
1278
+ if (item !== void 0) this._selectedItemCache.set(key, item);
1279
+ }
1280
+ }
1281
+ /** Look a value up in the currently loaded rows only. */
1282
+ _findLoadedItem(value) {
1283
+ if (value == null) return this._leafItems.find((item) => this._isSameValue(this._resolveItemValue(item), value));
1284
+ return this._leafIndex.get(unwrapReactive(value));
1285
+ }
1286
+ /**
1287
+ * Loaded leaf rows by RAW resolved value (first one wins, like the `.find()` it replaces).
1288
+ *
1289
+ * Every chip resolves its row through `_findLoadedItem` — `_resolvedValues` alone runs
1290
+ * 4–5 times a render, plus a label lookup per chip — and each was a linear scan of the
1291
+ * loaded rows: O(selected × loaded) per pass. Rebuilt when the source array, its length,
1292
+ * or the key / grouping config changes.
1293
+ */
1294
+ get _leafIndex() {
1295
+ const src = this._ds.sourceItems;
1296
+ const key = [
1297
+ src,
1298
+ Array.isArray(src) ? src.length : -1,
1299
+ this.keyValue,
1300
+ this._grouped,
1301
+ this.group,
1302
+ this.groupItems
1303
+ ];
1304
+ if (key.length !== this._leafIndexKey.length || key.some((v, i) => !Object.is(v, this._leafIndexKey[i]))) {
1305
+ const map = /* @__PURE__ */ new Map();
1306
+ for (const item of this._leafItems) {
1307
+ const v = unwrapReactive(this._resolveItemValue(item));
1308
+ if (v != null && !map.has(v)) map.set(v, item);
1309
+ }
1310
+ this._leafIndexMemo = map;
1311
+ this._leafIndexKey = key;
1312
+ }
1313
+ return this._leafIndexMemo;
1314
+ }
1315
+ _getItemByValue(value) {
1316
+ const loaded = this._findLoadedItem(value);
1317
+ if (loaded !== void 0) {
1318
+ const key = this._cacheKey(value);
1319
+ if (key !== void 0 && this.keyValue) this._selectedItemCache.set(key, loaded);
1320
+ return loaded;
1321
+ }
1322
+ const key = this._cacheKey(value);
1323
+ return key === void 0 ? void 0 : this._selectedItemCache.get(key);
1324
+ }
1325
+ /**
1326
+ * Whether this value still has no row behind it — the list has not loaded it (or
1327
+ * not loaded yet).
1328
+ *
1329
+ * Only meaningful when `keyValue` is set: that prop is the statement "values are
1330
+ * KEYS into the items", so an unmatched key has no label anyone would want to
1331
+ * read — showing the bare key renders a chip like `1` or `1|MKT`. Without
1332
+ * `keyValue` the value IS its own label (and `allow-custom` tags rely on that),
1333
+ * so nothing is ever hidden there.
1334
+ */
1335
+ _isUnresolvedKey(value) {
1336
+ if (!this.keyValue) return false;
1337
+ if (value == null) return false;
1338
+ if (typeof value === "object") return false;
1339
+ return this._getItemByValue(value) === void 0;
1340
+ }
1341
+ _getLabelByValue(value) {
1342
+ const item = this._getItemByValue(value);
1343
+ if (item) return this._resolveItemDisplay(item);
1344
+ if (value == null) return "";
1345
+ if (typeof value === "object") return this._resolveItemDisplay(value);
1346
+ if (this._isUnresolvedKey(value)) return "";
1347
+ return String(value);
1348
+ }
1349
+ _createModelDetail(args) {
1350
+ return {
1351
+ modelValue: args.modelValue,
1352
+ currentValue: args.modelValue,
1353
+ oldValue: args.oldValue,
1354
+ value: args.modelValue,
1355
+ addedValue: args.addedValue,
1356
+ removedValue: args.removedValue,
1357
+ selectedItem: args.addedValue !== void 0 ? this._getItemByValue(args.addedValue) : void 0,
1358
+ sourceEvent: args.sourceEvent
1359
+ };
1360
+ }
1361
+ _emitChange(detail) {
1362
+ dispatchMonoEvent(this, "change", detail);
1363
+ }
1364
+ _emitAdd(detail) {
1365
+ dispatchMonoEvent(this, "add", detail);
1366
+ }
1367
+ _emitRemove(detail) {
1368
+ dispatchMonoEvent(this, "remove", detail);
1369
+ }
1370
+ _emitClear(detail) {
1371
+ dispatchMonoEvent(this, "clear", detail);
1372
+ }
1373
+ _setValue(nextValue, args = {}) {
1374
+ const oldValue = [...this._selectedValues];
1375
+ const normalized = [...nextValue];
1376
+ this._rememberSelected(normalized);
1377
+ this.value = normalized;
1378
+ this.modelValue = normalized;
1379
+ const detail = this._createModelDetail({
1380
+ modelValue: normalized,
1381
+ oldValue,
1382
+ addedValue: args.addedValue,
1383
+ removedValue: args.removedValue,
1384
+ sourceEvent: args.sourceEvent
1385
+ });
1386
+ this._emitChange(detail);
1387
+ if (args.emitAdd) this._emitAdd(detail);
1388
+ if (args.emitRemove) this._emitRemove(detail);
1389
+ if (args.emitClear) this._emitClear(detail);
1390
+ if (args.emitAdd && this._chipBehaviour === "inline") this.updateComplete.then(() => this._chipStrip.scrollToEnd());
1391
+ }
1392
+ _addValue(value, event) {
1393
+ if (this._inert) return;
1394
+ if (!this._canAddMore) return this._resetSearch();
1395
+ if (!this.allowCustom && !this._getItemByValue(value)) return;
1396
+ if (!this._selectedValues.some((v) => this._isSameValue(v, value))) this._setValue([...this._selectedValues, value], {
1397
+ addedValue: value,
1398
+ sourceEvent: event,
1399
+ emitAdd: true
1400
+ });
1401
+ this._close();
1402
+ this._inputEl?.focus();
1403
+ }
1404
+ /**
1405
+ * Checkbox-mode toggle: add or remove a value while keeping the dropdown open
1406
+ * (and the typed filter intact), so the user can check several rows in a row.
1407
+ */
1408
+ _toggleValue(value, event) {
1409
+ if (this._inert) return;
1410
+ if (this._selectedValues.some((v) => this._isSameValue(v, value))) {
1411
+ if (!this._canRemoveMore) return;
1412
+ this._setValue(this._selectedValues.filter((v) => !this._isSameValue(v, value)), {
1413
+ removedValue: value,
1414
+ sourceEvent: event,
1415
+ emitRemove: true
1416
+ });
1417
+ } else {
1418
+ if (!this._canAddMore) return this._resetSearch();
1419
+ this._setValue([...this._selectedValues, value], {
1420
+ addedValue: value,
1421
+ sourceEvent: event,
1422
+ emitAdd: true
1423
+ });
1424
+ }
1425
+ if (!this.checkable) {
1426
+ this._close();
1427
+ this._inputEl?.focus();
1428
+ return;
1429
+ }
1430
+ this._open = true;
1431
+ this._inputEl?.focus();
1432
+ }
1433
+ /**
1434
+ * How much of `items` is selected — what paints a select-all box unchecked,
1435
+ * indeterminate or checked. Disabled options are ignored, so a group whose only
1436
+ * unselected row is disabled still reads as fully selected.
1437
+ */
1438
+ _selectionStateOf(items) {
1439
+ const usable = items.filter((item) => this._isSelectable(item));
1440
+ if (!usable.length) return "none";
1441
+ let hit = 0;
1442
+ for (const item of usable) if (this._isSelected(item)) hit += 1;
1443
+ if (hit === 0) return "none";
1444
+ return hit === usable.length ? "all" : "some";
1445
+ }
1446
+ /**
1447
+ * Whether the "All" row reaches the SERVER rather than the loaded rows.
1448
+ *
1449
+ * Gated on a bound `DataSource` + `load-more`, which together mean "the list is
1450
+ * a window onto something bigger". A plain `items` array is static and already
1451
+ * wholly in memory, so it keeps the loaded-only behaviour.
1452
+ */
1453
+ get _serverSelectAll() {
1454
+ return !!this.dataSource && !!this._loadMoreMode;
1455
+ }
1456
+ /** The source's own page size, when it pages; `0` otherwise. */
1457
+ _sourcePageSize() {
1458
+ const ds = this.dataSource;
1459
+ if (!ds || typeof ds.pageSize !== "function") return 0;
1460
+ if (typeof ds.paginate === "function" && !ds.paginate()) return 0;
1461
+ const n = Number(ds.pageSize());
1462
+ return Number.isFinite(n) && n > 0 ? n : 0;
1463
+ }
1464
+ /** The source's row count, when it knows one. */
1465
+ _serverTotal() {
1466
+ const n = Number(this.dataSource?.totalCount?.());
1467
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
1468
+ }
1469
+ /**
1470
+ * The "All" box, judged against the SERVER total rather than the loaded rows.
1471
+ *
1472
+ * Without this the box reads `'all'` the moment the loaded page is selected —
1473
+ * and the next click would CLEAR, so the drain could never be reached. Every
1474
+ * state below `all` therefore has to mean "clicking me selects more".
1475
+ *
1476
+ * BOTH halves of the `'all'` test matter, and each rules out a way of being
1477
+ * wrong. `_selectionStateOf` alone says `'all'` after one page of 250. The count
1478
+ * alone says `'all'` whenever the selection is merely LARGER than the total,
1479
+ * which a search makes routine: hold 111 rows, narrow the query to 11, and a
1480
+ * bare `111 >= 11` paints a finished box over eleven rows none of which are
1481
+ * selected — and the next click would clear instead of selecting them.
1482
+ *
1483
+ * Falls back to the loaded-scope reading when the source cannot say how many
1484
+ * rows it has, which is the honest answer: nothing here can tell whether the
1485
+ * selection is complete.
1486
+ */
1487
+ _selectAllState(usable) {
1488
+ const loaded = this._selectionStateOf(usable);
1489
+ if (!this._serverSelectAll) return loaded;
1490
+ const total = this._serverTotal();
1491
+ if (total === void 0) return loaded;
1492
+ if (loaded === "all" && total > 0 && this._selectedValues.length >= total) return "all";
1493
+ return this._selectedValues.length > 0 ? "some" : "none";
1494
+ }
1495
+ /**
1496
+ * Select every row the source returns for the CURRENT search — the tag-input's
1497
+ * counterpart to `<mono-table-checkbox type="all">`.
1498
+ *
1499
+ * `readAllRows` walks the store in chunks and rebuilds the source's live filter
1500
+ * plus its folded search (`searchFilterOf`), so this means "everything the user
1501
+ * is currently looking at", not the whole table. Deliberately NOT routed through
1502
+ * `_ds`: its `_onChanged` resets the accumulated pages whenever a `changed`
1503
+ * fires outside its own load, and `readAllRows` talks to the store directly.
1504
+ */
1505
+ async _selectAllFromServer(event) {
1506
+ if (this._selectAllPending) return;
1507
+ if (this._searchTimer) {
1508
+ clearTimeout(this._searchTimer);
1509
+ this._searchTimer = void 0;
1510
+ await this._applySearch(this._inputValue);
1511
+ }
1512
+ this._selectAllPending = true;
1513
+ try {
1514
+ const rows = await readAllRows(this.dataSource, { chunkSize: Math.max(1, this._pageSize, this._sourcePageSize()) });
1515
+ const usable = this._normalizeItems(rows).filter((item) => this._isSelectable(item));
1516
+ const values = usable.map((item) => this._resolveItemValue(item));
1517
+ const seen = new Set(this._selectedValues.map((v) => this._cacheKey(v)).filter((k) => k !== void 0));
1518
+ const additions = [];
1519
+ for (let i = 0; i < values.length; i++) {
1520
+ const key = this._cacheKey(values[i]);
1521
+ if (key !== void 0 && seen.has(key)) continue;
1522
+ if (key !== void 0) seen.add(key);
1523
+ additions.push(values[i]);
1524
+ if (key !== void 0 && this.keyValue) this._selectedItemCache.set(key, usable[i]);
1525
+ }
1526
+ if (!additions.length) return;
1527
+ const room = this._room;
1528
+ if (!room) return;
1529
+ this._setValue([...this._selectedValues, ...additions.slice(0, room)], {
1530
+ sourceEvent: event,
1531
+ emitAdd: true
1532
+ });
1533
+ } catch (err) {
1534
+ this._ds.setError(err);
1535
+ } finally {
1536
+ this._selectAllPending = false;
1537
+ this._open = true;
1538
+ this._inputEl?.focus();
1539
+ }
1540
+ }
1541
+ /**
1542
+ * Where the "All" row's click goes.
1543
+ *
1544
+ * Only a state BELOW `all` drains; once `_selectAllState` says everything on the
1545
+ * server is held, the row goes back to being a clear button. `_toggleMany` still
1546
+ * owns both directions for a plain `items` array.
1547
+ *
1548
+ * A drain is for the rows NOT yet here. Once the controller has walked the source
1549
+ * to its last page — `load-more` scrolled to the end, or a source whose whole
1550
+ * result fit its first page — every row the query matches is already in
1551
+ * memory, and `items` (the visible, server-filtered set) IS the answer: select
1552
+ * it as a plain array would, with no request. A query still inside its debounce
1553
+ * disqualifies that shortcut, because the loaded rows belong to the PREVIOUS
1554
+ * search; the drain path flushes it first.
1555
+ */
1556
+ _onSelectAllClick(items, state, event) {
1557
+ if (this._selectAllPending) return;
1558
+ if (this._isSelectAllSlotControl(event)) return;
1559
+ if (!this._serverSelectAll) {
1560
+ this._toggleMany(items, event);
1561
+ return;
1562
+ }
1563
+ if (state !== "all") {
1564
+ if (this._ds.atLastPage && !this._searchTimer) {
1565
+ this._toggleMany(items, event);
1566
+ return;
1567
+ }
1568
+ this._selectAllFromServer(event);
1569
+ return;
1570
+ }
1571
+ this._clearAllFromServer(event);
1572
+ }
1573
+ /**
1574
+ * Untick "All" after a drain.
1575
+ *
1576
+ * NOT `_toggleMany`: that only removes the values present in `items`, which on a
1577
+ * paged source is one page — so clearing 1,085 drained rows would drop ten of
1578
+ * them and leave the box reading `'some'`. `'all'` here means the selection is
1579
+ * the drain, so the inverse is emptying it, the same reading
1580
+ * `<mono-table-checkbox>` gives its own clear.
1581
+ */
1582
+ _clearAllFromServer(event) {
1583
+ if (this._inert) return;
1584
+ if (!this._selectedValues.length) return;
1585
+ this._setValue([], {
1586
+ sourceEvent: event,
1587
+ emitRemove: true
1588
+ });
1589
+ this._open = true;
1590
+ this._inputEl?.focus();
1591
+ }
1592
+ /**
1593
+ * Select every one of `items`, or clear them all if they are already selected —
1594
+ * the "All" row and every group header run through here.
1595
+ *
1596
+ * ONE `_setValue` call, so a bulk toggle is a single `mno-change` carrying the
1597
+ * whole next array rather than N events a consumer would have to coalesce.
1598
+ * `addedValue` / `removedValue` are deliberately left undefined for the same
1599
+ * reason: there is no single value to name, and guessing one would be worse than
1600
+ * saying nothing (`modelValue` and `oldValue` carry the full before/after).
1601
+ */
1602
+ _toggleMany(items, event) {
1603
+ if (this._inert) return;
1604
+ const usable = items.filter((item) => this._isSelectable(item));
1605
+ if (!usable.length) return;
1606
+ const values = usable.map((item) => this._resolveItemValue(item));
1607
+ const current = this._selectedValues;
1608
+ const isOn = (v) => current.some((c) => this._isSameValue(c, v));
1609
+ if (values.every(isOn)) this._setValue(this._keepFloor(current.filter((c) => !values.some((v) => this._isSameValue(c, v)))), {
1610
+ sourceEvent: event,
1611
+ emitRemove: true
1612
+ });
1613
+ else {
1614
+ const missing = values.filter((v) => !isOn(v));
1615
+ const room = Math.min(missing.length, this._room);
1616
+ if (!room) return;
1617
+ this._setValue([...current, ...missing.slice(0, room)], {
1618
+ sourceEvent: event,
1619
+ emitAdd: true
1620
+ });
1621
+ }
1622
+ this._open = true;
1623
+ this._inputEl?.focus();
1624
+ }
1625
+ _removeValue(value, event) {
1626
+ event?.stopPropagation();
1627
+ if (this._inert) return;
1628
+ if (!this._canRemoveMore) return;
1629
+ this._setValue(this._selectedValues.filter((item) => !this._isSameValue(item, value)), {
1630
+ removedValue: value,
1631
+ sourceEvent: event,
1632
+ emitRemove: true
1633
+ });
1634
+ this._inputEl?.focus();
1635
+ }
1636
+ _clear(event) {
1637
+ event.stopPropagation();
1638
+ if (this._inert) return;
1639
+ this._setValue(this._keepFloor([]), {
1640
+ sourceEvent: event,
1641
+ emitClear: true
1642
+ });
1643
+ this._close();
1644
+ this._inputEl?.focus();
1645
+ }
1646
+ _handleInput(event) {
1647
+ if (this._inert) return;
1648
+ const input = event.currentTarget;
1649
+ if (!this.searchable) {
1650
+ input.value = "";
1651
+ return;
1652
+ }
1653
+ this._inputValue = input.value;
1654
+ this._open = true;
1655
+ this._activeIndex = this._filteredItems.length ? 0 : -1;
1656
+ this._cursorPending = false;
1657
+ this._scheduleSearch();
1658
+ }
1659
+ /**
1660
+ * Focus alone opens nothing: a Tab into the field is not a request to see the
1661
+ * list — typing, ↓, a click in a searchable field or the chevron are. (It used
1662
+ * to open, which also meant every chip-remove's refocus popped the list.)
1663
+ */
1664
+ _handleFocus() {}
1665
+ /**
1666
+ * A click in the field focuses the input and, when the field is searchable,
1667
+ * OPENS the list — the input is the point there, and clicking into it is how
1668
+ * you start typing; it never closes, because a click in a text input while
1669
+ * the list is open is caret placement, not a toggle (the chevron, Escape and
1670
+ * an outside click close). A non-searchable field has nothing to type into,
1671
+ * so there the click TOGGLES: a second click on the field closes the list.
1672
+ */
1673
+ _handleFieldClick() {
1674
+ if (this._inert) return;
1675
+ this._inputEl?.focus();
1676
+ this._moreOpen = false;
1677
+ if (this.searchable) this._open = true;
1678
+ else if (this._open) this._close();
1679
+ else this._open = true;
1680
+ }
1681
+ /** The chevron: the one mouse gesture that both opens and closes. */
1682
+ _toggleFromCaret(event) {
1683
+ event.stopPropagation();
1684
+ if (this._inert) return;
1685
+ this._moreOpen = false;
1686
+ if (this._open) this._close();
1687
+ else {
1688
+ this._open = true;
1689
+ this._inputEl?.focus();
1690
+ }
1691
+ }
1692
+ _handleKeydown(event) {
1693
+ if (this._retryKey(event)) return;
1694
+ if (this._inert) return;
1695
+ const items = this._filteredItems;
1696
+ if (event.key === "ArrowDown") {
1697
+ event.preventDefault();
1698
+ this._open = true;
1699
+ this._activeIndex = items.length ? Math.min(this._activeIndex + 1, items.length - 1) : -1;
1700
+ return;
1701
+ }
1702
+ if (event.key === "ArrowUp") {
1703
+ event.preventDefault();
1704
+ this._activeIndex = items.length ? Math.max(this._activeIndex - 1, 0) : -1;
1705
+ return;
1706
+ }
1707
+ if (event.key === "Enter") {
1708
+ event.preventDefault();
1709
+ const activeItem = this._activeIndex >= 0 ? items[this._activeIndex] : void 0;
1710
+ if (activeItem && !activeItem.disabled) {
1711
+ this._toggleValue(this._resolveItemValue(activeItem), event);
1712
+ return;
1713
+ }
1714
+ const customValue = this._inputValue.trim();
1715
+ if (customValue && this.allowCustom) this._addValue(customValue, event);
1716
+ return;
1717
+ }
1718
+ if (event.key === "," || event.key === "Tab") {
1719
+ const customValue = this._inputValue.trim();
1720
+ if (customValue && this.allowCustom) {
1721
+ event.preventDefault();
1722
+ this._addValue(customValue, event);
1723
+ }
1724
+ return;
1725
+ }
1726
+ if (event.key === "Backspace" && !this._inputValue && this._selectedValues.length) {
1727
+ event.preventDefault();
1728
+ const lastValue = this._selectedValues[this._selectedValues.length - 1];
1729
+ this._removeValue(lastValue, event);
1730
+ return;
1731
+ }
1732
+ if (event.key === "Escape") {
1733
+ event.preventDefault();
1734
+ this._close();
1735
+ }
1736
+ }
1737
+ _renderLabel() {
1738
+ const hasContent = !!this.label || this._hasLabelSlotState;
1739
+ if (!hasContent && !this._slotsAlwaysRender) return nothing;
1740
+ return html`
1741
+ <label
1742
+ class=${this._cls("mono-tag-input-label", "label")}
1743
+ mono-label
1744
+ for=${this._inputId}
1745
+ ?mono-empty=${!hasContent}
1746
+ >
1747
+ ${this._slotOutlet("label", this.label)}
1748
+
1749
+ ${this.required ? html`
1750
+ <span class=${this._cls("mono-tag-input-required", "required")} mono-required-mark>
1751
+ *
1752
+ </span>
1753
+ ` : nothing}
1754
+ ${this._tooltip.render()}
1755
+ </label>
1756
+ `;
1757
+ }
1758
+ /**
1759
+ * How many chips to draw before the rest collapse into "+N more" — Infinity
1760
+ * when uncapped or while the total sits under the collapse floor
1761
+ * (`visibleChipCap`). `chip.maxVisible` / `chip.minVisible` pin, the element
1762
+ * props are the fallback. Applies to `inline` as well: the "+N more" chip
1763
+ * then sits in the strip after the visible ones, and its panel lists the rest
1764
+ * exactly as in `flex`. Every downstream consumer (`_overflowChips`,
1765
+ * `_renderChips`, the more panel) reads this, so the rule lives in one place.
1766
+ */
1767
+ get _maxChips() {
1768
+ const chip = this._chipProps;
1769
+ return visibleChipCap(this._resolvedValues.length, resolveChipLimit(chip, "maxVisible", this.maxVisible), resolveChipLimit(chip, "minVisible", this.minVisible));
1770
+ }
1771
+ /** Selected values that have a row behind them — what the chips actually draw. */
1772
+ get _resolvedValues() {
1773
+ return this._selectedValues.filter((v) => !this._isUnresolvedKey(v));
1774
+ }
1775
+ get _overflowChips() {
1776
+ const max = this._maxChips;
1777
+ return max === Infinity ? [] : this._resolvedValues.slice(max);
1778
+ }
1779
+ _closeSvg() {
1780
+ return this.renderIcon("close");
1781
+ }
1782
+ /** The `chip` prop, always an object (it can be assigned null from a binding). */
1783
+ get _chipProps() {
1784
+ return this.chip ?? {};
1785
+ }
1786
+ /** Chip layout: `inline` = one scrolling line, anything else = today's wrap. */
1787
+ get _chipBehaviour() {
1788
+ return this._chipProps.behaviour === "inline" ? "inline" : "flex";
1789
+ }
1790
+ /**
1791
+ * Chip skin matching the field skin: a `filled` field already tints its own
1792
+ * surface, so a soft chip would vanish into it — go solid; `underlined` is
1793
+ * minimal, so the chip stays outline. `chip.variant` pins one instead.
1794
+ */
1795
+ get _chipVariantClass() {
1796
+ const pinned = this._chipProps.variant;
1797
+ if (pinned) return pinned;
1798
+ if (this.variant === "filled") return "solid";
1799
+ if (this.variant === "underlined") return "outline";
1800
+ return "soft";
1801
+ }
1802
+ /** Chips wear the control's color unless `chip.color` pins another. */
1803
+ get _chipColorClass() {
1804
+ return this._chipProps.color ?? this.color;
1805
+ }
1806
+ /**
1807
+ * Classes shared by the tag chips and the "+N more" chip, in `mono-chip`'s own
1808
+ * order (`mono-chip <size> <variant>-<color> <shape>`), so chip.css paints them
1809
+ * exactly as it paints a real `<mono-chip>`.
1810
+ *
1811
+ * `mono-tag-input-chip` is a marker only — it scopes the `--mono-chip-*` bridge
1812
+ * in tag-input.css so the bridge never reaches a `<mono-chip>` a consumer slots
1813
+ * into the field. `chip.color` deliberately picks a hue OTHER than the control
1814
+ * accent, so it adds the `-pinned` marker that switches the bridge back off.
1815
+ */
1816
+ get _chipBaseClasses() {
1817
+ const chip = this._chipProps;
1818
+ return [
1819
+ "mono-tag-input-chip",
1820
+ chip.color ? "mono-tag-input-chip-pinned" : "",
1821
+ chip.size ?? (this.size === "xs" || this.size === "sm" ? "xs" : "sm"),
1822
+ `${this._chipVariantClass}-${this._chipColorClass}`,
1823
+ "",
1824
+ chip.dot ? "has-dot" : ""
1825
+ ].filter(Boolean).join(" ");
1826
+ }
1827
+ /**
1828
+ * Class for one chip part. Both class APIs append: the tag-input-level key
1829
+ * (`cssClass.chip` / `.chipLabel` / `.chipRemove`, the established surface) and
1830
+ * the chip-level one (`chip.cssClass.*`, which additionally reaches the parts
1831
+ * tag-input has no key for — `main`, `content`, `dot`).
1832
+ */
1833
+ _chipCls(base, chipKey, key) {
1834
+ return [
1835
+ base,
1836
+ key ? this.cssClass?.[key] : void 0,
1837
+ this._chipProps.cssClass?.[chipKey]
1838
+ ].filter(Boolean).join(" ");
1839
+ }
1840
+ /** Render one chip with shared mono-chip markup/classes. */
1841
+ _renderChip(value, removable) {
1842
+ const chip = this._chipProps;
1843
+ const label = this._getLabelByValue(value);
1844
+ const canRemove = removable && !this._inert && this._canRemoveMore;
1845
+ return html`
1846
+ <div
1847
+ class=${this._chipCls(`${this._chipBaseClasses}${canRemove ? " removable" : ""}`, "root", "chip")}
1848
+ mono-chip
1849
+ ?mono-removable=${canRemove}
1850
+ mono-chip-color=${chip.color ?? nothing}
1851
+ >
1852
+ <span class=${ifDefined(chip.cssClass?.main)} mono-chip-main>
1853
+ <span class=${this._chipCls("chip-content", "content")} mono-chip-content>
1854
+ ${chip.dot ? html`<span
1855
+ class=${this._chipCls("chip-dot", "dot")}
1856
+ mono-chip-dot
1857
+ aria-hidden="true"
1858
+ ></span>` : nothing}
1859
+ <span class=${this._chipCls("chip-label", "label", "chipLabel")} mono-chip-label>${label}</span>
1860
+ ${canRemove ? html`
1861
+ <button
1862
+ type="button"
1863
+ class=${this._chipCls("chip-close", "close", "chipRemove")}
1864
+ mono-chip-close
1865
+ aria-label=${chip.closeLabel ?? `Remove ${label}`}
1866
+ @click=${(event) => this._removeValue(value, event)}
1867
+ >
1868
+ ${this._closeSvg()}
1869
+ </button>
1870
+ ` : nothing}
1871
+ </span>
1872
+ </span>
1873
+ </div>
1874
+ `;
1875
+ }
1876
+ /**
1877
+ * The chip region. `flex` returns the chips as bare siblings of the input, so
1878
+ * they wrap with it — the historical markup, unchanged. `inline` nests them in
1879
+ * one `overflow-x: hidden` strip instead, which is what makes the row scroll
1880
+ * as a unit while the input keeps its own space beside it.
1881
+ */
1882
+ _renderChipRegion() {
1883
+ const chips = this._renderChips();
1884
+ if (this._chipBehaviour !== "inline") return chips;
1885
+ return html`
1886
+ <div class=${this._cls("mono-tag-input-chip-strip", "chipStrip")} mono-chip-strip>${chips}</div>
1887
+ `;
1888
+ }
1889
+ _renderChips() {
1890
+ const max = this._maxChips;
1891
+ const visible = max === Infinity ? this._resolvedValues : this._resolvedValues.slice(0, max);
1892
+ this._renderedChipCount = visible.length;
1893
+ const chips = visible.map((value) => this._renderChip(value, true));
1894
+ const overflow = this._overflowChips;
1895
+ if (overflow.length) chips.push(html`
1896
+ <div
1897
+ class=${this._cls(`${this._chipBaseClasses.replace(" has-dot", "")} clickable mono-tag-input-more`, "moreChip")}
1898
+ mono-chip
1899
+ mono-more
1900
+ mono-chip-color=${this._chipProps.color ?? nothing}
1901
+ >
1902
+ <span
1903
+ class=${ifDefined(this._chipProps.cssClass?.main)}
1904
+ mono-chip-main
1905
+ role="button"
1906
+ tabindex="0"
1907
+ @mousedown=${(event) => event.preventDefault()}
1908
+ @click=${(event) => {
1909
+ event.stopPropagation();
1910
+ this._moreOpen = !this._moreOpen;
1911
+ if (this._moreOpen) this._close();
1912
+ }}
1913
+ >
1914
+ <span class=${this._chipCls("chip-content", "content")} mono-chip-content>
1915
+ <span class=${this._chipCls("chip-label", "label")} mono-chip-label>+${overflow.length} more</span>
1916
+ </span>
1917
+ </span>
1918
+ </div>
1919
+ `);
1920
+ return chips;
1921
+ }
1922
+ /**
1923
+ * The trailing control row: `‹ › ✕` or `‹ › ⌄`.
1924
+ *
1925
+ * The scroll buttons only exist in `inline` mode, and each only while there is
1926
+ * something to scroll toward — so a strip that fits shows neither, and a strip
1927
+ * parked at its start shows only `›`.
1928
+ *
1929
+ * Clear and caret are MUTUALLY EXCLUSIVE, in both layouts: whenever the clear
1930
+ * button is available the field has a value and clearing is the useful action,
1931
+ * and whenever it is not (empty field, or `clearable`/`disabled`/`readonly`
1932
+ * ruling it out) the caret advertises the dropdown instead. That keeps the row
1933
+ * one glyph wide, which is what lets `flex` mode go on reserving a single-icon
1934
+ * gutter.
1935
+ *
1936
+ * The scroll buttons suppress `mousedown` and stay out of the tab order: they
1937
+ * are a view control, so paging must not blur the input (which would close the
1938
+ * dropdown mid-interaction) or move focus off the field. The clear button
1939
+ * keeps its existing focus behaviour.
1940
+ */
1941
+ /**
1942
+ * One scroll button. `active` false keeps the box — and therefore the row's
1943
+ * width — but hides it (`is-idle` → `visibility: hidden`), so reaching an end
1944
+ * of the strip costs no layout. `disabled` and `aria-hidden` keep the hidden
1945
+ * one out of reach of the pointer and of assistive tech.
1946
+ */
1947
+ _renderScrollButton(dir, active) {
1948
+ const back = dir === -1;
1949
+ const base = `mono-tag-input-scroll mono-tag-input-scroll-${back ? "prev" : "next"}${active ? "" : " is-idle"}`;
1950
+ return html`
1951
+ <button
1952
+ type="button"
1953
+ class=${this._cls(base, back ? "scrollPrev" : "scrollNext")}
1954
+ mono-scroll=${back ? "prev" : "next"}
1955
+ ?mono-idle=${!active}
1956
+ aria-label=${back ? "Scroll tags backward" : "Scroll tags forward"}
1957
+ aria-hidden=${active ? "false" : "true"}
1958
+ ?disabled=${!active}
1959
+ tabindex="-1"
1960
+ @mousedown=${(event) => event.preventDefault()}
1961
+ @click=${(event) => {
1962
+ event.stopPropagation();
1963
+ this._chipStrip.page(dir);
1964
+ }}
1965
+ >
1966
+ ${chevronIcon(dir)}
1967
+ </button>
1968
+ `;
1969
+ }
1970
+ /** Spinner while the source first loads, ↻ after it failed; else nothing. */
1971
+ _renderSourceState() {
1972
+ if (this._busy) return html`<span mono-ds-spinner role="status" aria-label=${this.loadingText}>${spinnerIcon()}</span>`;
1973
+ if (this._failure && !this.disabled) return html`
1974
+ <button
1975
+ type="button"
1976
+ tabindex="-1"
1977
+ mono-ds-retry
1978
+ aria-label=${this.retryLabel}
1979
+ title=${this.retryLabel}
1980
+ @mousedown=${(event) => event.preventDefault()}
1981
+ @click=${this._retry}
1982
+ >
1983
+ ${refreshIcon()}
1984
+ </button>
1985
+ `;
1986
+ return nothing;
1987
+ }
1988
+ _renderActions() {
1989
+ const showScroll = this._chipBehaviour === "inline" && this._chipStrip.overflowing;
1990
+ const showArrow = !this._showClear && !this._inert;
1991
+ return html`
1992
+ <div class=${this._cls("mono-tag-input-actions", "actions")} mono-actions>
1993
+ ${showScroll ? html`
1994
+ ${this._renderScrollButton(-1, this._chipStrip.canScrollStart)}
1995
+ ${this._renderScrollButton(1, this._chipStrip.canScrollEnd)}
1996
+ ` : nothing}
1997
+ ${this._showClear ? html`
1998
+ <button
1999
+ type="button"
2000
+ class=${this._cls("mono-tag-input-clear", "clear")}
2001
+ mono-clear
2002
+ aria-label="Clear tags"
2003
+ @click=${this._clear}
2004
+ >
2005
+ ${this.renderIcon("close")}
2006
+ </button>
2007
+ ` : nothing}
2008
+ ${showArrow ? html`
2009
+ <span
2010
+ role="button"
2011
+ tabindex="-1"
2012
+ class=${this._cls("mono-tag-input-arrow", "arrow")}
2013
+ mono-arrow
2014
+ aria-label="Toggle suggestions"
2015
+ aria-expanded=${this._open ? "true" : "false"}
2016
+ @mousedown=${(event) => event.preventDefault()}
2017
+ @click=${this._toggleFromCaret}
2018
+ >
2019
+ ${caretIcon()}
2020
+ </span>
2021
+ ` : nothing}
2022
+ ${this._renderSourceState()}
2023
+ </div>
2024
+ `;
2025
+ }
2026
+ /**
2027
+ * Always rendered, shown by the root's `more-open` class: the panel lives in a
2028
+ * body portal while open and a portaled node cannot be removed by a
2029
+ * conditional render (Lit's ChildPart no longer contains it).
2030
+ */
2031
+ _renderMorePanel() {
2032
+ const overflow = this._moreOpen ? this._overflowChips : [];
2033
+ return html`
2034
+ <div class=${this._cls("mono-tag-input-more-panel", "morePanel")} mono-more-panel>
2035
+ ${overflow.map((value) => this._renderChip(value, true))}
2036
+ </div>
2037
+ `;
2038
+ }
2039
+ _toCssLength(value) {
2040
+ if (value == null || value === "") return void 0;
2041
+ if (typeof value === "number") return `${value}px`;
2042
+ const trimmed = String(value).trim();
2043
+ if (!trimmed) return void 0;
2044
+ return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
2045
+ }
2046
+ /**
2047
+ * The author's cap goes out as the custom property `--_mono-tag-input-panel-max-h`
2048
+ * rather than a direct `max-height`: the popup controller also constrains this
2049
+ * same element (via `--mono-popup-avail-h`) to keep it inside the viewport, and
2050
+ * two writers on one declaration would clobber each other. The CSS `min()`s them.
2051
+ */
2052
+ get _dropdownStyle() {
2053
+ const style = {};
2054
+ const panel = this.dropdown;
2055
+ const width = toCssSize(panel?.width);
2056
+ if (width) style.width = width;
2057
+ const minWidth = toCssSize(panel?.minWidth);
2058
+ if (minWidth) style["min-width"] = minWidth;
2059
+ const maxWidth = toCssSize(panel?.maxWidth);
2060
+ if (maxWidth) style["max-width"] = maxWidth;
2061
+ const minHeight = toCssSize(panel?.minHeight);
2062
+ if (minHeight) style["min-height"] = minHeight;
2063
+ const height = toCssSize(panel?.height) ?? this._toCssLength(this.dropdownHeight);
2064
+ if (height) style.height = height;
2065
+ const maxHeight = toCssSize(panel?.maxHeight) ?? this._toCssLength(this.dropdownMaxHeight);
2066
+ if (maxHeight) style["--_mono-tag-input-panel-max-h"] = maxHeight;
2067
+ else if (height) style["--_mono-tag-input-panel-max-h"] = height;
2068
+ else if (this._scrollAutoMaxHeight) style["--_mono-tag-input-panel-max-h"] = this._scrollAutoMaxHeight;
2069
+ return style;
2070
+ }
2071
+ /**
2072
+ * Scroll-mode ergonomics (see mono-select): cap the list height to the loaded
2073
+ * rows minus a sliver so a short page is still scrollable, instead of eagerly
2074
+ * fetching another page on open.
2075
+ */
2076
+ _updateScrollAutoHeight() {
2077
+ const userSetHeight = this._toCssLength(this.dropdownHeight) != null || this._toCssLength(this.dropdownMaxHeight) != null || toCssSize(this.dropdown?.height) != null || toCssSize(this.dropdown?.maxHeight) != null;
2078
+ const rowsShown = this._filteredItems.length;
2079
+ if (!(this._loadMoreMode === "scroll" && this._open && !this._ds.atLastPage && !userSetHeight && rowsShown > 0)) {
2080
+ this._setScrollAutoMaxHeight("");
2081
+ return;
2082
+ }
2083
+ const el = this._dropdownEl;
2084
+ const rowH = (el?.querySelector(".mono-tag-input-item"))?.offsetHeight ?? 0;
2085
+ if (!el || !rowH) return;
2086
+ const CAP_ROWS = 7;
2087
+ let visible = Math.min(rowsShown, CAP_ROWS);
2088
+ if (rowsShown <= CAP_ROWS) visible -= .5;
2089
+ this._setScrollAutoMaxHeight(`${Math.round(visible * rowH)}px`);
2090
+ }
2091
+ _setScrollAutoMaxHeight(value) {
2092
+ if (value === this._scrollAutoMaxHeight) return;
2093
+ queueMicrotask(() => {
2094
+ this._scrollAutoMaxHeight = value;
2095
+ });
2096
+ }
2097
+ _renderLoadMore() {
2098
+ if (!this._loadMoreMode) return nothing;
2099
+ if (!this._visibleItems.length || this._ds.atLastPage) return nothing;
2100
+ if (this._ds.loadingMore) return html`
2101
+ <div class=${`${this._cls("mono-tag-input-load-more", "loadMore")} loading`} mono-load-more mono-loading>
2102
+ Loading…
2103
+ </div>
2104
+ `;
2105
+ if (this._loadMoreMode !== "button") return nothing;
2106
+ return html`
2107
+ <button
2108
+ type="button"
2109
+ class=${this._cls("mono-tag-input-load-more", "loadMore")}
2110
+ mono-load-more
2111
+ @mousedown=${(event) => event.preventDefault()}
2112
+ @click=${(event) => {
2113
+ event.preventDefault();
2114
+ event.stopPropagation();
2115
+ this._ds.loadMore();
2116
+ }}
2117
+ >
2118
+ Load more
2119
+ </button>
2120
+ `;
2121
+ }
2122
+ /**
2123
+ * The reused `.mono-checkbox` box. Tri-state: `'some'` paints the indeterminate
2124
+ * dash, which is what tells a user a group is PARTLY selected — the one thing a
2125
+ * two-state box could not say.
2126
+ */
2127
+ _renderCheckBox(state, loading = false) {
2128
+ const cls = [
2129
+ this._cls("mono-checkbox", "check"),
2130
+ this.color,
2131
+ state === "all" ? "mono-checkbox-checked" : "",
2132
+ state === "some" ? "mono-checkbox-indeterminate" : "",
2133
+ loading ? "is-loading" : ""
2134
+ ].filter(Boolean).join(" ");
2135
+ const boxCls = [
2136
+ "mono-checkbox-box",
2137
+ "sm",
2138
+ loading ? "has-custom-icon has-custom-indeterminate-icon" : ""
2139
+ ].filter(Boolean).join(" ");
2140
+ return html`
2141
+ <span
2142
+ class=${cls}
2143
+ mono-check-box
2144
+ mono-checkbox
2145
+ mono-size="sm"
2146
+ mono-color=${this.color === "primary" ? nothing : this.color}
2147
+ ?mono-checked=${state === "all"}
2148
+ ?mono-indeterminate=${state === "some"}
2149
+ ?mono-loading=${loading}
2150
+ aria-hidden="true"
2151
+ >
2152
+ <span class=${boxCls} mono-box ?mono-custom-icon=${loading} ?mono-custom-indeterminate-icon=${loading}>${loading ? this._renderCheckSpinner() : nothing}</span>
2153
+ </span>
2154
+ `;
2155
+ }
2156
+ /**
2157
+ * The drain spinner. Inline SVG rather than the `i-mdi-loading` UnoCSS class
2158
+ * because a page stylesheet cannot reach the shadow build's root — one template
2159
+ * serves both, the same reason `composables/field-icons` exists.
2160
+ *
2161
+ * `.mono-checkbox-spinner` carries the sizing and the keyframes; checkbox.css is
2162
+ * already on the page for the light build and adopted into the shadow root for
2163
+ * the other, which is what the box itself depends on too.
2164
+ */
2165
+ _renderCheckSpinner() {
2166
+ return html`<svg
2167
+ class="mono-checkbox-spinner"
2168
+ mono-spinner
2169
+ viewBox="0 0 24 24"
2170
+ fill="none"
2171
+ stroke="currentColor"
2172
+ stroke-width="3"
2173
+ stroke-linecap="round"
2174
+ aria-hidden="true"
2175
+ >
2176
+ <path d="M12 3a9 9 0 1 0 9 9" />
2177
+ </svg>`;
2178
+ }
2179
+ /**
2180
+ * The leading "All" row. Scoped to the rows the current search leaves visible,
2181
+ * which is what `items` already is at every call site.
2182
+ *
2183
+ * `role="option"` because that is what it behaves like — an option meaning "every
2184
+ * one of these" — so a screen reader announces it inside the same listbox as the
2185
+ * rows it toggles.
2186
+ */
2187
+ _renderSelectAll(items) {
2188
+ if (!this.selectAll || this.disabled || this.readonly) return nothing;
2189
+ const usable = items.filter((item) => this._isSelectable(item));
2190
+ if (!usable.length) return nothing;
2191
+ const state = this._selectAllState(usable);
2192
+ const pending = this._selectAllPending;
2193
+ const cls = [
2194
+ this._cls("mono-tag-input-item", "item"),
2195
+ this._cls("mono-tag-input-select-all", "selectAll"),
2196
+ "has-check",
2197
+ state !== "none" ? "selected" : "",
2198
+ pending ? "is-loading" : ""
2199
+ ].filter(Boolean).join(" ");
2200
+ if (this._hasSelectAllSlotState) return html`
2201
+ <div
2202
+ role="option"
2203
+ tabindex="-1"
2204
+ class=${cls}
2205
+ mono-item
2206
+ mono-select-all
2207
+ mono-check
2208
+ ?mono-selected=${state !== "none"}
2209
+ ?mono-loading=${pending}
2210
+ aria-selected=${state === "all" ? "true" : "false"}
2211
+ aria-busy=${pending ? "true" : "false"}
2212
+ aria-disabled=${pending ? "true" : "false"}
2213
+ @mousedown=${(event) => event.preventDefault()}
2214
+ @click=${(event) => this._onSelectAllClick(usable, state, event)}
2215
+ >
2216
+ ${this._renderCheckBox(state, pending)}
2217
+ <div class=${this._cls("mono-tag-input-select-all-slot", "selectAllSlot")} mono-select-all-slot>
2218
+ ${this.renderSelectAllSlot()}
2219
+ </div>
2220
+ </div>
2221
+ `;
2222
+ return html`
2223
+ <button
2224
+ type="button"
2225
+ role="option"
2226
+ class=${cls}
2227
+ mono-item
2228
+ mono-select-all
2229
+ mono-check
2230
+ ?mono-selected=${state !== "none"}
2231
+ ?mono-loading=${pending}
2232
+ aria-selected=${state === "all" ? "true" : "false"}
2233
+ aria-busy=${pending ? "true" : "false"}
2234
+ ?disabled=${pending}
2235
+ @mousedown=${(event) => event.preventDefault()}
2236
+ @click=${(event) => this._onSelectAllClick(usable, state, event)}
2237
+ >
2238
+ ${this._renderCheckBox(state, pending)}
2239
+ <div class=${this._cls("mono-tag-input-item-text", "itemText")} mono-item-text>
2240
+ <div class=${this._cls("mono-tag-input-item-title", "itemTitle")} mono-item-title>
2241
+ ${this.selectAllLabel}
2242
+ </div>
2243
+ </div>
2244
+ </button>
2245
+ `;
2246
+ }
2247
+ /**
2248
+ * Where the consumer's `slot="select-all"` content renders — overridden by the shadow build,
2249
+ * which projects it through a native `<slot>`. Light: a placeholder the build moves the
2250
+ * captured wrapper into, exactly as `renderListSlot` does for the rows.
2251
+ */
2252
+ renderSelectAllSlot() {
2253
+ return html`<span data-mono-slot="select-all"></span>`;
2254
+ }
2255
+ /**
2256
+ * Whether a click on the "All" row actually landed on a control inside the consumer's slot
2257
+ * content. Walked through `composedPath()` so projected content counts in the shadow build too;
2258
+ * it stops at the row, so a `<label>` wrapping the whole panel cannot claim every click.
2259
+ */
2260
+ _isSelectAllSlotControl(event) {
2261
+ if (!this._hasSelectAllSlotState) return false;
2262
+ const row = event.currentTarget;
2263
+ let inSlot = false;
2264
+ let control = false;
2265
+ for (const node of event.composedPath()) {
2266
+ if (node === row) break;
2267
+ if (!(node instanceof Element)) continue;
2268
+ if (node.matches(SELECT_ALL_SLOT_INTERACTIVE)) control = true;
2269
+ if (node.getAttribute("slot") === "select-all" || node.hasAttribute("mono-select-all-slot")) inSlot = true;
2270
+ }
2271
+ return inSlot && control;
2272
+ }
2273
+ /**
2274
+ * A group header — a plain label, or its own select-all when `groupSelectAll` is
2275
+ * on and the group has rows to toggle. It stays a header either way: same class,
2276
+ * same sticky behaviour, same indent, so a grouped list does not reflow when the
2277
+ * feature is switched off.
2278
+ */
2279
+ _renderGroupHeader(row) {
2280
+ const base = `${this._cls("mono-tag-input-group", "group")}${this.groupSticky ? " sticky" : ""}`;
2281
+ const usable = row.items.filter((item) => this._isSelectable(item));
2282
+ if (!this.groupSelectAll || this.disabled || this.readonly || !usable.length) return html`<div class=${base} mono-group mono-level=${row.level} ?mono-sticky=${this.groupSticky} data-level=${row.level} role="presentation">
2283
+ ${row.label}
2284
+ </div>`;
2285
+ const state = this._selectionStateOf(usable);
2286
+ return html`
2287
+ <button
2288
+ type="button"
2289
+ class=${`${base} is-toggle${state !== "none" ? " selected" : ""}`}
2290
+ mono-group
2291
+ mono-toggle
2292
+ mono-level=${row.level}
2293
+ ?mono-sticky=${this.groupSticky}
2294
+ ?mono-selected=${state !== "none"}
2295
+ data-level=${row.level}
2296
+ aria-pressed=${state === "all" ? "true" : "false"}
2297
+ @mousedown=${(event) => event.preventDefault()}
2298
+ @click=${(event) => this._toggleMany(usable, event)}
2299
+ >
2300
+ ${this._renderCheckBox(state)}
2301
+ <span class=${this._cls("mono-tag-input-group-label", "groupLabel")} mono-group-label>
2302
+ ${row.label}
2303
+ </span>
2304
+ </button>
2305
+ `;
2306
+ }
2307
+ /**
2308
+ * The flat entry list published to `form.items()[key].list`.
2309
+ *
2310
+ * The SAME sequence the component renders — group headers interleaved with their leaves — so a
2311
+ * consumer looping it produces one node per row, in order, and the two stay paired by position
2312
+ * with nothing to key by hand. `_groupRows()` already computes exactly this for the grouped case.
2313
+ *
2314
+ * `key` is `item[keyValue]` via `_resolveItemValue`, the same value selection is keyed by, so the
2315
+ * feature introduces no second notion of a key.
2316
+ */
2317
+ _listEntries() {
2318
+ const rowEntry = (item, level, index) => ({
2319
+ type: "row",
2320
+ key: String(this._resolveItemValue(item) ?? ""),
2321
+ item,
2322
+ level,
2323
+ selected: this._isSelected(item),
2324
+ active: index >= 0 && index === this._activeIndex
2325
+ });
2326
+ if (this._grouped) return this._groupRows().map((row) => row.kind === "group" ? {
2327
+ type: "group",
2328
+ key: row.key,
2329
+ label: row.label,
2330
+ level: row.level,
2331
+ items: row.items
2332
+ } : rowEntry(row.item, row.level, row.index));
2333
+ return this._filteredItems.map((item, index) => rowEntry(item, 0, index));
2334
+ }
2335
+ /**
2336
+ * Hand the form the current entries — but only a NEW array when they actually moved.
2337
+ *
2338
+ * This runs from `updated()`, so it fires on every render; the controller notifies on a changed
2339
+ * list, and that notify re-renders this control. Returning the same array reference is what keeps
2340
+ * that from looping forever. Identity of `item` counts as a change: a reloaded DataSource hands
2341
+ * back fresh row objects under the same keys, and a consumer reading `e.item.Nama` must see them.
2342
+ */
2343
+ _syncListEntries() {
2344
+ if (!this.dataForm || !this.keyForm) return;
2345
+ const next = this._listEntries();
2346
+ const prev = this._publishedEntries;
2347
+ let same = prev.length === next.length;
2348
+ if (same) for (let i = 0; i < next.length; i++) {
2349
+ const a = prev[i];
2350
+ const b = next[i];
2351
+ if (a.type !== b.type || a.key !== b.key || a.item !== b.item || a.selected !== b.selected || a.active !== b.active) {
2352
+ same = false;
2353
+ break;
2354
+ }
2355
+ }
2356
+ if (same) return;
2357
+ this._publishedEntries = next;
2358
+ this._publishList(next);
2359
+ }
2360
+ _renderOption(item, index, level = 0) {
2361
+ const active = index === this._activeIndex;
2362
+ const checked = this._isSelected(item);
2363
+ const itemClass = [
2364
+ this._cls("mono-tag-input-item", "item"),
2365
+ this.checkable ? "has-check" : "",
2366
+ active ? `active ${this.cssClass?.itemActive ?? ""}` : "",
2367
+ checked ? `selected ${this.cssClass?.itemSelected ?? ""}` : "",
2368
+ item.disabled ? `disabled ${this.cssClass?.itemDisabled ?? ""}` : ""
2369
+ ].filter(Boolean).join(" ");
2370
+ const itemValue = this._resolveItemValue(item);
2371
+ return html`
2372
+ <button
2373
+ type="button"
2374
+ role="option"
2375
+ class=${itemClass}
2376
+ mono-item
2377
+ ?mono-check=${this.checkable}
2378
+ ?mono-active=${active}
2379
+ ?mono-selected=${checked}
2380
+ ?mono-disabled=${!!item.disabled}
2381
+ mono-level=${level}
2382
+ data-level=${level}
2383
+ aria-selected=${checked ? "true" : "false"}
2384
+ ?disabled=${item.disabled}
2385
+ @mousedown=${(event) => event.preventDefault()}
2386
+ @click=${(event) => itemValue === void 0 ? void 0 : this._toggleValue(itemValue, event)}
2387
+ >
2388
+ ${this.checkable ? this._renderCheckBox(checked ? "all" : "none") : nothing}
2389
+
2390
+ <div class=${this._cls("mono-tag-input-item-text", "itemText")} mono-item-text>
2391
+ <div class=${this._cls("mono-tag-input-item-title", "itemTitle")} mono-item-title>
2392
+ ${this._resolveItemDisplay(item)}
2393
+ </div>
2394
+
2395
+ ${item.description ? html`
2396
+ <div class=${this._cls("mono-tag-input-item-sub", "itemSub")} mono-item-sub>
2397
+ ${item.description}
2398
+ </div>
2399
+ ` : nothing}
2400
+ </div>
2401
+ </button>
2402
+ `;
2403
+ }
2404
+ _renderEmpty() {
2405
+ const text = this.dataSource && this._ds.loading ? this.loadingText : "No items";
2406
+ return html`<div class=${this._cls("mono-tag-input-empty", "empty")} mono-empty-row>${text}</div>`;
2407
+ }
2408
+ _renderItems() {
2409
+ const visible = this._filteredItems;
2410
+ const empty = !visible.length;
2411
+ if (this._hasListSlotState) return html`
2412
+ ${empty ? this._renderEmpty() : this._renderSelectAll(visible)}
2413
+ ${this.renderListSlot()}
2414
+ `;
2415
+ if (empty) return this._renderEmpty();
2416
+ if (this._grouped) return html`
2417
+ ${this._renderSelectAll(visible)}
2418
+ ${this._groupRows().map((row) => row.kind === "group" ? this._renderGroupHeader(row) : this._renderOption(row.item, row.index, row.level))}
2419
+ `;
2420
+ return html`
2421
+ ${this._renderSelectAll(visible)}
2422
+ ${visible.map((item, index) => this._renderOption(item, index))}
2423
+ `;
2424
+ }
2425
+ /**
2426
+ * Where the consumer's `slot="list"` content goes — overridden per build.
2427
+ *
2428
+ * mono places that content as ONE block and never reaches inside it. Moving a `v-for`'s children
2429
+ * is not survivable: Vue inserts a new row with `parent.insertBefore(node, anchor)` where the
2430
+ * anchor is the existing node at that position, so relocating one makes the consumer's next
2431
+ * insert throw `NotFoundError`. Hence one block, and hence `data-mono-item-key` — with the rows
2432
+ * out of reach, a delegated click is the only way back to the item.
2433
+ */
2434
+ renderListSlot() {
2435
+ return html``;
2436
+ }
2437
+ /**
2438
+ * The entry a click landed on — by POSITION, so a consumer writes a plain `v-for` and nothing else.
2439
+ *
2440
+ * mono cannot read a node's item off the node: it published the list, but the DOM was built by
2441
+ * someone else. What it CAN do is count. The wrapper's Nth element child is the Nth published
2442
+ * entry, because that is the contract the slot already states — one flat loop, one node per entry,
2443
+ * in order — and here that contract is CHECKED rather than assumed: if the two lengths disagree,
2444
+ * this refuses the click instead of picking the wrong row.
2445
+ *
2446
+ * Position is resolved at CLICK time, never cached. That is what makes it safe with no observer
2447
+ * and no re-stamping: by the time a human clicks, the framework has long finished patching, so
2448
+ * there is no window where a stale pairing could be consulted.
2449
+ *
2450
+ * `data-mono-item-key` still wins when present — the escape hatch for a wrapper that cannot emit
2451
+ * exactly one element per entry (a sticky header of your own, a row that renders as a fragment).
2452
+ */
2453
+ /**
2454
+ * The consumer's `slot="list"` wrapper, wherever the build keeps it.
2455
+ *
2456
+ * The shadow build never moves it — it stays a light child of the host and is PROJECTED — so
2457
+ * that is the default. The light build moves it into the panel and overrides this with the node
2458
+ * it captured, which is also findable while the panel is closed and the wrapper parked.
2459
+ */
2460
+ get _listSlotWrapper() {
2461
+ return this._lateListSlot.find();
2462
+ }
2463
+ /**
2464
+ * Re-decorate whenever the consumer re-renders their rows.
2465
+ *
2466
+ * `_syncListChrome()` runs from `updated()`, but a framework patches the wrapper on ITS next
2467
+ * tick — so a row the search box just revealed would be born undecorated and stay that way until
2468
+ * something else made mono render. This closes that gap.
2469
+ *
2470
+ * `childList` on the WRAPPER, which is exactly the churn {@link LateSlotWatcher} refuses on the
2471
+ * host: there the consumer's row traffic is noise, here it IS the signal. No `subtree`, so
2472
+ * mono's own writes — attributes, and a checkbox appended INSIDE a row — cannot re-trigger it.
2473
+ */
2474
+ _watchListChrome() {
2475
+ if (typeof MutationObserver === "undefined") return;
2476
+ const wrapper = this._listSlotWrapper;
2477
+ if (!wrapper || wrapper === this._observedListWrapper) return;
2478
+ this._listChromeObserver?.disconnect();
2479
+ this._observedListWrapper = wrapper;
2480
+ this._listChromeObserver = new MutationObserver(() => this._syncListChrome());
2481
+ this._listChromeObserver.observe(wrapper, { childList: true });
2482
+ }
2483
+ _stopWatchingListChrome() {
2484
+ this._listChromeObserver?.disconnect();
2485
+ this._listChromeObserver = void 0;
2486
+ this._observedListWrapper = void 0;
2487
+ }
2488
+ /**
2489
+ * Decorate the lines the consumer rendered — the half of this feature that keeps the SLOT about
2490
+ * markup and mono about behaviour.
2491
+ *
2492
+ * Your template says what a line LOOKS like. Everything that carries state or acts on the value
2493
+ * stays here: mono walks the lines it just published and stamps the state as `data-*`
2494
+ * attributes, then puts its own checkbox in — the same markup `_renderCheckBox` produces for
2495
+ * mono's own rows, so it inherits the same CSS and the same colour prop.
2496
+ *
2497
+ * Three rules keep this safe beside a framework:
2498
+ * · children are ANNOTATED and APPENDED TO, never moved, reordered or removed — every
2499
+ * `insertBefore` anchor a `v-for` patches against stays valid;
2500
+ * · `data-*` only, never `class` — `class` is the consumer's binding and would be clobbered;
2501
+ * · the injected node is marked `data-mono-chrome`, so it is never mistaken for content and is
2502
+ * updated in place instead of recreated.
2503
+ */
2504
+ _syncListChrome() {
2505
+ if (!this._hasListSlotState) return;
2506
+ const wrapper = this._listSlotWrapper;
2507
+ if (!wrapper) return;
2508
+ const children = Array.from(wrapper.children);
2509
+ const entries = this._publishedEntries;
2510
+ if (children.length !== entries.length) return;
2511
+ for (let i = 0; i < children.length; i++) {
2512
+ const node = children[i];
2513
+ const entry = entries[i];
2514
+ node.setAttribute("data-mono-type", entry.type);
2515
+ node.setAttribute("data-mono-level", String(entry.level ?? 0));
2516
+ node.setAttribute("data-mono-key", entry.key);
2517
+ if (entry.type === "group") {
2518
+ const usable = (entry.items ?? []).filter((item) => this._isSelectable(item));
2519
+ const state = this._selectionStateOf(usable);
2520
+ node.setAttribute("data-mono-state", state);
2521
+ node.toggleAttribute("data-mono-selected", state === "all");
2522
+ this._syncListCheckBox(node, this.checkable && this.groupSelectAll && !this.disabled && !this.readonly && usable.length > 0, state);
2523
+ continue;
2524
+ }
2525
+ node.toggleAttribute("data-mono-selected", !!entry.selected);
2526
+ node.toggleAttribute("data-mono-active", !!entry.active);
2527
+ this._syncListCheckBox(node, this.checkable, entry.selected ? "all" : "none");
2528
+ }
2529
+ }
2530
+ /**
2531
+ * mono's own checkbox, inside a line mono does not own.
2532
+ *
2533
+ * Same markup as `_renderCheckBox`, built by hand because Lit cannot render into a subtree it
2534
+ * does not control. It is created once and then only re-classed, so the consumer's framework
2535
+ * never sees a node appear and disappear under it.
2536
+ */
2537
+ _syncListCheckBox(row, wanted, state) {
2538
+ let box = row.querySelector("[data-mono-chrome]");
2539
+ if (!wanted) {
2540
+ box?.remove();
2541
+ return;
2542
+ }
2543
+ if (!box) {
2544
+ box = document.createElement("span");
2545
+ box.setAttribute("data-mono-chrome", "");
2546
+ box.setAttribute("aria-hidden", "true");
2547
+ const inner = document.createElement("span");
2548
+ inner.className = "mono-checkbox-box sm";
2549
+ inner.setAttribute("mono-box", "");
2550
+ box.setAttribute("mono-check-box", "");
2551
+ box.setAttribute("mono-checkbox", "");
2552
+ box.setAttribute("mono-size", "sm");
2553
+ box.appendChild(inner);
2554
+ const anchor = row.querySelector("[data-mono-check]");
2555
+ if (anchor) anchor.appendChild(box);
2556
+ else row.insertBefore(box, row.firstChild);
2557
+ }
2558
+ box.className = [
2559
+ this._cls("mono-checkbox", "check"),
2560
+ this.color,
2561
+ state === "all" ? "mono-checkbox-checked" : "",
2562
+ state === "some" ? "mono-checkbox-indeterminate" : ""
2563
+ ].filter(Boolean).join(" ");
2564
+ if (this.color === "primary") box.removeAttribute("mono-color");
2565
+ else box.setAttribute("mono-color", this.color);
2566
+ box.toggleAttribute("mono-checked", state === "all");
2567
+ box.toggleAttribute("mono-indeterminate", state === "some");
2568
+ }
2569
+ _entryFromNode(node) {
2570
+ const keyed = node.closest?.("[data-mono-item-key]");
2571
+ if (keyed) {
2572
+ const key = keyed.getAttribute("data-mono-item-key");
2573
+ return this._publishedEntries.find((e) => e.key === key);
2574
+ }
2575
+ const wrapper = node.closest?.("[slot=\"list\"]");
2576
+ if (!wrapper) return void 0;
2577
+ let row = node;
2578
+ while (row && row.parentElement !== wrapper) row = row.parentElement;
2579
+ if (!row) return void 0;
2580
+ const children = Array.from(wrapper.children);
2581
+ if (children.length !== this._publishedEntries.length) {
2582
+ console.warn(`[mono-tag-input] 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.`);
2583
+ return;
2584
+ }
2585
+ return this._publishedEntries[children.indexOf(row)];
2586
+ }
2587
+ _renderHelper() {
2588
+ const base = this._cls("mono-tag-input-message", "message");
2589
+ if (this._validation.moved) return html`${this._renderHelperText()}${this._renderMovedMessage()}`;
2590
+ if (this.validationMessage) {
2591
+ const state = this._resolvedValidationState;
2592
+ return html`
2593
+ <div class=${`${base} ${state}`} mono-message=${state} role=${state === "invalid" ? "alert" : nothing}>
2594
+ ${this.validationMessage}
2595
+ </div>
2596
+ `;
2597
+ }
2598
+ if (this.errorMessage) return html`
2599
+ <div class=${`${base} invalid`} mono-message="invalid" role="alert">
2600
+ ${this.errorMessage}
2601
+ </div>
2602
+ `;
2603
+ if (this.successMessage) return html`
2604
+ <div class=${`${base} valid`} mono-message="valid">
2605
+ ${this.successMessage}
2606
+ </div>
2607
+ `;
2608
+ return this._renderHelperText();
2609
+ }
2610
+ _renderHelperText() {
2611
+ const base = this._cls("mono-tag-input-message", "message");
2612
+ if (this.helperText || this._hasHelperSlot || this._slotsAlwaysRender) return html`
2613
+ <div
2614
+ class=${`${base} helper`}
2615
+ mono-message="helper"
2616
+ ?mono-empty=${!this.helperText && !this._hasHelperSlot}
2617
+ >
2618
+ ${this._slotOutlet("helper", this.helperText)}
2619
+ </div>
2620
+ `;
2621
+ return nothing;
2622
+ }
2623
+ /** Inline sizing applied to the root wrapper. */
2624
+ _sizeStyle() {
2625
+ return buildSizeStyle(this);
2626
+ }
2627
+ render() {
2628
+ const describedBy = this.validationMessage || this.errorMessage || this.successMessage || this.helperText || this._hasHelperSlot ? this._messageId : void 0;
2629
+ const ariaLabel = this.ariaLabelText || this.label || this.placeholder || void 0;
2630
+ const state = this._resolvedValidationState;
2631
+ return html`
2632
+ <div
2633
+ class=${this._wrapperClasses}
2634
+ style=${styleMap(this._sizeStyle())}
2635
+ mono-tag-input
2636
+ mono-size=${this.size === "md" ? nothing : this.size}
2637
+ mono-color=${this.color === "primary" ? nothing : this.color}
2638
+ mono-variant=${this.variant === "outlined" ? nothing : this.variant}
2639
+ mono-label-position=${this.labelPosition === "top" ? nothing : this.labelPosition}
2640
+ mono-validation-position=${this.validationPosition === "bottom" ? nothing : this.validationPosition}
2641
+ mono-validation-state=${state === "default" ? nothing : state}
2642
+ ?mono-open=${this._open}
2643
+ ?mono-more-open=${this._moreOpen}
2644
+ ?mono-disabled=${this.disabled}
2645
+ ?mono-readonly=${this.readonly}
2646
+ ?mono-required=${this.required}
2647
+ ?mono-clearable=${this.clearable}
2648
+ ?mono-checkable=${this.checkable}
2649
+ ?mono-searchable=${this.searchable}
2650
+ ?mono-ds-loading=${this._busy}
2651
+ ?mono-ds-error=${!!this._failure}
2652
+ aria-busy=${this._busy ? "true" : nothing}
2653
+ >
2654
+ ${this._renderLabel()}
2655
+
2656
+ <div
2657
+ class=${this._fieldClasses}
2658
+ mono-field
2659
+ ?mono-inline=${this._chipBehaviour === "inline"}
2660
+ ?mono-typing=${!!this._inputValue}
2661
+ @click=${this._handleFieldClick}
2662
+ >
2663
+ ${this._renderChipRegion()}
2664
+
2665
+ <input
2666
+ id=${this._inputId}
2667
+ class=${this._cls("mono-tag-input-native", "native")}
2668
+ mono-native
2669
+ type="text"
2670
+ .value=${this._inputValue}
2671
+ name=${ifDefined(this.name || void 0)}
2672
+ placeholder=${ifDefined(this._inputPlaceholder)}
2673
+ title=${ifDefined(this._dsErrorTitle)}
2674
+ ?disabled=${this.disabled}
2675
+ ?readonly=${this.readonly || !this.searchable || this._dsInert}
2676
+ ?required=${this.required && !this._hasValue}
2677
+ aria-label=${ifDefined(ariaLabel)}
2678
+ aria-invalid=${this._resolvedValidationState === "invalid" ? "true" : "false"}
2679
+ aria-describedby=${ifDefined(describedBy)}
2680
+ aria-controls=${this._listboxId}
2681
+ aria-expanded=${this._open ? "true" : "false"}
2682
+ autocomplete="off"
2683
+ @input=${this._handleInput}
2684
+ @focus=${this._handleFocus}
2685
+ @keydown=${this._handleKeydown}
2686
+ />
2687
+
2688
+ ${this._validation.renderIcon()}
2689
+ ${this._renderActions()}
2690
+ </div>
2691
+
2692
+ ${this._renderMorePanel()}
2693
+
2694
+ <div
2695
+ id=${this._listboxId}
2696
+ role="listbox"
2697
+ class=${this._cls("mono-tag-input-dropdown", "dropdown")}
2698
+ mono-dropdown
2699
+ style=${styleMap(this._dropdownStyle)}
2700
+ @scroll=${this._handleDropdownScroll}
2701
+ >
2702
+ ${this._renderItems()}
2703
+ ${this._renderLoadMore()}
2704
+ </div>
2705
+
2706
+ <div id=${this._messageId} class=${this.cssClass?.messageWrap ?? ""} mono-message-wrap>
2707
+ ${this._renderHelper()}
2708
+ </div>
2709
+
2710
+ ${this._validation.renderFloat()}
2711
+ </div>
2712
+ `;
2713
+ }
2714
+ focus(options) {
2715
+ this._inputEl?.focus(options);
2716
+ }
2717
+ blur() {
2718
+ this._inputEl?.blur();
2719
+ }
2720
+ /**
2721
+ * Open / close the suggestion panel. Mirrors `mono-select`'s API so anything
2722
+ * driving an editor generically — e.g. the data grid opening the focused
2723
+ * inline editor on `Enter` — can treat the two the same.
2724
+ */
2725
+ /** Whether the suggestion panel is currently open. */
2726
+ get isOpen() {
2727
+ return this._open;
2728
+ }
2729
+ open() {
2730
+ if (this._inert) return;
2731
+ this._open = true;
2732
+ this._inputEl?.focus();
2733
+ }
2734
+ close() {
2735
+ this._close();
2736
+ }
2737
+ toggle() {
2738
+ if (this._open) this.close();
2739
+ else this.open();
2740
+ }
2741
+ clearTags() {
2742
+ if (this._inert) return;
2743
+ this._setValue(this._keepFloor([]), { emitClear: true });
2744
+ this._resetSearch();
2745
+ }
2746
+ addTag(value) {
2747
+ this._addValue(value);
2748
+ }
2749
+ removeTag(value) {
2750
+ this._removeValue(value);
2751
+ }
2752
+ /** Whether the slot regions render even when empty. Light: no (omit empty
2753
+ * regions). Shadow: yes — the native `<slot>`s must exist to project DSD
2754
+ * content + be scanned (hidden via `[mono-empty]` when empty). */
2755
+ get _slotsAlwaysRender() {
2756
+ return false;
2757
+ }
2758
+ _hasSlot(name) {
2759
+ return name === "label" ? this._hasLabelSlotState : this._hasHelperSlotState;
2760
+ }
2761
+ _setSlotState(name, has) {
2762
+ if (name === "label") this._hasLabelSlotState = has;
2763
+ else if (name === "list") this._hasListSlotState = has;
2764
+ else if (name === "select-all") this._hasSelectAllSlotState = has;
2765
+ else this._hasHelperSlotState = has;
2766
+ }
2767
+ /**
2768
+ * Slot outlet. Light build (default): a `data-mono-slot` placeholder the
2769
+ * captured light-DOM nodes are re-parented into when present, else the prop
2770
+ * `fallback`. Shadow build overrides this with a native `<slot name>` carrying
2771
+ * the fallback as native slot content.
2772
+ */
2773
+ _slotOutlet(name, fallback = nothing) {
2774
+ return this._hasSlot(name) ? html`<span data-mono-slot=${name}></span>` : html`${fallback}`;
2775
+ }
2776
+ /**
2777
+ * Icon hook. Light build (default): the global `.mono-icon` / `i-mdi-*` UnoCSS
2778
+ * icon. Shadow overrides with inline SVG (UnoCSS can't reach a shadow root).
2779
+ *
2780
+ * Only the close glyph goes through here. The caret and the scroll chevrons
2781
+ * are inline SVG from `composables/field-icons`, shared with
2782
+ * `<mono-dropdown-table>` — see `_renderActions`.
2783
+ */
2784
+ renderIcon(_name) {
2785
+ return html`<span class="mono-icon i-mdi-close" mono-icon aria-hidden="true"></span>`;
2786
+ }
2787
+ }
2788
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "size", void 0);
2789
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "color", void 0);
2790
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "variant", void 0);
2791
+ __decorate([property({
2792
+ type: String,
2793
+ attribute: "label-position"
2794
+ })], MonoTagInputCoreClass.prototype, "labelPosition", void 0);
2795
+ __decorate([property({
2796
+ type: String,
2797
+ attribute: "validation-position"
2798
+ })], MonoTagInputCoreClass.prototype, "validationPosition", void 0);
2799
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "chip", void 0);
2800
+ __decorate([property({
2801
+ attribute: "model-value",
2802
+ reflect: false,
2803
+ hasChanged: arrayHasChanged
2804
+ })], MonoTagInputCoreClass.prototype, "modelValue", void 0);
2805
+ __decorate([property({
2806
+ attribute: false,
2807
+ hasChanged: arrayHasChanged
2808
+ })], MonoTagInputCoreClass.prototype, "value", void 0);
2809
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "name", void 0);
2810
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "label", void 0);
2811
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "placeholder", void 0);
2812
+ __decorate([property({
2813
+ type: String,
2814
+ attribute: "loading-text"
2815
+ })], MonoTagInputCoreClass.prototype, "loadingText", void 0);
2816
+ __decorate([property({
2817
+ type: String,
2818
+ attribute: "retry-label"
2819
+ })], MonoTagInputCoreClass.prototype, "retryLabel", void 0);
2820
+ __decorate([property({ converter: booleanStringConverter })], MonoTagInputCoreClass.prototype, "loading", void 0);
2821
+ __decorate([property({ attribute: "loading-error" })], MonoTagInputCoreClass.prototype, "loadingError", void 0);
2822
+ __decorate([property({
2823
+ type: String,
2824
+ attribute: "helper-text"
2825
+ })], MonoTagInputCoreClass.prototype, "helperText", void 0);
2826
+ __decorate([property()], MonoTagInputCoreClass.prototype, "tooltip", void 0);
2827
+ __decorate([property({
2828
+ type: String,
2829
+ attribute: "validation-state"
2830
+ })], MonoTagInputCoreClass.prototype, "validationState", void 0);
2831
+ __decorate([property({
2832
+ type: String,
2833
+ attribute: "validation-message"
2834
+ })], MonoTagInputCoreClass.prototype, "validationMessage", void 0);
2835
+ __decorate([property({
2836
+ type: String,
2837
+ attribute: "error-message"
2838
+ })], MonoTagInputCoreClass.prototype, "errorMessage", void 0);
2839
+ __decorate([property({
2840
+ type: String,
2841
+ attribute: "success-message"
2842
+ })], MonoTagInputCoreClass.prototype, "successMessage", void 0);
2843
+ __decorate([property({
2844
+ type: String,
2845
+ attribute: "aria-label"
2846
+ })], MonoTagInputCoreClass.prototype, "ariaLabelText", void 0);
2847
+ __decorate([property({
2848
+ reflect: true,
2849
+ converter: booleanStringConverter
2850
+ })], MonoTagInputCoreClass.prototype, "disabled", void 0);
2851
+ __decorate([property({
2852
+ reflect: true,
2853
+ converter: booleanStringConverter
2854
+ })], MonoTagInputCoreClass.prototype, "readonly", void 0);
2855
+ __decorate([property({
2856
+ reflect: true,
2857
+ converter: booleanStringConverter
2858
+ })], MonoTagInputCoreClass.prototype, "required", void 0);
2859
+ __decorate([property({
2860
+ reflect: true,
2861
+ converter: booleanStringConverter
2862
+ })], MonoTagInputCoreClass.prototype, "clearable", void 0);
2863
+ __decorate([property({
2864
+ attribute: "allow-custom",
2865
+ reflect: true,
2866
+ converter: booleanStringConverter
2867
+ })], MonoTagInputCoreClass.prototype, "allowCustom", void 0);
2868
+ __decorate([property({
2869
+ attribute: "max",
2870
+ converter: numberStringConverter
2871
+ })], MonoTagInputCoreClass.prototype, "max", void 0);
2872
+ __decorate([property({
2873
+ attribute: "min",
2874
+ converter: numberStringConverter
2875
+ })], MonoTagInputCoreClass.prototype, "min", void 0);
2876
+ __decorate([property({
2877
+ reflect: true,
2878
+ converter: booleanStringConverter
2879
+ })], MonoTagInputCoreClass.prototype, "checkable", void 0);
2880
+ __decorate([property({
2881
+ attribute: "select-all",
2882
+ reflect: true,
2883
+ converter: booleanStringConverter
2884
+ })], MonoTagInputCoreClass.prototype, "selectAll", void 0);
2885
+ __decorate([property({
2886
+ type: String,
2887
+ attribute: "select-all-label",
2888
+ reflect: true
2889
+ })], MonoTagInputCoreClass.prototype, "selectAllLabel", void 0);
2890
+ __decorate([property({
2891
+ attribute: "group-select-all",
2892
+ reflect: true,
2893
+ converter: booleanStringConverter
2894
+ })], MonoTagInputCoreClass.prototype, "groupSelectAll", void 0);
2895
+ __decorate([property({
2896
+ attribute: "max-visible",
2897
+ converter: numberStringConverter
2898
+ })], MonoTagInputCoreClass.prototype, "maxVisible", void 0);
2899
+ __decorate([property({
2900
+ attribute: "min-visible",
2901
+ converter: numberStringConverter
2902
+ })], MonoTagInputCoreClass.prototype, "minVisible", void 0);
2903
+ __decorate([property({
2904
+ attribute: false,
2905
+ hasChanged: arrayHasChanged
2906
+ })], MonoTagInputCoreClass.prototype, "items", void 0);
2907
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "dataSource", void 0);
2908
+ __decorate([property({
2909
+ attribute: "immediate",
2910
+ reflect: true,
2911
+ converter: booleanStringConverter
2912
+ })], MonoTagInputCoreClass.prototype, "immediate", void 0);
2913
+ __decorate([property({
2914
+ attribute: "load-more",
2915
+ reflect: true
2916
+ })], MonoTagInputCoreClass.prototype, "loadMore", void 0);
2917
+ __decorate([property({
2918
+ attribute: "page-size",
2919
+ reflect: true,
2920
+ type: Number
2921
+ })], MonoTagInputCoreClass.prototype, "pageSize", void 0);
2922
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "dropdown", void 0);
2923
+ __decorate([property({
2924
+ attribute: "dropdown-height",
2925
+ reflect: true
2926
+ })], MonoTagInputCoreClass.prototype, "dropdownHeight", void 0);
2927
+ __decorate([property({
2928
+ attribute: "dropdown-max-height",
2929
+ reflect: true
2930
+ })], MonoTagInputCoreClass.prototype, "dropdownMaxHeight", void 0);
2931
+ __decorate([property({ converter: booleanStringConverter })], MonoTagInputCoreClass.prototype, "flip", void 0);
2932
+ __decorate([property({ converter: booleanStringConverter })], MonoTagInputCoreClass.prototype, "shift", void 0);
2933
+ __decorate([property({
2934
+ type: String,
2935
+ attribute: "key-value",
2936
+ reflect: true
2937
+ })], MonoTagInputCoreClass.prototype, "keyValue", void 0);
2938
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "displayValue", void 0);
2939
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "displayGroup", void 0);
2940
+ __decorate([property({
2941
+ type: String,
2942
+ attribute: "group-key",
2943
+ reflect: true
2944
+ })], MonoTagInputCoreClass.prototype, "groupKey", void 0);
2945
+ __decorate([property({
2946
+ type: String,
2947
+ attribute: "group-items",
2948
+ reflect: true
2949
+ })], MonoTagInputCoreClass.prototype, "groupItems", void 0);
2950
+ __decorate([property({
2951
+ attribute: "group",
2952
+ reflect: true,
2953
+ converter: booleanStringConverter
2954
+ })], MonoTagInputCoreClass.prototype, "group", void 0);
2955
+ __decorate([property({
2956
+ attribute: "group-sticky",
2957
+ reflect: true,
2958
+ converter: booleanStringConverter
2959
+ })], MonoTagInputCoreClass.prototype, "groupSticky", void 0);
2960
+ __decorate([property({
2961
+ attribute: "searchable",
2962
+ reflect: true,
2963
+ converter: booleanStringConverter
2964
+ })], MonoTagInputCoreClass.prototype, "searchable", void 0);
2965
+ __decorate([property({
2966
+ attribute: "stay-open",
2967
+ reflect: true,
2968
+ converter: booleanStringConverter
2969
+ })], MonoTagInputCoreClass.prototype, "stayOpen", void 0);
2970
+ __decorate([property({ attribute: "search-value" })], MonoTagInputCoreClass.prototype, "searchValue", void 0);
2971
+ __decorate([property({
2972
+ type: String,
2973
+ attribute: "search-operation"
2974
+ })], MonoTagInputCoreClass.prototype, "searchOperation", void 0);
2975
+ __decorate([property({
2976
+ attribute: "search-debounce",
2977
+ type: Number
2978
+ })], MonoTagInputCoreClass.prototype, "searchDebounce", void 0);
2979
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "cssClass", void 0);
2980
+ __decorate([property({ attribute: false })], MonoTagInputCoreClass.prototype, "cssClassName", void 0);
2981
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_inputValue", void 0);
2982
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_open", void 0);
2983
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_activeIndex", void 0);
2984
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_moreOpen", void 0);
2985
+ __decorate([state(), state()], MonoTagInputCoreClass.prototype, "_selectAllPending", void 0);
2986
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_hasLabelSlotState", void 0);
2987
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_hasHelperSlotState", void 0);
2988
+ __decorate([query(".mono-tag-input-native")], MonoTagInputCoreClass.prototype, "_inputEl", void 0);
2989
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_hasListSlotState", void 0);
2990
+ __decorate([state()], MonoTagInputCoreClass.prototype, "_hasSelectAllSlotState", void 0);
2991
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "width", void 0);
2992
+ __decorate([property({ type: String })], MonoTagInputCoreClass.prototype, "height", void 0);
2993
+ __decorate([property({
2994
+ type: String,
2995
+ attribute: "min-width"
2996
+ })], MonoTagInputCoreClass.prototype, "minWidth", void 0);
2997
+ __decorate([property({
2998
+ type: String,
2999
+ attribute: "max-width"
3000
+ })], MonoTagInputCoreClass.prototype, "maxWidth", void 0);
3001
+ __decorate([property({
3002
+ type: String,
3003
+ attribute: "min-height"
3004
+ })], MonoTagInputCoreClass.prototype, "minHeight", void 0);
3005
+ __decorate([property({
3006
+ type: String,
3007
+ attribute: "max-height"
3008
+ })], MonoTagInputCoreClass.prototype, "maxHeight", void 0);
3009
+ return MonoTagInputCoreClass;
3010
+ };
3011
+
3012
+ //#endregion
3013
+ //#region src/components/tag-input/tag-input-utils.ts
3014
+ function validateTagInputProps(props) {
3015
+ const validSizes = [
3016
+ "xs",
3017
+ "sm",
3018
+ "md",
3019
+ "lg",
3020
+ "xl",
3021
+ "xxl"
3022
+ ];
3023
+ const validColors = [
3024
+ "primary",
3025
+ "secondary",
3026
+ "success",
3027
+ "danger",
3028
+ "warning",
3029
+ "info",
3030
+ "teal",
3031
+ "purple",
3032
+ "neutral",
3033
+ "dark"
3034
+ ];
3035
+ const validVariants = [
3036
+ "outlined",
3037
+ "filled",
3038
+ "underlined"
3039
+ ];
3040
+ const validStates = [
3041
+ "default",
3042
+ "valid",
3043
+ "invalid",
3044
+ "warning"
3045
+ ];
3046
+ if (props.size && !validSizes.includes(props.size)) return false;
3047
+ if (props.color && !validColors.includes(props.color)) return false;
3048
+ if (props.variant && !validVariants.includes(props.variant)) return false;
3049
+ if (props.validationState && !validStates.includes(props.validationState)) return false;
3050
+ return true;
3051
+ }
3052
+ function normalizeSuggestions(items) {
3053
+ return items.map((item) => ({
3054
+ ...item,
3055
+ value: item.value ?? item.label
3056
+ }));
3057
+ }
3058
+
3059
+ //#endregion
3060
+ export { validateTagInputProps as n, MonoTagInputCore as r, normalizeSuggestions as t };