@spfx-fast/sharepoint-ui 0.4.2 → 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 (276) hide show
  1. package/QUICKSTART.md +18 -0
  2. package/README.md +19 -24
  3. package/components.json +240 -5694
  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 -76
  55. package/dist/index.js +60 -66
  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 -4583
  89. package/dist/react.js +1 -6837
  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 -167
  117. package/dist/theme.js +36 -162
  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 +19 -42
  130. package/src/theme.css +125 -0
  131. package/AGENTS.md +0 -211
  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 -62
  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 -1112
  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 -117
  165. package/dist/elements/data-grid-model.js +0 -294
  166. package/dist/elements/data-grid.d.ts +0 -29
  167. package/dist/elements/data-grid.js +0 -1567
  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 -26
  183. package/dist/elements/floating-position.js +0 -39
  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 -299
  200. package/dist/elements/nav.d.ts +0 -32
  201. package/dist/elements/nav.js +0 -791
  202. package/dist/elements/overflow.d.ts +0 -9
  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 -18
  217. package/dist/elements/rich-text-editor.js +0 -508
  218. package/dist/elements/search.d.ts +0 -3
  219. package/dist/elements/search.js +0 -207
  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 -19
  223. package/dist/elements/shared.js +0 -396
  224. package/dist/elements/skeleton.d.ts +0 -6
  225. package/dist/elements/skeleton.js +0 -91
  226. package/dist/elements/slider.d.ts +0 -1
  227. package/dist/elements/slider.js +0 -248
  228. package/dist/elements/spin-button.d.ts +0 -7
  229. package/dist/elements/spin-button.js +0 -499
  230. package/dist/elements/spinner.d.ts +0 -4
  231. package/dist/elements/spinner.js +0 -124
  232. package/dist/elements/swatch-picker.d.ts +0 -5
  233. package/dist/elements/swatch-picker.js +0 -363
  234. package/dist/elements/table.d.ts +0 -21
  235. package/dist/elements/table.js +0 -261
  236. package/dist/elements/tabs.d.ts +0 -1
  237. package/dist/elements/tabs.js +0 -276
  238. package/dist/elements/tag-picker.d.ts +0 -29
  239. package/dist/elements/tag-picker.js +0 -697
  240. package/dist/elements/tags.d.ts +0 -21
  241. package/dist/elements/tags.js +0 -421
  242. package/dist/elements/teaching-popover.d.ts +0 -32
  243. package/dist/elements/teaching-popover.js +0 -412
  244. package/dist/elements/text.d.ts +0 -9
  245. package/dist/elements/text.js +0 -75
  246. package/dist/elements/textarea.d.ts +0 -1
  247. package/dist/elements/textarea.js +0 -182
  248. package/dist/elements/toast.d.ts +0 -25
  249. package/dist/elements/toast.js +0 -337
  250. package/dist/elements/toaster.d.ts +0 -6
  251. package/dist/elements/toaster.js +0 -356
  252. package/dist/elements/toggle.d.ts +0 -2
  253. package/dist/elements/toggle.js +0 -246
  254. package/dist/elements/toolbar.d.ts +0 -12
  255. package/dist/elements/toolbar.js +0 -381
  256. package/dist/elements/tooltip.d.ts +0 -76
  257. package/dist/elements/tooltip.js +0 -510
  258. package/dist/elements/tree-model.d.ts +0 -19
  259. package/dist/elements/tree-model.js +0 -34
  260. package/dist/elements/tree.d.ts +0 -5
  261. package/dist/elements/tree.js +0 -273
  262. package/dist/elements/view-card.d.ts +0 -2
  263. package/dist/elements/view-card.js +0 -78
  264. package/dist/elements/virtual-list.d.ts +0 -41
  265. package/dist/elements/virtual-list.js +0 -369
  266. package/dist/elements.d.ts +0 -72
  267. package/dist/elements.js +0 -207
  268. package/dist/virtualizer-hooks.d.ts +0 -80
  269. package/dist/virtualizer-hooks.js +0 -253
  270. package/scripts/generate-token-index.mjs +0 -43
  271. package/scripts/sync-component-catalog-signatures.mjs +0 -55
  272. package/scripts/validate-component-catalog.mjs +0 -44
  273. package/skills/sharepoint-ui/SKILL.md +0 -12
  274. package/skills/sharepoint-ui/references/choosing-components.md +0 -20
  275. package/skills/sharepoint-ui/references/sharepoint-patterns.md +0 -17
  276. package/tokens.json +0 -3777
