@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,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,:host-context([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,36 +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 type FloatingBoundary = Element | {
21
- x: number;
22
- y: number;
23
- width: number;
24
- height: number;
25
- } | readonly Element[] | "clippingParents" | "scrollParent" | "window" | null;
26
- export declare function getFloatingBoundary(boundary: FloatingBoundary | undefined, context: Element, viewport?: {
27
- width: number;
28
- height: number;
29
- }): Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom"> | undefined;
30
- export declare function getFloatingPosition(anchor: FloatingAnchorRect, surface: Pick<DOMRectReadOnly, "width" | "height">, placement: FloatingPlacement, direction: "ltr" | "rtl", gap?: number, viewport?: {
31
- width: number;
32
- height: number;
33
- }, align?: FloatingAlign, fallbackPlacements?: Array<{
34
- placement: FloatingPlacement;
35
- align?: FloatingAlign;
36
- }>, pinned?: boolean, flipBoundary?: Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom">, overflowBoundary?: Pick<DOMRectReadOnly, "left" | "top" | "right" | "bottom">, modifiers?: FloatingModifiers): FloatingPosition;
@@ -1,92 +0,0 @@
1
- export function getFloatingBoundary(boundary, context, viewport = { width: innerWidth, height: innerHeight }) {
2
- if (boundary === undefined || boundary === null || boundary === "window")
3
- return undefined;
4
- const rectEdges = (rect) => {
5
- const left = rect.left ?? rect.x ?? 0;
6
- const top = rect.top ?? rect.y ?? 0;
7
- return { left, top, right: left + rect.width, bottom: top + rect.height };
8
- };
9
- const isElement = (value) => Boolean(value && typeof value === "object" && value.nodeType === 1 && typeof value.getBoundingClientRect === "function");
10
- if (Array.isArray(boundary)) {
11
- const rects = boundary.map(element => rectEdges(element.getBoundingClientRect()));
12
- if (!rects.length)
13
- return undefined;
14
- const left = Math.max(...rects.map(rect => rect.left));
15
- const top = Math.max(...rects.map(rect => rect.top));
16
- return { left, top, right: Math.max(left, Math.min(...rects.map(rect => rect.right))), bottom: Math.max(top, Math.min(...rects.map(rect => rect.bottom))) };
17
- }
18
- if (isElement(boundary))
19
- return rectEdges(boundary.getBoundingClientRect());
20
- if (typeof boundary === "object" && "width" in boundary && "height" in boundary)
21
- return rectEdges(boundary);
22
- if (boundary !== "clippingParents" && boundary !== "scrollParent")
23
- return undefined;
24
- const bounds = { left: 0, top: 0, right: viewport.width, bottom: viewport.height };
25
- const view = context.ownerDocument.defaultView;
26
- const parent = (current) => current.parentElement ?? (current.getRootNode() instanceof ShadowRoot ? current.getRootNode().host : null);
27
- for (let current = parent(context); current; current = parent(current)) {
28
- const style = view?.getComputedStyle(current);
29
- if (!style)
30
- continue;
31
- const clipsX = /^(auto|scroll|hidden|clip|overlay)$/.test(style.overflowX);
32
- const clipsY = /^(auto|scroll|hidden|clip|overlay)$/.test(style.overflowY);
33
- const scrolls = /^(auto|scroll|hidden|overlay)$/.test(style.overflowX) || /^(auto|scroll|hidden|overlay)$/.test(style.overflowY);
34
- if (boundary === "scrollParent" && !scrolls)
35
- continue;
36
- if (!clipsX && !clipsY)
37
- continue;
38
- const rect = current.getBoundingClientRect();
39
- const left = rect.left + current.clientLeft;
40
- const top = rect.top + current.clientTop;
41
- if (boundary === "scrollParent")
42
- return { left, top, right: left + current.clientWidth, bottom: top + current.clientHeight };
43
- if (clipsX) {
44
- bounds.left = Math.max(bounds.left, left);
45
- bounds.right = Math.min(bounds.right, left + current.clientWidth);
46
- }
47
- if (clipsY) {
48
- bounds.top = Math.max(bounds.top, top);
49
- bounds.bottom = Math.min(bounds.bottom, top + current.clientHeight);
50
- }
51
- }
52
- return { left: bounds.left, top: bounds.top, right: Math.max(bounds.left, bounds.right), bottom: Math.max(bounds.top, bounds.bottom) };
53
- }
54
- export function getFloatingPosition(anchor, surface, placement, direction, gap = 8, viewport = { width: innerWidth, height: innerHeight }, align = "center", fallbackPlacements = [], pinned = false, flipBoundary, overflowBoundary, modifiers = {}) {
55
- const viewportBoundary = { left: 0, top: 0, right: viewport.width, bottom: viewport.height };
56
- const flipBounds = flipBoundary ?? viewportBoundary;
57
- const overflowBounds = overflowBoundary ?? viewportBoundary;
58
- const overflowPadding = modifiers.overflowBoundaryPadding ?? {};
59
- const side = placement === "start" ? direction === "rtl" ? "right" : "left" : placement === "end" ? direction === "rtl" ? "left" : "right" : placement;
60
- const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
61
- const coordinates = (candidate, candidateAlign = align) => {
62
- const candidateInlineAlign = candidateAlign === "start" || candidateAlign === "top" ? "start" : candidateAlign === "end" || candidateAlign === "bottom" ? "end" : "center";
63
- const candidateBlockAlign = candidateAlign === "start" || candidateAlign === "top" ? "start" : candidateAlign === "end" || candidateAlign === "bottom" ? "end" : "center";
64
- const inlineStartLeft = direction === "rtl" ? anchor.right - surface.width : anchor.left;
65
- const inlineEndLeft = direction === "rtl" ? anchor.left : anchor.right - surface.width;
66
- return candidate === "top" || candidate === "bottom"
67
- ? { 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) }
68
- : { 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 };
69
- };
70
- 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;
71
- const candidates = [
72
- { placement: side, align },
73
- ...(pinned ? [] : fallbackPlacements),
74
- ...(!pinned ? [{ placement: opposite[side], align }] : []),
75
- ].map(candidate => ({ placement: candidate.placement === "start" ? direction === "rtl" ? "right" : "left" : candidate.placement === "end" ? direction === "rtl" ? "left" : "right" : candidate.placement, align: candidate.align ?? align }));
76
- const unique = candidates.filter((candidate, index) => candidates.findIndex(other => other.placement === candidate.placement && other.align === candidate.align) === index);
77
- let selected = unique.map(candidate => ({ ...candidate, point: coordinates(candidate.placement, candidate.align) })).find(candidate => fits(candidate.placement, candidate.point));
78
- if (!selected) {
79
- selected = unique.map(candidate => ({ ...candidate, point: coordinates(candidate.placement, candidate.align) })).sort((a, b) => {
80
- 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));
81
- return overflow(a) - overflow(b);
82
- })[0];
83
- }
84
- const actual = selected.placement;
85
- const point = selected.point;
86
- return {
87
- placement: actual,
88
- align: selected.align,
89
- left: Math.max(overflowBounds.left + gap + (overflowPadding.left ?? 0), Math.min(point.left, overflowBounds.right - surface.width - gap - (overflowPadding.right ?? 0))),
90
- top: Math.max(overflowBounds.top + gap + (overflowPadding.top ?? 0), Math.min(point.top, overflowBounds.bottom - surface.height - gap - (overflowPadding.bottom ?? 0))),
91
- };
92
- }
@@ -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,:host-context([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;