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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/AGENTS.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +897 -2
  4. package/components.json +5810 -0
  5. package/dist/appearance.d.ts +6 -0
  6. package/dist/appearance.js +217 -0
  7. package/dist/elements/accordion.d.ts +9 -0
  8. package/dist/elements/accordion.js +338 -0
  9. package/dist/elements/alert.d.ts +2 -0
  10. package/dist/elements/alert.js +62 -0
  11. package/dist/elements/avatar-group-primitives.d.ts +3 -0
  12. package/dist/elements/avatar-group-primitives.js +189 -0
  13. package/dist/elements/avatar-group.d.ts +2 -0
  14. package/dist/elements/avatar-group.js +110 -0
  15. package/dist/elements/avatar.d.ts +4 -0
  16. package/dist/elements/avatar.js +251 -0
  17. package/dist/elements/badge.d.ts +8 -0
  18. package/dist/elements/badge.js +182 -0
  19. package/dist/elements/breadcrumb.d.ts +4 -0
  20. package/dist/elements/breadcrumb.js +233 -0
  21. package/dist/elements/button-group.d.ts +1 -0
  22. package/dist/elements/button-group.js +20 -0
  23. package/dist/elements/button-variants.d.ts +6 -0
  24. package/dist/elements/button-variants.js +339 -0
  25. package/dist/elements/button.d.ts +5 -0
  26. package/dist/elements/button.js +233 -0
  27. package/dist/elements/card-primitives.d.ts +3 -0
  28. package/dist/elements/card-primitives.js +83 -0
  29. package/dist/elements/card.d.ts +5 -0
  30. package/dist/elements/card.js +213 -0
  31. package/dist/elements/carousel.d.ts +92 -0
  32. package/dist/elements/carousel.js +1112 -0
  33. package/dist/elements/color-picker.d.ts +24 -0
  34. package/dist/elements/color-picker.js +1004 -0
  35. package/dist/elements/combobox.d.ts +1 -0
  36. package/dist/elements/combobox.js +1012 -0
  37. package/dist/elements/data-grid-model.d.ts +117 -0
  38. package/dist/elements/data-grid-model.js +294 -0
  39. package/dist/elements/data-grid.d.ts +29 -0
  40. package/dist/elements/data-grid.js +1567 -0
  41. package/dist/elements/date-picker.d.ts +1 -0
  42. package/dist/elements/date-picker.js +486 -0
  43. package/dist/elements/dialog.d.ts +23 -0
  44. package/dist/elements/dialog.js +409 -0
  45. package/dist/elements/divider.d.ts +2 -0
  46. package/dist/elements/divider.js +66 -0
  47. package/dist/elements/drawer.d.ts +3 -0
  48. package/dist/elements/drawer.js +308 -0
  49. package/dist/elements/field-composition.d.ts +26 -0
  50. package/dist/elements/field-composition.js +133 -0
  51. package/dist/elements/field.d.ts +1 -0
  52. package/dist/elements/field.js +67 -0
  53. package/dist/elements/file-upload.d.ts +6 -0
  54. package/dist/elements/file-upload.js +211 -0
  55. package/dist/elements/floating-position.d.ts +26 -0
  56. package/dist/elements/floating-position.js +39 -0
  57. package/dist/elements/image.d.ts +28 -0
  58. package/dist/elements/image.js +38 -0
  59. package/dist/elements/info.d.ts +9 -0
  60. package/dist/elements/info.js +102 -0
  61. package/dist/elements/input.d.ts +5 -0
  62. package/dist/elements/input.js +255 -0
  63. package/dist/elements/label.d.ts +15 -0
  64. package/dist/elements/label.js +56 -0
  65. package/dist/elements/link.d.ts +1 -0
  66. package/dist/elements/link.js +92 -0
  67. package/dist/elements/list.d.ts +19 -0
  68. package/dist/elements/list.js +297 -0
  69. package/dist/elements/menu.d.ts +33 -0
  70. package/dist/elements/menu.js +1103 -0
  71. package/dist/elements/message-bar.d.ts +10 -0
  72. package/dist/elements/message-bar.js +299 -0
  73. package/dist/elements/nav.d.ts +32 -0
  74. package/dist/elements/nav.js +791 -0
  75. package/dist/elements/overflow.d.ts +9 -0
  76. package/dist/elements/overflow.js +218 -0
  77. package/dist/elements/persona.d.ts +3 -0
  78. package/dist/elements/persona.js +124 -0
  79. package/dist/elements/popover.d.ts +77 -0
  80. package/dist/elements/popover.js +840 -0
  81. package/dist/elements/progress-bar.d.ts +4 -0
  82. package/dist/elements/progress-bar.js +82 -0
  83. package/dist/elements/radio-group.d.ts +1 -0
  84. package/dist/elements/radio-group.js +303 -0
  85. package/dist/elements/rating-display.d.ts +2 -0
  86. package/dist/elements/rating-display.js +125 -0
  87. package/dist/elements/rating.d.ts +2 -0
  88. package/dist/elements/rating.js +206 -0
  89. package/dist/elements/rich-text-editor.d.ts +18 -0
  90. package/dist/elements/rich-text-editor.js +508 -0
  91. package/dist/elements/search.d.ts +3 -0
  92. package/dist/elements/search.js +207 -0
  93. package/dist/elements/select.d.ts +6 -0
  94. package/dist/elements/select.js +291 -0
  95. package/dist/elements/shared.d.ts +19 -0
  96. package/dist/elements/shared.js +396 -0
  97. package/dist/elements/skeleton.d.ts +6 -0
  98. package/dist/elements/skeleton.js +91 -0
  99. package/dist/elements/slider.d.ts +1 -0
  100. package/dist/elements/slider.js +248 -0
  101. package/dist/elements/spin-button.d.ts +7 -0
  102. package/dist/elements/spin-button.js +499 -0
  103. package/dist/elements/spinner.d.ts +4 -0
  104. package/dist/elements/spinner.js +124 -0
  105. package/dist/elements/swatch-picker.d.ts +5 -0
  106. package/dist/elements/swatch-picker.js +363 -0
  107. package/dist/elements/table.d.ts +21 -0
  108. package/dist/elements/table.js +261 -0
  109. package/dist/elements/tabs.d.ts +1 -0
  110. package/dist/elements/tabs.js +276 -0
  111. package/dist/elements/tag-picker.d.ts +29 -0
  112. package/dist/elements/tag-picker.js +697 -0
  113. package/dist/elements/tags.d.ts +21 -0
  114. package/dist/elements/tags.js +421 -0
  115. package/dist/elements/teaching-popover.d.ts +32 -0
  116. package/dist/elements/teaching-popover.js +412 -0
  117. package/dist/elements/text.d.ts +5 -0
  118. package/dist/elements/text.js +69 -0
  119. package/dist/elements/textarea.d.ts +1 -0
  120. package/dist/elements/textarea.js +182 -0
  121. package/dist/elements/toast.d.ts +25 -0
  122. package/dist/elements/toast.js +337 -0
  123. package/dist/elements/toaster.d.ts +6 -0
  124. package/dist/elements/toaster.js +356 -0
  125. package/dist/elements/toggle.d.ts +2 -0
  126. package/dist/elements/toggle.js +246 -0
  127. package/dist/elements/toolbar.d.ts +12 -0
  128. package/dist/elements/toolbar.js +381 -0
  129. package/dist/elements/tooltip.d.ts +76 -0
  130. package/dist/elements/tooltip.js +510 -0
  131. package/dist/elements/tree-model.d.ts +19 -0
  132. package/dist/elements/tree-model.js +34 -0
  133. package/dist/elements/tree.d.ts +5 -0
  134. package/dist/elements/tree.js +273 -0
  135. package/dist/elements/view-card.d.ts +2 -0
  136. package/dist/elements/view-card.js +78 -0
  137. package/dist/elements/virtual-list.d.ts +41 -0
  138. package/dist/elements/virtual-list.js +369 -0
  139. package/dist/elements.d.ts +72 -0
  140. package/dist/elements.js +207 -0
  141. package/dist/index.d.ts +76 -0
  142. package/dist/index.js +66 -0
  143. package/dist/react-theme-styles.d.ts +2 -0
  144. package/dist/react-theme-styles.js +75 -0
  145. package/dist/react.d.ts +4540 -0
  146. package/dist/react.js +6799 -0
  147. package/dist/theme.d.ts +169 -0
  148. package/dist/theme.js +167 -0
  149. package/dist/virtualizer-hooks.d.ts +80 -0
  150. package/dist/virtualizer-hooks.js +253 -0
  151. package/package.json +87 -4
  152. package/scripts/generate-token-index.mjs +43 -0
  153. package/scripts/sync-component-catalog-signatures.mjs +55 -0
  154. package/scripts/validate-component-catalog.mjs +44 -0
  155. package/skills/sharepoint-ui/SKILL.md +12 -0
  156. package/skills/sharepoint-ui/references/choosing-components.md +20 -0
  157. package/skills/sharepoint-ui/references/sharepoint-patterns.md +17 -0
  158. package/tokens.json +3777 -0