@@ -1,211 +0,0 @@
1
- import { base, syncHostAccessibleName } from "./shared.js";
2
- /** File selection surface. Uploading and SharePoint persistence remain the consumer's responsibility. */
3
- export function defineFileUpload() {
4
- if (customElements.get("spui-file-upload"))
5
- return;
6
- class SpuiFileUpload extends HTMLElement {
7
- static formAssociated = true;
8
- static observedAttributes = ["accept", "multiple", "disabled", "required", "name", "max-files", "max-file-size", "label", "aria-label", "aria-labelledby", "aria-description", "aria-describedby", "aria-invalid", "browse-label", "remove-label", "drop-label", "selected-label", "invalid-message", "required-message"];
9
- internals;
10
- input;
11
- list;
12
- status;
13
- selected = [];
14
- formDisabled = false;
15
- get form() { return this.internals?.form ?? null; }
16
- get files() { return this.selected; }
17
- constructor() { super(); try {
18
- this.internals = this.attachInternals();
19
- }
20
- catch { /* Native picker continues to work where form-associated elements are unavailable. */ } }
21
- connectedCallback() { if (!this.shadowRoot)
22
- this.render(); this.sync(); }
23
- attributeChangedCallback() { if (this.isConnected && this.input)
24
- this.sync(); }
25
- formDisabledCallback(disabled) { this.formDisabled = disabled; if (this.input)
26
- this.sync(); }
27
- formResetCallback() { if (this.status)
28
- this.status.textContent = ""; this.setFiles([], false); }
29
- formStateRestoreCallback(state) { if (state instanceof File)
30
- this.setFiles([state], false);
31
- else if (state instanceof FormData)
32
- this.setFiles(state.getAll(this.getAttribute("name") || "").filter((value) => value instanceof File), false); }
33
- render() {
34
- const shadow = this.attachShadow({ mode: "open" });
35
- const style = document.createElement("style");
36
- style.textContent = `${base}:host{display:block;min-width:0;color:var(--spui-ui-text,var(--spui-semantic-body-text,#242424));font-family:var(--spui-font-family,inherit)}.label{display:block;margin-block-end:6px;font-size:14px;font-weight:600}.drop{box-sizing:border-box;display:grid;place-items:center;gap:6px;min-height:104px;padding:16px;border:1px dashed var(--spui-ui-stroke,var(--spui-semantic-input-border,#8a8886));border-radius:var(--spui-control-radius,6px);background:var(--spui-ui-surface,var(--spui-semantic-input-background,#fff));text-align:center;transition:border-color var(--spui-motion-duration-fast,120ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),background-color var(--spui-motion-duration-fast,120ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}.drop[data-dragging=true]{border-color:var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));background:var(--spui-ui-surface-hover,var(--spui-ui-canvas,#f5f5f5))}.hint{color:var(--spui-ui-muted,var(--spui-semantic-body-subtext,#616161));font-size:12px}.browse{min-height:32px;padding:4px 12px;border:1px solid var(--spui-control-border,var(--spui-ui-stroke,#8a8886));border-radius:var(--spui-control-radius,4px);background:var(--spui-ui-surface,#fff);color:inherit;font:inherit;cursor:pointer}.browse:hover:not(:disabled){background:var(--spui-ui-surface-hover,var(--spui-ui-canvas,#f5f5f5))}.browse:focus-visible,.remove:focus-visible{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:2px}.browse:disabled,.remove:disabled{opacity:.55;cursor:not-allowed}.input{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.files{display:grid;gap:4px;margin:8px 0 0;padding:0;list-style:none}.files:empty{display:none}.file{display:flex;align-items:center;gap:8px;min-width:0;padding:6px 8px;border:1px solid var(--spui-ui-stroke,#d1d1d1);border-radius:var(--spui-control-radius,4px)}.filename{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.size{flex:none;color:var(--spui-ui-muted,#616161);font-size:12px;font-variant-numeric:tabular-nums}.remove{min-width:32px;min-height:32px;border:0;border-radius:var(--spui-control-radius,4px);background:transparent;color:inherit;font:inherit;cursor:pointer}.status{margin-block-start:6px;color:var(--spui-semantic-error-text,var(--spui-ui-danger,#b10e1c));font-size:12px}.status:empty{display:none}:host([data-spui-appearance=glass]) .drop{backdrop-filter:var(--spui-card-backdrop,blur(12px) saturate(1.2))}@media(prefers-reduced-motion:reduce){.drop{transition:none}}@media(forced-colors:active){.drop,.file{border-color:CanvasText;background:Canvas;color:CanvasText}.drop[data-dragging=true]{outline:2px solid Highlight}.browse{border-color:ButtonText;background:ButtonFace;color:ButtonText}}`;
37
- const label = document.createElement("label");
38
- label.className = "label";
39
- label.part.add("label");
40
- const drop = document.createElement("div");
41
- drop.className = "drop";
42
- drop.part.add("dropzone");
43
- drop.tabIndex = -1;
44
- const hint = document.createElement("span");
45
- hint.className = "hint";
46
- const browse = document.createElement("button");
47
- browse.type = "button";
48
- browse.className = "browse";
49
- browse.part.add("browse-button");
50
- this.input = document.createElement("input");
51
- this.input.type = "file";
52
- this.input.className = "input";
53
- this.input.part.add("input");
54
- browse.addEventListener("click", () => this.input.click());
55
- this.input.addEventListener("change", () => { this.addFiles(Array.from(this.input.files ?? [])); this.input.value = ""; });
56
- drop.addEventListener("dragover", event => { event.preventDefault(); if (!this.isDisabled())
57
- drop.dataset.dragging = "true"; });
58
- drop.addEventListener("dragleave", event => { if (!drop.contains(event.relatedTarget))
59
- delete drop.dataset.dragging; });
60
- drop.addEventListener("drop", event => { event.preventDefault(); delete drop.dataset.dragging; if (!this.isDisabled())
61
- this.addFiles(Array.from(event.dataTransfer?.files ?? [])); });
62
- drop.addEventListener("keydown", event => { if (event.key === "Enter" || event.key === " ") {
63
- event.preventDefault();
64
- this.input.click();
65
- } });
66
- this.input.addEventListener("focus", () => browse.focus());
67
- this.list = document.createElement("ul");
68
- this.list.className = "files";
69
- this.list.part.add("file-list");
70
- this.list.setAttribute("aria-label", "Selected files");
71
- this.status = document.createElement("div");
72
- this.status.className = "status";
73
- this.status.part.add("status");
74
- this.status.setAttribute("role", "status");
75
- this.status.setAttribute("aria-live", "polite");
76
- drop.append(hint, browse, this.input);
77
- shadow.append(style, label, drop, this.list, this.status);
78
- }
79
- sync() {
80
- const disabled = this.isDisabled();
81
- this.input.accept = this.getAttribute("accept") || "";
82
- this.input.multiple = this.hasAttribute("multiple");
83
- this.input.disabled = disabled;
84
- this.input.required = this.hasAttribute("required") && this.selected.length === 0;
85
- const label = this.getAttribute("aria-label") || this.getAttribute("label") || "Add files";
86
- const browse = this.getAttribute("browse-label") || "Browse files";
87
- if (!this.hasAttribute("role"))
88
- this.setAttribute("role", "group");
89
- const labelElement = this.shadowRoot?.querySelector(".label");
90
- if (labelElement) {
91
- labelElement.textContent = this.getAttribute("label") || "";
92
- labelElement.hidden = !this.hasAttribute("label");
93
- }
94
- this.input.setAttribute("aria-label", label);
95
- syncHostAccessibleName(this, this.input);
96
- const invalid = this.getAttribute("aria-invalid");
97
- if (invalid === null)
98
- this.input.removeAttribute("aria-invalid");
99
- else
100
- this.input.setAttribute("aria-invalid", invalid);
101
- const hint = this.shadowRoot?.querySelector(".hint");
102
- if (hint)
103
- hint.textContent = this.getAttribute("drop-label") || "Drag files here or browse to select";
104
- const button = this.shadowRoot?.querySelector(".browse");
105
- if (button) {
106
- button.textContent = browse;
107
- button.disabled = disabled;
108
- button.setAttribute("aria-label", browse);
109
- }
110
- this.list?.setAttribute("aria-label", `${label}: ${this.getAttribute("selected-label") || "selected files"}`);
111
- this.renderFiles();
112
- this.syncForm();
113
- }
114
- addFiles(files) {
115
- if (!files.length || this.isDisabled())
116
- return;
117
- const max = Number(this.getAttribute("max-files"));
118
- const limit = Number(this.getAttribute("max-file-size"));
119
- const next = this.hasAttribute("multiple") ? [...this.selected] : [];
120
- const errors = [];
121
- for (const file of files) {
122
- if (!this.accepts(file)) {
123
- errors.push({ code: "file-type-not-accepted", file });
124
- continue;
125
- }
126
- if (Number.isFinite(limit) && limit > 0 && file.size > limit) {
127
- errors.push({ code: "file-too-large", file });
128
- continue;
129
- }
130
- if (Number.isFinite(max) && max > 0 && next.length >= max) {
131
- errors.push({ code: "too-many-files", file });
132
- continue;
133
- }
134
- if (!next.some(existing => existing.name === file.name && existing.size === file.size && existing.lastModified === file.lastModified))
135
- next.push(file);
136
- }
137
- this.status.textContent = errors.length ? (this.getAttribute("invalid-message") || this.getAttribute(errors[0].code === "file-too-large" ? "file-too-large-message" : errors[0].code === "too-many-files" ? "max-files-message" : "file-type-message") || this.describeError(errors[0])) : "";
138
- this.setFiles(next, true);
139
- if (errors.length)
140
- this.dispatchEvent(new CustomEvent("spui-file-error", { detail: { errors }, bubbles: true, composed: true }));
141
- }
142
- describeError(error) {
143
- if (error.code === "file-too-large")
144
- return `${error.file?.name || "File"} exceeds the allowed size.`;
145
- if (error.code === "too-many-files")
146
- return "The maximum number of files has been reached.";
147
- return "The selected file type is not accepted.";
148
- }
149
- accepts(file) {
150
- const accepted = (this.getAttribute("accept") || "").split(",").map(value => value.trim().toLowerCase()).filter(Boolean);
151
- if (!accepted.length)
152
- return true;
153
- const name = file.name.toLowerCase();
154
- const type = file.type.toLowerCase();
155
- return accepted.some(value => value.startsWith(".") ? name.endsWith(value) : value.endsWith("/*") ? type.startsWith(value.slice(0, -1)) : type === value);
156
- }
157
- setFiles(files, emit) {
158
- this.selected = files;
159
- this.renderFiles();
160
- this.syncForm();
161
- if (emit)
162
- this.dispatchEvent(new CustomEvent("spui-file-change", { detail: { files: [...files] }, bubbles: true, composed: true }));
163
- }
164
- renderFiles() {
165
- if (!this.list)
166
- return;
167
- this.list.replaceChildren(...this.selected.map((file, index) => {
168
- const item = document.createElement("li");
169
- item.className = "file";
170
- item.part.add("file");
171
- const name = document.createElement("span");
172
- name.className = "filename";
173
- name.textContent = file.name;
174
- const size = document.createElement("span");
175
- size.className = "size";
176
- size.textContent = this.formatSize(file.size);
177
- const remove = document.createElement("button");
178
- remove.type = "button";
179
- remove.className = "remove";
180
- remove.textContent = "×";
181
- remove.disabled = this.isDisabled();
182
- remove.setAttribute("aria-label", `${this.getAttribute("remove-label") || "Remove"} ${file.name}`);
183
- remove.addEventListener("click", () => {
184
- this.status.textContent = "";
185
- const next = this.selected.filter((_, i) => i !== index);
186
- this.setFiles(next, true);
187
- const nextRemove = this.list.querySelectorAll(".remove")[Math.min(index, next.length - 1)];
188
- (nextRemove ?? this.shadowRoot?.querySelector(".browse"))?.focus();
189
- });
190
- item.append(name, size, remove);
191
- return item;
192
- }));
193
- }
194
- formatSize(size) { return size < 1024 ? `${size} B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(1)} KB` : `${(size / (1024 * 1024)).toFixed(1)} MB`; }
195
- syncForm() {
196
- if (!this.internals)
197
- return;
198
- const name = this.getAttribute("name") || "";
199
- const data = new FormData();
200
- if (name)
201
- this.selected.forEach(file => data.append(name, file, file.name));
202
- this.internals.setFormValue(this.selected.length && name ? data : null, JSON.stringify(this.selected.map(file => ({ name: file.name, size: file.size, lastModified: file.lastModified }))));
203
- if (this.hasAttribute("required") && !this.selected.length)
204
- this.internals.setValidity({ valueMissing: true }, this.getAttribute("required-message") || "Select at least one file.", this.shadowRoot?.querySelector(".browse") ?? undefined);
205
- else
206
- this.internals.setValidity({});
207
- }
208
- isDisabled() { return this.formDisabled || this.hasAttribute("disabled"); }
209
- }
210
- customElements.define("spui-file-upload", SpuiFileUpload);
211
- }
@@ -1,26 +0,0 @@
1
- export type FloatingPlacement = "top" | "bottom" | "start" | "end" | "left" | "right";
2
- export type FloatingPosition = {
3
- top: number;
4
- left: number;
5
- placement: Exclude<FloatingPlacement, "start" | "end">;
6
- align: FloatingAlign;
7
- };
8
- export type FloatingAlign = "start" | "center" | "end" | "top" | "bottom";
9
- export type FloatingModifiers = {
10
- crossAxisOffset?: number;
11
- coverTarget?: boolean;
12
- overflowBoundaryPadding?: {
13
- top?: number;
14
- right?: number;
15
- bottom?: number;
16
- left?: number;
17
- };
18
- };
19
- export type FloatingAnchorRect = Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom" | "width" | "height">;
20
- export declare function getFloatingPosition(anchor: FloatingAnchorRect, surface: Pick<DOMRectReadOnly, "width" | "height">, placement: FloatingPlacement, direction: "ltr" | "rtl", gap?: number, viewport?: {
21
- width: number;
22
- height: number;
23
- }, align?: FloatingAlign, fallbackPlacements?: Array<{
24
- placement: FloatingPlacement;
25
- align?: FloatingAlign;
26
- }>, pinned?: boolean, flipBoundary?: Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom">, overflowBoundary?: Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom">, modifiers?: FloatingModifiers): FloatingPosition;
@@ -1,39 +0,0 @@
1
- export function getFloatingPosition(anchor, surface, placement, direction, gap = 8, viewport = { width: innerWidth, height: innerHeight }, align = "center", fallbackPlacements = [], pinned = false, flipBoundary, overflowBoundary, modifiers = {}) {
2
- const viewportBoundary = { left: 0, top: 0, right: viewport.width, bottom: viewport.height };
3
- const flipBounds = flipBoundary ?? viewportBoundary;
4
- const overflowBounds = overflowBoundary ?? viewportBoundary;
5
- const overflowPadding = modifiers.overflowBoundaryPadding ?? {};
6
- const side = placement === "start" ? direction === "rtl" ? "right" : "left" : placement === "end" ? direction === "rtl" ? "left" : "right" : placement;
7
- const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
8
- const coordinates = (candidate, candidateAlign = align) => {
9
- const candidateInlineAlign = candidateAlign === "start" || candidateAlign === "top" ? "start" : candidateAlign === "end" || candidateAlign === "bottom" ? "end" : "center";
10
- const candidateBlockAlign = candidateAlign === "start" || candidateAlign === "top" ? "start" : candidateAlign === "end" || candidateAlign === "bottom" ? "end" : "center";
11
- const inlineStartLeft = direction === "rtl" ? anchor.right - surface.width : anchor.left;
12
- const inlineEndLeft = direction === "rtl" ? anchor.left : anchor.right - surface.width;
13
- return candidate === "top" || candidate === "bottom"
14
- ? { top: candidate === "top" ? (modifiers.coverTarget ? anchor.bottom : anchor.top) - surface.height - gap : (modifiers.coverTarget ? anchor.top : anchor.bottom) + gap, left: (candidateInlineAlign === "start" ? inlineStartLeft : candidateInlineAlign === "end" ? inlineEndLeft : anchor.left + (anchor.width - surface.width) / 2) + (modifiers.crossAxisOffset ?? 0) }
15
- : { top: (candidateBlockAlign === "start" ? anchor.top : candidateBlockAlign === "end" ? anchor.bottom - surface.height : anchor.top + (anchor.height - surface.height) / 2) + (modifiers.crossAxisOffset ?? 0), left: candidate === "left" ? (modifiers.coverTarget ? anchor.right : anchor.left) - surface.width - gap : (modifiers.coverTarget ? anchor.left : anchor.right) + gap };
16
- };
17
- const fits = (_candidate, point) => point.top >= flipBounds.top + gap && point.left >= flipBounds.left + gap && point.top + surface.height <= flipBounds.bottom - gap && point.left + surface.width <= flipBounds.right - gap;
18
- const candidates = [
19
- { placement: side, align },
20
- ...(pinned ? [] : fallbackPlacements),
21
- ...(!pinned ? [{ placement: opposite[side], align }] : []),
22
- ].map(candidate => ({ placement: candidate.placement === "start" ? direction === "rtl" ? "right" : "left" : candidate.placement === "end" ? direction === "rtl" ? "left" : "right" : candidate.placement, align: candidate.align ?? align }));
23
- const unique = candidates.filter((candidate, index) => candidates.findIndex(other => other.placement === candidate.placement && other.align === candidate.align) === index);
24
- let selected = unique.map(candidate => ({ ...candidate, point: coordinates(candidate.placement, candidate.align) })).find(candidate => fits(candidate.placement, candidate.point));
25
- if (!selected) {
26
- selected = unique.map(candidate => ({ ...candidate, point: coordinates(candidate.placement, candidate.align) })).sort((a, b) => {
27
- const overflow = (candidate) => Math.max(0, flipBounds.left + gap - candidate.point.left) + Math.max(0, candidate.point.left + surface.width - (flipBounds.right - gap)) + Math.max(0, flipBounds.top + gap - candidate.point.top) + Math.max(0, candidate.point.top + surface.height - (flipBounds.bottom - gap));
28
- return overflow(a) - overflow(b);
29
- })[0];
30
- }
31
- const actual = selected.placement;
32
- const point = selected.point;
33
- return {
34
- placement: actual,
35
- align: selected.align,
36
- left: Math.max(overflowBounds.left + gap + (overflowPadding.left ?? 0), Math.min(point.left, overflowBounds.right - surface.width - gap - (overflowPadding.right ?? 0))),
37
- top: Math.max(overflowBounds.top + gap + (overflowPadding.top ?? 0), Math.min(point.top, overflowBounds.bottom - surface.height - gap - (overflowPadding.bottom ?? 0))),
38
- };
39
- }
@@ -1,28 +0,0 @@
1
- export type ImageFit = "none" | "center" | "contain" | "cover" | "default";
2
- export type ImageShape = "square" | "rounded" | "circular";
3
- export type ImageOptions = {
4
- src: string;
5
- alt: string;
6
- srcSet?: string;
7
- sizes?: string;
8
- width?: number;
9
- height?: number;
10
- title?: string;
11
- className?: string;
12
- loading?: "eager" | "lazy";
13
- decoding?: "async" | "sync" | "auto";
14
- fetchPriority?: "high" | "low" | "auto";
15
- fit?: ImageFit;
16
- shape?: ImageShape;
17
- block?: boolean;
18
- bordered?: boolean;
19
- shadow?: boolean;
20
- };
21
- type ImageStyleOptions = Pick<ImageOptions, "fit" | "shape" | "block" | "bordered" | "shadow"> & {
22
- width?: number | string;
23
- height?: number | string;
24
- };
25
- export declare function getImageStyle({ fit, shape, block, bordered, shadow, width, height }: ImageStyleOptions): Record<string, string>;
26
- /** Create a native image with browser-native responsive loading and meaningful alt text. */
27
- export declare function createImage(options: ImageOptions): HTMLImageElement;
28
- export {};
@@ -1,38 +0,0 @@
1
- export function getImageStyle({ fit = "default", shape = "square", block, bordered, shadow, width, height }) {
2
- const fitFill = fit !== "default" && width == null && height == null;
3
- return {
4
- display: block ? "block" : "inline-block",
5
- maxWidth: "100%",
6
- ...(block ? { width: "100%" } : {}),
7
- ...(fitFill ? { width: "100%", height: "100%" } : {}),
8
- ...(fit === "default" ? {} : { objectFit: fit === "center" ? "none" : fit }),
9
- ...(fit === "default" ? {} : { objectPosition: fit === "none" ? "left top" : "center" }),
10
- borderRadius: shape === "circular" ? "50%" : shape === "rounded" ? "var(--spui-image-radius,var(--spui-control-radius,4px))" : "0",
11
- ...(bordered ? { border: "1px solid var(--spui-image-border,var(--spui-ui-stroke,var(--spui-semantic-body-divider,#d1d1d1)))" } : {}),
12
- ...(shadow ? { boxShadow: "var(--spui-image-shadow,var(--spui-card-shadow,0 1px 3px #0002))" } : {}),
13
- };
14
- }
15
- /** Create a native image with browser-native responsive loading and meaningful alt text. */
16
- export function createImage(options) {
17
- const image = document.createElement("img");
18
- image.alt = options.alt;
19
- if (options.srcSet)
20
- image.srcset = options.srcSet;
21
- if (options.sizes)
22
- image.sizes = options.sizes;
23
- if (options.width !== undefined)
24
- image.width = options.width;
25
- if (options.height !== undefined)
26
- image.height = options.height;
27
- if (options.title)
28
- image.title = options.title;
29
- if (options.className)
30
- image.className = options.className;
31
- image.loading = options.loading ?? "lazy";
32
- image.decoding = options.decoding ?? "async";
33
- image.fetchPriority = options.fetchPriority ?? "auto";
34
- Object.assign(image.style, getImageStyle(options));
35
- // Configure the candidate set and intrinsic layout before starting any image request.
36
- image.src = options.src;
37
- return image;
38
- }
@@ -1,9 +0,0 @@
1
- import { type LabelOptions } from "./label.js";
2
- export type InfoPlacement = "top" | "bottom" | "start" | "end";
3
- export type InfoLabelOptions = LabelOptions & {
4
- infoButtonLabel?: string;
5
- placement?: InfoPlacement;
6
- };
7
- export declare function defineInfoButton(): void;
8
- /** Build a native form label with an adjacent, separately focusable information button. */
9
- export declare function createInfoLabel(labelContent: string | Node, info: string | Node, options?: InfoLabelOptions): HTMLSpanElement;
@@ -1,102 +0,0 @@
1
- import { definePopover } from "./popover.js";
2
- import { createLabel } from "./label.js";
3
- export function defineInfoButton() {
4
- if (customElements.get("spui-info-button"))
5
- return;
6
- class SpuiInfoButton extends HTMLElement {
7
- static observedAttributes = ["label", "info", "placement", "open", "disabled", "size", "inline", "popover-placement", "popover-label", "popover-arrow", "popover-trap-focus"];
8
- popoverElement;
9
- trigger;
10
- contentSlot;
11
- get open() { return this.popoverElement?.open ?? this.hasAttribute("open"); }
12
- set open(value) { if (this.popoverElement)
13
- this.popoverElement.open = Boolean(value);
14
- else
15
- this.toggleAttribute("open", Boolean(value)); }
16
- get inline() { return this.getAttribute("inline") !== "false"; }
17
- set inline(value) { if (value)
18
- this.removeAttribute("inline");
19
- else
20
- this.setAttribute("inline", "false"); }
21
- connectedCallback() {
22
- definePopover();
23
- if (!this.shadowRoot)
24
- this.render();
25
- this.sync();
26
- }
27
- attributeChangedCallback() { if (this.isConnected)
28
- this.sync(); }
29
- render() {
30
- const shadow = this.attachShadow({ mode: "open" });
31
- const style = document.createElement("style");
32
- style.textContent = `:host{display:inline-flex;vertical-align:middle;line-height:1}:host([hidden]){display:none}button{display:inline-grid;place-items:center;box-sizing:border-box;width:var(--spui-info-button-size,20px);height:var(--spui-info-button-size,20px);padding:2px;border:1px solid transparent;border-radius:50%;background:transparent;color:var(--spui-info-button-color,var(--spui-ui-muted,var(--spui-semantic-body-text,#484644)));cursor:pointer;font:inherit}button:hover:not(:disabled){background:var(--spui-info-button-hover,var(--spui-ui-surface-hover,#f3f2f1));color:var(--spui-ui-accent,var(--spui-semantic-link,#0f6cbd))}button:focus-visible{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#005a9e));outline-offset:2px}button:disabled{opacity:.55;cursor:not-allowed}svg{width:calc(var(--spui-info-button-size,20px) * .8);height:calc(var(--spui-info-button-size,20px) * .8);fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;pointer-events:none}:host([size=small]){--spui-info-button-size:16px}:host([size=large]){--spui-info-button-size:24px}:host([data-spui-appearance=glass]) button{backdrop-filter:blur(8px)}@media(forced-colors:active){button{border-color:ButtonText;color:ButtonText}button:focus-visible{outline-color:Highlight}}`;
33
- const popover = document.createElement("spui-popover");
34
- this.popoverElement = popover;
35
- const trigger = document.createElement("button");
36
- this.trigger = trigger;
37
- trigger.type = "button";
38
- trigger.slot = "trigger";
39
- trigger.part.add("control");
40
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
41
- svg.setAttribute("viewBox", "0 0 20 20");
42
- svg.setAttribute("aria-hidden", "true");
43
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
44
- path.setAttribute("d", "M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0-9v5m0-8h.01");
45
- svg.append(path);
46
- trigger.append(svg);
47
- const content = document.createElement("div");
48
- content.slot = "content";
49
- content.part.add("content");
50
- const slot = document.createElement("slot");
51
- this.contentSlot = slot;
52
- slot.addEventListener("slotchange", () => this.syncContent());
53
- content.append(slot);
54
- popover.append(trigger, content);
55
- popover.addEventListener("spui-open-change", event => {
56
- if (event.detail.open)
57
- requestAnimationFrame(() => {
58
- const surface = popover.shadowRoot?.querySelector(".surface");
59
- surface?.setAttribute("tabindex", "-1");
60
- surface?.focus();
61
- });
62
- });
63
- shadow.append(style, popover);
64
- }
65
- sync() {
66
- if (!this.popoverElement || !this.trigger)
67
- return;
68
- const label = this.getAttribute("label") || "More information";
69
- this.trigger.setAttribute("aria-label", label);
70
- this.trigger.disabled = this.hasAttribute("disabled");
71
- this.popoverElement.setAttribute("placement", this.getAttribute("popover-placement") || this.getAttribute("placement") || "top");
72
- this.popoverElement.setAttribute("label", this.getAttribute("popover-label") || label);
73
- this.popoverElement.inline = this.inline;
74
- this.popoverElement.toggleAttribute("arrow", this.hasAttribute("popover-arrow"));
75
- this.popoverElement.toggleAttribute("trap-focus", this.hasAttribute("popover-trap-focus"));
76
- if (this.popoverElement.open !== this.hasAttribute("open"))
77
- this.popoverElement.open = this.hasAttribute("open");
78
- this.syncContent();
79
- }
80
- syncContent() {
81
- if (!this.contentSlot)
82
- return;
83
- this.contentSlot.textContent = !this.childNodes.length && this.hasAttribute("info") ? this.getAttribute("info") ?? "" : "";
84
- }
85
- }
86
- customElements.define("spui-info-button", SpuiInfoButton);
87
- }
88
- /** Build a native form label with an adjacent, separately focusable information button. */
89
- export function createInfoLabel(labelContent, info, options = {}) {
90
- defineInfoButton();
91
- const wrapper = document.createElement("span");
92
- Object.assign(wrapper.style, { display: "inline-flex", alignItems: "baseline", gap: "0.25em" });
93
- const label = createLabel(labelContent, options);
94
- wrapper.append(label);
95
- const button = document.createElement("spui-info-button");
96
- button.setAttribute("label", options.infoButtonLabel ?? "More information");
97
- if (options.placement)
98
- button.setAttribute("placement", options.placement);
99
- button.append(typeof info === "string" ? document.createTextNode(info) : info);
100
- wrapper.append(button);
101
- return wrapper;
102
- }
@@ -1,5 +0,0 @@
1
- export type InputSize = "small" | "medium" | "large";
2
- export type InputAppearance = "outline" | "underline" | "filled-darker" | "filled-lighter" | "filled-darker-shadow" | "filled-lighter-shadow";
3
- export type InputType = "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week" | (string & {});
4
- /** Bare, form-associated input; pair it with Label or Field for an accessible name. */
5
- export declare function defineInput(): void;