@spfx-fast/sharepoint-ui 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/QUICKSTART.md +18 -0
  2. package/README.md +19 -24
  3. package/components.json +240 -5694
  4. package/dist/accordion.d.ts +11 -0
  5. package/dist/accordion.js +4 -0
  6. package/dist/alert.d.ts +3 -0
  7. package/dist/alert.js +4 -0
  8. package/dist/attachment-list.d.ts +15 -0
  9. package/dist/attachment-list.js +14 -0
  10. package/dist/avatar-group.d.ts +12 -0
  11. package/dist/avatar-group.js +7 -0
  12. package/dist/avatar.d.ts +6 -0
  13. package/dist/avatar.js +5 -0
  14. package/dist/badge.d.ts +3 -0
  15. package/dist/badge.js +4 -0
  16. package/dist/breadcrumb.d.ts +10 -0
  17. package/dist/breadcrumb.js +9 -0
  18. package/dist/button-group.d.ts +6 -0
  19. package/dist/button-group.js +4 -0
  20. package/dist/button.d.ts +3 -0
  21. package/dist/button.js +4 -0
  22. package/dist/card.d.ts +3 -0
  23. package/dist/card.js +4 -0
  24. package/dist/carousel.d.ts +11 -0
  25. package/dist/carousel.js +6 -0
  26. package/dist/checkbox.d.ts +3 -0
  27. package/dist/checkbox.js +4 -0
  28. package/dist/combobox.d.ts +9 -0
  29. package/dist/combobox.js +6 -0
  30. package/dist/command-bar.d.ts +6 -0
  31. package/dist/command-bar.js +4 -0
  32. package/dist/copy-button.d.ts +7 -0
  33. package/dist/copy-button.js +19 -0
  34. package/dist/data-grid.d.ts +25 -0
  35. package/dist/data-grid.js +39 -0
  36. package/dist/date-range-picker.d.ts +12 -0
  37. package/dist/date-range-picker.js +4 -0
  38. package/dist/dialog.d.ts +9 -0
  39. package/dist/dialog.js +21 -0
  40. package/dist/divider.d.ts +3 -0
  41. package/dist/divider.js +4 -0
  42. package/dist/drawer.d.ts +6 -0
  43. package/dist/drawer.js +5 -0
  44. package/dist/empty-state.d.ts +7 -0
  45. package/dist/empty-state.js +5 -0
  46. package/dist/field.d.ts +15 -0
  47. package/dist/field.js +11 -0
  48. package/dist/file-dropzone.d.ts +15 -0
  49. package/dist/file-dropzone.js +21 -0
  50. package/dist/filter-bar.d.ts +9 -0
  51. package/dist/filter-bar.js +9 -0
  52. package/dist/image.d.ts +5 -0
  53. package/dist/image.js +4 -0
  54. package/dist/index.d.ts +68 -76
  55. package/dist/index.js +60 -66
  56. package/dist/input.d.ts +3 -0
  57. package/dist/input.js +4 -0
  58. package/dist/label.d.ts +3 -0
  59. package/dist/label.js +4 -0
  60. package/dist/link.d.ts +3 -0
  61. package/dist/link.js +9 -0
  62. package/dist/list-view.d.ts +29 -0
  63. package/dist/list-view.js +14 -0
  64. package/dist/list.d.ts +6 -0
  65. package/dist/list.js +5 -0
  66. package/dist/lookup-picker.d.ts +12 -0
  67. package/dist/lookup-picker.js +4 -0
  68. package/dist/menu.d.ts +6 -0
  69. package/dist/menu.js +4 -0
  70. package/dist/nav.d.ts +12 -0
  71. package/dist/nav.js +4 -0
  72. package/dist/pagination.d.ts +11 -0
  73. package/dist/pagination.js +5 -0
  74. package/dist/person-picker.d.ts +13 -0
  75. package/dist/person-picker.js +4 -0
  76. package/dist/persona.d.ts +7 -0
  77. package/dist/persona.js +5 -0
  78. package/dist/popover.d.ts +6 -0
  79. package/dist/popover.js +4 -0
  80. package/dist/progress-bar.d.ts +5 -0
  81. package/dist/progress-bar.js +5 -0
  82. package/dist/radio-group.d.ts +6 -0
  83. package/dist/radio-group.js +4 -0
  84. package/dist/radio.d.ts +3 -0
  85. package/dist/radio.js +4 -0
  86. package/dist/rating.d.ts +8 -0
  87. package/dist/rating.js +5 -0
  88. package/dist/react.d.ts +1 -4583
  89. package/dist/react.js +1 -6837
  90. package/dist/select.d.ts +3 -0
  91. package/dist/select.js +4 -0
  92. package/dist/sharepoint-field.d.ts +18 -0
  93. package/dist/sharepoint-field.js +54 -0
  94. package/dist/skeleton.d.ts +6 -0
  95. package/dist/skeleton.js +9 -0
  96. package/dist/spinner.d.ts +5 -0
  97. package/dist/spinner.js +4 -0
  98. package/dist/swatch-picker.d.ts +17 -0
  99. package/dist/swatch-picker.js +11 -0
  100. package/dist/switch.d.ts +3 -0
  101. package/dist/switch.js +4 -0
  102. package/dist/table.d.ts +6 -0
  103. package/dist/table.js +4 -0
  104. package/dist/tabs.d.ts +14 -0
  105. package/dist/tabs.js +30 -0
  106. package/dist/tag-group.d.ts +6 -0
  107. package/dist/tag-group.js +4 -0
  108. package/dist/tag-picker.d.ts +12 -0
  109. package/dist/tag-picker.js +4 -0
  110. package/dist/tag.d.ts +7 -0
  111. package/dist/tag.js +5 -0
  112. package/dist/text.d.ts +5 -0
  113. package/dist/text.js +4 -0
  114. package/dist/textarea.d.ts +3 -0
  115. package/dist/textarea.js +4 -0
  116. package/dist/theme.d.ts +15 -167
  117. package/dist/theme.js +36 -162
  118. package/dist/toast.d.ts +9 -0
  119. package/dist/toast.js +12 -0
  120. package/dist/tree.d.ts +13 -0
  121. package/dist/tree.js +9 -0
  122. package/dist/view-card.d.ts +8 -0
  123. package/dist/view-card.js +4 -0
  124. package/dist/view-switcher.d.ts +13 -0
  125. package/dist/view-switcher.js +7 -0
  126. package/dist/virtual-list.d.ts +11 -0
  127. package/dist/virtual-list.js +25 -0
  128. package/measurements.json +365 -0
  129. package/package.json +19 -42
  130. package/src/theme.css +125 -0
  131. package/AGENTS.md +0 -211
  132. package/dist/appearance.d.ts +0 -6
  133. package/dist/appearance.js +0 -217
  134. package/dist/elements/accordion.d.ts +0 -9
  135. package/dist/elements/accordion.js +0 -338
  136. package/dist/elements/alert.d.ts +0 -2
  137. package/dist/elements/alert.js +0 -62
  138. package/dist/elements/avatar-group-primitives.d.ts +0 -3
  139. package/dist/elements/avatar-group-primitives.js +0 -189
  140. package/dist/elements/avatar-group.d.ts +0 -2
  141. package/dist/elements/avatar-group.js +0 -110
  142. package/dist/elements/avatar.d.ts +0 -4
  143. package/dist/elements/avatar.js +0 -251
  144. package/dist/elements/badge.d.ts +0 -8
  145. package/dist/elements/badge.js +0 -182
  146. package/dist/elements/breadcrumb.d.ts +0 -4
  147. package/dist/elements/breadcrumb.js +0 -233
  148. package/dist/elements/button-group.d.ts +0 -1
  149. package/dist/elements/button-group.js +0 -20
  150. package/dist/elements/button-variants.d.ts +0 -6
  151. package/dist/elements/button-variants.js +0 -339
  152. package/dist/elements/button.d.ts +0 -5
  153. package/dist/elements/button.js +0 -233
  154. package/dist/elements/card-primitives.d.ts +0 -3
  155. package/dist/elements/card-primitives.js +0 -83
  156. package/dist/elements/card.d.ts +0 -5
  157. package/dist/elements/card.js +0 -213
  158. package/dist/elements/carousel.d.ts +0 -92
  159. package/dist/elements/carousel.js +0 -1112
  160. package/dist/elements/color-picker.d.ts +0 -24
  161. package/dist/elements/color-picker.js +0 -1004
  162. package/dist/elements/combobox.d.ts +0 -1
  163. package/dist/elements/combobox.js +0 -1012
  164. package/dist/elements/data-grid-model.d.ts +0 -117
  165. package/dist/elements/data-grid-model.js +0 -294
  166. package/dist/elements/data-grid.d.ts +0 -29
  167. package/dist/elements/data-grid.js +0 -1567
  168. package/dist/elements/date-picker.d.ts +0 -1
  169. package/dist/elements/date-picker.js +0 -486
  170. package/dist/elements/dialog.d.ts +0 -23
  171. package/dist/elements/dialog.js +0 -409
  172. package/dist/elements/divider.d.ts +0 -2
  173. package/dist/elements/divider.js +0 -66
  174. package/dist/elements/drawer.d.ts +0 -3
  175. package/dist/elements/drawer.js +0 -308
  176. package/dist/elements/field-composition.d.ts +0 -26
  177. package/dist/elements/field-composition.js +0 -133
  178. package/dist/elements/field.d.ts +0 -1
  179. package/dist/elements/field.js +0 -67
  180. package/dist/elements/file-upload.d.ts +0 -6
  181. package/dist/elements/file-upload.js +0 -211
  182. package/dist/elements/floating-position.d.ts +0 -26
  183. package/dist/elements/floating-position.js +0 -39
  184. package/dist/elements/image.d.ts +0 -28
  185. package/dist/elements/image.js +0 -38
  186. package/dist/elements/info.d.ts +0 -9
  187. package/dist/elements/info.js +0 -102
  188. package/dist/elements/input.d.ts +0 -5
  189. package/dist/elements/input.js +0 -255
  190. package/dist/elements/label.d.ts +0 -15
  191. package/dist/elements/label.js +0 -56
  192. package/dist/elements/link.d.ts +0 -1
  193. package/dist/elements/link.js +0 -92
  194. package/dist/elements/list.d.ts +0 -19
  195. package/dist/elements/list.js +0 -297
  196. package/dist/elements/menu.d.ts +0 -33
  197. package/dist/elements/menu.js +0 -1103
  198. package/dist/elements/message-bar.d.ts +0 -10
  199. package/dist/elements/message-bar.js +0 -299
  200. package/dist/elements/nav.d.ts +0 -32
  201. package/dist/elements/nav.js +0 -791
  202. package/dist/elements/overflow.d.ts +0 -9
  203. package/dist/elements/overflow.js +0 -218
  204. package/dist/elements/persona.d.ts +0 -3
  205. package/dist/elements/persona.js +0 -125
  206. package/dist/elements/popover.d.ts +0 -77
  207. package/dist/elements/popover.js +0 -840
  208. package/dist/elements/progress-bar.d.ts +0 -4
  209. package/dist/elements/progress-bar.js +0 -82
  210. package/dist/elements/radio-group.d.ts +0 -1
  211. package/dist/elements/radio-group.js +0 -303
  212. package/dist/elements/rating-display.d.ts +0 -2
  213. package/dist/elements/rating-display.js +0 -125
  214. package/dist/elements/rating.d.ts +0 -2
  215. package/dist/elements/rating.js +0 -206
  216. package/dist/elements/rich-text-editor.d.ts +0 -18
  217. package/dist/elements/rich-text-editor.js +0 -508
  218. package/dist/elements/search.d.ts +0 -3
  219. package/dist/elements/search.js +0 -207
  220. package/dist/elements/select.d.ts +0 -6
  221. package/dist/elements/select.js +0 -291
  222. package/dist/elements/shared.d.ts +0 -19
  223. package/dist/elements/shared.js +0 -396
  224. package/dist/elements/skeleton.d.ts +0 -6
  225. package/dist/elements/skeleton.js +0 -91
  226. package/dist/elements/slider.d.ts +0 -1
  227. package/dist/elements/slider.js +0 -248
  228. package/dist/elements/spin-button.d.ts +0 -7
  229. package/dist/elements/spin-button.js +0 -499
  230. package/dist/elements/spinner.d.ts +0 -4
  231. package/dist/elements/spinner.js +0 -124
  232. package/dist/elements/swatch-picker.d.ts +0 -5
  233. package/dist/elements/swatch-picker.js +0 -363
  234. package/dist/elements/table.d.ts +0 -21
  235. package/dist/elements/table.js +0 -261
  236. package/dist/elements/tabs.d.ts +0 -1
  237. package/dist/elements/tabs.js +0 -276
  238. package/dist/elements/tag-picker.d.ts +0 -29
  239. package/dist/elements/tag-picker.js +0 -697
  240. package/dist/elements/tags.d.ts +0 -21
  241. package/dist/elements/tags.js +0 -421
  242. package/dist/elements/teaching-popover.d.ts +0 -32
  243. package/dist/elements/teaching-popover.js +0 -412
  244. package/dist/elements/text.d.ts +0 -9
  245. package/dist/elements/text.js +0 -75
  246. package/dist/elements/textarea.d.ts +0 -1
  247. package/dist/elements/textarea.js +0 -182
  248. package/dist/elements/toast.d.ts +0 -25
  249. package/dist/elements/toast.js +0 -337
  250. package/dist/elements/toaster.d.ts +0 -6
  251. package/dist/elements/toaster.js +0 -356
  252. package/dist/elements/toggle.d.ts +0 -2
  253. package/dist/elements/toggle.js +0 -246
  254. package/dist/elements/toolbar.d.ts +0 -12
  255. package/dist/elements/toolbar.js +0 -381
  256. package/dist/elements/tooltip.d.ts +0 -76
  257. package/dist/elements/tooltip.js +0 -510
  258. package/dist/elements/tree-model.d.ts +0 -19
  259. package/dist/elements/tree-model.js +0 -34
  260. package/dist/elements/tree.d.ts +0 -5
  261. package/dist/elements/tree.js +0 -273
  262. package/dist/elements/view-card.d.ts +0 -2
  263. package/dist/elements/view-card.js +0 -78
  264. package/dist/elements/virtual-list.d.ts +0 -41
  265. package/dist/elements/virtual-list.js +0 -369
  266. package/dist/elements.d.ts +0 -72
  267. package/dist/elements.js +0 -207
  268. package/dist/virtualizer-hooks.d.ts +0 -80
  269. package/dist/virtualizer-hooks.js +0 -253
  270. package/scripts/generate-token-index.mjs +0 -43
  271. package/scripts/sync-component-catalog-signatures.mjs +0 -55
  272. package/scripts/validate-component-catalog.mjs +0 -44
  273. package/skills/sharepoint-ui/SKILL.md +0 -12
  274. package/skills/sharepoint-ui/references/choosing-components.md +0 -20
  275. package/skills/sharepoint-ui/references/sharepoint-patterns.md +0 -17
  276. package/tokens.json +0 -3777
@@ -1,840 +0,0 @@
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
- }