@spfx-fast/sharepoint-ui 0.0.0-stage → 0.4.1

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 (156) hide show
  1. package/AGENTS.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +897 -2
  4. package/components.json +5811 -0
  5. package/dist/appearance.d.ts +6 -0
  6. package/dist/appearance.js +217 -0
  7. package/dist/elements/accordion.d.ts +9 -0
  8. package/dist/elements/accordion.js +338 -0
  9. package/dist/elements/alert.d.ts +2 -0
  10. package/dist/elements/alert.js +62 -0
  11. package/dist/elements/avatar-group-primitives.d.ts +3 -0
  12. package/dist/elements/avatar-group-primitives.js +189 -0
  13. package/dist/elements/avatar-group.d.ts +2 -0
  14. package/dist/elements/avatar-group.js +110 -0
  15. package/dist/elements/avatar.d.ts +4 -0
  16. package/dist/elements/avatar.js +251 -0
  17. package/dist/elements/badge.d.ts +8 -0
  18. package/dist/elements/badge.js +182 -0
  19. package/dist/elements/breadcrumb.d.ts +4 -0
  20. package/dist/elements/breadcrumb.js +233 -0
  21. package/dist/elements/button-group.d.ts +1 -0
  22. package/dist/elements/button-group.js +20 -0
  23. package/dist/elements/button-variants.d.ts +6 -0
  24. package/dist/elements/button-variants.js +339 -0
  25. package/dist/elements/button.d.ts +5 -0
  26. package/dist/elements/button.js +233 -0
  27. package/dist/elements/card-primitives.d.ts +3 -0
  28. package/dist/elements/card-primitives.js +83 -0
  29. package/dist/elements/card.d.ts +5 -0
  30. package/dist/elements/card.js +213 -0
  31. package/dist/elements/carousel.d.ts +92 -0
  32. package/dist/elements/carousel.js +1112 -0
  33. package/dist/elements/color-picker.d.ts +24 -0
  34. package/dist/elements/color-picker.js +1004 -0
  35. package/dist/elements/combobox.d.ts +1 -0
  36. package/dist/elements/combobox.js +1012 -0
  37. package/dist/elements/data-grid-model.d.ts +117 -0
  38. package/dist/elements/data-grid-model.js +294 -0
  39. package/dist/elements/data-grid.d.ts +29 -0
  40. package/dist/elements/data-grid.js +1567 -0
  41. package/dist/elements/date-picker.d.ts +1 -0
  42. package/dist/elements/date-picker.js +486 -0
  43. package/dist/elements/dialog.d.ts +23 -0
  44. package/dist/elements/dialog.js +409 -0
  45. package/dist/elements/divider.d.ts +2 -0
  46. package/dist/elements/divider.js +66 -0
  47. package/dist/elements/drawer.d.ts +3 -0
  48. package/dist/elements/drawer.js +308 -0
  49. package/dist/elements/field-composition.d.ts +26 -0
  50. package/dist/elements/field-composition.js +133 -0
  51. package/dist/elements/field.d.ts +1 -0
  52. package/dist/elements/field.js +67 -0
  53. package/dist/elements/file-upload.d.ts +6 -0
  54. package/dist/elements/file-upload.js +211 -0
  55. package/dist/elements/floating-position.d.ts +26 -0
  56. package/dist/elements/floating-position.js +39 -0
  57. package/dist/elements/image.d.ts +28 -0
  58. package/dist/elements/image.js +38 -0
  59. package/dist/elements/info.d.ts +9 -0
  60. package/dist/elements/info.js +102 -0
  61. package/dist/elements/input.d.ts +5 -0
  62. package/dist/elements/input.js +255 -0
  63. package/dist/elements/label.d.ts +15 -0
  64. package/dist/elements/label.js +56 -0
  65. package/dist/elements/link.d.ts +1 -0
  66. package/dist/elements/link.js +92 -0
  67. package/dist/elements/list.d.ts +19 -0
  68. package/dist/elements/list.js +297 -0
  69. package/dist/elements/menu.d.ts +33 -0
  70. package/dist/elements/menu.js +1103 -0
  71. package/dist/elements/message-bar.d.ts +10 -0
  72. package/dist/elements/message-bar.js +299 -0
  73. package/dist/elements/nav.d.ts +32 -0
  74. package/dist/elements/nav.js +791 -0
  75. package/dist/elements/overflow.d.ts +9 -0
  76. package/dist/elements/overflow.js +218 -0
  77. package/dist/elements/persona.d.ts +3 -0
  78. package/dist/elements/persona.js +125 -0
  79. package/dist/elements/popover.d.ts +77 -0
  80. package/dist/elements/popover.js +840 -0
  81. package/dist/elements/progress-bar.d.ts +4 -0
  82. package/dist/elements/progress-bar.js +82 -0
  83. package/dist/elements/radio-group.d.ts +1 -0
  84. package/dist/elements/radio-group.js +303 -0
  85. package/dist/elements/rating-display.d.ts +2 -0
  86. package/dist/elements/rating-display.js +125 -0
  87. package/dist/elements/rating.d.ts +2 -0
  88. package/dist/elements/rating.js +206 -0
  89. package/dist/elements/rich-text-editor.d.ts +18 -0
  90. package/dist/elements/rich-text-editor.js +508 -0
  91. package/dist/elements/search.d.ts +3 -0
  92. package/dist/elements/search.js +207 -0
  93. package/dist/elements/select.d.ts +6 -0
  94. package/dist/elements/select.js +291 -0
  95. package/dist/elements/shared.d.ts +19 -0
  96. package/dist/elements/shared.js +396 -0
  97. package/dist/elements/skeleton.d.ts +6 -0
  98. package/dist/elements/skeleton.js +91 -0
  99. package/dist/elements/slider.d.ts +1 -0
  100. package/dist/elements/slider.js +248 -0
  101. package/dist/elements/spin-button.d.ts +7 -0
  102. package/dist/elements/spin-button.js +499 -0
  103. package/dist/elements/spinner.d.ts +4 -0
  104. package/dist/elements/spinner.js +124 -0
  105. package/dist/elements/swatch-picker.d.ts +5 -0
  106. package/dist/elements/swatch-picker.js +363 -0
  107. package/dist/elements/table.d.ts +21 -0
  108. package/dist/elements/table.js +261 -0
  109. package/dist/elements/tabs.d.ts +1 -0
  110. package/dist/elements/tabs.js +276 -0
  111. package/dist/elements/tag-picker.d.ts +29 -0
  112. package/dist/elements/tag-picker.js +697 -0
  113. package/dist/elements/tags.d.ts +21 -0
  114. package/dist/elements/tags.js +421 -0
  115. package/dist/elements/teaching-popover.d.ts +32 -0
  116. package/dist/elements/teaching-popover.js +412 -0
  117. package/dist/elements/text.d.ts +9 -0
  118. package/dist/elements/text.js +75 -0
  119. package/dist/elements/textarea.d.ts +1 -0
  120. package/dist/elements/textarea.js +182 -0
  121. package/dist/elements/toast.d.ts +25 -0
  122. package/dist/elements/toast.js +337 -0
  123. package/dist/elements/toaster.d.ts +6 -0
  124. package/dist/elements/toaster.js +356 -0
  125. package/dist/elements/toggle.d.ts +2 -0
  126. package/dist/elements/toggle.js +246 -0
  127. package/dist/elements/toolbar.d.ts +12 -0
  128. package/dist/elements/toolbar.js +381 -0
  129. package/dist/elements/tooltip.d.ts +76 -0
  130. package/dist/elements/tooltip.js +510 -0
  131. package/dist/elements/tree-model.d.ts +19 -0
  132. package/dist/elements/tree-model.js +34 -0
  133. package/dist/elements/tree.d.ts +5 -0
  134. package/dist/elements/tree.js +273 -0
  135. package/dist/elements/view-card.d.ts +2 -0
  136. package/dist/elements/view-card.js +78 -0
  137. package/dist/elements/virtual-list.d.ts +41 -0
  138. package/dist/elements/virtual-list.js +369 -0
  139. package/dist/elements.d.ts +72 -0
  140. package/dist/elements.js +207 -0
  141. package/dist/index.d.ts +76 -0
  142. package/dist/index.js +66 -0
  143. package/dist/react.d.ts +4546 -0
  144. package/dist/react.js +6837 -0
  145. package/dist/theme.d.ts +169 -0
  146. package/dist/theme.js +167 -0
  147. package/dist/virtualizer-hooks.d.ts +80 -0
  148. package/dist/virtualizer-hooks.js +253 -0
  149. package/package.json +87 -4
  150. package/scripts/generate-token-index.mjs +43 -0
  151. package/scripts/sync-component-catalog-signatures.mjs +55 -0
  152. package/scripts/validate-component-catalog.mjs +44 -0
  153. package/skills/sharepoint-ui/SKILL.md +12 -0
  154. package/skills/sharepoint-ui/references/choosing-components.md +20 -0
  155. package/skills/sharepoint-ui/references/sharepoint-patterns.md +17 -0
  156. package/tokens.json +3777 -0
