@spfx-fast/sharepoint-ui 0.4.3 → 0.5.0

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