@pure-ds/core 0.7.42 → 0.7.43
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.
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/assets/js/app.js +1 -1
- package/public/assets/js/pds-enhancers.js +1 -1
- package/public/assets/js/pds-manager.js +67 -63
- package/public/assets/pds/core/pds-enhancers.js +1 -1
- package/public/assets/pds/core/pds-manager.js +67 -63
- package/src/js/pds-core/pds-enhancers.js +89 -192
- package/src/js/pds-core/pds-generator.js +16 -12
|
@@ -61,6 +61,11 @@ function enhanceDropdown(elem) {
|
|
|
61
61
|
elem.querySelector("[data-dropdown-toggle]") ||
|
|
62
62
|
elem.querySelector("button");
|
|
63
63
|
|
|
64
|
+
const supportsPopover =
|
|
65
|
+
typeof HTMLElement !== "undefined" &&
|
|
66
|
+
"showPopover" in HTMLElement.prototype &&
|
|
67
|
+
"hidePopover" in HTMLElement.prototype;
|
|
68
|
+
|
|
64
69
|
if (trigger && !trigger.hasAttribute("type")) {
|
|
65
70
|
trigger.setAttribute("type", "button");
|
|
66
71
|
}
|
|
@@ -84,6 +89,19 @@ function enhanceDropdown(elem) {
|
|
|
84
89
|
trigger.setAttribute("aria-expanded", "false");
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
if (!supportsPopover) {
|
|
93
|
+
const warnKey = "__PDS_DROPDOWN_POPOVER_WARNED__";
|
|
94
|
+
if (!globalThis[warnKey]) {
|
|
95
|
+
globalThis[warnKey] = true;
|
|
96
|
+
console.warn(
|
|
97
|
+
"[PDS] nav[data-dropdown] requires the Popover API. Add a popover polyfill (recommended: @oddbird/popover-polyfill) for browsers without support.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
menu.setAttribute("popover", "auto");
|
|
104
|
+
|
|
87
105
|
const measureMenuSize = () => {
|
|
88
106
|
const previousStyle = menu.getAttribute("style");
|
|
89
107
|
menu.style.visibility = "hidden";
|
|
@@ -108,6 +126,24 @@ function enhanceDropdown(elem) {
|
|
|
108
126
|
return { width, height };
|
|
109
127
|
};
|
|
110
128
|
|
|
129
|
+
const isPopoverOpen = () => {
|
|
130
|
+
try {
|
|
131
|
+
return menu.matches(":popover-open");
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const syncClosedState = () => {
|
|
138
|
+
menu.setAttribute("aria-hidden", "true");
|
|
139
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const syncOpenState = () => {
|
|
143
|
+
menu.setAttribute("aria-hidden", "false");
|
|
144
|
+
trigger?.setAttribute("aria-expanded", "true");
|
|
145
|
+
};
|
|
146
|
+
|
|
111
147
|
const resolveDirection = () => {
|
|
112
148
|
const mode = (
|
|
113
149
|
elem.getAttribute("data-direction") ||
|
|
@@ -170,136 +206,23 @@ function enhanceDropdown(elem) {
|
|
|
170
206
|
};
|
|
171
207
|
|
|
172
208
|
const clearFloatingMenuPosition = () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
[
|
|
210
|
+
"position",
|
|
211
|
+
"left",
|
|
212
|
+
"top",
|
|
213
|
+
"right",
|
|
214
|
+
"bottom",
|
|
215
|
+
"margin-top",
|
|
216
|
+
"margin-bottom",
|
|
217
|
+
"max-width",
|
|
218
|
+
"max-inline-size",
|
|
219
|
+
"max-height",
|
|
220
|
+
"overflow",
|
|
221
|
+
].forEach((prop) => menu.style.removeProperty(prop));
|
|
184
222
|
};
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
if (!
|
|
188
|
-
if (node.parentElement) return node.parentElement;
|
|
189
|
-
const root = node.getRootNode?.();
|
|
190
|
-
return root instanceof ShadowRoot ? root.host : null;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const hasNonViewportFixedContainingBlock = () => {
|
|
194
|
-
let current = getContainingAncestor(menu);
|
|
195
|
-
while (current && current !== document.documentElement) {
|
|
196
|
-
const style = getComputedStyle(current);
|
|
197
|
-
const contain = style.contain || "";
|
|
198
|
-
const willChange = style.willChange || "";
|
|
199
|
-
const createsContainingBlock =
|
|
200
|
-
style.transform !== "none" ||
|
|
201
|
-
style.perspective !== "none" ||
|
|
202
|
-
style.filter !== "none" ||
|
|
203
|
-
style.backdropFilter !== "none" ||
|
|
204
|
-
contain.includes("paint") ||
|
|
205
|
-
contain.includes("layout") ||
|
|
206
|
-
contain.includes("strict") ||
|
|
207
|
-
contain.includes("content") ||
|
|
208
|
-
willChange.includes("transform") ||
|
|
209
|
-
willChange.includes("perspective") ||
|
|
210
|
-
willChange.includes("filter");
|
|
211
|
-
|
|
212
|
-
if (createsContainingBlock) {
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
current = getContainingAncestor(current);
|
|
217
|
-
}
|
|
218
|
-
return false;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const collectClippingAncestors = (startNode, stopAt = null) => {
|
|
222
|
-
const targets = [];
|
|
223
|
-
let current = getContainingAncestor(startNode);
|
|
224
|
-
|
|
225
|
-
while (current instanceof Element) {
|
|
226
|
-
const tagName = String(current.tagName || "").toUpperCase();
|
|
227
|
-
if (tagName === "HTML" || tagName === "BODY") {
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const style = getComputedStyle(current);
|
|
232
|
-
const clips = [style.overflow, style.overflowX, style.overflowY].some(
|
|
233
|
-
(value) => value && value !== "visible",
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (clips) {
|
|
237
|
-
targets.push(current);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (stopAt && current === stopAt) {
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
current = getContainingAncestor(current);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return targets;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const setOverlayClippingOverride = (enabled, stopAt = null) => {
|
|
251
|
-
if (enabled) {
|
|
252
|
-
if (overlayOverflowOverrides.size) return;
|
|
253
|
-
|
|
254
|
-
const targets = collectClippingAncestors(menu, stopAt);
|
|
255
|
-
targets.forEach((element) => {
|
|
256
|
-
overlayOverflowOverrides.set(element, {
|
|
257
|
-
overflow: element.style.overflow,
|
|
258
|
-
overflowX: element.style.overflowX,
|
|
259
|
-
overflowY: element.style.overflowY,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
element.style.overflow = "visible";
|
|
263
|
-
element.style.overflowX = "visible";
|
|
264
|
-
element.style.overflowY = "visible";
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
overlayOverflowOverrides.forEach((previous, element) => {
|
|
271
|
-
element.style.overflow = previous.overflow;
|
|
272
|
-
element.style.overflowX = previous.overflowX;
|
|
273
|
-
element.style.overflowY = previous.overflowY;
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
overlayOverflowOverrides.clear();
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const reattachFloatingMenu = () => {
|
|
280
|
-
if (menu.getAttribute("aria-hidden") !== "false") return;
|
|
281
|
-
clearFloatingMenuPosition();
|
|
282
|
-
requestAnimationFrame(() => {
|
|
283
|
-
requestAnimationFrame(() => {
|
|
284
|
-
positionFloatingMenu();
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const positionFloatingMenu = () => {
|
|
290
|
-
if (menu.getAttribute("aria-hidden") !== "false") return;
|
|
291
|
-
const hasFixedContainingBlock = hasNonViewportFixedContainingBlock();
|
|
292
|
-
|
|
293
|
-
if (hasFixedContainingBlock) {
|
|
294
|
-
// Fixed overlay is unsafe in this context; keep local positioning,
|
|
295
|
-
// but temporarily unclip ancestor overflow containers.
|
|
296
|
-
setOverlayClippingOverride(true);
|
|
297
|
-
clearFloatingMenuPosition();
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Fixed overlay path does not need clipping overrides.
|
|
302
|
-
setOverlayClippingOverride(false);
|
|
223
|
+
|
|
224
|
+
const positionPopoverMenu = () => {
|
|
225
|
+
if (!isPopoverOpen()) return;
|
|
303
226
|
|
|
304
227
|
const anchorRect = (trigger || elem).getBoundingClientRect();
|
|
305
228
|
const viewport = window.visualViewport;
|
|
@@ -350,19 +273,21 @@ function enhanceDropdown(elem) {
|
|
|
350
273
|
),
|
|
351
274
|
);
|
|
352
275
|
|
|
353
|
-
menu.style
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
276
|
+
Object.assign(menu.style, {
|
|
277
|
+
position: "fixed",
|
|
278
|
+
left: `${Math.round(left)}px`,
|
|
279
|
+
top: `${Math.round(top)}px`,
|
|
280
|
+
right: "auto",
|
|
281
|
+
bottom: "auto",
|
|
282
|
+
marginTop: "0",
|
|
283
|
+
marginBottom: "0",
|
|
284
|
+
});
|
|
360
285
|
};
|
|
361
286
|
|
|
362
287
|
let repositionHandler = null;
|
|
363
288
|
const bindReposition = () => {
|
|
364
289
|
if (repositionHandler) return;
|
|
365
|
-
repositionHandler = () =>
|
|
290
|
+
repositionHandler = () => positionPopoverMenu();
|
|
366
291
|
window.addEventListener("resize", repositionHandler);
|
|
367
292
|
window.addEventListener("scroll", repositionHandler, true);
|
|
368
293
|
};
|
|
@@ -376,11 +301,10 @@ function enhanceDropdown(elem) {
|
|
|
376
301
|
|
|
377
302
|
let configChangedHandler = null;
|
|
378
303
|
let configRepositionFrame = null;
|
|
379
|
-
const overlayOverflowOverrides = new Map();
|
|
380
304
|
const bindConfigChanged = () => {
|
|
381
305
|
if (configChangedHandler || typeof document === "undefined") return;
|
|
382
306
|
configChangedHandler = () => {
|
|
383
|
-
if (
|
|
307
|
+
if (!isPopoverOpen()) return;
|
|
384
308
|
elem.dataset.dropdownDirection = resolveDirection();
|
|
385
309
|
elem.dataset.dropdownAlign = resolveAlign();
|
|
386
310
|
|
|
@@ -389,8 +313,8 @@ function enhanceDropdown(elem) {
|
|
|
389
313
|
}
|
|
390
314
|
configRepositionFrame = requestAnimationFrame(() => {
|
|
391
315
|
configRepositionFrame = null;
|
|
392
|
-
if (
|
|
393
|
-
|
|
316
|
+
if (!isPopoverOpen()) return;
|
|
317
|
+
positionPopoverMenu();
|
|
394
318
|
});
|
|
395
319
|
};
|
|
396
320
|
document.addEventListener("pds:config-changed", configChangedHandler);
|
|
@@ -406,60 +330,46 @@ function enhanceDropdown(elem) {
|
|
|
406
330
|
}
|
|
407
331
|
};
|
|
408
332
|
|
|
409
|
-
|
|
410
|
-
|
|
333
|
+
menu.addEventListener("toggle", (event) => {
|
|
334
|
+
const isOpen = event.newState === "open";
|
|
411
335
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
bindReposition();
|
|
419
|
-
bindConfigChanged();
|
|
420
|
-
reattachFloatingMenu();
|
|
421
|
-
|
|
422
|
-
// Add click-outside handler when opening
|
|
423
|
-
if (!clickHandler) {
|
|
424
|
-
clickHandler = (event) => {
|
|
425
|
-
// Use composedPath() to handle Shadow DOM
|
|
426
|
-
const path = event.composedPath ? event.composedPath() : [event.target];
|
|
427
|
-
const clickedInside = path.some((node) => node === elem);
|
|
428
|
-
|
|
429
|
-
if (!clickedInside) {
|
|
430
|
-
closeMenu();
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
// Use a slight delay to avoid closing immediately if this was triggered by a click
|
|
434
|
-
setTimeout(() => {
|
|
435
|
-
document.addEventListener("click", clickHandler);
|
|
436
|
-
}, 0);
|
|
336
|
+
if (isOpen) {
|
|
337
|
+
syncOpenState();
|
|
338
|
+
positionPopoverMenu();
|
|
339
|
+
bindReposition();
|
|
340
|
+
bindConfigChanged();
|
|
341
|
+
return;
|
|
437
342
|
}
|
|
438
|
-
};
|
|
439
343
|
|
|
440
|
-
|
|
441
|
-
menu.setAttribute("aria-hidden", "true");
|
|
442
|
-
trigger?.setAttribute("aria-expanded", "false");
|
|
344
|
+
syncClosedState();
|
|
443
345
|
unbindReposition();
|
|
444
346
|
unbindConfigChanged();
|
|
445
347
|
clearFloatingMenuPosition();
|
|
446
|
-
|
|
348
|
+
});
|
|
447
349
|
|
|
448
|
-
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
350
|
+
const openMenu = () => {
|
|
351
|
+
if (isPopoverOpen()) return;
|
|
352
|
+
elem.dataset.dropdownDirection = resolveDirection();
|
|
353
|
+
elem.dataset.dropdownAlign = resolveAlign();
|
|
354
|
+
menu.showPopover();
|
|
355
|
+
requestAnimationFrame(() => positionPopoverMenu());
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const closeMenu = () => {
|
|
359
|
+
if (!isPopoverOpen()) return;
|
|
360
|
+
menu.hidePopover();
|
|
453
361
|
};
|
|
454
362
|
|
|
455
363
|
const toggleMenu = () => {
|
|
456
|
-
if (
|
|
364
|
+
if (isPopoverOpen()) {
|
|
457
365
|
closeMenu();
|
|
458
366
|
} else {
|
|
459
367
|
openMenu();
|
|
460
368
|
}
|
|
461
369
|
};
|
|
462
370
|
|
|
371
|
+
syncClosedState();
|
|
372
|
+
|
|
463
373
|
trigger?.addEventListener("click", (event) => {
|
|
464
374
|
event.preventDefault();
|
|
465
375
|
event.stopPropagation();
|
|
@@ -472,19 +382,6 @@ function enhanceDropdown(elem) {
|
|
|
472
382
|
trigger?.focus();
|
|
473
383
|
}
|
|
474
384
|
});
|
|
475
|
-
|
|
476
|
-
elem.addEventListener("focusout", (event) => {
|
|
477
|
-
// Only close if focus is explicitly moving to an element outside the dropdown
|
|
478
|
-
// Don't close if relatedTarget is null (which happens when clicking non-focusable elements inside)
|
|
479
|
-
// Use composedPath() to handle Shadow DOM properly
|
|
480
|
-
if (event.relatedTarget) {
|
|
481
|
-
const path = event.composedPath ? event.composedPath() : [event.relatedTarget];
|
|
482
|
-
const focusedInside = path.some((node) => node === elem);
|
|
483
|
-
if (!focusedInside) {
|
|
484
|
-
closeMenu();
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
385
|
}
|
|
489
386
|
|
|
490
387
|
function enhanceToggle(elem) {
|
|
@@ -3527,6 +3527,11 @@ dialog[open] {
|
|
|
3527
3527
|
animation: pds-dialog-enter var(--transition-normal) ease;
|
|
3528
3528
|
}
|
|
3529
3529
|
|
|
3530
|
+
html:has(dialog[open]:modal) {
|
|
3531
|
+
overflow: hidden;
|
|
3532
|
+
scrollbar-gutter: stable;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3530
3535
|
@keyframes pds-dialog-enter {
|
|
3531
3536
|
from {
|
|
3532
3537
|
opacity: 0;
|
|
@@ -3582,12 +3587,11 @@ dialog {
|
|
|
3582
3587
|
|
|
3583
3588
|
/*
|
|
3584
3589
|
* Overlay safety valve:
|
|
3585
|
-
* Some controls (e.g. pds-daterange panel
|
|
3590
|
+
* Some controls (e.g. pds-daterange panel) need to escape
|
|
3586
3591
|
* the dialog bounds. Scope overflow visibility to custom dialogs that contain
|
|
3587
3592
|
* those controls instead of enabling it for all dialogs.
|
|
3588
3593
|
*/
|
|
3589
|
-
dialog.dialog-custom:has(pds-daterange)
|
|
3590
|
-
dialog.dialog-custom:has([data-dropdown]) {
|
|
3594
|
+
dialog.dialog-custom:has(pds-daterange) {
|
|
3591
3595
|
overflow: visible;
|
|
3592
3596
|
}
|
|
3593
3597
|
|
|
@@ -3657,13 +3661,6 @@ dialog {
|
|
|
3657
3661
|
overflow-x: visible;
|
|
3658
3662
|
}
|
|
3659
3663
|
|
|
3660
|
-
/* Allow overlay menus (e.g. data-dropdown) to escape dialog-body clipping while open */
|
|
3661
|
-
article:has([data-dropdown] > :last-child[aria-hidden="false"]),
|
|
3662
|
-
form > article:has([data-dropdown] > :last-child[aria-hidden="false"]),
|
|
3663
|
-
.dialog-body:has([data-dropdown] > :last-child[aria-hidden="false"]) {
|
|
3664
|
-
overflow: visible;
|
|
3665
|
-
}
|
|
3666
|
-
|
|
3667
3664
|
article:has(pds-daterange),
|
|
3668
3665
|
form > article:has(pds-daterange),
|
|
3669
3666
|
.dialog-body:has(pds-daterange) {
|
|
@@ -4096,7 +4093,13 @@ nav[data-dropdown] {
|
|
|
4096
4093
|
transition-behavior: allow-discrete;
|
|
4097
4094
|
}
|
|
4098
4095
|
|
|
4099
|
-
& > :last-child[
|
|
4096
|
+
& > :last-child[popover] {
|
|
4097
|
+
inset: auto;
|
|
4098
|
+
margin: 0;
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
& > :last-child[aria-hidden="false"],
|
|
4102
|
+
& > :last-child:popover-open {
|
|
4100
4103
|
display: inline-block;
|
|
4101
4104
|
opacity: 1;
|
|
4102
4105
|
visibility: visible;
|
|
@@ -4198,7 +4201,8 @@ nav[data-dropdown] {
|
|
|
4198
4201
|
}
|
|
4199
4202
|
|
|
4200
4203
|
@starting-style {
|
|
4201
|
-
nav[data-dropdown] > :last-child[aria-hidden="false"]
|
|
4204
|
+
nav[data-dropdown] > :last-child[aria-hidden="false"],
|
|
4205
|
+
nav[data-dropdown] > :last-child:popover-open {
|
|
4202
4206
|
opacity: 0;
|
|
4203
4207
|
}
|
|
4204
4208
|
}
|