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