@spfx-fast/sharepoint-ui 0.0.0-stage → 0.4.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 (158) hide show
  1. package/AGENTS.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +897 -2
  4. package/components.json +5810 -0
  5. package/dist/appearance.d.ts +6 -0
  6. package/dist/appearance.js +217 -0
  7. package/dist/elements/accordion.d.ts +9 -0
  8. package/dist/elements/accordion.js +338 -0
  9. package/dist/elements/alert.d.ts +2 -0
  10. package/dist/elements/alert.js +62 -0
  11. package/dist/elements/avatar-group-primitives.d.ts +3 -0
  12. package/dist/elements/avatar-group-primitives.js +189 -0
  13. package/dist/elements/avatar-group.d.ts +2 -0
  14. package/dist/elements/avatar-group.js +110 -0
  15. package/dist/elements/avatar.d.ts +4 -0
  16. package/dist/elements/avatar.js +251 -0
  17. package/dist/elements/badge.d.ts +8 -0
  18. package/dist/elements/badge.js +182 -0
  19. package/dist/elements/breadcrumb.d.ts +4 -0
  20. package/dist/elements/breadcrumb.js +233 -0
  21. package/dist/elements/button-group.d.ts +1 -0
  22. package/dist/elements/button-group.js +20 -0
  23. package/dist/elements/button-variants.d.ts +6 -0
  24. package/dist/elements/button-variants.js +339 -0
  25. package/dist/elements/button.d.ts +5 -0
  26. package/dist/elements/button.js +233 -0
  27. package/dist/elements/card-primitives.d.ts +3 -0
  28. package/dist/elements/card-primitives.js +83 -0
  29. package/dist/elements/card.d.ts +5 -0
  30. package/dist/elements/card.js +213 -0
  31. package/dist/elements/carousel.d.ts +92 -0
  32. package/dist/elements/carousel.js +1112 -0
  33. package/dist/elements/color-picker.d.ts +24 -0
  34. package/dist/elements/color-picker.js +1004 -0
  35. package/dist/elements/combobox.d.ts +1 -0
  36. package/dist/elements/combobox.js +1012 -0
  37. package/dist/elements/data-grid-model.d.ts +117 -0
  38. package/dist/elements/data-grid-model.js +294 -0
  39. package/dist/elements/data-grid.d.ts +29 -0
  40. package/dist/elements/data-grid.js +1567 -0
  41. package/dist/elements/date-picker.d.ts +1 -0
  42. package/dist/elements/date-picker.js +486 -0
  43. package/dist/elements/dialog.d.ts +23 -0
  44. package/dist/elements/dialog.js +409 -0
  45. package/dist/elements/divider.d.ts +2 -0
  46. package/dist/elements/divider.js +66 -0
  47. package/dist/elements/drawer.d.ts +3 -0
  48. package/dist/elements/drawer.js +308 -0
  49. package/dist/elements/field-composition.d.ts +26 -0
  50. package/dist/elements/field-composition.js +133 -0
  51. package/dist/elements/field.d.ts +1 -0
  52. package/dist/elements/field.js +67 -0
  53. package/dist/elements/file-upload.d.ts +6 -0
  54. package/dist/elements/file-upload.js +211 -0
  55. package/dist/elements/floating-position.d.ts +26 -0
  56. package/dist/elements/floating-position.js +39 -0
  57. package/dist/elements/image.d.ts +28 -0
  58. package/dist/elements/image.js +38 -0
  59. package/dist/elements/info.d.ts +9 -0
  60. package/dist/elements/info.js +102 -0
  61. package/dist/elements/input.d.ts +5 -0
  62. package/dist/elements/input.js +255 -0
  63. package/dist/elements/label.d.ts +15 -0
  64. package/dist/elements/label.js +56 -0
  65. package/dist/elements/link.d.ts +1 -0
  66. package/dist/elements/link.js +92 -0
  67. package/dist/elements/list.d.ts +19 -0
  68. package/dist/elements/list.js +297 -0
  69. package/dist/elements/menu.d.ts +33 -0
  70. package/dist/elements/menu.js +1103 -0
  71. package/dist/elements/message-bar.d.ts +10 -0
  72. package/dist/elements/message-bar.js +299 -0
  73. package/dist/elements/nav.d.ts +32 -0
  74. package/dist/elements/nav.js +791 -0
  75. package/dist/elements/overflow.d.ts +9 -0
  76. package/dist/elements/overflow.js +218 -0
  77. package/dist/elements/persona.d.ts +3 -0
  78. package/dist/elements/persona.js +124 -0
  79. package/dist/elements/popover.d.ts +77 -0
  80. package/dist/elements/popover.js +840 -0
  81. package/dist/elements/progress-bar.d.ts +4 -0
  82. package/dist/elements/progress-bar.js +82 -0
  83. package/dist/elements/radio-group.d.ts +1 -0
  84. package/dist/elements/radio-group.js +303 -0
  85. package/dist/elements/rating-display.d.ts +2 -0
  86. package/dist/elements/rating-display.js +125 -0
  87. package/dist/elements/rating.d.ts +2 -0
  88. package/dist/elements/rating.js +206 -0
  89. package/dist/elements/rich-text-editor.d.ts +18 -0
  90. package/dist/elements/rich-text-editor.js +508 -0
  91. package/dist/elements/search.d.ts +3 -0
  92. package/dist/elements/search.js +207 -0
  93. package/dist/elements/select.d.ts +6 -0
  94. package/dist/elements/select.js +291 -0
  95. package/dist/elements/shared.d.ts +19 -0
  96. package/dist/elements/shared.js +396 -0
  97. package/dist/elements/skeleton.d.ts +6 -0
  98. package/dist/elements/skeleton.js +91 -0
  99. package/dist/elements/slider.d.ts +1 -0
  100. package/dist/elements/slider.js +248 -0
  101. package/dist/elements/spin-button.d.ts +7 -0
  102. package/dist/elements/spin-button.js +499 -0
  103. package/dist/elements/spinner.d.ts +4 -0
  104. package/dist/elements/spinner.js +124 -0
  105. package/dist/elements/swatch-picker.d.ts +5 -0
  106. package/dist/elements/swatch-picker.js +363 -0
  107. package/dist/elements/table.d.ts +21 -0
  108. package/dist/elements/table.js +261 -0
  109. package/dist/elements/tabs.d.ts +1 -0
  110. package/dist/elements/tabs.js +276 -0
  111. package/dist/elements/tag-picker.d.ts +29 -0
  112. package/dist/elements/tag-picker.js +697 -0
  113. package/dist/elements/tags.d.ts +21 -0
  114. package/dist/elements/tags.js +421 -0
  115. package/dist/elements/teaching-popover.d.ts +32 -0
  116. package/dist/elements/teaching-popover.js +412 -0
  117. package/dist/elements/text.d.ts +5 -0
  118. package/dist/elements/text.js +69 -0
  119. package/dist/elements/textarea.d.ts +1 -0
  120. package/dist/elements/textarea.js +182 -0
  121. package/dist/elements/toast.d.ts +25 -0
  122. package/dist/elements/toast.js +337 -0
  123. package/dist/elements/toaster.d.ts +6 -0
  124. package/dist/elements/toaster.js +356 -0
  125. package/dist/elements/toggle.d.ts +2 -0
  126. package/dist/elements/toggle.js +246 -0
  127. package/dist/elements/toolbar.d.ts +12 -0
  128. package/dist/elements/toolbar.js +381 -0
  129. package/dist/elements/tooltip.d.ts +76 -0
  130. package/dist/elements/tooltip.js +510 -0
  131. package/dist/elements/tree-model.d.ts +19 -0
  132. package/dist/elements/tree-model.js +34 -0
  133. package/dist/elements/tree.d.ts +5 -0
  134. package/dist/elements/tree.js +273 -0
  135. package/dist/elements/view-card.d.ts +2 -0
  136. package/dist/elements/view-card.js +78 -0
  137. package/dist/elements/virtual-list.d.ts +41 -0
  138. package/dist/elements/virtual-list.js +369 -0
  139. package/dist/elements.d.ts +72 -0
  140. package/dist/elements.js +207 -0
  141. package/dist/index.d.ts +76 -0
  142. package/dist/index.js +66 -0
  143. package/dist/react-theme-styles.d.ts +2 -0
  144. package/dist/react-theme-styles.js +75 -0
  145. package/dist/react.d.ts +4540 -0
  146. package/dist/react.js +6799 -0
  147. package/dist/theme.d.ts +169 -0
  148. package/dist/theme.js +167 -0
  149. package/dist/virtualizer-hooks.d.ts +80 -0
  150. package/dist/virtualizer-hooks.js +253 -0
  151. package/package.json +87 -4
  152. package/scripts/generate-token-index.mjs +43 -0
  153. package/scripts/sync-component-catalog-signatures.mjs +55 -0
  154. package/scripts/validate-component-catalog.mjs +44 -0
  155. package/skills/sharepoint-ui/SKILL.md +12 -0
  156. package/skills/sharepoint-ui/references/choosing-components.md +20 -0
  157. package/skills/sharepoint-ui/references/sharepoint-patterns.md +17 -0
  158. package/tokens.json +3777 -0
