@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,840 @@
1
+ import { base, findFocusable } from "./shared.js";
2
+ import { getFloatingPosition } from "./floating-position.js";
3
+ function intersectBoundaryRects(rects) {
4
+ if (!rects.length)
5
+ return undefined;
6
+ const left = Math.max(...rects.map(rect => rect.left));
7
+ const top = Math.max(...rects.map(rect => rect.top));
8
+ const right = Math.max(left, Math.min(...rects.map(rect => rect.right)));
9
+ const bottom = Math.max(top, Math.min(...rects.map(rect => rect.bottom)));
10
+ return { left, top, right, bottom };
11
+ }
12
+ function viewportBoundaryRect() { return { left: 0, top: 0, right: innerWidth, bottom: innerHeight }; }
13
+ function parentElement(element) {
14
+ if (element.parentElement)
15
+ return element.parentElement;
16
+ const root = element.getRootNode();
17
+ return root instanceof ShadowRoot ? root.host : null;
18
+ }
19
+ function scrollBoundary(element, clippingOnly) {
20
+ const rects = clippingOnly ? [viewportBoundaryRect()] : [];
21
+ let current = parentElement(element);
22
+ while (current) {
23
+ const view = current.ownerDocument.defaultView ?? window;
24
+ const style = view.getComputedStyle(current);
25
+ const clipX = /^(auto|scroll|hidden|clip|overlay)$/.test(style.overflowX);
26
+ const clipY = /^(auto|scroll|hidden|clip|overlay)$/.test(style.overflowY);
27
+ const isScrollParent = /^(auto|scroll|hidden|overlay)$/.test(style.overflowX) || /^(auto|scroll|hidden|overlay)$/.test(style.overflowY);
28
+ if (clippingOnly ? clipX || clipY : isScrollParent) {
29
+ const bounds = current.getBoundingClientRect();
30
+ const left = bounds.left + current.clientLeft;
31
+ const top = bounds.top + current.clientTop;
32
+ if (!clippingOnly)
33
+ return { left, top, right: left + current.clientWidth, bottom: top + current.clientHeight };
34
+ rects.push({
35
+ left: clipX ? left : 0,
36
+ right: clipX ? left + current.clientWidth : innerWidth,
37
+ top: clipY ? top : 0,
38
+ bottom: clipY ? top + current.clientHeight : innerHeight
39
+ });
40
+ }
41
+ current = parentElement(current);
42
+ }
43
+ return clippingOnly ? intersectBoundaryRects(rects) : undefined;
44
+ }
45
+ function getBoundaryRect(boundary, contextElement) {
46
+ if (!boundary || boundary === "window")
47
+ return undefined;
48
+ if (boundary === "clippingParents" || boundary === "scrollParent")
49
+ return contextElement ? scrollBoundary(contextElement, boundary === "clippingParents") : undefined;
50
+ if (Array.isArray(boundary))
51
+ return intersectBoundaryRects(boundary.map(element => element.getBoundingClientRect()));
52
+ const element = boundary;
53
+ if (typeof element.getBoundingClientRect === "function")
54
+ return element.getBoundingClientRect();
55
+ const rect = boundary;
56
+ return { left: rect.x, top: rect.y, right: rect.x + rect.width, bottom: rect.y + rect.height };
57
+ }
58
+ const nextId = (() => { let value = 0; return () => `spui-popover-${++value}`; })();
59
+ const inertFocusStates = new WeakMap();
60
+ function syncInertFocusState(document, host, triggers, active, surface) {
61
+ let state = inertFocusStates.get(document);
62
+ if (active) {
63
+ if (!state) {
64
+ state = { overlays: new Map(), baseline: new Map(), applied: new Set() };
65
+ inertFocusStates.set(document, state);
66
+ }
67
+ state.overlays.set(host, { triggers, surface });
68
+ }
69
+ else if (state)
70
+ state.overlays.delete(host);
71
+ if (!state)
72
+ return;
73
+ for (const element of state.applied)
74
+ element.inert = state.baseline.get(element) ?? false;
75
+ state.applied.clear();
76
+ const hosts = new Set(state.overlays.keys());
77
+ const surfaces = new Set([...state.overlays.values()].flatMap(overlay => overlay.surface ? [overlay.surface] : []));
78
+ const inert = (element) => {
79
+ if (!state.baseline.has(element))
80
+ state.baseline.set(element, element.inert);
81
+ state.applied.add(element);
82
+ element.inert = true;
83
+ };
84
+ const visit = (element) => {
85
+ if (hosts.has(element) || surfaces.has(element))
86
+ return;
87
+ if ([...hosts, ...surfaces].some(overlayElement => element.contains(overlayElement))) {
88
+ for (const child of [...element.children])
89
+ if (child instanceof HTMLElement)
90
+ visit(child);
91
+ return;
92
+ }
93
+ inert(element);
94
+ };
95
+ for (const child of [...document.body.children])
96
+ if (child instanceof HTMLElement)
97
+ visit(child);
98
+ for (const overlay of state.overlays.values())
99
+ for (const trigger of overlay.triggers)
100
+ inert(trigger);
101
+ if (!state.overlays.size) {
102
+ for (const [element, previous] of state.baseline)
103
+ element.inert = previous;
104
+ state.baseline.clear();
105
+ inertFocusStates.delete(document);
106
+ }
107
+ }
108
+ function definePopoverPart(tag, styles) {
109
+ if (customElements.get(tag))
110
+ return;
111
+ class SpuiPopoverPart extends HTMLElement {
112
+ connectedCallback() {
113
+ if (!this.hasAttribute("slot") && tag === "spui-popover-trigger")
114
+ this.slot = "trigger";
115
+ if (this.shadowRoot)
116
+ return;
117
+ const shadow = this.attachShadow({ mode: "open" });
118
+ const style = document.createElement("style");
119
+ style.textContent = `${base}:host{${styles}}`;
120
+ shadow.append(style, document.createElement("slot"));
121
+ }
122
+ }
123
+ customElements.define(tag, SpuiPopoverPart);
124
+ }
125
+ export function definePopoverTrigger() { definePopoverPart("spui-popover-trigger", "display:contents"); }
126
+ export function definePopoverSurface() { definePopoverPart("spui-popover-surface", "display:block;min-width:0;color:inherit"); }
127
+ export function definePopover() {
128
+ definePopoverTrigger();
129
+ definePopoverSurface();
130
+ if (customElements.get("spui-popover"))
131
+ return;
132
+ class SpuiPopover extends HTMLElement {
133
+ static observedAttributes = ["open", "default-open", "motion", "placement", "positioning", "align", "offset", "label", "inline", "trap-focus", "inert-trap-focus", "legacy-trap-focus", "unstable-disable-auto-focus", "arrow", "appearance", "size", "close-on-scroll", "close-on-iframe-focus", "open-on-context", "open-on-hover", "mouse-leave-delay"];
134
+ events;
135
+ observer;
136
+ surfaceObserver;
137
+ resizeObserver;
138
+ panel;
139
+ internalPanel;
140
+ externalPanel;
141
+ surfaceEvents;
142
+ trigger;
143
+ triggerState;
144
+ isOpen = false;
145
+ focusEdge = "first";
146
+ positioningValue;
147
+ positioningTarget;
148
+ contextPoint;
149
+ hoverTimer;
150
+ iframeBlurTimer;
151
+ exitGeneration = 0;
152
+ get trapFocusEnabled() { return this.hasAttribute("trap-focus"); }
153
+ get inertTrapFocusEnabled() {
154
+ const inert = this.getAttribute("inert-trap-focus");
155
+ if (inert !== null)
156
+ return inert !== "false";
157
+ const legacy = this.getAttribute("legacy-trap-focus");
158
+ return legacy === "false";
159
+ }
160
+ get positioning() { return this.positioningValue ?? this.getAttribute("positioning") ?? this.getAttribute("placement") ?? (this.hasAttribute("open-on-context") ? "above" : "bottom"); }
161
+ set positioning(value) { this.positioningValue = value; this.syncPositionObserver(); this.position(); }
162
+ get open() { return this.isOpen; }
163
+ set open(value) { this.setOpen(Boolean(value)); }
164
+ get inline() { return this.hasAttribute("inline"); }
165
+ set inline(value) { this.toggleAttribute("inline", Boolean(value)); }
166
+ set popoverSurface(value) {
167
+ const previousPanel = this.panel;
168
+ this.surfaceEvents?.abort();
169
+ this.surfaceObserver?.disconnect();
170
+ this.externalPanel = value ?? undefined;
171
+ this.panel = this.externalPanel ?? this.internalPanel;
172
+ if (previousPanel && previousPanel !== this.panel) {
173
+ previousPanel.hidePopover?.();
174
+ previousPanel.hidden = true;
175
+ }
176
+ if (this.panel) {
177
+ this.panel.id ||= nextId();
178
+ this.panel.setAttribute("popover", "manual");
179
+ this.panel.tabIndex = -1;
180
+ }
181
+ if (this.isConnected && this.externalPanel) {
182
+ this.surfaceEvents = new AbortController();
183
+ const options = { signal: this.surfaceEvents.signal };
184
+ this.externalPanel.addEventListener("click", this.onClick, options);
185
+ this.externalPanel.addEventListener("spui-popover-request-close", this.onRequestClose, options);
186
+ this.externalPanel.addEventListener("keydown", this.onKeyDown, options);
187
+ this.externalPanel.addEventListener("focusout", this.onFocusOut, options);
188
+ this.externalPanel.addEventListener("pointerenter", this.onHoverEnter, options);
189
+ this.externalPanel.addEventListener("pointerleave", this.onHoverLeave, options);
190
+ this.surfaceObserver = new MutationObserver(() => this.syncLabel());
191
+ this.surfaceObserver.observe(this.externalPanel, { childList: true, subtree: true, attributes: true, attributeFilter: ["slot", "id"] });
192
+ }
193
+ if (this.isConnected) {
194
+ this.syncLabel();
195
+ this.syncPositionObserver();
196
+ this.syncVisibility();
197
+ }
198
+ }
199
+ get popoverSurface() { return this.externalPanel ?? null; }
200
+ syncOpen(value) { this.setOpen(Boolean(value), true, false); }
201
+ show() { this.setOpen(true); }
202
+ hide() { this.setOpen(false); }
203
+ connectedCallback() {
204
+ this.style.display ||= "contents";
205
+ if (!this.shadowRoot)
206
+ this.render();
207
+ this.events?.abort();
208
+ this.events = new AbortController();
209
+ const options = { signal: this.events.signal };
210
+ this.addEventListener("click", this.onClick, options);
211
+ this.addEventListener("spui-popover-request-close", this.onRequestClose, options);
212
+ this.addEventListener("contextmenu", this.onContextMenu, options);
213
+ this.addEventListener("keydown", this.onKeyDown, options);
214
+ this.addEventListener("focusout", this.onFocusOut, options);
215
+ document.addEventListener("pointerdown", this.onOutsidePointerDown, { ...options, capture: true });
216
+ window.addEventListener("blur", this.onWindowBlur, options);
217
+ window.addEventListener("resize", this.position, options);
218
+ window.addEventListener("scroll", this.onScroll, { ...options, capture: true });
219
+ this.shadowRoot?.querySelector("slot[name=trigger]")?.addEventListener("slotchange", this.syncTrigger, options);
220
+ this.shadowRoot?.querySelector("slot[name=title]")?.addEventListener("slotchange", this.syncLabel, options);
221
+ this.observer = new MutationObserver(() => { this.syncTrigger(); this.syncLabel(); });
222
+ this.observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ["disable-button-enhancement"] });
223
+ this.internalPanel = this.shadowRoot?.querySelector(".surface");
224
+ this.panel = this.externalPanel ?? this.internalPanel;
225
+ this.panel?.addEventListener("pointerenter", this.onHoverEnter, options);
226
+ this.panel?.addEventListener("pointerleave", this.onHoverLeave, options);
227
+ this.resizeObserver = new ResizeObserver(() => { if (!this.positioningOptions?.disableUpdateOnResize)
228
+ this.position(); });
229
+ this.syncPositionObserver();
230
+ this.isOpen = this.hasAttribute("open") || this.hasAttribute("default-open");
231
+ this.syncTrigger();
232
+ this.syncLabel();
233
+ this.syncVisibility();
234
+ }
235
+ disconnectedCallback() {
236
+ this.observer?.disconnect();
237
+ this.surfaceObserver?.disconnect();
238
+ this.resizeObserver?.disconnect();
239
+ this.events?.abort();
240
+ this.surfaceEvents?.abort();
241
+ const positioningRef = this.positioningOptions?.positioningRef;
242
+ if (typeof positioningRef === "function")
243
+ positioningRef(null);
244
+ else if (positioningRef)
245
+ positioningRef.current = null;
246
+ if (this.hoverTimer !== undefined)
247
+ clearTimeout(this.hoverTimer);
248
+ if (this.iframeBlurTimer !== undefined)
249
+ clearTimeout(this.iframeBlurTimer);
250
+ this.restoreTrigger();
251
+ if (this.panel?.hasAttribute("popover"))
252
+ this.panel.hidePopover?.();
253
+ syncInertFocusState(this.ownerDocument, this, [], false);
254
+ }
255
+ attributeChangedCallback(name) {
256
+ if (!this.isConnected)
257
+ return;
258
+ if (name === "open") {
259
+ const next = this.hasAttribute("open");
260
+ if (next !== this.isOpen) {
261
+ this.isOpen = next;
262
+ this.syncVisibility();
263
+ this.emitOpenChange();
264
+ }
265
+ }
266
+ else if (name === "default-open") {
267
+ if (this.hasAttribute(name) && !this.hasAttribute("open"))
268
+ this.syncOpen(true);
269
+ }
270
+ else if (name === "trap-focus" || name === "inert-trap-focus" || name === "legacy-trap-focus") {
271
+ this.syncInertFocus();
272
+ this.syncModalSemantics();
273
+ if (this.isOpen && this.trapFocusEnabled && this.inertTrapFocusEnabled)
274
+ requestAnimationFrame(this.focusFirst);
275
+ }
276
+ else if (name === "unstable-disable-auto-focus") {
277
+ if (this.isOpen && !this.hasAttribute(name))
278
+ requestAnimationFrame(this.focusFirst);
279
+ }
280
+ else if (name === "label")
281
+ this.syncLabel();
282
+ else if (name === "inline") {
283
+ this.syncVisibility();
284
+ this.position();
285
+ }
286
+ else
287
+ this.position();
288
+ }
289
+ render() {
290
+ const shadow = this.attachShadow({ mode: "open" });
291
+ const style = document.createElement("style");
292
+ style.textContent = `${base}:host{display:contents}.trigger{display:contents}.surface{position:fixed;inset:auto;margin:0;z-index:var(--spui-layer-popover,10000);box-sizing:border-box;width:max-content;max-width:var(--spui-popover-max-width,min(360px,calc(100vw - 16px)));max-height:var(--spui-popover-max-height,min(70vh,calc(100dvh - 24px)));overflow:auto;padding:16px;border:1px solid var(--spui-popover-border,var(--spui-ui-stroke,var(--spui-semantic-body-divider,#d1d1d1)));border-radius:var(--spui-popover-radius,var(--spui-card-radius,8px));background:var(--spui-popover-background,var(--spui-ui-surface,var(--colorNeutralBackground1,#fff)));color:var(--spui-popover-color,var(--spui-ui-text,var(--colorNeutralForeground1,#242424)));box-shadow:var(--spui-popover-shadow,0 8px 24px rgb(0 0 0 / 18%));font-family:var(--spui-font-family,inherit);font-size:14px;line-height:1.45;opacity:1;transform:scale(1);transform-origin:center;transition:opacity var(--spui-motion-duration-normal,140ms) cubic-bezier(.16,1,.3,1),transform var(--spui-motion-duration-normal,140ms) cubic-bezier(.16,1,.3,1)}@starting-style{.surface:popover-open{opacity:0;transform:scale(.97)}}.surface:not(:popover-open){display:none}.surface[hidden]{display:none}h2{margin:0 0 8px;font:600 16px/1.35 var(--spui-font-family,inherit)}h2:empty{display:none}slot[name=trigger]{display:contents}slot[name=title]:not(:defined){display:none}:host([data-spui-appearance=glass]) .surface{backdrop-filter:blur(18px) saturate(1.35)}:host([appearance=brand]){--spui-popover-background:var(--spui-ui-accent,var(--colorBrandBackground,#0078d4));--spui-popover-color:var(--spui-ui-accent-text,var(--spui-semantic-primary-button-text,#fff));--spui-popover-border:var(--spui-ui-accent,var(--colorBrandBackground,#0078d4))}:host([appearance=inverted]){--spui-popover-background:var(--spui-ui-text,var(--spui-semantic-body-text,#242424));--spui-popover-color:var(--spui-ui-surface,var(--spui-semantic-body-background,#fff));--spui-popover-border:var(--spui-ui-text,var(--spui-semantic-body-text,#242424))}:host([size=small]) .surface{padding:8px;font-size:12px}:host([size=large]) .surface{padding:20px;font-size:16px}@media(prefers-reduced-motion:reduce){.surface{transition:none}}@media(forced-colors:active){.surface{border-color:CanvasText;box-shadow:none}.surface:popover-open{outline:1px solid CanvasText}}`;
293
+ style.textContent += `:host([arrow]) .surface:before{content:"";position:absolute;z-index:1;width:8px;height:8px;border:1px solid var(--spui-popover-border,var(--spui-ui-stroke,#d1d1d1));background:var(--spui-popover-background,var(--spui-ui-surface,#fff));transform:rotate(45deg)}:host([arrow]) .surface[data-placement=bottom]:before{inset-block-start:-5px;inset-inline-start:var(--spui-popover-arrow-inline-offset,calc(50% - 5px));border-inline-end:0;border-block-end:0}:host([arrow]) .surface[data-placement=top]:before{inset-block-end:-5px;inset-inline-start:var(--spui-popover-arrow-inline-offset,calc(50% - 5px));border-inline-start:0;border-block-start:0}:host([arrow]) .surface[data-placement=left]:before{inset-inline-end:-5px;inset-block-start:var(--spui-popover-arrow-block-offset,calc(50% - 5px));border-inline-start:0;border-block-end:0}:host([arrow]) .surface[data-placement=right]:before{inset-inline-start:-5px;inset-block-start:var(--spui-popover-arrow-block-offset,calc(50% - 5px));border-inline-end:0;border-block-start:0}`;
294
+ const panel = document.createElement("div");
295
+ panel.className = "surface";
296
+ panel.part.add("surface");
297
+ panel.setAttribute("popover", "manual");
298
+ panel.setAttribute("role", "dialog");
299
+ panel.tabIndex = -1;
300
+ panel.id = nextId();
301
+ const heading = document.createElement("h2");
302
+ heading.id = `${panel.id}-title`;
303
+ const titleSlot = document.createElement("slot");
304
+ titleSlot.name = "title";
305
+ heading.append(titleSlot);
306
+ const contentSlot = document.createElement("slot");
307
+ contentSlot.name = "content";
308
+ panel.append(heading, contentSlot);
309
+ const triggerSlot = document.createElement("slot");
310
+ triggerSlot.name = "trigger";
311
+ shadow.append(style, triggerSlot, panel);
312
+ this.internalPanel = panel;
313
+ this.panel = this.externalPanel ?? panel;
314
+ }
315
+ assigned(slotName) {
316
+ if (slotName === "content" && this.externalPanel)
317
+ return [this.externalPanel];
318
+ return this.shadowRoot?.querySelector(`slot[name=${slotName}]`)?.assignedElements({ flatten: true }).filter((node) => node instanceof HTMLElement) ?? [];
319
+ }
320
+ syncTrigger = () => {
321
+ const slotted = this.assigned("trigger")[0];
322
+ const next = slotted ? findFocusable(slotted.shadowRoot ?? slotted) ?? slotted : undefined;
323
+ const disableButtonEnhancement = Boolean(slotted?.hasAttribute("disable-button-enhancement") || next?.hasAttribute("disable-button-enhancement"));
324
+ if (next !== this.trigger) {
325
+ this.trigger?.removeEventListener("pointerenter", this.onHoverEnter);
326
+ this.trigger?.removeEventListener("pointerleave", this.onHoverLeave);
327
+ if (this.trigger)
328
+ this.resizeObserver?.unobserve(this.trigger);
329
+ this.restoreTrigger();
330
+ this.trigger = next;
331
+ if (next) {
332
+ next.addEventListener("pointerenter", this.onHoverEnter, { signal: this.events?.signal });
333
+ next.addEventListener("pointerleave", this.onHoverLeave, { signal: this.events?.signal });
334
+ }
335
+ if (next)
336
+ this.resizeObserver?.observe(next);
337
+ this.triggerState = next ? { haspopup: next.getAttribute("aria-haspopup"), controls: next.getAttribute("aria-controls"), expanded: next.getAttribute("aria-expanded") } : undefined;
338
+ }
339
+ if (this.trigger && this.panel) {
340
+ if (disableButtonEnhancement)
341
+ this.restoreTriggerAttributes();
342
+ else {
343
+ if (this.trigger.getRootNode() === this.panel.getRootNode())
344
+ this.trigger.setAttribute("aria-controls", this.panel.id);
345
+ else
346
+ this.trigger.removeAttribute("aria-controls");
347
+ this.trigger.setAttribute("aria-haspopup", "dialog");
348
+ this.trigger.setAttribute("aria-expanded", String(this.isOpen));
349
+ }
350
+ }
351
+ this.syncInertFocus();
352
+ };
353
+ syncLabel = () => {
354
+ if (!this.panel)
355
+ return;
356
+ const title = this.externalPanel ? [...this.externalPanel.querySelectorAll("h1,h2,h3,h4,h5,h6,[slot=title]")] : this.assigned("title");
357
+ const heading = this.panel.querySelector("h2") ?? title[0];
358
+ if (heading && this.externalPanel && !heading.id)
359
+ heading.id = `${this.panel.id}-title`;
360
+ if (heading && title.length) {
361
+ this.panel.setAttribute("aria-labelledby", heading.id || `${this.panel.id}-title`);
362
+ this.panel.removeAttribute("aria-label");
363
+ }
364
+ else {
365
+ this.panel.removeAttribute("aria-labelledby");
366
+ const label = this.getAttribute("label") || "More details";
367
+ this.panel.setAttribute("aria-label", label);
368
+ }
369
+ };
370
+ restoreTrigger() {
371
+ if (!this.trigger || !this.triggerState)
372
+ return;
373
+ this.restoreTriggerAttributes();
374
+ this.trigger = undefined;
375
+ this.triggerState = undefined;
376
+ }
377
+ restoreTriggerAttributes() {
378
+ if (!this.trigger || !this.triggerState)
379
+ return;
380
+ for (const [name, value] of [["aria-haspopup", this.triggerState.haspopup], ["aria-expanded", this.triggerState.expanded]]) {
381
+ if (value === null)
382
+ this.trigger.removeAttribute(name);
383
+ else
384
+ this.trigger.setAttribute(name, value);
385
+ }
386
+ if (this.triggerState.controls === null)
387
+ this.trigger.removeAttribute("aria-controls");
388
+ else
389
+ this.trigger.setAttribute("aria-controls", this.triggerState.controls);
390
+ }
391
+ syncVisibility() {
392
+ if (!this.panel)
393
+ return;
394
+ this.syncModalSemantics();
395
+ this.syncInertFocus();
396
+ this.panel.style.zIndex = this.inline ? "1" : "10000";
397
+ if (this.isOpen) {
398
+ this.exitGeneration++;
399
+ this.focusEdge = "first";
400
+ this.panel.hidden = false;
401
+ this.panel.inert = false;
402
+ this.panel.removeAttribute("aria-hidden");
403
+ try {
404
+ this.panel.showPopover?.();
405
+ }
406
+ catch {
407
+ this.panel.hidden = false;
408
+ }
409
+ this.position();
410
+ requestAnimationFrame(this.position);
411
+ if (!this.hasAttribute("unstable-disable-auto-focus"))
412
+ requestAnimationFrame(() => {
413
+ if (this.isOpen && !this.hasAttribute("unstable-disable-auto-focus"))
414
+ this.focusFirst();
415
+ });
416
+ }
417
+ else {
418
+ if (this.hasAttribute("motion")) {
419
+ this.panel.inert = true;
420
+ this.panel.setAttribute("aria-hidden", "true");
421
+ if (!this.panel.matches(":popover-open")) {
422
+ try {
423
+ this.panel.showPopover?.();
424
+ }
425
+ catch { /* Keep the closed state if the browser cannot restore the top layer. */ }
426
+ }
427
+ const generation = ++this.exitGeneration;
428
+ requestAnimationFrame(() => requestAnimationFrame(() => {
429
+ const animations = new Set();
430
+ const visit = (element) => {
431
+ for (const animation of element.getAnimations())
432
+ if (animation.playState === "running")
433
+ animations.add(animation);
434
+ if (element.shadowRoot)
435
+ for (const child of element.shadowRoot.children)
436
+ visit(child);
437
+ for (const child of element.children)
438
+ visit(child);
439
+ };
440
+ visit(this);
441
+ const finish = () => {
442
+ if (generation !== this.exitGeneration || this.isOpen || !this.panel)
443
+ return;
444
+ if (this.panel.hasAttribute("popover"))
445
+ this.panel.hidePopover?.();
446
+ this.panel.hidden = true;
447
+ };
448
+ if (animations.size)
449
+ void Promise.allSettled([...animations].map(animation => animation.finished)).then(finish);
450
+ else
451
+ finish();
452
+ }));
453
+ }
454
+ else {
455
+ if (this.panel.hasAttribute("popover"))
456
+ this.panel.hidePopover?.();
457
+ this.panel.hidden = true;
458
+ }
459
+ }
460
+ this.syncTrigger();
461
+ }
462
+ syncModalSemantics() {
463
+ if (!this.panel)
464
+ return;
465
+ if (this.trapFocusEnabled) {
466
+ this.panel.setAttribute("role", "dialog");
467
+ this.panel.setAttribute("aria-modal", "true");
468
+ }
469
+ else {
470
+ this.panel.setAttribute("role", "group");
471
+ this.panel.removeAttribute("aria-modal");
472
+ }
473
+ }
474
+ syncInertFocus() {
475
+ syncInertFocusState(this.ownerDocument, this, this.assigned("trigger"), this.isOpen && this.trapFocusEnabled && this.inertTrapFocusEnabled, this.externalPanel);
476
+ }
477
+ get positioningOptions() { return typeof this.positioning === "object" && this.positioning !== null ? this.positioning : undefined; }
478
+ syncPositionObserver() {
479
+ if (!this.resizeObserver)
480
+ return;
481
+ this.resizeObserver.disconnect();
482
+ const options = this.positioningOptions;
483
+ const target = options?.target;
484
+ const context = target && "contextElement" in target ? target.contextElement : target && "nodeType" in target ? target : undefined;
485
+ this.positioningTarget = context;
486
+ if (!options?.disableUpdateOnResize) {
487
+ if (this.panel)
488
+ this.resizeObserver.observe(this.panel);
489
+ if (this.trigger)
490
+ this.resizeObserver.observe(this.trigger);
491
+ if (context && context !== this.trigger)
492
+ this.resizeObserver.observe(context);
493
+ }
494
+ const ref = options?.positioningRef;
495
+ const value = {
496
+ updatePosition: this.position,
497
+ setTarget: targetValue => {
498
+ this.positioningValue = { ...(this.positioningOptions ?? {}), target: targetValue };
499
+ this.syncPositionObserver();
500
+ this.position();
501
+ },
502
+ };
503
+ if (typeof ref === "function")
504
+ ref(value);
505
+ else if (ref)
506
+ ref.current = value;
507
+ }
508
+ position = () => {
509
+ const trigger = this.trigger;
510
+ const panel = this.panel;
511
+ if (panel)
512
+ panel.style.zIndex = this.inline ? "1" : "10000";
513
+ const point = this.contextPoint;
514
+ const configured = this.positioning;
515
+ const target = typeof configured === "object" ? configured.target : undefined;
516
+ const targetElement = target && "nodeType" in target && target.nodeType === 1 ? target : undefined;
517
+ const targetContext = targetElement ?? (target && "contextElement" in target ? target.contextElement : undefined);
518
+ if (targetContext !== this.positioningTarget) {
519
+ if (this.positioningTarget)
520
+ this.resizeObserver?.unobserve(this.positioningTarget);
521
+ this.positioningTarget = targetContext;
522
+ if (targetContext && targetContext !== trigger)
523
+ this.resizeObserver?.observe(targetContext);
524
+ }
525
+ if (!this.isOpen || !panel || panel.hidden || (!trigger && !point && !target))
526
+ return;
527
+ const targetRect = target && "getBoundingClientRect" in target ? target.getBoundingClientRect() : undefined;
528
+ const anchor = targetRect ?? (point ? new DOMRect(point.x, point.y, 0, 0) : trigger.getBoundingClientRect());
529
+ const direction = this.getAttribute("dir") || (targetContext && getComputedStyle(targetContext).direction) || (trigger && getComputedStyle(trigger).direction) || getComputedStyle(this).direction || "ltr";
530
+ const shorthand = typeof configured === "string" ? configured : configured.position ?? "bottom";
531
+ const [rawPosition, shorthandAlign] = shorthand.split(/-(?=(?:start|end|top|bottom)$)/);
532
+ const aliases = { above: "top", below: "bottom", before: "start", after: "end" };
533
+ const placement = aliases[rawPosition] ?? rawPosition;
534
+ const align = (typeof configured === "object" ? configured.align : undefined) ?? shorthandAlign ?? this.getAttribute("align") ?? "center";
535
+ const rawOffset = typeof configured === "object" ? configured.offset : undefined;
536
+ const computedOffset = typeof rawOffset === "function" ? rawOffset({
537
+ positionedRect: { x: 0, y: 0, width: panel.offsetWidth, height: panel.offsetHeight },
538
+ targetRect: { x: anchor.x, y: anchor.y, width: anchor.width, height: anchor.height },
539
+ position: rawPosition === "above" || rawPosition === "below" || rawPosition === "before" || rawPosition === "after" ? rawPosition : "below",
540
+ alignment: align,
541
+ }) : rawOffset;
542
+ const offset = typeof computedOffset === "number" ? computedOffset : computedOffset?.mainAxis ?? Number(this.getAttribute("offset") ?? 8);
543
+ const crossAxisOffset = typeof computedOffset === "object" ? computedOffset.crossAxis ?? 0 : 0;
544
+ const fallbackPlacements = typeof configured === "object" ? (configured.fallbackPositions ?? []).map(fallback => {
545
+ const [fallbackSide, fallbackAlign] = fallback.split(/-(?=(?:start|end|top|bottom)$)/);
546
+ return { placement: aliases[fallbackSide] ?? fallbackSide, align: fallbackAlign ?? align };
547
+ }) : [];
548
+ const autoSize = typeof configured === "object" ? configured.autoSize : false;
549
+ const matchTargetSize = typeof configured === "object" && (configured.matchTargetSize === true || configured.matchTargetSize === "width");
550
+ const pinned = typeof configured === "object" && configured.pinned === true;
551
+ const coverTarget = typeof configured === "object" && configured.coverTarget === true;
552
+ const arrowPadding = Math.max(0, typeof configured === "object" ? configured.arrowPadding ?? 8 : 8);
553
+ const boundaryContext = targetContext ?? trigger ?? this;
554
+ const flipBoundary = typeof configured === "object" ? getBoundaryRect(configured.flipBoundary, boundaryContext) : undefined;
555
+ const overflowBoundary = typeof configured === "object" ? getBoundaryRect(configured.overflowBoundary, boundaryContext) : undefined;
556
+ const rawBoundaryPadding = typeof configured === "object" ? configured.overflowBoundaryPadding : undefined;
557
+ const boundaryPadding = typeof rawBoundaryPadding === "number"
558
+ ? { top: rawBoundaryPadding, right: rawBoundaryPadding, bottom: rawBoundaryPadding, left: rawBoundaryPadding }
559
+ : { top: rawBoundaryPadding?.top ?? 0, bottom: rawBoundaryPadding?.bottom ?? 0, left: direction === "rtl" ? rawBoundaryPadding?.end ?? 0 : rawBoundaryPadding?.start ?? 0, right: direction === "rtl" ? rawBoundaryPadding?.start ?? 0 : rawBoundaryPadding?.end ?? 0 };
560
+ panel.style.position = typeof configured === "object" ? configured.strategy ?? (configured.positionFixed === true ? "fixed" : "absolute") : "absolute";
561
+ panel.style.maxWidth = "";
562
+ panel.style.maxHeight = "";
563
+ panel.style.height = "";
564
+ panel.style.overflowX = "";
565
+ panel.style.overflowY = "";
566
+ panel.style.width = matchTargetSize ? `${anchor.width}px` : "";
567
+ const modifiers = { crossAxisOffset, coverTarget, overflowBoundaryPadding: boundaryPadding };
568
+ let result = getFloatingPosition(anchor, { width: panel.offsetWidth, height: panel.offsetHeight }, placement, direction === "rtl" ? "rtl" : "ltr", offset, undefined, align, fallbackPlacements, pinned, flipBoundary, overflowBoundary, modifiers);
569
+ const flipBounds = flipBoundary ?? viewportBoundaryRect();
570
+ const overflowsFlipBoundary = result.left < flipBounds.left || result.top < flipBounds.top || result.left + panel.offsetWidth > flipBounds.right || result.top + panel.offsetHeight > flipBounds.bottom;
571
+ if (this.positioningOptions?.shiftToCoverTarget && overflowsFlipBoundary && !coverTarget)
572
+ result = getFloatingPosition(anchor, { width: panel.offsetWidth, height: panel.offsetHeight }, placement, direction === "rtl" ? "rtl" : "ltr", offset, undefined, align, fallbackPlacements, pinned, flipBoundary, overflowBoundary, { ...modifiers, coverTarget: true });
573
+ const clampArrow = (value, size) => Math.max(arrowPadding, Math.min(value, Math.max(arrowPadding, size - arrowPadding - 8)));
574
+ const arrowInline = direction === "rtl" ? result.left + panel.offsetWidth - anchor.left - anchor.width / 2 - 4 : anchor.left + anchor.width / 2 - result.left - 4;
575
+ const arrowBlock = anchor.top + anchor.height / 2 - result.top - 4;
576
+ panel.style.setProperty("--spui-popover-arrow-inline-offset", `${clampArrow(arrowInline, panel.offsetWidth)}px`);
577
+ panel.style.setProperty("--spui-popover-arrow-block-offset", `${clampArrow(arrowBlock, panel.offsetHeight)}px`);
578
+ const applyMaxWidth = autoSize === true || autoSize === "always" || autoSize === "width" || autoSize === "width-always";
579
+ const applyMaxHeight = autoSize === true || autoSize === "always" || autoSize === "height" || autoSize === "height-always";
580
+ const viewportWidth = window.innerWidth;
581
+ const viewportHeight = window.innerHeight;
582
+ const availableWidth = Math.max(0, result.placement === "left" ? anchor.left - (overflowBoundary?.left ?? 0) - boundaryPadding.left - offset : result.placement === "right" ? (overflowBoundary?.right ?? viewportWidth) - boundaryPadding.right - anchor.right - offset : (overflowBoundary?.right ?? viewportWidth) - boundaryPadding.right - (overflowBoundary?.left ?? 0) - boundaryPadding.left - offset * 2);
583
+ const availableHeight = Math.max(0, result.placement === "top" ? anchor.top - (overflowBoundary?.top ?? 0) - boundaryPadding.top - offset : result.placement === "bottom" ? (overflowBoundary?.bottom ?? viewportHeight) - boundaryPadding.bottom - anchor.bottom - offset : (overflowBoundary?.bottom ?? viewportHeight) - boundaryPadding.bottom - (overflowBoundary?.top ?? 0) - boundaryPadding.top - offset * 2);
584
+ if (applyMaxWidth) {
585
+ panel.style.maxWidth = `${availableWidth}px`;
586
+ if (panel.offsetWidth > availableWidth) {
587
+ panel.style.width = `${availableWidth}px`;
588
+ panel.style.overflowX = "auto";
589
+ }
590
+ }
591
+ if (applyMaxHeight) {
592
+ panel.style.maxHeight = `${availableHeight}px`;
593
+ if (panel.offsetHeight > availableHeight) {
594
+ panel.style.height = `${availableHeight}px`;
595
+ panel.style.overflowY = "auto";
596
+ }
597
+ }
598
+ panel.dataset.placement = result.placement;
599
+ const strategy = this.positioningOptions?.strategy ?? (this.positioningOptions?.positionFixed === true ? "fixed" : "absolute");
600
+ panel.style.position = strategy;
601
+ const pageOffset = strategy === "absolute" ? { x: window.scrollX, y: window.scrollY } : { x: 0, y: 0 };
602
+ const left = result.left + pageOffset.x;
603
+ const top = result.top + pageOffset.y;
604
+ const useTransform = this.positioningOptions?.useTransform !== false;
605
+ panel.style.left = useTransform ? "0px" : `${left}px`;
606
+ panel.style.top = useTransform ? "0px" : `${top}px`;
607
+ panel.style.translate = useTransform ? `${left}px ${top}px` : "0px 0px";
608
+ const inlinePlacement = result.placement === "top" || result.placement === "bottom" ? result.align === "start" || result.align === "end" ? `-${result.align}` : "" : result.align === "top" ? "-start" : result.align === "bottom" ? "-end" : "";
609
+ const bounds = overflowBoundary ?? viewportBoundaryRect();
610
+ const detail = {
611
+ placement: `${result.placement}${inlinePlacement}`,
612
+ escaped: result.left < bounds.left || result.top < bounds.top || result.left + panel.offsetWidth > bounds.right || result.top + panel.offsetHeight > bounds.bottom,
613
+ referenceHidden: anchor.bottom <= 0 || anchor.top >= window.innerHeight || anchor.right <= 0 || anchor.left >= window.innerWidth,
614
+ };
615
+ this.positioningOptions?.onPositioningEnd?.(new CustomEvent("fui-positioningend", { detail }));
616
+ panel.dispatchEvent(new CustomEvent("fui-positioningend", { bubbles: true, detail }));
617
+ };
618
+ onScroll = (event) => {
619
+ if (!this.hasAttribute("close-on-scroll") && !this.hasAttribute("open-on-context")) {
620
+ this.position();
621
+ return;
622
+ }
623
+ const target = event.target;
624
+ if (!(target instanceof Node) || !this.contains(target) && !this.externalPanel?.contains(target))
625
+ this.setOpen(false, false, true, event);
626
+ };
627
+ onClick = (event) => {
628
+ const target = event.target;
629
+ if (!(target instanceof Node))
630
+ return;
631
+ const path = event.composedPath();
632
+ if (this.trigger && (this.trigger === target || this.trigger.contains(target) || path.includes(this.trigger))) {
633
+ if (!this.hasAttribute("open-on-context"))
634
+ this.setOpen(!this.isOpen, false, true, event);
635
+ return;
636
+ }
637
+ if (!event.defaultPrevented && this.assigned("content").some(content => content === target || content.contains(target)) && path.some(node => node instanceof Element && node.matches("[data-spui-popover-close]"))) {
638
+ this.setOpen(false, false, true, event);
639
+ this.focusTrigger();
640
+ }
641
+ };
642
+ onRequestClose = (event) => {
643
+ if (event.defaultPrevented)
644
+ return;
645
+ this.setOpen(false, false, true, event);
646
+ this.focusTrigger();
647
+ };
648
+ onContextMenu = (event) => {
649
+ if (!this.hasAttribute("open-on-context"))
650
+ return;
651
+ const target = event.target;
652
+ if (this.trigger && target instanceof Node && !this.trigger.contains(target) && !event.composedPath().includes(this.trigger))
653
+ return;
654
+ event.preventDefault();
655
+ this.contextPoint = { x: event.clientX, y: event.clientY };
656
+ this.setOpen(true, false, true, event);
657
+ this.position();
658
+ };
659
+ onHoverEnter = (event) => {
660
+ if (!this.hasAttribute("open-on-hover") || this.hasAttribute("open-on-context"))
661
+ return;
662
+ if (this.hoverTimer !== undefined)
663
+ clearTimeout(this.hoverTimer);
664
+ this.hoverTimer = undefined;
665
+ this.setOpen(true, false, true, event);
666
+ };
667
+ onHoverLeave = (event) => {
668
+ if (!this.hasAttribute("open-on-hover") || this.hasAttribute("open-on-context"))
669
+ return;
670
+ if (this.hoverTimer !== undefined)
671
+ clearTimeout(this.hoverTimer);
672
+ const delay = Number(this.getAttribute("mouse-leave-delay") ?? 500);
673
+ this.hoverTimer = setTimeout(() => { this.hoverTimer = undefined; this.setOpen(false, false, true, event); }, Number.isFinite(delay) ? Math.max(0, delay) : 500);
674
+ };
675
+ onWindowBlur = (event) => {
676
+ if (!this.isOpen || this.getAttribute("close-on-iframe-focus") === "false")
677
+ return;
678
+ if (this.iframeBlurTimer !== undefined)
679
+ clearTimeout(this.iframeBlurTimer);
680
+ this.iframeBlurTimer = setTimeout(() => {
681
+ this.iframeBlurTimer = undefined;
682
+ const active = this.ownerDocument.activeElement;
683
+ if (this.isConnected && this.isOpen && active instanceof HTMLIFrameElement && !this.contains(active) && !this.externalPanel?.contains(active))
684
+ this.setOpen(false, false, true, event);
685
+ }, 0);
686
+ };
687
+ onKeyDown = (event) => {
688
+ if (!this.isOpen)
689
+ return;
690
+ if (event.key === "Escape") {
691
+ event.preventDefault();
692
+ event.stopPropagation();
693
+ this.setOpen(false, false, true, event);
694
+ this.focusTrigger();
695
+ }
696
+ else if (event.key === "Tab" && this.trapFocusEnabled) {
697
+ this.focusEdge = event.shiftKey ? "last" : "first";
698
+ this.cycleFocus(event);
699
+ }
700
+ };
701
+ focusTrigger() {
702
+ const find = (root) => {
703
+ const selector = "button,a[href],input:not([type=hidden]),select,textarea,[tabindex],slot";
704
+ const candidates = [...(root instanceof HTMLElement && root.matches(selector) ? [root] : []), ...root.querySelectorAll(selector)];
705
+ for (const node of candidates) {
706
+ if (!node.hasAttribute("disabled") && node.tabIndex >= 0 && node.getClientRects().length)
707
+ return node;
708
+ if (node.shadowRoot) {
709
+ const nested = find(node.shadowRoot);
710
+ if (nested)
711
+ return nested;
712
+ }
713
+ if (node instanceof HTMLSlotElement)
714
+ for (const assigned of node.assignedElements({ flatten: true })) {
715
+ const nested = find(assigned);
716
+ if (nested)
717
+ return nested;
718
+ }
719
+ }
720
+ return undefined;
721
+ };
722
+ const trigger = this.trigger;
723
+ const target = trigger && find(trigger.shadowRoot ?? trigger);
724
+ if (target)
725
+ target.focus();
726
+ else
727
+ trigger?.focus();
728
+ }
729
+ onFocusOut = (event) => {
730
+ if (event.relatedTarget instanceof Node && (this.contains(event.relatedTarget) || this.externalPanel?.contains(event.relatedTarget)))
731
+ return;
732
+ if (this.trapFocusEnabled && this.inertTrapFocusEnabled)
733
+ return;
734
+ queueMicrotask(() => {
735
+ if (!this.isOpen || this.focusIsInside())
736
+ return;
737
+ if (!this.trapFocusEnabled)
738
+ this.setOpen(false, false, true, event);
739
+ else if (!this.inertTrapFocusEnabled)
740
+ this.focusEdge === "last" ? this.focusLast() : this.focusFirst();
741
+ });
742
+ };
743
+ focusables() {
744
+ const result = new Set();
745
+ const selector = "button,a[href],input:not([type=hidden]),select,textarea,[tabindex]";
746
+ const inspect = (node) => {
747
+ if (node instanceof HTMLElement && node.matches(selector) && !node.hasAttribute("disabled") && node.tabIndex >= 0 && node.getClientRects().length)
748
+ result.add(node);
749
+ if (node.shadowRoot)
750
+ visit(node.shadowRoot);
751
+ if (node instanceof HTMLSlotElement)
752
+ for (const assigned of node.assignedElements({ flatten: true }))
753
+ inspect(assigned);
754
+ };
755
+ const visit = (root) => {
756
+ for (const node of root.querySelectorAll("*"))
757
+ inspect(node);
758
+ };
759
+ for (const content of this.assigned("content"))
760
+ visit(content);
761
+ return [...result];
762
+ }
763
+ focusFirst = () => { (this.focusables()[0] ?? this.panel)?.focus(); };
764
+ focusLast = () => { const items = this.focusables(); items[items.length - 1]?.focus(); };
765
+ cycleFocus(event) {
766
+ const items = this.focusables();
767
+ if (!items.length) {
768
+ event.preventDefault();
769
+ this.panel?.focus();
770
+ return;
771
+ }
772
+ let active = document.activeElement;
773
+ while (active?.shadowRoot?.activeElement)
774
+ active = active.shadowRoot.activeElement;
775
+ const target = event.composedPath().find((node) => node instanceof HTMLElement && items.includes(node));
776
+ const activeIndex = target ? items.indexOf(target) : items.findIndex(item => item === active || Boolean(active && item.contains(active)));
777
+ if (event.shiftKey && activeIndex === 0) {
778
+ event.preventDefault();
779
+ items[items.length - 1].focus();
780
+ }
781
+ else if (!event.shiftKey && activeIndex === items.length - 1) {
782
+ event.preventDefault();
783
+ items[0].focus();
784
+ }
785
+ }
786
+ focusIsInside() {
787
+ let active = document.activeElement;
788
+ while (active) {
789
+ if (active === this || this.contains(active) || this.externalPanel?.contains(active))
790
+ return true;
791
+ active = active.shadowRoot?.activeElement ?? null;
792
+ }
793
+ return false;
794
+ }
795
+ onOutsidePointerDown = (event) => {
796
+ if (this.isOpen && event.target instanceof Node && !this.contains(event.target) && !this.externalPanel?.contains(event.target) && !event.composedPath().includes(this) && !event.composedPath().includes(this.externalPanel))
797
+ this.setOpen(false, false, true, event);
798
+ };
799
+ setOpen(value, force = false, notify = true, sourceEvent) {
800
+ const next = Boolean(value);
801
+ if (next === this.isOpen)
802
+ return;
803
+ if (!force && this.hasAttribute("data-spui-controlled-open")) {
804
+ this.emitOpenChange(next, sourceEvent);
805
+ return;
806
+ }
807
+ this.isOpen = next;
808
+ if (!next)
809
+ this.contextPoint = undefined;
810
+ if (this.hasAttribute("open") !== next)
811
+ this.toggleAttribute("open", next);
812
+ this.syncVisibility();
813
+ if (notify)
814
+ this.emitOpenChange(this.isOpen, sourceEvent);
815
+ }
816
+ emitOpenChange(open = this.isOpen, event) {
817
+ this.dispatchEvent(new CustomEvent("spui-open-change", { bubbles: true, composed: true, detail: { open, event } }));
818
+ }
819
+ }
820
+ customElements.define("spui-popover", SpuiPopover);
821
+ }
822
+ export function definePopoverVariant(tagName, defaults = {}) {
823
+ definePopover();
824
+ if (customElements.get(tagName))
825
+ return;
826
+ const BasePopover = customElements.get("spui-popover");
827
+ class SpuiPopoverVariant extends BasePopover {
828
+ static observedAttributes = BasePopover.observedAttributes;
829
+ connectedCallback() {
830
+ if (defaults.label && !this.hasAttribute("label"))
831
+ this.setAttribute("label", defaults.label);
832
+ if (defaults.trapFocus && !this.hasAttribute("trap-focus"))
833
+ this.setAttribute("trap-focus", "");
834
+ if (defaults.arrow && !this.hasAttribute("arrow"))
835
+ this.setAttribute("arrow", "");
836
+ super.connectedCallback();
837
+ }
838
+ }
839
+ customElements.define(tagName, SpuiPopoverVariant);
840
+ }