@mono-lit/helper 0.0.9 → 0.1.0

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