@@ -0,0 +1,233 @@
1
+ import { base, stopHostAccessibleNameSync, syncHostAccessibleName } from "./shared.js";
2
+ const buttonCss = `${base}button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:var(--spui-control-height,32px);padding-inline:var(--spui-control-padding-inline,14px);border:1px solid var(--spui-control-border,var(--spui-ui-stroke,var(--spui-semantic-button-border,var(--colorNeutralStroke1,#8a8886))));border-radius:var(--spui-control-radius,4px);background:var(--spui-control-background,var(--spui-ui-surface,var(--spui-semantic-button-background,var(--colorNeutralBackground1,#fff))));box-shadow:var(--spui-control-shadow,none);color:var(--spui-ui-text,var(--spui-semantic-button-text,var(--colorNeutralForeground1,#323130)));font:inherit;line-height:1.25;text-align:center;text-decoration:none;cursor:pointer;transition:background-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),border-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),transform var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}button:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-button-secondary-hover,color-mix(in srgb,var(--spui-control-background,var(--spui-ui-surface,#fff)) 90%,var(--spui-ui-text,#242424) 10%))}button:active:not(:disabled):not([aria-disabled=true]){transform:scale(.98)}button.primary{border-color:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));background:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));color:var(--spui-ui-accent-text,var(--spui-semantic-primary-button-text,var(--colorNeutralForegroundOnBrand,#fff)))}button.primary:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-primary-hover,var(--spui-ui-accent-hover,var(--colorBrandBackgroundHover,#106ebe)))}button.primary:active:not(:disabled):not([aria-disabled=true]){background:var(--spui-ui-accent-pressed,var(--colorBrandBackgroundPressed,var(--spui-ui-accent-hover,#106ebe)))}button.outline{background:transparent}button.subtle,button.transparent{border-color:transparent;box-shadow:none}button.subtle{background:var(--spui-button-subtle-background,transparent)}button.transparent{background:transparent}button:disabled,button[aria-disabled=true]{opacity:.55;cursor:not-allowed}:host([size=small]) button{min-height:24px;padding-inline:8px;font-size:12px}:host([size=large]) button{min-height:40px;padding-inline:16px;font-size:16px}:host([shape=circular]) button{border-radius:999px}:host([shape=square]) button{border-radius:0}:host([data-spui-split-primary]) button{border-start-end-radius:0;border-end-end-radius:0}:host([data-spui-split-trigger]) button{border-start-start-radius:0;border-end-start-radius:0}:host([icon-only]) button{min-width:var(--spui-control-height,32px);padding-inline:0}:host([size=small][icon-only]) button{min-width:24px}:host([size=large][icon-only]) button{min-width:40px}:host([vertical]) button{flex-direction:column}.icon{display:inline-flex;align-items:center;justify-content:center;line-height:0}:host([vertical]) .icon{font-size:24px}.content{display:inline-flex;align-items:center;justify-content:center}:host([icon-position=after]) .icon{order:2}:host([icon-position=after]) .content{order:1}@media(prefers-reduced-motion:reduce){button{transition:none}}@media(forced-colors:active){button{border-color:ButtonText}button.primary{border-color:Highlight;background:Highlight;color:HighlightText}}`;
3
+ export const buttonControlCss = buttonCss.replace(/\bbutton(?=[.#:[\s,{])/g, ":is(button,a)");
4
+ const buttonSheets = new WeakMap();
5
+ const buttonSheetUnsupported = new WeakSet();
6
+ function buttonStyleNode(document) {
7
+ const style = document.createElement("style");
8
+ style.textContent = buttonControlCss;
9
+ return style;
10
+ }
11
+ function applyButtonStyle(shadow, button) {
12
+ const document = shadow.ownerDocument;
13
+ let sheet = buttonSheets.get(document);
14
+ if (!sheet && !buttonSheetUnsupported.has(document)) {
15
+ try {
16
+ if (typeof shadow.adoptedStyleSheets === "undefined" || !document.defaultView?.CSSStyleSheet)
17
+ throw new Error("Constructable stylesheets are unavailable");
18
+ sheet = new document.defaultView.CSSStyleSheet();
19
+ sheet.replaceSync(buttonControlCss);
20
+ buttonSheets.set(document, sheet);
21
+ }
22
+ catch {
23
+ buttonSheetUnsupported.add(document);
24
+ }
25
+ }
26
+ if (sheet) {
27
+ if (!shadow.adoptedStyleSheets.includes(sheet))
28
+ shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
29
+ shadow.replaceChildren(button);
30
+ }
31
+ else
32
+ shadow.replaceChildren(buttonStyleNode(document), button);
33
+ }
34
+ const unsafeHref = (href) => /^(?:javascript|data|vbscript):/i.test(href.trim());
35
+ export function defineButton() {
36
+ if (customElements.get("spui-button"))
37
+ return;
38
+ class SpuiButton extends HTMLElement {
39
+ static formAssociated = true;
40
+ static observedAttributes = ["as", "href", "target", "rel", "download", "hreflang", "referrerpolicy", "form", "formaction", "formenctype", "formnovalidate", "formtarget", "name", "value", "variant", "appearance", "size", "shape", "icon-only", "icon-position", "vertical", "disabled", "disabled-focusable", "type", "tabindex", "aria-label", "aria-labelledby", "aria-description", "aria-describedby", "aria-pressed", "aria-expanded", "aria-haspopup", "aria-controls", "aria-current", "aria-disabled", "title"];
41
+ internals = this.attachInternals();
42
+ formSubmitter;
43
+ formDisabled = false;
44
+ get form() { return this.internals.form; }
45
+ connectedCallback() { this.render(); }
46
+ formDisabledCallback(disabled) { this.formDisabled = disabled; this.render(); }
47
+ disconnectedCallback() { stopHostAccessibleNameSync(this); this.formSubmitter?.remove(); this.formSubmitter = undefined; }
48
+ attributeChangedCallback(name) {
49
+ if (!this.isConnected)
50
+ return;
51
+ if (name === "aria-label" || name === "aria-labelledby") {
52
+ const button = this.shadowRoot?.querySelector("button,a");
53
+ if (button)
54
+ syncHostAccessibleName(this, button);
55
+ return;
56
+ }
57
+ if (["aria-description", "aria-describedby", "aria-pressed", "aria-expanded", "aria-haspopup", "aria-controls", "title"].includes(name)) {
58
+ const button = this.shadowRoot?.querySelector("button,a");
59
+ const value = this.getAttribute(name);
60
+ if (button && (name === "aria-description" || name === "aria-describedby"))
61
+ syncHostAccessibleName(this, button);
62
+ else if (button)
63
+ value === null ? button.removeAttribute(name) : button.setAttribute(name, value);
64
+ return;
65
+ }
66
+ if (name === "tabindex") {
67
+ const button = this.shadowRoot?.querySelector("button,a");
68
+ const value = this.getAttribute(name);
69
+ if (button)
70
+ value === null ? button.removeAttribute("tabindex") : button.setAttribute("tabindex", value);
71
+ return;
72
+ }
73
+ this.render();
74
+ }
75
+ render() {
76
+ const shadow = this.shadowRoot ?? this.attachShadow({ mode: "open", delegatesFocus: true });
77
+ const previous = shadow.querySelector("button,a");
78
+ const restoreFocus = previous !== null && previous === shadow.activeElement;
79
+ const as = this.getAttribute("as") === "a" ? "a" : "button";
80
+ const button = document.createElement(as);
81
+ const type = this.getAttribute("type");
82
+ if (button instanceof HTMLButtonElement)
83
+ button.type = type === "submit" || type === "reset" ? type : "button";
84
+ const href = this.getAttribute("href")?.trim() ?? "";
85
+ const disabled = this.formDisabled || this.hasAttribute("disabled") || this.getAttribute("aria-disabled") === "true";
86
+ const disabledFocusable = this.hasAttribute("disabled-focusable");
87
+ const inactive = disabled || disabledFocusable;
88
+ const hasHref = as === "a" && !!href && !unsafeHref(href);
89
+ if (button instanceof HTMLAnchorElement) {
90
+ if (!inactive && hasHref)
91
+ button.href = href;
92
+ for (const name of ["target", "rel", "download", "hreflang", "referrerpolicy"]) {
93
+ const value = this.getAttribute(name);
94
+ if (value !== null)
95
+ button.setAttribute(name, value);
96
+ }
97
+ if (!disabled && button.target.toLowerCase() === "_blank") {
98
+ const rel = new Set((button.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean));
99
+ rel.add("noopener");
100
+ rel.add("noreferrer");
101
+ button.rel = [...rel].join(" ");
102
+ }
103
+ if (inactive)
104
+ button.setAttribute("aria-disabled", "true");
105
+ if (disabledFocusable)
106
+ button.tabIndex = this.hasAttribute("tabindex") ? Number(this.getAttribute("tabindex")) : 0;
107
+ else if (!hasHref && !disabled)
108
+ button.tabIndex = this.hasAttribute("tabindex") ? Number(this.getAttribute("tabindex")) : 0;
109
+ if (hasHref)
110
+ button.setAttribute("role", "link");
111
+ else
112
+ button.setAttribute("role", "button");
113
+ button.addEventListener("click", event => { if (disabled || !hasHref) {
114
+ event.preventDefault();
115
+ if (disabled)
116
+ event.stopPropagation();
117
+ } });
118
+ button.addEventListener("keydown", event => {
119
+ if (disabled) {
120
+ if (event.key === "Enter" || event.key === " ") {
121
+ event.preventDefault();
122
+ event.stopPropagation();
123
+ }
124
+ }
125
+ else if (event.key === "Enter") {
126
+ event.preventDefault();
127
+ button.click();
128
+ }
129
+ else if (event.key === " ")
130
+ event.preventDefault();
131
+ });
132
+ button.addEventListener("keyup", event => {
133
+ if (disabled) {
134
+ if (event.key === "Enter" || event.key === " ") {
135
+ event.preventDefault();
136
+ event.stopPropagation();
137
+ }
138
+ }
139
+ else if (event.key === " " && !hasHref) {
140
+ event.preventDefault();
141
+ button.click();
142
+ }
143
+ });
144
+ }
145
+ else {
146
+ button.disabled = disabled && !disabledFocusable;
147
+ if (disabled && disabledFocusable) {
148
+ button.setAttribute("aria-disabled", "true");
149
+ button.addEventListener("click", event => { event.preventDefault(); event.stopImmediatePropagation(); event.stopPropagation(); });
150
+ }
151
+ for (const name of ["formaction", "formenctype", "formmethod", "formnovalidate", "formtarget"]) {
152
+ const value = this.getAttribute(name);
153
+ if (value === null)
154
+ button.removeAttribute(name);
155
+ else
156
+ button.setAttribute(name, value);
157
+ }
158
+ }
159
+ this.syncFormSubmitter(as, type, disabled);
160
+ if (button instanceof HTMLButtonElement && (type === "submit" || type === "reset"))
161
+ button.addEventListener("click", event => {
162
+ this.syncFormSubmitter(as, type, disabled);
163
+ document.addEventListener("click", () => { if (!event.defaultPrevented && !disabled && this.formSubmitter?.isConnected)
164
+ this.formSubmitter.click(); }, { once: true });
165
+ });
166
+ if (this.hasAttribute("tabindex"))
167
+ button.setAttribute("tabindex", this.getAttribute("tabindex"));
168
+ for (const name of ["aria-description", "aria-describedby", "aria-pressed", "aria-expanded", "aria-haspopup", "aria-controls", "aria-current", "title"]) {
169
+ const value = this.getAttribute(name);
170
+ if (value !== null)
171
+ button.setAttribute(name, value);
172
+ }
173
+ const appearance = this.getAttribute("appearance") ?? (this.getAttribute("variant") === "primary" ? "primary" : "secondary");
174
+ button.className = appearance === "primary" ? "primary" : appearance === "outline" || appearance === "subtle" || appearance === "transparent" ? appearance : "secondary";
175
+ button.part.add("control");
176
+ const icon = document.createElement("span");
177
+ icon.className = "icon";
178
+ icon.setAttribute("aria-hidden", "true");
179
+ const iconSlot = document.createElement("slot");
180
+ iconSlot.name = "icon";
181
+ icon.append(iconSlot);
182
+ iconSlot.addEventListener("slotchange", () => { icon.hidden = !iconSlot.assignedNodes({ flatten: true }).length; });
183
+ icon.hidden = !iconSlot.assignedNodes({ flatten: true }).length;
184
+ const content = document.createElement("span");
185
+ content.className = "content";
186
+ content.append(document.createElement("slot"));
187
+ button.append(icon, content);
188
+ applyButtonStyle(shadow, button);
189
+ syncHostAccessibleName(this, button);
190
+ if (restoreFocus)
191
+ button.focus();
192
+ }
193
+ syncFormSubmitter(as, type, disabled) {
194
+ if (type !== "submit" && type !== "reset") {
195
+ this.formSubmitter?.remove();
196
+ this.formSubmitter = undefined;
197
+ return;
198
+ }
199
+ const form = as === "button" ? this.internals.form ?? this.closest("form") : null;
200
+ if (!form) {
201
+ this.formSubmitter?.remove();
202
+ this.formSubmitter = undefined;
203
+ return;
204
+ }
205
+ const submitter = this.formSubmitter ?? document.createElement("button");
206
+ submitter.hidden = true;
207
+ submitter.tabIndex = -1;
208
+ submitter.setAttribute("aria-hidden", "true");
209
+ submitter.type = type;
210
+ submitter.disabled = disabled;
211
+ submitter.name = this.getAttribute("name") ?? "";
212
+ submitter.value = this.getAttribute("value") ?? "";
213
+ for (const name of ["formaction", "formenctype", "formmethod", "formnovalidate", "formtarget"]) {
214
+ const value = this.getAttribute(name);
215
+ if (value === null)
216
+ submitter.removeAttribute(name);
217
+ else
218
+ submitter.setAttribute(name, value);
219
+ }
220
+ const formId = this.getAttribute("form");
221
+ if (formId)
222
+ submitter.setAttribute("form", formId);
223
+ else
224
+ submitter.removeAttribute("form");
225
+ if (!this.formSubmitter)
226
+ submitter.addEventListener("click", event => event.stopPropagation());
227
+ if (!this.formSubmitter || submitter.form !== form)
228
+ form.append(submitter);
229
+ this.formSubmitter = submitter;
230
+ }
231
+ }
232
+ customElements.define("spui-button", SpuiButton);
233
+ }
@@ -0,0 +1,3 @@
1
+ export declare function defineCardHeader(): void;
2
+ export declare function defineCardFooter(): void;
3
+ export declare function defineCardPreview(): void;
@@ -0,0 +1,83 @@
1
+ import { base } from "./shared.js";
2
+ export function defineCardHeader() {
3
+ if (customElements.get("spui-card-header"))
4
+ return;
5
+ class SpuiCardHeader extends HTMLElement {
6
+ connectedCallback() {
7
+ if (this.shadowRoot)
8
+ return;
9
+ const shadow = this.attachShadow({ mode: "open" });
10
+ const style = document.createElement("style");
11
+ style.textContent = `${base}:host{display:block;min-width:0}.layout{display:grid;grid-template-columns:max-content minmax(0,1fr) max-content;align-items:center;column-gap:var(--spui-control-gap,12px);row-gap:3px}.main{display:grid;grid-template-columns:minmax(0,1fr);row-gap:3px;min-width:0}slot[name=image]{grid-column:1;grid-row:1 / span 2}slot[name=action]{grid-column:3;grid-row:1 / span 2;justify-self:end}slot[name=header]{font-size:var(--spui-card-header-size,14px);font-weight:600;line-height:1.35}slot[name=description]{color:var(--spui-ui-muted,var(--spui-semantic-body-text,#616161));font-size:12px;line-height:1.4}::slotted([slot=image]){display:inline-flex;max-width:48px;max-height:48px;align-items:center;justify-content:center}::slotted([slot=action]){display:inline-flex;align-items:center;justify-content:flex-end}@media(forced-colors:active){slot[name=description]{color:CanvasText}}`;
12
+ const layout = document.createElement("div");
13
+ layout.className = "layout";
14
+ layout.part.add("root");
15
+ const image = document.createElement("slot");
16
+ image.name = "image";
17
+ image.part.add("image");
18
+ const main = document.createElement("div");
19
+ main.className = "main";
20
+ main.part.add("content");
21
+ const header = document.createElement("slot");
22
+ header.name = "header";
23
+ header.part.add("header");
24
+ const defaultHeader = document.createElement("slot");
25
+ const description = document.createElement("slot");
26
+ description.name = "description";
27
+ description.part.add("description");
28
+ main.append(header);
29
+ header.append(defaultHeader);
30
+ main.append(description);
31
+ const action = document.createElement("slot");
32
+ action.name = "action";
33
+ action.part.add("action");
34
+ layout.append(image, main, action);
35
+ shadow.append(style, layout);
36
+ }
37
+ }
38
+ customElements.define("spui-card-header", SpuiCardHeader);
39
+ }
40
+ export function defineCardFooter() {
41
+ if (customElements.get("spui-card-footer"))
42
+ return;
43
+ class SpuiCardFooter extends HTMLElement {
44
+ connectedCallback() {
45
+ if (this.shadowRoot)
46
+ return;
47
+ const shadow = this.attachShadow({ mode: "open" });
48
+ const style = document.createElement("style");
49
+ style.textContent = `${base}:host{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spui-control-gap,12px);min-width:0}slot[name=action]{margin-inline-start:auto}::slotted([slot=action]){display:inline-flex;align-items:center;justify-content:flex-end}@media(forced-colors:active){:host{color:CanvasText}}`;
50
+ const content = document.createElement("slot");
51
+ content.part.add("content");
52
+ const action = document.createElement("slot");
53
+ action.name = "action";
54
+ action.part.add("action");
55
+ shadow.append(style, content, action);
56
+ }
57
+ }
58
+ customElements.define("spui-card-footer", SpuiCardFooter);
59
+ }
60
+ export function defineCardPreview() {
61
+ if (customElements.get("spui-card-preview"))
62
+ return;
63
+ class SpuiCardPreview extends HTMLElement {
64
+ connectedCallback() {
65
+ if (this.shadowRoot)
66
+ return;
67
+ const shadow = this.attachShadow({ mode: "open" });
68
+ const style = document.createElement("style");
69
+ style.textContent = `${base}:host{position:relative;display:block;min-width:0;overflow:hidden;margin-inline:calc(-1 * var(--spui-card-padding,16px));border-radius:inherit}:host(:first-child){margin-block-start:calc(-1 * var(--spui-card-padding,16px))}.preview{position:relative;min-width:0;overflow:hidden}slot:not([name=logo]){display:block}::slotted(img),::slotted(video),::slotted(picture){display:block;width:100%;max-width:100%;height:auto;max-height:360px;object-fit:cover}slot[name=logo]{position:absolute;inset-block-end:10px;inset-inline-start:10px;display:flex;align-items:center;justify-content:center;max-width:calc(100% - 20px);padding:4px;border:1px solid var(--spui-ui-stroke,#d1d1d1);border-radius:var(--spui-control-radius,6px);background:var(--spui-ui-surface,#fff);box-shadow:var(--spui-card-shadow,0 1px 3px #0002)}::slotted([slot=logo]){max-width:100%;max-height:40px}@media(forced-colors:active){:host{border:1px solid CanvasText}slot[name=logo]{border-color:CanvasText;background:Canvas;box-shadow:none}}`;
70
+ const preview = document.createElement("div");
71
+ preview.className = "preview";
72
+ preview.part.add("root");
73
+ const content = document.createElement("slot");
74
+ content.part.add("content");
75
+ const logo = document.createElement("slot");
76
+ logo.name = "logo";
77
+ logo.part.add("logo");
78
+ preview.append(content, logo);
79
+ shadow.append(style, preview);
80
+ }
81
+ }
82
+ customElements.define("spui-card-preview", SpuiCardPreview);
83
+ }
@@ -0,0 +1,5 @@
1
+ export type CardAppearance = "filled" | "filled-alternative" | "outline" | "subtle";
2
+ export type CardOrientation = "horizontal" | "vertical";
3
+ export type CardSize = "small" | "medium" | "large";
4
+ export type CardFocusMode = "off" | "no-tab" | "tab-exit" | "tab-only";
5
+ export declare function defineCard(): void;
@@ -0,0 +1,213 @@
1
+ import { base } from "./shared.js";
2
+ const focusableSelector = 'a[href],area[href],button,input:not([type="hidden"]),select,textarea,iframe,object,embed,audio[controls],video[controls],summary,[contenteditable]:not([contenteditable="false"]),[tabindex]';
3
+ function findCardFocusables(card) {
4
+ const found = [];
5
+ const visit = (element) => {
6
+ if (element instanceof HTMLElement && element.matches(focusableSelector) && element.tabIndex >= 0 && !element.matches(":disabled,[hidden],[aria-hidden=true]") && element.getClientRects().length > 0)
7
+ found.push(element);
8
+ if (element instanceof HTMLSlotElement) {
9
+ const assigned = element.assignedElements({ flatten: true });
10
+ for (const child of assigned.length ? assigned : [...element.children])
11
+ visit(child);
12
+ return;
13
+ }
14
+ const children = element.shadowRoot ? [...element.shadowRoot.children] : [...element.children];
15
+ for (const child of children)
16
+ visit(child);
17
+ };
18
+ for (const child of card.children)
19
+ visit(child);
20
+ return found;
21
+ }
22
+ function deepestActiveElement(document) {
23
+ let active = document.activeElement;
24
+ while (active?.shadowRoot?.activeElement)
25
+ active = active.shadowRoot.activeElement;
26
+ return active;
27
+ }
28
+ export function defineCard() {
29
+ if (customElements.get("spui-card"))
30
+ return;
31
+ class SpuiCard extends HTMLElement {
32
+ static observedAttributes = ["aria-label", "selectable", "selected", "default-selected", "disabled", "selection-label", "focus-mode"];
33
+ defaultSelectedValue;
34
+ focusGroupActive = false;
35
+ managedTabIndex = false;
36
+ managedRole = false;
37
+ selectionInput() {
38
+ const slot = this.shadowRoot?.querySelector("slot[name=checkbox]");
39
+ return slot?.assignedElements({ flatten: true }).find((element) => element instanceof HTMLInputElement)
40
+ ?? this.shadowRoot?.querySelector(".selection") ?? undefined;
41
+ }
42
+ get selected() { return this.hasAttribute("selected"); }
43
+ set selected(value) { this.toggleAttribute("selected", Boolean(value)); }
44
+ get defaultSelected() { return this.defaultSelectedValue ?? this.hasAttribute("default-selected"); }
45
+ set defaultSelected(value) { this.defaultSelectedValue = Boolean(value); this.setAttribute("selectable", ""); if (!this.hasAttribute("selected"))
46
+ this.toggleAttribute("selected", this.defaultSelectedValue); }
47
+ get focusMode() { const value = this.getAttribute("focus-mode"); return value === "no-tab" || value === "tab-exit" || value === "tab-only" ? value : "off"; }
48
+ set focusMode(value) { if (value === "off")
49
+ this.removeAttribute("focus-mode");
50
+ else
51
+ this.setAttribute("focus-mode", value); }
52
+ connectedCallback() {
53
+ if (this.hasAttribute("default-selected")) {
54
+ this.defaultSelectedValue = this.hasAttribute("default-selected");
55
+ this.setAttribute("selectable", "");
56
+ if (this.hasAttribute("default-selected") && !this.hasAttribute("selected"))
57
+ this.toggleAttribute("selected", this.defaultSelectedValue ?? true);
58
+ this.removeAttribute("default-selected");
59
+ }
60
+ if (this.hasAttribute("aria-label"))
61
+ this.setAttribute("role", "region");
62
+ if (!this.shadowRoot) {
63
+ const shadow = this.attachShadow({ mode: "open" });
64
+ const style = document.createElement("style");
65
+ style.textContent = `${base}:host{display:block;position:relative;min-width:0;border:1px solid var(--spui-card-border,var(--spui-ui-stroke,var(--spui-semantic-card-border,var(--colorNeutralStroke2,#edebe9))));border-radius:var(--spui-card-radius,8px);background:var(--spui-card-background,var(--spui-ui-surface,var(--spui-semantic-card-background,var(--colorNeutralBackground1,#fff))));box-shadow:var(--spui-card-shadow,0 1px 3px #0001)}.content{min-width:0;padding:var(--spui-card-padding,16px)}:host([selectable]){cursor:pointer}:host([selectable]) .content{padding-inline-start:44px}:host([selected]){border-color:var(--spui-selection-border,var(--spui-semantic-focus-border,var(--colorCompoundBrandStroke,#0f6cbd)));background:var(--spui-selection-background,color-mix(in srgb,var(--spui-base-accent,var(--spui-ui-accent,#0f6cbd)) 7%,var(--spui-card-background,var(--spui-ui-surface,#fff)) 93%))}.selection{position:absolute;inset-block-start:12px;inset-inline-start:12px;width:18px;height:18px;margin:0;accent-color:var(--spui-base-accent,var(--spui-ui-accent,#0f6cbd));cursor:pointer}:host(:focus-within){outline:2px solid var(--spui-focus-ring,var(--spui-semantic-focus-border,var(--colorStrokeFocus2,#005a9e)));outline-offset:2px}:host([disabled]),:host([disabled]) .selection{cursor:default}:host([appearance=filled-alternative]){background:color-mix(in srgb,var(--spui-base-surface,var(--spui-ui-surface,#fff)) 90%,var(--spui-base-text,var(--spui-ui-text,#242424)) 10%)}:host([appearance=outline]){background:transparent;box-shadow:none}:host([appearance=subtle]){border-color:transparent;background:var(--spui-ui-canvas,var(--spui-semantic-body-background,#f5f5f5));box-shadow:none}:host([size=small]){--spui-card-padding:12px;--spui-control-gap:8px}:host([size=large]){--spui-card-padding:20px;--spui-control-gap:12px}:host([orientation=horizontal]) .content>slot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spui-control-gap,8px)}:host([data-spui-appearance=glass]),:host-context([data-spui-appearance=glass]){backdrop-filter:var(--spui-card-backdrop,none)}:host([selected]){border-color:var(--spui-selection-border,var(--spui-semantic-focus-border,var(--colorCompoundBrandStroke,#0f6cbd)));background:var(--spui-selection-background,color-mix(in srgb,var(--spui-base-accent,var(--spui-ui-accent,#0f6cbd)) 7%,var(--spui-card-background,var(--spui-ui-surface,#fff)) 93%))}@media(forced-colors:active){:host{border-color:CanvasText;box-shadow:none}:host([appearance=subtle]),:host([appearance=outline]){background:Canvas}:host([selected]){outline:2px solid Highlight}.selection{accent-color:Highlight}}`;
66
+ style.textContent += ":host([has-floating-action]) .content{padding-block-start:48px}.floating-action{position:absolute;z-index:1;inset-block-start:8px;inset-inline-end:8px;display:flex;align-items:center;justify-content:center}.floating-action:empty{display:none}";
67
+ const root = document.createElement("div");
68
+ root.className = "root";
69
+ root.part.add("root");
70
+ root.style.position = "relative";
71
+ root.style.minWidth = "0";
72
+ const content = document.createElement("div");
73
+ content.className = "content";
74
+ content.part.add("content");
75
+ content.append(document.createElement("slot"));
76
+ const floatingAction = document.createElement("div");
77
+ floatingAction.className = "floating-action";
78
+ const floatingSlot = document.createElement("slot");
79
+ floatingSlot.name = "floating-action";
80
+ floatingSlot.addEventListener("slotchange", () => this.toggleAttribute("has-floating-action", floatingSlot.assignedNodes({ flatten: true }).length > 0));
81
+ floatingAction.append(floatingSlot);
82
+ const checkbox = document.createElement("input");
83
+ checkbox.className = "selection";
84
+ checkbox.type = "checkbox";
85
+ checkbox.part.add("selection");
86
+ checkbox.addEventListener("change", event => this.setSelected(checkbox.checked, event));
87
+ const checkboxSlot = document.createElement("slot");
88
+ checkboxSlot.name = "checkbox";
89
+ checkboxSlot.append(checkbox);
90
+ checkboxSlot.addEventListener("slotchange", () => this.syncSelection());
91
+ root.append(checkboxSlot, floatingAction, content);
92
+ shadow.append(style, root);
93
+ this.addEventListener("change", event => {
94
+ const input = event.target;
95
+ if (input instanceof HTMLInputElement && input.slot === "checkbox")
96
+ this.setSelected(input.checked, event);
97
+ });
98
+ this.addEventListener("click", event => {
99
+ if (!this.isSelectable() || this.hasAttribute("disabled") || event.composedPath().some(target => target instanceof Element && target !== this && target.matches("a,button,input,select,textarea,[contenteditable=true],[role=button],[role=link],[role=checkbox]")))
100
+ return;
101
+ this.setSelected(!this.selected, event);
102
+ });
103
+ this.addEventListener("keydown", event => this.handleCardKeydown(event));
104
+ this.addEventListener("focusout", () => setTimeout(() => {
105
+ const active = deepestActiveElement(this.ownerDocument);
106
+ if (active !== this && !this.contains(active))
107
+ this.focusGroupActive = false;
108
+ }, 0));
109
+ }
110
+ this.syncSelection();
111
+ this.syncFocusMode();
112
+ }
113
+ attributeChangedCallback(name) {
114
+ if (name === "aria-label" && this.hasAttribute("aria-label") && !this.hasAttribute("role"))
115
+ this.setAttribute("role", "region");
116
+ if (this.shadowRoot) {
117
+ this.syncSelection();
118
+ this.syncFocusMode();
119
+ }
120
+ }
121
+ isSelectable() { return this.hasAttribute("selectable") || this.hasAttribute("selected") || Boolean(this.defaultSelectedValue); }
122
+ syncSelection() {
123
+ const checkbox = this.selectionInput();
124
+ if (!checkbox)
125
+ return;
126
+ const selectable = this.isSelectable();
127
+ if (!checkbox.hasAttribute("type"))
128
+ checkbox.type = "checkbox";
129
+ checkbox.hidden = !selectable;
130
+ checkbox.disabled = this.hasAttribute("disabled");
131
+ checkbox.checked = this.selected;
132
+ checkbox.setAttribute("aria-label", this.getAttribute("selection-label") || `Select ${this.getAttribute("aria-label") || "card"}`);
133
+ }
134
+ syncFocusMode() {
135
+ const enabled = this.focusMode !== "off" && !this.isSelectable() && !this.hasAttribute("disabled");
136
+ if (enabled && !this.hasAttribute("tabindex")) {
137
+ this.tabIndex = 0;
138
+ this.managedTabIndex = true;
139
+ }
140
+ else if (!enabled && this.managedTabIndex) {
141
+ this.removeAttribute("tabindex");
142
+ this.managedTabIndex = false;
143
+ }
144
+ if (enabled && !this.hasAttribute("role")) {
145
+ this.setAttribute("role", "group");
146
+ this.managedRole = true;
147
+ }
148
+ else if (!enabled && this.managedRole) {
149
+ this.removeAttribute("role");
150
+ this.managedRole = false;
151
+ }
152
+ if (!enabled)
153
+ this.focusGroupActive = false;
154
+ }
155
+ handleFocusKeydown(event) {
156
+ const mode = this.focusMode;
157
+ if (mode === "off" || this.hasAttribute("disabled") || this.isSelectable())
158
+ return;
159
+ const focusables = findCardFocusables(this);
160
+ const focused = event.composedPath().find(target => target instanceof HTMLElement && focusables.includes(target)) ?? deepestActiveElement(this.ownerDocument);
161
+ if ((mode === "no-tab" || mode === "tab-exit") && event.key === "Enter" && focused === this && focusables.length) {
162
+ event.preventDefault();
163
+ this.focusGroupActive = true;
164
+ focusables[0].focus();
165
+ return;
166
+ }
167
+ if (!this.focusGroupActive)
168
+ return;
169
+ if (event.key === "Escape") {
170
+ event.preventDefault();
171
+ this.focusGroupActive = false;
172
+ this.focus();
173
+ return;
174
+ }
175
+ if (event.key !== "Tab" || !focusables.length)
176
+ return;
177
+ const index = focusables.indexOf(focused);
178
+ if (mode === "no-tab") {
179
+ if (event.shiftKey && index <= 0) {
180
+ event.preventDefault();
181
+ focusables.at(-1)?.focus();
182
+ }
183
+ else if (!event.shiftKey && index === focusables.length - 1) {
184
+ event.preventDefault();
185
+ focusables[0].focus();
186
+ }
187
+ }
188
+ else if (mode === "tab-exit" && ((event.shiftKey && index === 0) || (!event.shiftKey && index === focusables.length - 1))) {
189
+ this.focusGroupActive = false;
190
+ }
191
+ }
192
+ handleCardKeydown(event) {
193
+ if (event.key === "Enter" && !this.hasAttribute("disabled") && this.isSelectable()) {
194
+ const target = event.composedPath()[0];
195
+ if (target === this || target === this.selectionInput()) {
196
+ event.preventDefault();
197
+ this.setSelected(!this.selected, event);
198
+ return;
199
+ }
200
+ }
201
+ this.handleFocusKeydown(event);
202
+ }
203
+ setSelected(selected, sourceEvent) {
204
+ if (this.hasAttribute("disabled") || !this.isSelectable() || this.selected === selected) {
205
+ this.syncSelection();
206
+ return;
207
+ }
208
+ this.toggleAttribute("selected", selected);
209
+ this.dispatchEvent(new CustomEvent("spui-selection-change", { bubbles: true, composed: true, detail: { selected, event: sourceEvent } }));
210
+ }
211
+ }
212
+ customElements.define("spui-card", SpuiCard);
213
+ }
@@ -0,0 +1,92 @@
1
+ export type CarouselAlign = "start" | "center" | "end";
2
+ export type CarouselAppearance = "flat" | "elevated";
3
+ export type CarouselMotion = "slide" | "fade" | {
4
+ kind: "slide";
5
+ duration?: number;
6
+ };
7
+ export type CarouselAnnouncerFunction = (index: number, totalSlides: number, slideGroupList: number[][]) => string;
8
+ export type CarouselIndexChangeData = {
9
+ event: MouseEvent;
10
+ type: "click";
11
+ index: number;
12
+ } | {
13
+ event: FocusEvent;
14
+ type: "focus";
15
+ index: number;
16
+ } | {
17
+ event: PointerEvent | MouseEvent;
18
+ type: "drag";
19
+ index: number;
20
+ } | {
21
+ event: Event;
22
+ type: "autoplay" | "scroll" | "programmatic";
23
+ index: number;
24
+ } | {
25
+ event: KeyboardEvent;
26
+ type: "keydown";
27
+ index: number;
28
+ };
29
+ export type CarouselIndexChangeSource = CarouselIndexChangeData extends infer T ? T extends {
30
+ index: number;
31
+ } ? Omit<T, "index"> : never : never;
32
+ export interface SharePointCarouselElement extends HTMLElement {
33
+ align: CarouselAlign;
34
+ appearance: CarouselAppearance;
35
+ motion: CarouselMotion;
36
+ motionDuration: number;
37
+ slideWidth: string;
38
+ groupSize: number | "auto";
39
+ draggable: boolean;
40
+ whitespace: boolean;
41
+ selectedIndex: number;
42
+ defaultSelectedIndex: number;
43
+ activeIndex: number;
44
+ defaultActiveIndex: number;
45
+ circular: boolean;
46
+ goTo(index: number): void;
47
+ nextSlide(): void;
48
+ previousSlide(): void;
49
+ announcement?: CarouselAnnouncerFunction;
50
+ }
51
+ export interface SharePointCarouselNavButtonElement extends HTMLElement {
52
+ index: number;
53
+ selected: boolean;
54
+ disabled: boolean;
55
+ }
56
+ export interface SharePointCarouselCardElement extends HTMLElement {
57
+ autoSize: boolean;
58
+ }
59
+ export interface SharePointCarouselButtonElement extends HTMLElement {
60
+ navType: "prev" | "next";
61
+ disabled: boolean;
62
+ }
63
+ export interface SharePointCarouselAutoplayButtonElement extends HTMLElement {
64
+ selected: boolean;
65
+ }
66
+ export interface SharePointCarouselSliderElement extends HTMLElement {
67
+ cardFocus: boolean;
68
+ }
69
+ declare global {
70
+ interface HTMLElementTagNameMap {
71
+ "spui-carousel": SharePointCarouselElement;
72
+ "spui-carousel-card": SharePointCarouselCardElement;
73
+ "spui-carousel-nav": HTMLElement;
74
+ "spui-carousel-nav-button": SharePointCarouselNavButtonElement;
75
+ "spui-carousel-nav-image-button": SharePointCarouselNavButtonElement;
76
+ "spui-carousel-button": SharePointCarouselButtonElement;
77
+ "spui-carousel-autoplay-button": SharePointCarouselAutoplayButtonElement;
78
+ "spui-carousel-slider": SharePointCarouselSliderElement;
79
+ "spui-carousel-viewport": HTMLElement;
80
+ "spui-carousel-nav-container": HTMLElement;
81
+ }
82
+ }
83
+ export declare function defineCarouselCard(): void;
84
+ export declare function defineCarouselNav(): void;
85
+ export declare function defineCarouselNavButton(): void;
86
+ export declare function defineCarouselNavImageButton(): void;
87
+ export declare function defineCarouselButton(): void;
88
+ export declare function defineCarouselAutoplayButton(): void;
89
+ export declare function defineCarouselNavContainer(): void;
90
+ export declare function defineCarouselSlider(): void;
91
+ export declare function defineCarouselViewport(): void;
92
+ export declare function defineCarousel(): void;