@@ -0,0 +1,697 @@
1
+ import { base, syncHostAccessibleName } from "./shared.js";
2
+ import { getFloatingPosition } from "./floating-position.js";
3
+ /** Searchable multi-select picker with native form association and light-DOM options. */
4
+ export function defineTagPicker() {
5
+ if (customElements.get("spui-tag-picker"))
6
+ return;
7
+ class SpuiTagPicker extends HTMLElement {
8
+ static formAssociated = true;
9
+ static observedAttributes = ["label", "placeholder", "selected-values", "selected-options", "default-selected-options", "input-value", "input-appearance", "input-disabled", "input-aria-label", "input-aria-describedby", "clearable", "selected-label", "inline", "no-popover", "disabled", "required", "name", "no-results", "remove-label", "open", "default-open", "positioning", "disable-auto-focus", "aria-label", "aria-labelledby", "aria-description", "aria-describedby"];
10
+ internals;
11
+ observer;
12
+ resizeObserver;
13
+ events;
14
+ buttonDisabledDefaults = new WeakMap();
15
+ instanceId = `tag-picker-${crypto.randomUUID()}`;
16
+ selected = new Set();
17
+ query = "";
18
+ activeIndex = -1;
19
+ openState = false;
20
+ openControlled = false;
21
+ formDisabled = false;
22
+ initialValues = [];
23
+ positioningValue;
24
+ get form() { return this.internals?.form ?? null; }
25
+ constructor() {
26
+ super();
27
+ try {
28
+ this.internals = this.attachInternals();
29
+ }
30
+ catch { /* Keep selection usable where form-associated custom elements are unavailable. */ }
31
+ }
32
+ connectedCallback() {
33
+ if (!this.shadowRoot)
34
+ this.render();
35
+ this.events?.abort();
36
+ this.events = new AbortController();
37
+ const options = { signal: this.events.signal };
38
+ window.addEventListener("resize", this.positionList, options);
39
+ window.addEventListener("scroll", this.positionList, { ...options, capture: true });
40
+ document.addEventListener("pointerdown", this.onOutsidePointerDown, { ...options, capture: true });
41
+ this.resizeObserver?.disconnect();
42
+ this.resizeObserver = new ResizeObserver(this.positionList);
43
+ const control = this.shadowRoot?.querySelector(".control");
44
+ const list = this.shadowRoot?.querySelector(".popup");
45
+ if (control)
46
+ this.resizeObserver.observe(control);
47
+ if (list)
48
+ this.resizeObserver.observe(list);
49
+ this.addEventListener("click", this.onButtonClick);
50
+ this.addEventListener("keydown", this.onButtonKeyDown);
51
+ this.addEventListener("focusout", this.onPickerFocusOut);
52
+ if (this.hasAttribute("open")) {
53
+ this.openControlled = true;
54
+ this.openState = this.getAttribute("open") !== "false";
55
+ }
56
+ else
57
+ this.openState = this.hasAttribute("default-open");
58
+ this.initialValues = this.readSelectedValues();
59
+ this.selected = new Set(this.initialValues);
60
+ this.query = this.getAttribute("input-value") ?? "";
61
+ this.observer = new MutationObserver(() => { this.renderOptions(); this.syncForm(); });
62
+ this.observer.observe(this, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["value", "text", "label", "group", "disabled", "selected"] });
63
+ this.syncButtonSlot();
64
+ this.renderOptions();
65
+ this.syncForm();
66
+ }
67
+ disconnectedCallback() { this.observer?.disconnect(); this.resizeObserver?.disconnect(); this.events?.abort(); this.hideList(); }
68
+ attributeChangedCallback(name, _oldValue, value) {
69
+ if (!this.isConnected)
70
+ return;
71
+ if (name === "selected-values" || name === "selected-options" || name === "default-selected-options") {
72
+ this.initialValues = this.readSelectedValues();
73
+ this.selected = new Set(this.initialValues);
74
+ this.renderOptions();
75
+ }
76
+ else if (name === "label") {
77
+ const label = this.shadowRoot?.querySelector("label");
78
+ if (label)
79
+ label.textContent = this.getAttribute("label") ?? "";
80
+ }
81
+ else if (name === "placeholder") {
82
+ const input = this.input();
83
+ if (input)
84
+ input.placeholder = this.getAttribute("placeholder") ?? "";
85
+ }
86
+ else if (name === "input-value") {
87
+ this.query = value ?? "";
88
+ const input = this.input();
89
+ if (input)
90
+ input.value = this.query;
91
+ this.renderOptions();
92
+ }
93
+ else if (name === "input-disabled") {
94
+ const input = this.input();
95
+ if (input)
96
+ input.disabled = this.isDisabled() || this.hasAttribute("input-disabled");
97
+ }
98
+ else if (name === "input-aria-label" || name === "input-aria-describedby") {
99
+ const input = this.input();
100
+ const target = name === "input-aria-label" ? "aria-label" : "aria-describedby";
101
+ if (input)
102
+ this.hasAttribute(name) ? input.setAttribute(target, this.getAttribute(name)) : input.removeAttribute(target);
103
+ }
104
+ else if (name === "clearable") {
105
+ const input = this.input();
106
+ if (input)
107
+ input.type = this.hasAttribute("clearable") ? "search" : "text";
108
+ }
109
+ else if (name === "aria-label" || name === "aria-labelledby" || name === "aria-description" || name === "aria-describedby") {
110
+ const input = this.input();
111
+ if (input)
112
+ syncHostAccessibleName(this, input);
113
+ }
114
+ else if (name === "positioning") {
115
+ this.positionList();
116
+ }
117
+ else if (name === "open") {
118
+ this.openControlled = this.hasAttribute("open");
119
+ if (this.openControlled)
120
+ this.openState = this.getAttribute("open") !== "false";
121
+ this.syncAria();
122
+ }
123
+ else if (name === "default-open" && !this.openControlled) {
124
+ this.openState = this.hasAttribute("default-open");
125
+ this.syncAria();
126
+ }
127
+ else
128
+ this.renderOptions();
129
+ this.syncAria();
130
+ this.syncForm();
131
+ }
132
+ get value() { return [...this.selected]; }
133
+ set value(values) { this.setSelected(Array.isArray(values) ? values : []); }
134
+ get inputValue() { return this.query; }
135
+ set inputValue(value) { this.query = String(value ?? ""); const input = this.input(); if (input)
136
+ input.value = this.query; this.renderOptions(); }
137
+ get selectedValues() { return this.value; }
138
+ set selectedValues(values) { this.value = values; }
139
+ get selectedOptions() { return this.value; }
140
+ set selectedOptions(values) { this.value = values; }
141
+ get defaultSelectedOptions() { return this.readSelectedValues(); }
142
+ set defaultSelectedOptions(values) { this.setAttribute("default-selected-options", JSON.stringify(values)); }
143
+ get open() { return this.openState; }
144
+ set open(value) { this.openControlled = true; this.openState = Boolean(value); this.syncAria(); }
145
+ get defaultOpen() { return this.openState; }
146
+ set defaultOpen(value) { if (!this.openControlled) {
147
+ this.openState = Boolean(value);
148
+ this.syncAria();
149
+ } }
150
+ get positioning() { return this.positioningValue; }
151
+ set positioning(value) { this.positioningValue = value; this.positionList(); }
152
+ isDisabled() { return this.formDisabled || this.hasAttribute("disabled"); }
153
+ setOpen(value, reason, event) {
154
+ if (value && this.isDisabled())
155
+ return;
156
+ const changed = this.open !== value;
157
+ if (!changed)
158
+ return;
159
+ if (!this.openControlled)
160
+ this.openState = value;
161
+ this.syncAria();
162
+ const sourceEvent = event ?? new Event("programmatic");
163
+ this.dispatchEvent(new CustomEvent("spui-open-change", { detail: { open: value, reason, event: sourceEvent, type: sourceEvent.type }, bubbles: true, composed: true }));
164
+ }
165
+ formDisabledCallback(disabled) { this.formDisabled = disabled; const input = this.input(); if (input)
166
+ input.disabled = this.isDisabled(); this.syncAria(); }
167
+ formResetCallback() { this.selected = new Set(this.initialValues); this.query = ""; this.closeList(); this.renderOptions(); this.syncForm(); }
168
+ formStateRestoreCallback(state) {
169
+ if (state instanceof FormData)
170
+ this.setSelected(state.getAll(this.getAttribute("name") ?? "").map(String));
171
+ else if (typeof state === "string") {
172
+ try {
173
+ this.setSelected(JSON.parse(state));
174
+ }
175
+ catch {
176
+ this.setSelected(state ? [state] : []);
177
+ }
178
+ }
179
+ }
180
+ buttonTrigger() {
181
+ return this.shadowRoot?.querySelector('slot[name="button"]')?.assignedElements({ flatten: true }).find((element) => element instanceof HTMLElement) ?? null;
182
+ }
183
+ onButtonClick = (event) => {
184
+ const trigger = this.buttonTrigger();
185
+ if (!trigger || !event.composedPath().includes(trigger))
186
+ return;
187
+ if (this.isDisabled()) {
188
+ event.preventDefault();
189
+ return;
190
+ }
191
+ if (this.open)
192
+ this.closeList("pointer", event);
193
+ else {
194
+ this.setOpen(true, "pointer", event);
195
+ this.activeIndex = this.hasAttribute("disable-auto-focus") ? -1 : this.filteredOptions().findIndex(option => !option.disabled);
196
+ this.syncAria();
197
+ }
198
+ };
199
+ onButtonKeyDown = (event) => {
200
+ const trigger = this.buttonTrigger();
201
+ if (!trigger || !event.composedPath().includes(trigger))
202
+ return;
203
+ if (event.key === "Escape" && this.open) {
204
+ event.preventDefault();
205
+ this.closeList("escape", event);
206
+ trigger.focus();
207
+ }
208
+ else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
209
+ event.preventDefault();
210
+ this.moveActive(event.key === "ArrowDown" ? 1 : -1, event);
211
+ }
212
+ else if ((event.key === "Enter" || event.key === " ") && this.open && this.activeIndex >= 0) {
213
+ event.preventDefault();
214
+ const option = this.filteredOptions()[this.activeIndex];
215
+ if (option)
216
+ this.toggleOption(option.value, event.key === "Enter", "keyboard", event);
217
+ }
218
+ };
219
+ onPickerFocusOut = (event) => { window.setTimeout(() => { if (!this.matches(":focus-within"))
220
+ this.closeList("blur", event); }, 0); };
221
+ onOutsidePointerDown = (event) => { if (!event.composedPath().includes(this))
222
+ this.closeList("outside", event); };
223
+ popup() {
224
+ return this.shadowRoot?.querySelector(".popup");
225
+ }
226
+ hideList() {
227
+ const popup = this.popup();
228
+ if (!popup)
229
+ return;
230
+ try {
231
+ popup.hidePopover?.();
232
+ }
233
+ catch { /* It may not have been opened in a supporting browser. */ }
234
+ popup.hidden = true;
235
+ }
236
+ positionList = () => {
237
+ const popup = this.popup();
238
+ const anchor = this.shadowRoot?.querySelector(".control");
239
+ if ((!this.open && !this.hasAttribute("no-popover")) || !popup || !anchor)
240
+ return;
241
+ popup.hidden = false;
242
+ if (this.hasAttribute("inline") || this.hasAttribute("no-popover")) {
243
+ if (popup.matches(":popover-open")) {
244
+ try {
245
+ popup.hidePopover?.();
246
+ }
247
+ catch { /* Keep the in-flow popup usable when the native API rejects a transition. */ }
248
+ }
249
+ // A closed native popover is hidden by the browser's UA stylesheet. Remove
250
+ // the popover behavior when this surface is rendered in the regular layer.
251
+ popup.removeAttribute("popover");
252
+ popup.dataset.placement = "bottom";
253
+ return;
254
+ }
255
+ if (!popup.hasAttribute("popover"))
256
+ popup.setAttribute("popover", "manual");
257
+ if (typeof popup.showPopover === "function" && !popup.matches(":popover-open")) {
258
+ try {
259
+ popup.showPopover();
260
+ }
261
+ catch { /* Fixed positioning remains a fallback for browsers without Popover API support. */ }
262
+ }
263
+ const rect = anchor.getBoundingClientRect();
264
+ const width = Math.min(rect.width, window.innerWidth - 8);
265
+ popup.style.width = `${width}px`;
266
+ const direction = getComputedStyle(this).direction === "rtl" ? "rtl" : "ltr";
267
+ const config = this.positioningValue;
268
+ const raw = typeof config === "string" ? config : config?.position ?? "below";
269
+ const [side = "below", suffix] = raw.split("-");
270
+ const placementMap = { above: "top", below: "bottom", before: "start", after: "end", top: "top", bottom: "bottom", left: "left", right: "right", start: "start", end: "end" };
271
+ const placement = placementMap[side] ?? "bottom";
272
+ const align = (typeof config === "object" && config ? config.align : undefined) ?? (suffix === "start" || suffix === "end" || suffix === "top" || suffix === "bottom" ? suffix : "start");
273
+ const options = typeof config === "object" && config ? config : undefined;
274
+ const offset = typeof options?.offset === "number" ? options.offset : typeof options?.offset === "object" ? options.offset.mainAxis : 4;
275
+ const fallback = (options?.fallbackPositions ?? []).map(value => {
276
+ const [fallbackSide, fallbackAlign] = value.split("-");
277
+ return { placement: placementMap[fallbackSide] ?? "bottom", align: fallbackAlign };
278
+ });
279
+ const position = getFloatingPosition(rect, { width: popup.offsetWidth, height: popup.offsetHeight }, placement, direction, offset, undefined, align, fallback, options?.pinned, undefined, undefined, { coverTarget: options?.coverTarget });
280
+ popup.dataset.placement = position.placement;
281
+ popup.style.left = `${position.left}px`;
282
+ popup.style.top = `${position.top}px`;
283
+ };
284
+ syncButtonSlot() { this.toggleAttribute("data-button-trigger", this.buttonTrigger() !== null); this.syncAria(); }
285
+ input() { return this.shadowRoot?.querySelector("input") ?? null; }
286
+ optionContent(element, slot) { return element.querySelector(`:scope > [slot="${slot}"]`); }
287
+ optionLabel(element) {
288
+ const label = element.getAttribute("label") ?? this.optionContent(element, "label")?.textContent;
289
+ if (label !== null && label !== undefined)
290
+ return label.trim();
291
+ return [...element.childNodes].filter(node => !(node instanceof Element && ["media", "secondary-content"].includes(node.getAttribute("slot") ?? ""))).map(node => node.textContent ?? "").join("").trim();
292
+ }
293
+ appendOptionContent(target, option, slot) {
294
+ const source = this.optionContent(option.element, slot);
295
+ if (!source)
296
+ return;
297
+ const content = document.createElement("span");
298
+ content.className = slot === "media" ? "option-media" : "option-secondary";
299
+ if (slot === "media")
300
+ content.setAttribute("aria-hidden", "true");
301
+ for (const node of source.childNodes)
302
+ content.append(node.cloneNode(true));
303
+ target.append(content);
304
+ }
305
+ options() {
306
+ return [...this.querySelectorAll("spui-tag-picker-option,option")].map(element => ({
307
+ element,
308
+ value: element.getAttribute("value") ?? "",
309
+ label: element.getAttribute("text")?.trim() || this.optionLabel(element),
310
+ group: element.getAttribute("group")?.trim() || element.closest("spui-tag-picker-option-group")?.getAttribute("label")?.trim() || "",
311
+ disabled: element.hasAttribute("disabled") || Boolean(element.closest("spui-tag-picker-option-group")?.hasAttribute("disabled")),
312
+ selected: element.hasAttribute("selected")
313
+ })).filter(option => option.value && option.label);
314
+ }
315
+ readSelectedValues() {
316
+ const serialized = this.getAttribute("selected-options") ?? this.getAttribute("selected-values") ?? this.getAttribute("default-selected-options");
317
+ if (serialized !== null) {
318
+ try {
319
+ const values = JSON.parse(serialized);
320
+ return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
321
+ }
322
+ catch {
323
+ return [];
324
+ }
325
+ }
326
+ return this.options().filter(option => option.selected).map(option => option.value);
327
+ }
328
+ filteredOptions() {
329
+ const query = this.query.trim().toLocaleLowerCase(this.lang || undefined);
330
+ return this.options().filter(option => !query || option.label.toLocaleLowerCase(this.lang || undefined).includes(query));
331
+ }
332
+ setSelected(values) {
333
+ const available = new Set(this.options().map(option => option.value));
334
+ this.selected = new Set(values.filter(value => available.has(value)));
335
+ this.renderOptions();
336
+ this.syncForm();
337
+ }
338
+ toggleOption(value, closeAfterSelection, reason, event) {
339
+ const option = this.options().find(item => item.value === value);
340
+ if (!option || option.disabled || this.isDisabled())
341
+ return;
342
+ if (this.selected.has(value))
343
+ this.selected.delete(value);
344
+ else
345
+ this.selected.add(value);
346
+ this.query = "";
347
+ const input = this.input();
348
+ if (input)
349
+ input.value = "";
350
+ this.setOpen(!closeAfterSelection, reason, event);
351
+ this.renderOptions();
352
+ this.activeIndex = closeAfterSelection ? -1 : this.filteredOptions().findIndex(item => !item.disabled && !this.selected.has(item.value));
353
+ this.syncAria();
354
+ this.syncForm();
355
+ this.dispatchEvent(new CustomEvent("spui-change", { detail: { values: this.value, selectedValues: this.value }, bubbles: true, composed: true }));
356
+ const detail = event instanceof KeyboardEvent
357
+ ? { value, selectedOptions: this.value, event, type: "keydown" }
358
+ : { value, selectedOptions: this.value, event: event, type: "click" };
359
+ this.dispatchEvent(new CustomEvent("spui-option-select", { detail, bubbles: true, composed: true }));
360
+ }
361
+ removeValue(value) {
362
+ if (!this.selected.has(value) || this.isDisabled())
363
+ return;
364
+ this.selected.delete(value);
365
+ this.renderOptions();
366
+ this.syncForm();
367
+ this.input()?.focus();
368
+ this.dispatchEvent(new CustomEvent("spui-change", { detail: { values: this.value, selectedValues: this.value }, bubbles: true, composed: true }));
369
+ }
370
+ syncForm() {
371
+ const values = this.value;
372
+ const input = this.input();
373
+ input?.setAttribute("aria-required", String(this.hasAttribute("required")));
374
+ if (input)
375
+ input.disabled = this.isDisabled() || this.hasAttribute("input-disabled");
376
+ const name = this.getAttribute("name") ?? "";
377
+ const formData = new FormData();
378
+ if (name)
379
+ values.forEach(value => formData.append(name, value));
380
+ this.internals?.setFormValue(values.length && name ? formData : null, JSON.stringify(values));
381
+ const missing = this.hasAttribute("required") && values.length === 0;
382
+ try {
383
+ this.internals?.setValidity(missing ? { valueMissing: true } : {}, missing ? "Choose at least one option." : "", (this.buttonTrigger() ?? input) ?? undefined);
384
+ }
385
+ catch { /* The input itself retains required semantics as a fallback. */ }
386
+ }
387
+ syncAria() {
388
+ const input = this.input();
389
+ const list = this.shadowRoot?.querySelector("[role=listbox]");
390
+ if (!input || !list)
391
+ return;
392
+ const expanded = this.open || this.hasAttribute("no-popover");
393
+ input.setAttribute("aria-expanded", String(expanded));
394
+ if (expanded)
395
+ input.setAttribute("aria-controls", list.id);
396
+ else
397
+ input.removeAttribute("aria-controls");
398
+ const trigger = this.buttonTrigger();
399
+ if (trigger) {
400
+ trigger.setAttribute("aria-haspopup", "listbox");
401
+ trigger.setAttribute("aria-expanded", String(this.open));
402
+ trigger.setAttribute("aria-required", String(this.hasAttribute("required")));
403
+ if (this.open)
404
+ trigger.setAttribute("aria-controls", list.id);
405
+ else
406
+ trigger.removeAttribute("aria-controls");
407
+ const originallyDisabled = this.buttonDisabledDefaults.get(trigger) ?? trigger.hasAttribute("disabled");
408
+ this.buttonDisabledDefaults.set(trigger, originallyDisabled);
409
+ const disabled = this.isDisabled() || originallyDisabled;
410
+ if ("disabled" in trigger)
411
+ trigger.disabled = disabled;
412
+ else if (disabled)
413
+ trigger.setAttribute("disabled", "");
414
+ else
415
+ trigger.removeAttribute("disabled");
416
+ if (this.isDisabled())
417
+ trigger.setAttribute("aria-disabled", "true");
418
+ else
419
+ trigger.removeAttribute("aria-disabled");
420
+ }
421
+ const active = this.shadowRoot?.querySelector(`[data-option-index="${this.activeIndex}"]`);
422
+ if (expanded && active)
423
+ input.setAttribute("aria-activedescendant", active.id);
424
+ else
425
+ input.removeAttribute("aria-activedescendant");
426
+ list.querySelectorAll("[role=option]").forEach(option => { option.dataset.active = String(expanded && Number(option.dataset.optionIndex) === this.activeIndex); });
427
+ const empty = this.shadowRoot?.querySelector(".empty");
428
+ if (empty)
429
+ empty.hidden = !expanded || this.filteredOptions().length > 0;
430
+ list.hidden = false;
431
+ if (expanded)
432
+ this.positionList();
433
+ else
434
+ this.hideList();
435
+ }
436
+ closeList(reason = "programmatic", event) { this.setOpen(false, reason, event); this.activeIndex = -1; this.syncAria(); }
437
+ moveActive(delta, event) {
438
+ const options = this.filteredOptions();
439
+ if (!options.length) {
440
+ this.activeIndex = -1;
441
+ this.syncAria();
442
+ return;
443
+ }
444
+ let index = this.open ? this.activeIndex : delta > 0 ? -1 : 0;
445
+ for (let count = 0; count < options.length; count++) {
446
+ index = (index + delta + options.length) % options.length;
447
+ if (!options[index].disabled)
448
+ break;
449
+ }
450
+ this.setOpen(true, "keyboard", event);
451
+ this.activeIndex = index;
452
+ this.syncAria();
453
+ this.shadowRoot?.querySelector(`[data-option-index="${index}"]`)?.scrollIntoView({ block: "nearest" });
454
+ }
455
+ onKeyDown(event) {
456
+ if (event.key === "ArrowDown") {
457
+ event.preventDefault();
458
+ this.moveActive(1, event);
459
+ }
460
+ else if (event.key === "ArrowUp") {
461
+ event.preventDefault();
462
+ this.moveActive(-1, event);
463
+ }
464
+ else if ((event.key === "Enter" || event.key === " ") && this.open && this.activeIndex >= 0) {
465
+ event.preventDefault();
466
+ const option = this.filteredOptions()[this.activeIndex];
467
+ if (option)
468
+ this.toggleOption(option.value, event.key === "Enter", "keyboard", event);
469
+ }
470
+ else if (event.key === "Escape" && this.open) {
471
+ event.preventDefault();
472
+ this.closeList("escape", event);
473
+ }
474
+ else if (event.key === "Backspace" && !this.query && this.selected.size) {
475
+ event.preventDefault();
476
+ this.removeValue([...this.selected].at(-1));
477
+ }
478
+ }
479
+ renderOptions() {
480
+ const list = this.shadowRoot?.querySelector("[role=listbox]");
481
+ const selected = this.shadowRoot?.querySelector(".selected");
482
+ if (!list || !selected)
483
+ return;
484
+ const selectedOptions = this.options().filter(option => this.selected.has(option.value));
485
+ selected.replaceChildren();
486
+ const selectedGroup = this.querySelector("spui-tag-picker-group[slot=selected-group]");
487
+ const selectedSlot = document.createElement("slot");
488
+ selectedSlot.name = "selected-group";
489
+ selectedSlot.part.add("selected-group");
490
+ selectedSlot.hidden = !selectedGroup;
491
+ selected.setAttribute("role", "group");
492
+ selected.setAttribute("aria-label", this.getAttribute("selected-label") || "Selected options");
493
+ selected.append(selectedSlot);
494
+ for (const option of selectedGroup ? [] : selectedOptions) {
495
+ const chip = document.createElement("span");
496
+ chip.className = "chip";
497
+ chip.part.add("chip");
498
+ this.appendOptionContent(chip, option, "media");
499
+ const text = document.createElement("span");
500
+ text.textContent = option.label;
501
+ const remove = document.createElement("button");
502
+ remove.type = "button";
503
+ remove.className = "remove";
504
+ remove.textContent = "×";
505
+ remove.setAttribute("aria-label", `${this.getAttribute("remove-label") || "Remove"} ${option.label}`);
506
+ remove.disabled = option.disabled || this.isDisabled();
507
+ remove.addEventListener("click", () => this.removeValue(option.value));
508
+ chip.append(text, remove);
509
+ selected.append(chip);
510
+ }
511
+ const options = this.filteredOptions();
512
+ list.replaceChildren();
513
+ let previousGroup;
514
+ let groupOptions = null;
515
+ options.forEach((option, index) => {
516
+ if (option.group !== previousGroup) {
517
+ groupOptions = null;
518
+ if (option.group) {
519
+ const group = document.createElement("li");
520
+ group.className = "option-group";
521
+ group.setAttribute("role", "group");
522
+ group.setAttribute("aria-label", option.group);
523
+ const heading = document.createElement("div");
524
+ heading.className = "group-heading";
525
+ heading.setAttribute("role", "presentation");
526
+ heading.textContent = option.group;
527
+ groupOptions = document.createElement("div");
528
+ groupOptions.className = "option-group-options";
529
+ groupOptions.setAttribute("role", "presentation");
530
+ group.append(heading, groupOptions);
531
+ list.append(group);
532
+ }
533
+ }
534
+ const item = document.createElement(option.group ? "div" : "li");
535
+ item.id = `${this.instanceId}-option-${index}`;
536
+ item.dataset.optionIndex = String(index);
537
+ item.dataset.active = String(this.open && index === this.activeIndex);
538
+ item.setAttribute("role", "option");
539
+ item.setAttribute("aria-selected", String(this.selected.has(option.value)));
540
+ if (option.disabled)
541
+ item.setAttribute("aria-disabled", "true");
542
+ this.appendOptionContent(item, option, "media");
543
+ const label = document.createElement("span");
544
+ label.className = "option-label";
545
+ label.textContent = option.label;
546
+ item.append(label);
547
+ this.appendOptionContent(item, option, "secondary-content");
548
+ item.addEventListener("pointerdown", event => event.preventDefault());
549
+ item.addEventListener("click", event => this.toggleOption(option.value, true, "pointer", event));
550
+ (groupOptions ?? list).append(item);
551
+ previousGroup = option.group;
552
+ });
553
+ const empty = this.shadowRoot?.querySelector(".empty");
554
+ if (empty) {
555
+ empty.textContent = this.getAttribute("no-results") || "No results";
556
+ empty.hidden = options.length > 0;
557
+ }
558
+ this.syncAria();
559
+ }
560
+ render() {
561
+ const shadow = this.attachShadow({ mode: "open" });
562
+ const label = document.createElement("label");
563
+ label.htmlFor = `${this.instanceId}-input`;
564
+ label.textContent = this.getAttribute("label") ?? "";
565
+ label.part.add("label");
566
+ const control = document.createElement("div");
567
+ control.className = "control";
568
+ control.part.add("control");
569
+ const selected = document.createElement("div");
570
+ selected.className = "selected";
571
+ selected.part.add("selected");
572
+ const buttonSlot = document.createElement("slot");
573
+ buttonSlot.name = "button";
574
+ buttonSlot.addEventListener("slotchange", () => this.syncButtonSlot());
575
+ const secondarySlot = document.createElement("slot");
576
+ secondarySlot.name = "secondary-action";
577
+ secondarySlot.part.add("secondary-action");
578
+ const expandSlot = document.createElement("slot");
579
+ expandSlot.name = "expand-icon";
580
+ expandSlot.part.add("expand-icon");
581
+ const input = document.createElement("input");
582
+ input.id = `${this.instanceId}-input`;
583
+ input.type = this.hasAttribute("clearable") ? "search" : "text";
584
+ input.role = "combobox";
585
+ input.autocomplete = "off";
586
+ input.setAttribute("aria-autocomplete", "list");
587
+ input.setAttribute("aria-haspopup", "listbox");
588
+ input.setAttribute("aria-expanded", "false");
589
+ input.placeholder = this.getAttribute("placeholder") ?? "";
590
+ input.value = this.getAttribute("input-value") ?? "";
591
+ input.disabled = this.isDisabled() || this.hasAttribute("input-disabled");
592
+ input.part.add("input");
593
+ for (const name of ["input-aria-label", "input-aria-describedby"]) {
594
+ const value = this.getAttribute(name);
595
+ if (value !== null)
596
+ input.setAttribute(name.replace("input-", ""), value);
597
+ }
598
+ const popup = document.createElement("div");
599
+ popup.className = "popup";
600
+ popup.setAttribute("popover", "manual");
601
+ popup.hidden = true;
602
+ popup.part.add("popup");
603
+ const list = document.createElement("ul");
604
+ list.id = `${this.instanceId}-listbox`;
605
+ list.setAttribute("role", "listbox");
606
+ list.setAttribute("aria-multiselectable", "true");
607
+ list.part.add("listbox");
608
+ const empty = document.createElement("div");
609
+ empty.className = "empty";
610
+ empty.setAttribute("role", "status");
611
+ empty.hidden = true;
612
+ input.addEventListener("input", event => { this.query = input.value; this.setOpen(true, "input", event); this.activeIndex = this.hasAttribute("disable-auto-focus") ? -1 : this.filteredOptions().findIndex(option => !option.disabled); this.renderOptions(); this.dispatchEvent(new CustomEvent("spui-input", { detail: { value: this.query }, bubbles: true, composed: true })); });
613
+ input.addEventListener("focus", event => { this.setOpen(true, "focus", event); this.renderOptions(); });
614
+ input.addEventListener("keydown", event => this.onKeyDown(event));
615
+ input.addEventListener("blur", event => window.setTimeout(() => { if (!this.matches(":focus-within"))
616
+ this.closeList("blur", event); }, 0));
617
+ popup.append(list, empty);
618
+ control.append(selected, buttonSlot, input, popup, secondarySlot, expandSlot);
619
+ const style = document.createElement("style");
620
+ style.textContent = `${base}:host{display:grid;gap:var(--spui-control-gap,6px);min-width:0}label{font-size:14px;font-weight:600;color:var(--spui-ui-muted,var(--spui-semantic-body-text,inherit))}.control{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:4px;box-sizing:border-box;min-height:var(--spui-control-height,36px);padding:4px 8px;border:1px solid var(--spui-control-border,var(--spui-ui-stroke,var(--spui-semantic-input-border,#8a8886)));border-radius:var(--spui-control-radius,4px);background:var(--spui-control-background,var(--spui-ui-surface,var(--spui-semantic-input-background,#fff)));color:var(--spui-ui-text,inherit)}.control:focus-within{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:1px}.selected{display:contents}.chip{display:inline-flex;align-items:center;gap:4px;max-width:100%;padding:2px 5px 2px 8px;border:1px solid var(--spui-base-stroke,var(--spui-ui-stroke,#d1d1d1));border-radius:999px;background:color-mix(in srgb,var(--spui-base-accent,var(--spui-ui-accent,#0078d4)) 12%,var(--spui-base-surface,var(--spui-ui-surface,#fff)));font-size:12px}.chip>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.remove{display:grid;place-items:center;flex:none;width:20px;height:20px;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;font:inherit;font-size:16px;cursor:pointer}.remove:hover{background:color-mix(in srgb,currentColor 12%,transparent)}.remove:focus-visible{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4))}.remove:disabled{opacity:.5;cursor:not-allowed}input{flex:1 1 100px;min-width:72px;width:auto;min-height:28px;padding:2px 4px;border:0;outline:none;background:transparent;color:inherit;font:inherit}input::placeholder{color:var(--spui-ui-muted,#616161)}input:disabled{cursor:not-allowed}ul{position:absolute;z-index:var(--spui-layer-dropdown,20);top:calc(100% + 4px);inset-inline:0;box-sizing:border-box;max-height:240px;overflow:auto;margin:0;padding:4px;list-style:none;border:1px solid var(--spui-ui-stroke,#d1d1d1);border-radius:var(--spui-card-radius,8px);background:var(--spui-ui-surface,#fff);box-shadow:var(--spui-card-shadow,0 4px 14px #0002)}ul[hidden]{display:none}.group-heading{padding:7px 10px 3px;color:var(--spui-ui-muted,inherit);font-size:11px;font-weight:600}li[role=option]{padding:8px 10px;border-radius:var(--spui-control-radius,4px);color:var(--spui-ui-text,inherit);cursor:pointer}li[role=option][data-active=true],li[role=option][aria-selected=true]{background:var(--spui-ui-surface-hover,var(--spui-ui-canvas,#f5f5f5))}li[role=option][aria-disabled=true]{opacity:.5;cursor:not-allowed}.empty{position:absolute;z-index:calc(var(--spui-layer-dropdown,20) + 1);top:calc(100% + 4px);inset-inline:0;box-sizing:border-box;padding:12px;border:1px solid var(--spui-ui-stroke,#d1d1d1);border-radius:var(--spui-card-radius,8px);background:var(--spui-ui-surface,#fff);box-shadow:var(--spui-card-shadow,0 4px 14px #0002);color:var(--spui-ui-muted,inherit)}.empty[hidden]{display:none}:host([disabled]) .control{opacity:.55}@media(forced-colors:active){.control,ul,.empty{border-color:CanvasText}.chip{border-color:CanvasText}li[role=option][data-active=true]{outline:1px solid Highlight}}`;
621
+ style.textContent += `.option-group{list-style:none;margin:0;padding:0}.option-media{display:inline-flex;align-items:center;justify-content:center;flex:none}.option-media img,.option-media svg{width:24px;height:24px;object-fit:cover}.chip .option-media img,.chip .option-media svg{width:20px;height:20px;border-radius:50%}li[role=option],[role=group] [role=option]{display:flex;align-items:center;gap:8px}.option-group-options [role=option]{padding:8px 10px;border-radius:var(--spui-control-radius,4px);color:var(--spui-ui-text,inherit);cursor:pointer}.option-group-options [role=option][data-active=true],.option-group-options [role=option][aria-selected=true]{background:var(--spui-ui-surface-hover,var(--spui-ui-canvas,#f5f5f5))}.option-group-options [role=option][aria-disabled=true]{opacity:.5;cursor:not-allowed}.option-label{min-width:0;overflow:hidden;text-overflow:ellipsis}.option-secondary{margin-inline-start:auto;color:var(--spui-ui-muted,inherit);font-size:12px;white-space:nowrap}`;
622
+ style.textContent += `:host([data-button-trigger]) input{display:none}::slotted(button[slot=button]){align-self:center;display:inline-flex;align-items:center;min-width:32px;min-height:28px;padding:4px 8px;border:1px solid var(--spui-control-border,var(--spui-ui-stroke,#8a8886));border-radius:var(--spui-control-radius,4px);background:var(--spui-control-background,var(--spui-ui-surface,#fff));color:var(--spui-ui-text,inherit);font:inherit;cursor:pointer}::slotted(button[slot=button][data-appearance=filled-darker]){background:var(--spui-ui-surface-sunken,var(--spui-ui-canvas,#f5f5f5))}::slotted(button[slot=button][data-appearance=filled-lighter]){background:var(--spui-ui-surface,var(--spui-semantic-input-background,#fff))}::slotted(button[slot=button][data-appearance=underline]){border:0;border-bottom:1px solid currentColor;border-radius:0;background:transparent}::slotted(button[slot=button][data-size=small]){min-height:24px;padding:2px 6px;font-size:12px}::slotted(button[slot=button][data-size=large]){min-height:40px;padding:6px 12px;font-size:16px}::slotted(button[slot=button]:focus-visible){outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:1px}:host([disabled])::slotted(button[slot=button]){opacity:.55;cursor:not-allowed}`;
623
+ style.textContent += `::slotted(spui-tag-picker-group){display:contents}:host([input-appearance=filled-darker]) .control{background:var(--spui-ui-surface-sunken,var(--spui-ui-canvas,#f5f5f5))}:host([input-appearance=filled-lighter]) .control{background:var(--spui-ui-surface,var(--spui-semantic-input-background,#fff))}:host([input-appearance=underline]) .control{border:0;border-bottom:1px solid var(--spui-control-border,var(--spui-ui-stroke,#8a8886));border-radius:0;background:transparent}`;
624
+ style.textContent += `::slotted([slot=secondary-action]){display:inline-flex;align-items:center;flex:none}::slotted([slot=expand-icon]){display:inline-flex;align-items:center;justify-content:center;flex:none}:host([inline]) .popup,:host([no-popover]) .popup{position:absolute;inset-inline:0;top:calc(100% + 4px);left:auto;width:100%!important;max-height:min(240px,calc(100dvh - 8px));z-index:var(--spui-layer-dropdown,20)}`;
625
+ style.textContent += `.popup{position:fixed;inset:auto;z-index:var(--spui-layer-popover,10000);box-sizing:border-box;max-width:calc(100vw - 8px);max-height:min(240px,calc(100dvh - 8px));overflow:auto;margin:0;padding:4px;border:1px solid var(--spui-ui-stroke,#d1d1d1);border-radius:var(--spui-card-radius,8px);background:var(--spui-ui-surface,#fff);color:var(--spui-ui-text,inherit);box-shadow:var(--spui-card-shadow,0 4px 14px #0002)}.popup[hidden]{display:none}.popup ul{position:static;inset:auto;z-index:auto;max-height:none;overflow:visible;margin:0;padding:0;border:0;border-radius:0;background:transparent;box-shadow:none}.popup .empty{position:static;inset:auto;z-index:auto;margin:0;padding:12px;border:0;border-radius:0;background:transparent;box-shadow:none}`;
626
+ const slot = document.createElement("slot");
627
+ slot.hidden = true;
628
+ shadow.append(style, label, control, slot);
629
+ syncHostAccessibleName(this, input);
630
+ this.renderOptions();
631
+ }
632
+ }
633
+ customElements.define("spui-tag-picker", SpuiTagPicker);
634
+ const definePart = (tag) => {
635
+ if (customElements.get(tag))
636
+ return;
637
+ class SpuiTagPickerPart extends HTMLElement {
638
+ static observedAttributes = ["value", "placeholder", "disabled", "clearable", "appearance", "aria-label", "aria-describedby"];
639
+ previous = new Map();
640
+ picker;
641
+ connectedCallback() { this.sync(); }
642
+ attributeChangedCallback() { this.sync(); }
643
+ sync() {
644
+ const picker = this.closest("spui-tag-picker");
645
+ if (!picker)
646
+ return;
647
+ this.picker = picker;
648
+ if (tag === "spui-tag-picker-control" || tag === "spui-tag-picker-list") {
649
+ while (this.firstChild)
650
+ picker.insertBefore(this.firstChild, this);
651
+ this.remove();
652
+ return;
653
+ }
654
+ if (tag === "spui-tag-picker-group") {
655
+ this.slot = "selected-group";
656
+ if (!this.hasAttribute("role"))
657
+ this.setAttribute("role", "group");
658
+ return;
659
+ }
660
+ if (tag === "spui-tag-picker-input") {
661
+ const map = { value: "input-value", appearance: "input-appearance", disabled: "input-disabled", "aria-label": "input-aria-label", "aria-describedby": "input-aria-describedby" };
662
+ for (const name of SpuiTagPickerPart.observedAttributes) {
663
+ const attribute = map[name] ?? name;
664
+ if (this.hasAttribute(name)) {
665
+ if (!this.previous.has(attribute))
666
+ this.previous.set(attribute, picker.getAttribute(attribute));
667
+ picker.setAttribute(attribute, this.getAttribute(name));
668
+ }
669
+ else if (this.previous.has(attribute)) {
670
+ const original = this.previous.get(attribute);
671
+ if (original === null)
672
+ picker.removeAttribute(attribute);
673
+ else
674
+ picker.setAttribute(attribute, original);
675
+ this.previous.delete(attribute);
676
+ }
677
+ }
678
+ }
679
+ }
680
+ disconnectedCallback() {
681
+ if (tag !== "spui-tag-picker-input")
682
+ return;
683
+ for (const [attribute, original] of this.previous) {
684
+ if (original === null)
685
+ this.picker?.removeAttribute(attribute);
686
+ else
687
+ this.picker?.setAttribute(attribute, original);
688
+ }
689
+ this.previous.clear();
690
+ this.picker = undefined;
691
+ }
692
+ }
693
+ customElements.define(tag, SpuiTagPickerPart);
694
+ };
695
+ for (const tag of ["spui-tag-picker-control", "spui-tag-picker-group", "spui-tag-picker-input", "spui-tag-picker-list", "spui-tag-picker-option-group"])
696
+ definePart(tag);
697
+ }