@@ -0,0 +1,412 @@
1
+ import { buttonControlCss, defineButton } from "./button.js";
2
+ import { definePopoverVariant } from "./popover.js";
3
+ import { base } from "./shared.js";
4
+ const pageCountTag = "spui-teaching-popover-carousel-page-count";
5
+ const cardTag = "spui-teaching-popover-carousel-card";
6
+ const navButtonTag = "spui-teaching-popover-carousel-nav-button";
7
+ const footerButtonTag = "spui-teaching-popover-carousel-footer-button";
8
+ const carouselTag = "spui-teaching-popover-carousel";
9
+ const partTag = (name) => `spui-teaching-popover-${name}`;
10
+ function languageOf(element) {
11
+ return element.closest("[lang]")?.lang || document.documentElement.lang || "en";
12
+ }
13
+ function localize(template, current, total) {
14
+ return template.replaceAll("{current}", String(current)).replaceAll("{totalPages}", String(total)).replaceAll("{total}", String(total));
15
+ }
16
+ function definePart(tag, elementName, css, namedSlots = [], fallbacks = {}, closeSlots = namedSlots) {
17
+ if (customElements.get(tag))
18
+ return;
19
+ class SpuiTeachingPart extends HTMLElement {
20
+ connectedCallback() {
21
+ if (this.shadowRoot)
22
+ return;
23
+ const shadow = this.attachShadow({ mode: "open" });
24
+ const style = document.createElement("style");
25
+ style.textContent = `${base}:host{box-sizing:border-box;display:block}${css}`;
26
+ const element = document.createElement(elementName);
27
+ element.part.add("root");
28
+ for (const name of namedSlots) {
29
+ const slot = document.createElement("slot");
30
+ slot.name = name;
31
+ if (name === "icon")
32
+ slot.setAttribute("aria-hidden", "true");
33
+ const fallback = fallbacks[name]?.(this);
34
+ if (fallback)
35
+ slot.append(fallback);
36
+ element.append(slot);
37
+ }
38
+ element.append(document.createElement("slot"));
39
+ if (closeSlots.length)
40
+ this.addEventListener("click", event => {
41
+ const path = event.composedPath();
42
+ if (event.defaultPrevented || path.some(node => node instanceof HTMLElement && node.hasAttribute("data-spui-react-action")) || !path.some(node => node instanceof HTMLElement && closeSlots.includes(node.slot)))
43
+ return;
44
+ this.dispatchEvent(new CustomEvent("spui-popover-request-close", { bubbles: true, composed: true, detail: { sourceEvent: event } }));
45
+ });
46
+ shadow.append(style, element);
47
+ }
48
+ }
49
+ customElements.define(tag, SpuiTeachingPart);
50
+ }
51
+ function defineTeachingButton(tag, kind) {
52
+ if (customElements.get(tag))
53
+ return;
54
+ class SpuiTeachingButton extends HTMLElement {
55
+ static observedAttributes = ["nav-type", "value", "alt-text", "aria-label", "aria-current", "tabindex", "disabled", "size", "appearance", "shape", "icon-only", "data-edge"];
56
+ button;
57
+ fallback;
58
+ alternate;
59
+ connectedCallback() {
60
+ if (!this.shadowRoot)
61
+ this.render();
62
+ this.style.display ||= "inline-flex";
63
+ this.sync();
64
+ }
65
+ attributeChangedCallback() { this.sync(); }
66
+ render() {
67
+ const shadow = this.attachShadow({ mode: "open", delegatesFocus: true });
68
+ const style = document.createElement("style");
69
+ style.textContent = buttonControlCss;
70
+ const button = document.createElement("button");
71
+ button.type = "button";
72
+ button.part.add("control");
73
+ const fallback = document.createElement("span");
74
+ fallback.className = "fallback";
75
+ fallback.append(document.createElement("slot"));
76
+ button.append(fallback);
77
+ this.button = button;
78
+ this.fallback = fallback;
79
+ if (kind === "footer") {
80
+ const alternate = document.createElement("span");
81
+ alternate.className = "alternate";
82
+ alternate.hidden = true;
83
+ button.append(alternate);
84
+ this.alternate = alternate;
85
+ style.textContent += `.fallback[hidden],.alternate[hidden]{display:none}.alternate{white-space:nowrap}`;
86
+ }
87
+ shadow.append(style, button);
88
+ }
89
+ sync() {
90
+ const button = this.button;
91
+ if (!button)
92
+ return;
93
+ const navType = this.getAttribute("nav-type") ?? (kind === "nav" ? "step" : "next");
94
+ const appearance = this.getAttribute("appearance") ?? (kind === "nav" ? "subtle" : navType === "next" ? "primary" : navType === "dismiss" ? "subtle" : "secondary");
95
+ const size = this.getAttribute("size") ?? (kind === "nav" ? "small" : "medium");
96
+ const shape = this.getAttribute("shape") ?? (kind === "nav" ? "circular" : "rounded");
97
+ const setIfChanged = (name, value) => { if (this.getAttribute(name) !== value)
98
+ this.setAttribute(name, value); };
99
+ button.className = appearance === "primary" ? "primary" : ["outline", "subtle", "transparent"].includes(appearance) ? appearance : "secondary";
100
+ this.toggleAttribute("data-spui-popover-close", kind === "footer" && navType === "dismiss");
101
+ if (kind === "nav" && !this.hasAttribute("icon-only"))
102
+ this.setAttribute("icon-only", "");
103
+ button.disabled = this.hasAttribute("disabled");
104
+ button.setAttribute("part", "control");
105
+ button.setAttribute("aria-label", this.getAttribute("aria-label") || this.textContent?.trim() || (kind === "nav" ? "Tour step" : navType));
106
+ if (this.hasAttribute("aria-current"))
107
+ button.setAttribute("aria-current", this.getAttribute("aria-current"));
108
+ else
109
+ button.removeAttribute("aria-current");
110
+ button.tabIndex = this.hasAttribute("tabindex") ? Number(this.getAttribute("tabindex")) : kind === "nav" ? -1 : 0;
111
+ setIfChanged("appearance", appearance);
112
+ setIfChanged("size", size);
113
+ setIfChanged("shape", shape);
114
+ if (kind === "footer") {
115
+ const edge = this.getAttribute("data-edge");
116
+ const isEdge = (navType === "previous" || navType === "prev") ? edge === "first" : navType === "next" && edge === "last";
117
+ const altText = this.getAttribute("alt-text");
118
+ const alternate = this.alternate;
119
+ if (this.fallback)
120
+ this.fallback.hidden = Boolean(isEdge && altText);
121
+ if (alternate) {
122
+ alternate.hidden = !(isEdge && altText);
123
+ alternate.textContent = isEdge ? altText ?? "" : "";
124
+ }
125
+ const label = isEdge && altText ? altText : this.getAttribute("aria-label") || this.textContent?.trim() || (navType === "dismiss" ? "Finish tour" : navType === "previous" || navType === "prev" ? "Previous step" : "Next step");
126
+ button.setAttribute("aria-label", label);
127
+ setIfChanged("aria-label", label);
128
+ }
129
+ }
130
+ }
131
+ customElements.define(tag, SpuiTeachingButton);
132
+ }
133
+ function defineCarousel() {
134
+ if (customElements.get(carouselTag))
135
+ return;
136
+ class SpuiTeachingPopoverCarousel extends HTMLElement {
137
+ static observedAttributes = ["value", "default-value", "aria-label", "page-count-template", "page-label-template"];
138
+ currentValue = "";
139
+ events;
140
+ observer;
141
+ slotNode;
142
+ liveRegion;
143
+ announcement;
144
+ get value() { return this.getAttribute("value") ?? this.currentValue; }
145
+ set value(value) { this.setAttribute("value", value); }
146
+ get defaultValue() { return this.getAttribute("default-value") ?? ""; }
147
+ set defaultValue(value) { this.setAttribute("default-value", value); }
148
+ connectedCallback() {
149
+ this.style.display ||= "grid";
150
+ if (!this.shadowRoot)
151
+ this.render();
152
+ this.events?.abort();
153
+ this.events = new AbortController();
154
+ this.addEventListener("click", this.onClick, { signal: this.events.signal });
155
+ this.addEventListener("keydown", this.onKeyDown, { signal: this.events.signal });
156
+ this.slotNode?.addEventListener("slotchange", this.sync, { signal: this.events.signal });
157
+ this.observer?.disconnect();
158
+ this.observer = new MutationObserver(this.sync);
159
+ this.observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ["value", "nav-type", "alt-text"] });
160
+ this.setAttribute("role", "region");
161
+ this.setAttribute("aria-roledescription", "carousel");
162
+ if (!this.hasAttribute("aria-label"))
163
+ this.setAttribute("aria-label", languageOf(this).startsWith("ar") ? "خطوات الجولة" : "Tour steps");
164
+ this.currentValue = this.defaultValue || this.cards()[0]?.getAttribute("value") || "";
165
+ this.sync();
166
+ }
167
+ disconnectedCallback() { this.events?.abort(); this.observer?.disconnect(); }
168
+ attributeChangedCallback() { if (this.isConnected)
169
+ this.sync(); }
170
+ render() {
171
+ const shadow = this.attachShadow({ mode: "open" });
172
+ const style = document.createElement("style");
173
+ style.textContent = `${base}:host{display:grid;gap:8px;min-width:0}slot{display:contents}`;
174
+ this.slotNode = document.createElement("slot");
175
+ this.liveRegion = document.createElement("span");
176
+ this.liveRegion.setAttribute("aria-live", "polite");
177
+ this.liveRegion.setAttribute("aria-atomic", "true");
178
+ this.liveRegion.setAttribute("part", "announcement");
179
+ style.textContent += `span[part=announcement]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}`;
180
+ shadow.append(style, this.slotNode, this.liveRegion);
181
+ }
182
+ cards() { return [...this.querySelectorAll(cardTag)]; }
183
+ sync = () => {
184
+ const cards = this.cards();
185
+ if (!cards.length)
186
+ return;
187
+ const current = this.getAttribute("value") ?? this.currentValue ?? cards[0].getAttribute("value") ?? "";
188
+ const selectedIndex = Math.max(0, cards.findIndex(card => card.getAttribute("value") === current));
189
+ const selected = cards[selectedIndex];
190
+ if (!this.hasAttribute("value"))
191
+ this.currentValue = selected.getAttribute("value") ?? "";
192
+ const language = languageOf(this);
193
+ const pageTemplate = this.getAttribute("page-label-template") ?? (language.startsWith("ar") ? "الخطوة {current} من {total}" : "Step {current} of {total}");
194
+ cards.forEach((card, index) => {
195
+ const active = index === selectedIndex;
196
+ card.hidden = !active;
197
+ card.setAttribute("role", "group");
198
+ card.setAttribute("aria-roledescription", language.startsWith("ar") ? "شريحة" : "slide");
199
+ if (!card.hasAttribute("aria-label"))
200
+ card.setAttribute("aria-label", localize(pageTemplate, index + 1, cards.length));
201
+ card.toggleAttribute("data-active", active);
202
+ });
203
+ this.querySelectorAll(navButtonTag).forEach(button => {
204
+ const active = button.getAttribute("value") === selected.getAttribute("value");
205
+ if (active)
206
+ button.setAttribute("aria-current", "step");
207
+ else
208
+ button.removeAttribute("aria-current");
209
+ button.setAttribute("tabindex", active ? "0" : "-1");
210
+ });
211
+ const edge = selectedIndex === 0 ? "first" : selectedIndex === cards.length - 1 ? "last" : "";
212
+ this.querySelectorAll(footerButtonTag).forEach(button => edge ? button.setAttribute("data-edge", edge) : button.removeAttribute("data-edge"));
213
+ const countTemplate = this.getAttribute("page-count-template") ?? (language.startsWith("ar") ? "{current} من {total}" : "{current} of {total}");
214
+ const count = localize(countTemplate, selectedIndex + 1, cards.length);
215
+ this.querySelectorAll(pageCountTag).forEach(pageCount => {
216
+ const value = pageCount.shadowRoot?.querySelector("[aria-live]");
217
+ if (value)
218
+ value.textContent = count;
219
+ });
220
+ this.setAttribute("data-value", selected.getAttribute("value") ?? "");
221
+ this.setAttribute("data-current-page", String(selectedIndex + 1));
222
+ this.setAttribute("data-total-pages", String(cards.length));
223
+ };
224
+ changeTo(index, event) {
225
+ const cards = this.cards();
226
+ const next = cards[Math.max(0, Math.min(cards.length - 1, index))]?.getAttribute("value");
227
+ if (!next || next === this.value)
228
+ return;
229
+ const type = (event?.type ?? "programmatic");
230
+ if (this.hasAttribute("value")) {
231
+ this.dispatchEvent(new CustomEvent("spui-value-change", { bubbles: true, composed: true, detail: { value: next, event, type } }));
232
+ this.announceAfterUpdate(next, cards.length);
233
+ return;
234
+ }
235
+ this.currentValue = next;
236
+ this.sync();
237
+ const currentPage = cards.findIndex(card => card.getAttribute("value") === next) + 1;
238
+ const detail = { value: next, currentPage, totalPages: cards.length, event, type };
239
+ this.dispatchEvent(new CustomEvent("spui-value-change", { bubbles: true, composed: true, detail }));
240
+ this.dispatchEvent(new CustomEvent("spui-carousel-change", { bubbles: true, composed: true, detail }));
241
+ this.announceAfterUpdate(next, cards.length);
242
+ }
243
+ announceAfterUpdate(value, totalPages) {
244
+ if (!this.announcement)
245
+ return;
246
+ requestAnimationFrame(() => {
247
+ if (this.value !== value)
248
+ return;
249
+ const message = this.announcement?.(value) ?? "";
250
+ if (this.liveRegion)
251
+ this.liveRegion.textContent = message;
252
+ if (!message)
253
+ return;
254
+ const currentPage = this.cards().findIndex(card => card.getAttribute("value") === value) + 1;
255
+ this.dispatchEvent(new CustomEvent("spui-announcement", { bubbles: true, composed: true, detail: { value, message, currentPage, totalPages } }));
256
+ });
257
+ }
258
+ onClick = (event) => {
259
+ if (event.defaultPrevented)
260
+ return;
261
+ const path = event.composedPath().filter((node) => node instanceof HTMLElement);
262
+ const nav = path.find(node => node.localName === navButtonTag);
263
+ if (nav) {
264
+ const index = this.cards().findIndex(card => card.getAttribute("value") === nav.getAttribute("value"));
265
+ if (index >= 0)
266
+ this.changeTo(index, event);
267
+ return;
268
+ }
269
+ const button = path.find(node => node.localName === footerButtonTag);
270
+ if (!button)
271
+ return;
272
+ const type = button.getAttribute("nav-type");
273
+ const index = this.cards().findIndex(card => card.getAttribute("value") === this.value);
274
+ if (type === "prev" || type === "previous")
275
+ this.changeTo(index - 1, event);
276
+ else if (type === "next" && index === this.cards().length - 1)
277
+ this.dispatchEvent(new CustomEvent("spui-finish", { bubbles: true, composed: true, detail: { value: this.value, event, type: "click" } }));
278
+ else if (type === "next")
279
+ this.changeTo(index + 1, event);
280
+ };
281
+ onKeyDown = (event) => {
282
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
283
+ return;
284
+ const cards = this.cards();
285
+ const index = cards.findIndex(card => card.getAttribute("value") === this.value);
286
+ const rtl = getComputedStyle(this).direction === "rtl";
287
+ const delta = event.key === "ArrowRight" ? (rtl ? -1 : 1) : event.key === "ArrowLeft" ? (rtl ? 1 : -1) : 0;
288
+ const next = event.key === "Home" ? 0 : event.key === "End" ? cards.length - 1 : delta ? Math.max(0, Math.min(cards.length - 1, index + delta)) : -1;
289
+ if (next < 0 || next === index)
290
+ return;
291
+ event.preventDefault();
292
+ this.changeTo(next, event);
293
+ const value = cards[next]?.getAttribute("value");
294
+ requestAnimationFrame(() => {
295
+ const control = [...this.querySelectorAll(navButtonTag)].find(item => item.getAttribute("value") === value);
296
+ (control?.shadowRoot?.querySelector("button") ?? control)?.focus();
297
+ });
298
+ };
299
+ }
300
+ customElements.define(carouselTag, SpuiTeachingPopoverCarousel);
301
+ }
302
+ function defineCard() {
303
+ if (customElements.get(cardTag))
304
+ return;
305
+ class SpuiTeachingPopoverCarouselCard extends HTMLElement {
306
+ static observedAttributes = ["value", "aria-label", "hidden"];
307
+ connectedCallback() {
308
+ this.style.display ||= "grid";
309
+ this.style.minWidth ||= "0";
310
+ if (!this.shadowRoot) {
311
+ const shadow = this.attachShadow({ mode: "open" });
312
+ const style = document.createElement("style");
313
+ style.textContent = `${base}:host([hidden]){display:none!important}slot{display:contents}`;
314
+ const slide = document.createElement("div");
315
+ slide.part.add("slide");
316
+ slide.append(document.createElement("slot"));
317
+ shadow.append(style, slide);
318
+ }
319
+ }
320
+ }
321
+ customElements.define(cardTag, SpuiTeachingPopoverCarouselCard);
322
+ }
323
+ function definePageCount() {
324
+ if (customElements.get(pageCountTag))
325
+ return;
326
+ class SpuiTeachingPopoverPageCount extends HTMLElement {
327
+ connectedCallback() {
328
+ if (this.shadowRoot)
329
+ return;
330
+ const shadow = this.attachShadow({ mode: "open" });
331
+ const style = document.createElement("style");
332
+ style.textContent = `${base}:host{display:inline-flex;align-items:center;color:var(--spui-ui-text-muted,var(--colorNeutralForeground3,#616161));font-size:var(--spui-font-size-200,12px);white-space:nowrap}[aria-live]{display:inline}`;
333
+ const value = document.createElement("span");
334
+ value.setAttribute("aria-live", "polite");
335
+ value.setAttribute("part", "count");
336
+ shadow.append(style, value);
337
+ }
338
+ }
339
+ customElements.define(pageCountTag, SpuiTeachingPopoverPageCount);
340
+ }
341
+ function defineCarouselFooter() {
342
+ const tag = partTag("carousel-footer");
343
+ if (customElements.get(tag))
344
+ return;
345
+ class SpuiTeachingPopoverCarouselFooter extends HTMLElement {
346
+ static observedAttributes = ["layout", "initial-step-text", "final-step-text"];
347
+ slotNode;
348
+ footer;
349
+ connectedCallback() {
350
+ if (this.shadowRoot)
351
+ return;
352
+ this.style.display ||= "block";
353
+ const shadow = this.attachShadow({ mode: "open" });
354
+ const style = document.createElement("style");
355
+ style.textContent = `${base}:host{box-sizing:border-box}footer{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px}slot{display:contents}`;
356
+ this.footer = document.createElement("footer");
357
+ this.footer.part.add("root");
358
+ const previous = document.createElement("slot");
359
+ previous.name = "previous";
360
+ const next = document.createElement("slot");
361
+ next.name = "next";
362
+ this.slotNode = document.createElement("slot");
363
+ this.slotNode.addEventListener("slotchange", this.sync);
364
+ this.footer.append(previous, this.slotNode, next);
365
+ shadow.append(style, this.footer);
366
+ this.sync();
367
+ }
368
+ attributeChangedCallback() { this.sync(); }
369
+ sync = () => {
370
+ if (this.footer)
371
+ this.footer.style.justifyContent = this.getAttribute("layout") === "offset" ? "space-between" : "center";
372
+ for (const [type, attr] of [["previous", "initial-step-text"], ["prev", "initial-step-text"], ["next", "final-step-text"]]) {
373
+ const text = this.getAttribute(attr);
374
+ for (const button of this.querySelectorAll(`${footerButtonTag}[nav-type="${type}"]`)) {
375
+ const previousGenerated = button.getAttribute("data-spui-footer-alt-text");
376
+ const existingAlt = button.getAttribute("alt-text");
377
+ if ((previousGenerated && existingAlt !== previousGenerated) || (!previousGenerated && existingAlt))
378
+ continue;
379
+ if (text) {
380
+ button.setAttribute("alt-text", text);
381
+ button.setAttribute("data-spui-footer-alt-text", text);
382
+ }
383
+ else if (previousGenerated) {
384
+ button.removeAttribute("alt-text");
385
+ button.removeAttribute("data-spui-footer-alt-text");
386
+ }
387
+ }
388
+ }
389
+ };
390
+ }
391
+ customElements.define(tag, SpuiTeachingPopoverCarouselFooter);
392
+ }
393
+ /** Register the framework-neutral TeachingPopover and its composable guided-tour parts. */
394
+ export function defineTeachingPopover() {
395
+ defineButton();
396
+ definePopoverVariant("spui-teaching-popover", { label: "Guided tour", trapFocus: true, arrow: true });
397
+ definePart(partTag("trigger"), "span", ":host{display:contents!important}");
398
+ definePart(partTag("surface"), "div", ":host{display:grid;gap:12px;min-width:min(320px,calc(100vw - 64px));max-width:100%;box-sizing:border-box}");
399
+ const iconFallback = () => { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 16 16"); svg.setAttribute("width", "16"); svg.setAttribute("height", "16"); svg.setAttribute("fill", "none"); svg.setAttribute("stroke", "currentColor"); svg.setAttribute("stroke-width", "1.25"); const path = document.createElementNS(svg.namespaceURI, "path"); path.setAttribute("d", "M5.1 10.6c-.3-.8-.7-1.3-1.2-2A4.3 4.3 0 1 1 12.1 8c-.5.7-.9 1.2-1.2 2.6H5.2l-.1 0Zm.4 1.3h5m-4.5 1.4h4m-3.4 1.2h2.8"); svg.append(path); return svg; };
400
+ const dismissFallback = (host) => { const button = document.createElement("button"); button.type = "button"; button.setAttribute("aria-label", languageOf(host).startsWith("ar") ? "إغلاق" : "Dismiss"); button.setAttribute("data-spui-popover-close", ""); button.textContent = "×"; return button; };
401
+ definePart(partTag("header"), "header", "header{display:flex;align-items:center;gap:8px;padding-block-end:4px;color:var(--spui-ui-text-muted,var(--colorNeutralForeground3,#616161));font-size:var(--spui-font-size-200,12px);font-weight:600}slot[name=icon]{display:inline-flex;align-items:center;flex:none;color:var(--spui-ui-accent,var(--colorBrandForeground1,#0f6cbd))}slot[name=dismiss-button]{margin-inline-start:auto}slot[name=dismiss-button] button,slot[name=dismiss-button]::slotted(button){box-sizing:border-box;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:var(--spui-control-radius,4px);background:transparent;color:inherit;font:inherit;cursor:pointer}slot[name=dismiss-button] button:hover,slot[name=dismiss-button]::slotted(button):hover{background:var(--spui-ui-surface-hover,var(--colorNeutralBackground1Hover,#f5f5f5))}slot[name=dismiss-button] button:focus-visible,slot[name=dismiss-button]::slotted(button):focus-visible{outline:2px solid var(--spui-ui-border-focus,var(--spui-ui-accent,#0f6cbd));outline-offset:1px}", ["icon", "dismiss-button"], { icon: iconFallback, "dismiss-button": dismissFallback }, ["dismiss-button"]);
402
+ definePart(partTag("title"), "h2", "h2{display:flex;align-items:center;gap:8px;margin:0;color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424));font:600 20px/1.3 var(--spui-font-family,inherit)}slot[name=dismiss-button]{margin-inline-start:auto}", ["dismiss-button"]);
403
+ definePart(partTag("body"), "div", "div{display:grid;gap:8px;padding-block-end:8px;color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424));font:var(--spui-font-family,inherit);line-height:1.5}");
404
+ definePart(partTag("footer"), "footer", "footer{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}slot[name=primary],slot[name=secondary]{display:inline-flex}slot:not([name]){display:contents}", ["primary", "secondary"]);
405
+ defineCarouselFooter();
406
+ definePart(partTag("carousel-nav"), "nav", "nav{display:flex;align-items:center;gap:4px}");
407
+ defineCard();
408
+ definePageCount();
409
+ defineCarousel();
410
+ defineTeachingButton(navButtonTag, "nav");
411
+ defineTeachingButton(footerButtonTag, "footer");
412
+ }
@@ -0,0 +1,5 @@
1
+ export type TextSize = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 | 1100 | 1200;
2
+ export type TextWeight = "regular" | "medium" | "semibold" | "bold";
3
+ export type TextFont = "base" | "monospace" | "numeric";
4
+ export type TextTag = "span" | "p" | "div" | "strong" | "b" | "em" | "i" | "small" | "code" | "pre" | "blockquote" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
5
+ export declare function defineText(): void;
@@ -0,0 +1,69 @@
1
+ import { base } from "./shared.js";
2
+ const sizes = {
3
+ "100": ["10px", "14px"], "200": ["12px", "16px"], "300": ["14px", "20px"], "400": ["16px", "22px"],
4
+ "500": ["20px", "28px"], "600": ["24px", "32px"], "700": ["28px", "36px"], "800": ["32px", "40px"],
5
+ "900": ["40px", "52px"], "1000": ["68px", "92px"], "1100": ["80px", "104px"], "1200": ["96px", "128px"],
6
+ };
7
+ const fluentSizeTokens = {
8
+ "100": ["fontSizeBase100", "lineHeightBase100"], "200": ["fontSizeBase200", "lineHeightBase200"], "300": ["fontSizeBase300", "lineHeightBase300"],
9
+ "400": ["fontSizeBase400", "lineHeightBase400"], "500": ["fontSizeBase500", "lineHeightBase500"], "600": ["fontSizeBase600", "lineHeightBase600"],
10
+ "700": ["fontSizeHero700", "lineHeightHero700"], "800": ["fontSizeHero800", "lineHeightHero800"], "900": ["fontSizeHero900", "lineHeightHero900"], "1000": ["fontSizeHero1000", "lineHeightHero1000"],
11
+ };
12
+ const extendedSizeTokens = {
13
+ "1100": ["--spui-text-font-size-1100", "--spui-text-line-height-1100"],
14
+ "1200": ["--spui-text-font-size-1200", "--spui-text-line-height-1200"],
15
+ };
16
+ const tags = new Set(["span", "p", "div", "strong", "b", "em", "i", "small", "code", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"]);
17
+ export function defineText() {
18
+ if (customElements.get("spui-text"))
19
+ return;
20
+ class SpuiText extends HTMLElement {
21
+ static observedAttributes = ["as", "size", "weight", "font", "align", "italic", "underline", "strikethrough", "truncate", "wrap", "block"];
22
+ connectedCallback() { if (!this.shadowRoot)
23
+ this.render();
24
+ else {
25
+ this.syncTag();
26
+ this.syncSize();
27
+ } }
28
+ attributeChangedCallback(name) {
29
+ if (!this.isConnected)
30
+ return;
31
+ if (name === "as")
32
+ this.syncTag();
33
+ if (name === "size")
34
+ this.syncSize();
35
+ }
36
+ tag() { const tag = this.getAttribute("as") ?? "span"; return tags.has(tag) ? tag : "span"; }
37
+ content() {
38
+ const content = document.createElement(this.tag());
39
+ content.className = "text";
40
+ content.part.add("content");
41
+ content.append(document.createElement("slot"));
42
+ return content;
43
+ }
44
+ render() {
45
+ const shadow = this.attachShadow({ mode: "open" });
46
+ const style = document.createElement("style");
47
+ style.textContent = `${base}:host{display:inline;vertical-align:baseline}:host([block]),:host([as=p]),:host([as=div]),:host([as=pre]),:host([as=blockquote]),:host([as=h1]),:host([as=h2]),:host([as=h3]),:host([as=h4]),:host([as=h5]),:host([as=h6]){display:block}.text{box-sizing:border-box;margin:0;color:inherit;font-family:var(--spui-font-family,var(--fontFamilyBase,inherit));font-size:var(--spui-text-font-size,var(--fontSizeBase300,14px));font-weight:var(--spui-text-font-weight,var(--fontWeightRegular,400));line-height:var(--spui-text-line-height,var(--lineHeightBase300,20px));text-align:var(--spui-text-align,start);font-style:normal;text-decoration:none;white-space:normal;overflow-wrap:anywhere}:host([as=strong]) .text,:host([as=b]) .text{font-weight:var(--fontWeightBold,700)}:host([as=em]) .text,:host([as=i]) .text{font-style:italic}:host([weight=regular]) .text{font-weight:var(--fontWeightRegular,400)}:host([weight=medium]) .text{font-weight:var(--fontWeightMedium,500)}:host([weight=semibold]) .text{font-weight:var(--fontWeightSemibold,600)}:host([weight=bold]) .text{font-weight:var(--fontWeightBold,700)}:host([font=monospace]) .text{font-family:var(--spui-font-family-mono,var(--fontFamilyMonospace,ui-monospace,monospace))}:host([font=numeric]) .text{font-family:var(--fontFamilyNumeric,var(--fontFamilyBase,inherit));font-variant-numeric:tabular-nums}:host([align=center]) .text{text-align:center}:host([align=end]) .text{text-align:end}:host([align=justify]) .text{text-align:justify}:host([italic]) .text{font-style:italic}:host([underline]) .text{text-decoration:underline;text-underline-offset:.15em}:host([strikethrough]) .text{text-decoration:line-through}:host([underline][strikethrough]) .text{text-decoration-line:underline line-through}:host([truncate]){display:inline-block;max-width:100%;vertical-align:bottom}:host([truncate]) .text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;overflow-wrap:normal}:host([wrap=false]) .text{white-space:nowrap;overflow-wrap:normal}:host([as=code]) .text,:host([as=pre]) .text{font-family:var(--spui-font-family-mono,var(--fontFamilyMonospace,ui-monospace,monospace))}:host([as=pre]) .text{white-space:pre-wrap;overflow-wrap:anywhere}@media(forced-colors:active){.text{color:CanvasText}}`;
48
+ shadow.replaceChildren(style, this.content());
49
+ this.syncSize();
50
+ }
51
+ syncTag() {
52
+ const content = this.shadowRoot?.querySelector(".text");
53
+ if (content && content.localName !== this.tag())
54
+ content.replaceWith(this.content());
55
+ }
56
+ syncSize() {
57
+ const content = this.shadowRoot?.querySelector(".text");
58
+ if (!content)
59
+ return;
60
+ const size = this.getAttribute("size") ?? "300";
61
+ const [fallbackSize, fallbackLine] = sizes[size] ?? sizes["300"];
62
+ const fluentTokens = fluentSizeTokens[size];
63
+ const [fontToken, lineToken] = extendedSizeTokens[size] ?? (fluentTokens ? [`--${fluentTokens[0]}`, `--${fluentTokens[1]}`] : [`--${fluentSizeTokens["300"][0]}`, `--${fluentSizeTokens["300"][1]}`]);
64
+ content.style.fontSize = `var(--spui-text-font-size,var(${fontToken},${fallbackSize}))`;
65
+ content.style.lineHeight = `var(--spui-text-line-height,var(${lineToken},${fallbackLine}))`;
66
+ }
67
+ }
68
+ customElements.define("spui-text", SpuiText);
69
+ }
@@ -0,0 +1 @@
1
+ export declare function defineTextArea(): void;