@pine-ds/core 4.6.0 → 4.7.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.
- package/components/pds-combobox.js +1 -1
- package/components/pds-drawer.js +1 -1
- package/components/pds-dropdown-menu.js +1 -1
- package/components/pds-modal2.js +1 -1
- package/components/pds-multiselect.js +1 -1
- package/components/pds-popover.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/pds-combobox.cjs.entry.js +33 -3
- package/dist/cjs/pds-drawer.cjs.entry.js +23 -10
- package/dist/cjs/pds-dropdown-menu.cjs.entry.js +9 -5
- package/dist/cjs/pds-modal.cjs.entry.js +116 -31
- package/dist/cjs/pds-multiselect.cjs.entry.js +6 -5
- package/dist/cjs/pds-popover.cjs.entry.js +170 -26
- package/dist/cjs/pine-core.cjs.js +1 -1
- package/dist/collection/components/pds-combobox/pds-combobox.js +33 -3
- package/dist/collection/components/pds-drawer/pds-drawer.css +4 -1
- package/dist/collection/components/pds-drawer/pds-drawer.js +54 -17
- package/dist/collection/components/pds-drawer/stories/pds-drawer.stories.js +6 -1
- package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +15 -5
- package/dist/collection/components/pds-modal/pds-modal.js +117 -32
- package/dist/collection/components/pds-multiselect/pds-multiselect.js +8 -7
- package/dist/collection/components/pds-popover/pds-popover.js +171 -27
- package/dist/docs.json +42 -7
- package/dist/esm/loader.js +1 -1
- package/dist/esm/pds-combobox.entry.js +33 -3
- package/dist/esm/pds-drawer.entry.js +23 -10
- package/dist/esm/pds-dropdown-menu.entry.js +9 -5
- package/dist/esm/pds-modal.entry.js +116 -31
- package/dist/esm/pds-multiselect.entry.js +6 -5
- package/dist/esm/pds-popover.entry.js +171 -27
- package/dist/esm/pine-core.js +1 -1
- package/dist/esm-es5/loader.js +1 -1
- package/dist/esm-es5/pds-combobox.entry.js +1 -1
- package/dist/esm-es5/pds-drawer.entry.js +1 -1
- package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
- package/dist/esm-es5/pds-modal.entry.js +1 -1
- package/dist/esm-es5/pds-multiselect.entry.js +1 -1
- package/dist/esm-es5/pds-popover.entry.js +1 -1
- package/dist/esm-es5/pine-core.js +1 -1
- package/dist/pine-core/p-153ba77b.entry.js +1 -0
- package/dist/pine-core/p-21fe6ee1.system.entry.js +1 -0
- package/dist/pine-core/p-24fbbb74.system.entry.js +1 -0
- package/dist/pine-core/{p-df539c42.system.entry.js → p-5a10784e.system.entry.js} +1 -1
- package/dist/pine-core/p-74a05bcc.entry.js +1 -0
- package/dist/pine-core/p-92ae6082.entry.js +1 -0
- package/dist/pine-core/p-945fb0b1.entry.js +1 -0
- package/dist/pine-core/p-CuSNnuFu.system.js +1 -1
- package/dist/pine-core/p-a0344e30.entry.js +2 -0
- package/dist/pine-core/p-a787b34a.system.entry.js +1 -0
- package/dist/pine-core/{p-646afbca.system.entry.js → p-b60ac7eb.system.entry.js} +1 -1
- package/dist/pine-core/p-bf5887e8.system.entry.js +1 -0
- package/dist/pine-core/p-e21ff692.entry.js +1 -0
- package/dist/pine-core/pine-core.esm.js +1 -1
- package/dist/types/components/pds-combobox/pds-combobox.d.ts +1 -0
- package/dist/types/components/pds-drawer/pds-drawer.d.ts +21 -9
- package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +1 -0
- package/dist/types/components/pds-modal/pds-modal.d.ts +10 -3
- package/dist/types/components/pds-multiselect/pds-multiselect.d.ts +2 -1
- package/dist/types/components/pds-popover/pds-popover.d.ts +31 -1
- package/dist/types/components.d.ts +45 -30
- package/dist/vscode.html-data.json +11 -4
- package/hydrate/index.js +369 -90
- package/hydrate/index.mjs +369 -90
- package/package.json +2 -2
- package/dist/pine-core/p-01c9b8f3.entry.js +0 -1
- package/dist/pine-core/p-3c27088f.system.entry.js +0 -1
- package/dist/pine-core/p-73eb47fa.entry.js +0 -1
- package/dist/pine-core/p-7f367db4.system.entry.js +0 -1
- package/dist/pine-core/p-9a0083dd.system.entry.js +0 -1
- package/dist/pine-core/p-a3735199.entry.js +0 -1
- package/dist/pine-core/p-da103a54.system.entry.js +0 -1
- package/dist/pine-core/p-ec5428fb.entry.js +0 -1
- package/dist/pine-core/p-ed4b979e.entry.js +0 -1
- package/dist/pine-core/p-f1f17940.entry.js +0 -2
|
@@ -86,6 +86,34 @@ function containsComposed(ancestor, node) {
|
|
|
86
86
|
}
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Every open modal, with the order it opened in, which ranks top-layer modals and the modals
|
|
91
|
+
* inside them. Kept here rather than queried from the document, which can't see a modal inside
|
|
92
|
+
* another component's shadow root.
|
|
93
|
+
*/
|
|
94
|
+
const openModals = new Map();
|
|
95
|
+
let openSequence = 0;
|
|
96
|
+
/** Whether an open auto popover's panel sits in `root`, including inside shadow roots under it. */
|
|
97
|
+
function hasOpenPopover(root) {
|
|
98
|
+
if (root.querySelector('[data-pds-popover-open]'))
|
|
99
|
+
return true;
|
|
100
|
+
return Array.from(root.querySelectorAll('*')).some(el => !!el.shadowRoot && hasOpenPopover(el.shadowRoot));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Whether `a` comes before `b` in the page. `compareDocumentPosition` is arbitrary across a shadow
|
|
104
|
+
* boundary, so each side climbs to its host until both are in the same tree.
|
|
105
|
+
*/
|
|
106
|
+
function precedesComposed(a, b) {
|
|
107
|
+
// A document has no host, which ends each climb.
|
|
108
|
+
for (let x = a; x; x = x.getRootNode().host) {
|
|
109
|
+
for (let y = b; y; y = y.getRootNode().host) {
|
|
110
|
+
if (x.getRootNode() === y.getRootNode()) {
|
|
111
|
+
return !!(x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
89
117
|
/** The focused element, followed down through shadow roots to the control that has focus. */
|
|
90
118
|
function deepActiveElement() {
|
|
91
119
|
var _a;
|
|
@@ -101,7 +129,8 @@ const PdsModal = class {
|
|
|
101
129
|
this.pdsModalClose = index.createEvent(this, "pdsModalClose");
|
|
102
130
|
this.focusableElements = [];
|
|
103
131
|
/**
|
|
104
|
-
* Whether the modal can be dismissed by clicking the backdrop
|
|
132
|
+
* Whether the modal can be dismissed by clicking the backdrop, pressing Escape, or the browser's
|
|
133
|
+
* own close request (such as a back gesture)
|
|
105
134
|
* @default true
|
|
106
135
|
*/
|
|
107
136
|
this.backdropDismiss = true;
|
|
@@ -149,19 +178,33 @@ const PdsModal = class {
|
|
|
149
178
|
* Stores the list of focusable elements in the modal
|
|
150
179
|
*/
|
|
151
180
|
this.focusableElementsArray = [];
|
|
181
|
+
// The browser's own close request (an Escape nothing prevented, a back gesture) would close the
|
|
182
|
+
// dialog without updating `open`, so close it through hideModal() instead, or not at all.
|
|
183
|
+
this.handleCancel = (e) => {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
if (!this.open)
|
|
186
|
+
return;
|
|
187
|
+
// The request reaches the top-layer dialog even when a disableTopLayer modal inside it is on
|
|
188
|
+
// top; that one closes first, as it would on Escape.
|
|
189
|
+
const top = this.innermostOpenModal();
|
|
190
|
+
const target = (top && containsComposed(this.el, top) ? top : this.el);
|
|
191
|
+
if (target.backdropDismiss)
|
|
192
|
+
target.hideModal();
|
|
193
|
+
};
|
|
152
194
|
this.handleBackdropClick = (e) => {
|
|
153
195
|
if (!this.backdropDismiss || !this.open)
|
|
154
196
|
return;
|
|
155
|
-
|
|
197
|
+
// This modal's own backdrop; a nested modal's backdrop handles its own clicks.
|
|
198
|
+
if (e.target === this.modalRef) {
|
|
156
199
|
e.stopPropagation();
|
|
157
200
|
// Only close if this is the innermost modal
|
|
158
|
-
if (this.
|
|
201
|
+
if (this.innermostOpenModal() === this.el) {
|
|
159
202
|
this.hideModal();
|
|
160
203
|
}
|
|
161
204
|
}
|
|
162
205
|
};
|
|
163
206
|
this.handleKeyDown = (e) => {
|
|
164
|
-
var _a, _b, _c;
|
|
207
|
+
var _a, _b, _c, _d;
|
|
165
208
|
// If the modal is not open, don't handle any keyboard events
|
|
166
209
|
if (!this.open)
|
|
167
210
|
return;
|
|
@@ -184,24 +227,39 @@ const PdsModal = class {
|
|
|
184
227
|
if (!('__pdsActiveElementAtEscape' in eventSnapshot)) {
|
|
185
228
|
const activeAtDispatch = document.activeElement;
|
|
186
229
|
eventSnapshot.__pdsActiveElementAtEscape = activeAtDispatch;
|
|
230
|
+
// Followed into shadow roots: for a modal inside one, document.activeElement is only the host.
|
|
231
|
+
eventSnapshot.__pdsDeepActiveElementAtEscape = deepActiveElement();
|
|
187
232
|
const topLayerDialog = (_a = activeAtDispatch === null || activeAtDispatch === void 0 ? void 0 : activeAtDispatch.closest('dialog')) !== null && _a !== void 0 ? _a : null;
|
|
188
233
|
eventSnapshot.__pdsTopLayerDialogAtEscape = topLayerDialog;
|
|
189
234
|
eventSnapshot.__pdsTopLayerDialogWasModalAtEscape = !!(topLayerDialog && topLayerDialog.matches(':modal'));
|
|
235
|
+
// A control that closed on this Escape (a combobox on its own element, a dropdown menu or
|
|
236
|
+
// multiselect in document's capture phase) has prevented it before this bubble listener.
|
|
237
|
+
// Read before any modal prevents it below.
|
|
238
|
+
eventSnapshot.__pdsEscapeHandledInside = e.defaultPrevented;
|
|
239
|
+
// Which modal is innermost is read once too: the inner one closing first would make its
|
|
240
|
+
// outer modal the innermost by the time that modal's listener runs.
|
|
241
|
+
eventSnapshot.__pdsInnermostModalAtEscape = this.innermostOpenModal();
|
|
190
242
|
}
|
|
243
|
+
if (eventSnapshot.__pdsEscapeHandledInside)
|
|
244
|
+
return;
|
|
191
245
|
// Leave Escape to a genuinely stacked-above overlay (disableTopLayer's
|
|
192
246
|
// reason for existing) — not just to anything outside this modal.
|
|
193
247
|
const active = eventSnapshot.__pdsActiveElementAtEscape;
|
|
194
248
|
if (this.disableTopLayer &&
|
|
195
249
|
active &&
|
|
196
250
|
active !== document.body &&
|
|
197
|
-
!this.el.
|
|
198
|
-
this.isStackedAboveOverlay(active, (
|
|
251
|
+
!containsComposed(this.el, (_b = eventSnapshot.__pdsDeepActiveElementAtEscape) !== null && _b !== void 0 ? _b : active) &&
|
|
252
|
+
this.isStackedAboveOverlay(active, (_c = eventSnapshot.__pdsTopLayerDialogAtEscape) !== null && _c !== void 0 ? _c : null, (_d = eventSnapshot.__pdsTopLayerDialogWasModalAtEscape) !== null && _d !== void 0 ? _d : false)) {
|
|
199
253
|
return;
|
|
200
254
|
}
|
|
255
|
+
// An open popover inside takes Escape from its own document listener, which runs after this
|
|
256
|
+
// one, so leave the key for it. Its preventDefault() keeps the native dialog open.
|
|
257
|
+
if (this.modalRef && hasOpenPopover(this.modalRef))
|
|
258
|
+
return;
|
|
201
259
|
// Always prevent native dialog close behavior
|
|
202
260
|
e.preventDefault();
|
|
203
261
|
// Only close if backdropDismiss is enabled and this is the innermost modal
|
|
204
|
-
if (this.backdropDismiss && this.
|
|
262
|
+
if (this.backdropDismiss && eventSnapshot.__pdsInnermostModalAtEscape === this.el) {
|
|
205
263
|
this.hideModal();
|
|
206
264
|
}
|
|
207
265
|
return;
|
|
@@ -261,8 +319,6 @@ const PdsModal = class {
|
|
|
261
319
|
}
|
|
262
320
|
componentDidLoad() {
|
|
263
321
|
this.modalRef = this.el.querySelector('.pds-modal__backdrop');
|
|
264
|
-
// Add keyboard event listener
|
|
265
|
-
document.addEventListener('keydown', this.handleKeyDown);
|
|
266
322
|
// @Watch('open') only fires on a later change, not the prop's initial
|
|
267
323
|
// value — a modal mounted with `open` already true (static markup, or a
|
|
268
324
|
// framework passing it on first render) would otherwise never call
|
|
@@ -278,9 +334,18 @@ const PdsModal = class {
|
|
|
278
334
|
componentDidRender() {
|
|
279
335
|
reconnectedContent.unwrapReconnectedContent(this.el.querySelector('.pds-modal'), 'dialog.pds-modal__backdrop', '.pds-modal');
|
|
280
336
|
}
|
|
337
|
+
// Here rather than in componentDidLoad, which runs once: a modal taken out of the page and put
|
|
338
|
+
// back (a morph or a framework moving it) gets its keyboard handling, and its place among the
|
|
339
|
+
// open modals, back too. Adding the same listener twice is a no-op.
|
|
340
|
+
connectedCallback() {
|
|
341
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
342
|
+
if (this.open && !openModals.has(this.el))
|
|
343
|
+
openModals.set(this.el, ++openSequence);
|
|
344
|
+
}
|
|
281
345
|
disconnectedCallback() {
|
|
282
346
|
// Clean up event listener
|
|
283
347
|
document.removeEventListener('keydown', this.handleKeyDown);
|
|
348
|
+
openModals.delete(this.el);
|
|
284
349
|
}
|
|
285
350
|
handleOpenChange(newValue) {
|
|
286
351
|
if (newValue) {
|
|
@@ -345,6 +410,9 @@ const PdsModal = class {
|
|
|
345
410
|
this.modalRef.showModal();
|
|
346
411
|
}
|
|
347
412
|
this.open = true;
|
|
413
|
+
// Showing an open modal again doesn't move it above one opened after it.
|
|
414
|
+
if (!openModals.has(this.el))
|
|
415
|
+
openModals.set(this.el, ++openSequence);
|
|
348
416
|
// Update focusable elements and set initial focus
|
|
349
417
|
// Using a longer timeout to ensure all components are fully rendered
|
|
350
418
|
setTimeout(() => {
|
|
@@ -407,6 +475,7 @@ const PdsModal = class {
|
|
|
407
475
|
const focusAlreadyMovedElsewhere = activeElement !== null && activeElement !== document.body && !this.modalRef.contains(activeElement);
|
|
408
476
|
this.modalRef.close();
|
|
409
477
|
this.open = false;
|
|
478
|
+
openModals.delete(this.el);
|
|
410
479
|
// Restore focus to the element that was focused before the modal was opened
|
|
411
480
|
if (!focusAlreadyMovedElsewhere &&
|
|
412
481
|
this.previousActiveElement &&
|
|
@@ -425,7 +494,9 @@ const PdsModal = class {
|
|
|
425
494
|
*/
|
|
426
495
|
getBackdropZIndex(modal) {
|
|
427
496
|
const backdrop = modal.querySelector('.pds-modal__backdrop');
|
|
428
|
-
|
|
497
|
+
// `auto` (no z-index token loaded) parses to NaN, which no comparison can rank.
|
|
498
|
+
const zIndex = backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;
|
|
499
|
+
return Number.isNaN(zIndex) ? 0 : zIndex;
|
|
429
500
|
}
|
|
430
501
|
/**
|
|
431
502
|
* Whether `active` sits inside a surface actually stacked above this modal.
|
|
@@ -452,7 +523,8 @@ const PdsModal = class {
|
|
|
452
523
|
* lose that evidence depending on listener order.
|
|
453
524
|
*/
|
|
454
525
|
isStackedAboveOverlay(active, topLayerDialog, topLayerDialogWasModal) {
|
|
455
|
-
|
|
526
|
+
// A top-layer dialog this modal sits inside is underneath it, not an overlay above.
|
|
527
|
+
if (topLayerDialog && topLayerDialog !== this.modalRef && topLayerDialogWasModal && !containsComposed(topLayerDialog, this.el)) {
|
|
456
528
|
return true;
|
|
457
529
|
}
|
|
458
530
|
const ownZIndex = this.getBackdropZIndex(this.el);
|
|
@@ -468,24 +540,37 @@ const PdsModal = class {
|
|
|
468
540
|
return false;
|
|
469
541
|
}
|
|
470
542
|
/**
|
|
471
|
-
*
|
|
543
|
+
* The open modal that takes Escape or a backdrop click, among those with no other open modal
|
|
544
|
+
* inside them (a nested modal's backdrop can share its outer modal's z-index). A top-layer dialog
|
|
545
|
+
* (or a modal inside one) paints above anything outside it, and within the top layer the last
|
|
546
|
+
* opened is on top. Modals in the same layer (the page, or one top-layer dialog) then rank by
|
|
547
|
+
* the highest backdrop, then the one later in the page.
|
|
472
548
|
*/
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
//
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
549
|
+
innermostOpenModal() {
|
|
550
|
+
const open = Array.from(openModals.keys()).filter(modal => modal.isConnected);
|
|
551
|
+
const innermost = open.filter(modal => !open.some(other => other !== modal && containsComposed(modal, other)));
|
|
552
|
+
const byDocumentOrder = [...innermost].sort((a, b) => (precedesComposed(a, b) ? -1 : 1));
|
|
553
|
+
// A modal inside a top-layer dialog paints in that dialog's place in the top layer, even when it
|
|
554
|
+
// isn't in the top layer itself (disableTopLayer), so that dialog's open order ranks it first.
|
|
555
|
+
const rank = (modal) => {
|
|
556
|
+
var _a;
|
|
557
|
+
const backdrop = modal.querySelector('.pds-modal__backdrop');
|
|
558
|
+
const topLayerDialog = backdrop ? closest.closest('dialog:modal', backdrop) : null;
|
|
559
|
+
const topLayerModal = topLayerDialog === null || topLayerDialog === void 0 ? void 0 : topLayerDialog.closest('pds-modal');
|
|
560
|
+
const layer = topLayerDialog ? ((_a = (topLayerModal && openModals.get(topLayerModal))) !== null && _a !== void 0 ? _a : 0) : 0;
|
|
561
|
+
return [topLayerDialog ? 1 : 0, layer, this.getBackdropZIndex(modal), byDocumentOrder.indexOf(modal)];
|
|
562
|
+
};
|
|
563
|
+
let top = null;
|
|
564
|
+
let topRank = [-1, -1, -1, -1];
|
|
565
|
+
for (const modal of innermost) {
|
|
566
|
+
const modalRank = rank(modal);
|
|
567
|
+
const above = modalRank.findIndex((value, i) => value !== topRank[i]);
|
|
568
|
+
if (above >= 0 && modalRank[above] > topRank[above]) {
|
|
569
|
+
top = modal;
|
|
570
|
+
topRank = modalRank;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return top;
|
|
489
574
|
}
|
|
490
575
|
/** Whether a modal dialog other than this one sits between the focused element and this modal. */
|
|
491
576
|
modalDialogAbove(active) {
|
|
@@ -525,14 +610,14 @@ const PdsModal = class {
|
|
|
525
610
|
}
|
|
526
611
|
render() {
|
|
527
612
|
const [sizeClass = 'md', customSize] = this.resolveSize();
|
|
528
|
-
return (index.h("dialog", { key: '
|
|
613
|
+
return (index.h("dialog", { key: '0633d4e8a7cb81f2f9280e754564960a00a4d60b', class: {
|
|
529
614
|
'pds-modal__backdrop': true,
|
|
530
615
|
'open': this.open
|
|
531
|
-
}, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick }, index.h("div", { key: '
|
|
616
|
+
}, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick, onCancel: this.handleCancel }, index.h("div", { key: '466f8a3476cf066817554f47ec7d3a0e3ab10b89', class: {
|
|
532
617
|
'pds-modal': true,
|
|
533
618
|
[`pds-modal--${sizeClass}`]: true,
|
|
534
619
|
'pds-modal--scrollable': this.scrollable
|
|
535
|
-
}, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, index.h("slot", { key: '
|
|
620
|
+
}, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, index.h("slot", { key: '01ca45c96540416216a1082bad52b137720f0f4d' }))));
|
|
536
621
|
}
|
|
537
622
|
get el() { return index.getElement(this); }
|
|
538
623
|
static get watchers() { return {
|
|
@@ -539,11 +539,12 @@ const PdsMultiselect = class {
|
|
|
539
539
|
}
|
|
540
540
|
/**
|
|
541
541
|
* Handle global keyboard events for accessibility.
|
|
542
|
-
* Closes dropdown on Escape key press regardless of focus location.
|
|
542
|
+
* Closes dropdown on Escape key press regardless of focus location. Taken in document's capture
|
|
543
|
+
* phase, before a modal around the multiselect hears it on document and closes too.
|
|
543
544
|
*/
|
|
544
545
|
handleWindowKeyDown(event) {
|
|
545
546
|
var _a;
|
|
546
|
-
if (!this.isOpen)
|
|
547
|
+
if (!this.isOpen || event.defaultPrevented)
|
|
547
548
|
return;
|
|
548
549
|
if (event.key === 'Escape') {
|
|
549
550
|
event.preventDefault();
|
|
@@ -1233,10 +1234,10 @@ const PdsMultiselect = class {
|
|
|
1233
1234
|
}
|
|
1234
1235
|
render() {
|
|
1235
1236
|
const hasSelections = this.selectedItems.length > 0;
|
|
1236
|
-
return (index.h(index.Host, { key: '
|
|
1237
|
+
return (index.h(index.Host, { key: 'fcc35a82c16d5cf35a49ce997f19618104f9cc45', "aria-disabled": this.disabled ? 'true' : null }, index.h("div", { key: '72d1fdf759d9173f54bac80ec50aff651fd2b357', class: "pds-multiselect", onFocusout: this.handleContainerFocusOut }, this.label && (index.h("label", { key: 'e28f02c2beb23dc2c0deefadbd0645b20d938d91', id: `${this.componentId}-label`, htmlFor: this.componentId, class: {
|
|
1237
1238
|
'pds-multiselect__label': true,
|
|
1238
1239
|
'visually-hidden': this.hideLabel,
|
|
1239
|
-
} }, this.label)), index.h("div", { key: '
|
|
1240
|
+
} }, this.label)), index.h("div", { key: '06539bf2c4b8da2889505a9f042d5cd6721b1853', class: "pds-multiselect__wrapper", ref: el => (this.containerEl = el), style: { width: this.triggerWidth } }, this.slottedTriggerEl ? (index.h("slot", { name: "trigger" })) : this.selectedDisplay === 'pill' && this.pillPosition === 'inline' ? (index.h("div", { ref: el => (this.pillInlineTriggerEl = el || undefined), part: "trigger", class: {
|
|
1240
1241
|
'pds-multiselect__trigger': true,
|
|
1241
1242
|
'pds-multiselect__trigger--open': this.isOpen,
|
|
1242
1243
|
'pds-multiselect__trigger--disabled': this.disabled,
|
|
@@ -1252,7 +1253,7 @@ const PdsMultiselect = class {
|
|
|
1252
1253
|
}, id: this.componentId, disabled: this.disabled, "aria-required": this.required ? 'true' : undefined, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-haspopup": "listbox", "aria-describedby": form.assignDescription(this.componentId, this.invalid || !!this.errorMessage, this.helperMessage, this.errorMessage), "aria-invalid": this.invalid || !!this.errorMessage ? 'true' : undefined, onClick: this.handleTriggerClick, onKeyDown: this.handleTriggerKeyDown }, index.h("span", { class: {
|
|
1253
1254
|
'pds-multiselect__trigger-text': true,
|
|
1254
1255
|
'pds-multiselect__trigger-text--placeholder': !hasSelections || (this.selectedDisplay === 'pill' && this.pillPosition === 'below'),
|
|
1255
|
-
} }, this.getTriggerText()), index.h("pds-icon", { "aria-hidden": "true", class: "pds-multiselect__icon", icon: index$1.enlarge }))), this.renderDropdown()), this.selectedDisplay === 'pill' && this.pillPosition === 'below' && this.renderBelowPills(), index.h("span", { key: '
|
|
1256
|
+
} }, this.getTriggerText()), index.h("pds-icon", { "aria-hidden": "true", class: "pds-multiselect__icon", icon: index$1.enlarge }))), this.renderDropdown()), this.selectedDisplay === 'pill' && this.pillPosition === 'below' && this.renderBelowPills(), index.h("span", { key: '3364fc00bc744fcd599436a9cb6dc563564d89d9', class: "visually-hidden", "aria-live": "polite", "aria-atomic": "true" }, this.removalAnnouncement), this.helperMessage && !(this.errorMessage && this.errorMessage.length > 0) && (index.h("p", { key: 'bf712c65b9acb66e2998a43686fed408fc1ff48a', class: "pds-multiselect__helper", id: form.messageId(this.componentId, 'helper') }, this.helperMessage)), this.errorMessage && (index.h("p", { key: 'b485db6c2c3ae312cce2b90db60e850ad6dfdcf5', class: "pds-multiselect__error", id: form.messageId(this.componentId, 'error') }, index.h("pds-icon", { key: '20daf861cf9584f33f5a9574e9581acc487e7dfe', icon: index$1.danger, size: "small" }), this.errorMessage)), index.h("div", { key: '018d333254771695a8c191f1cea40af6e78427a2', style: { display: 'none' } }, index.h("slot", { key: '2ef4a01fbe5d294a9ead5cc1183c28341834539b' })))));
|
|
1256
1257
|
}
|
|
1257
1258
|
static get formAssociated() { return true; }
|
|
1258
1259
|
get el() { return index.getElement(this); }
|
|
@@ -50,6 +50,16 @@ const cssEscape = (value) => {
|
|
|
50
50
|
|
|
51
51
|
const pdsPopoverCss = () => `:host{--sizing-max-width-default:352px;display:inline-block}:host .pds-popover__trigger-wrapper{display:inline-block}:host .pds-popover__content-slot-wrapper{display:none}`;
|
|
52
52
|
|
|
53
|
+
/** Open auto popovers, in the order they opened: Escape goes to the last one it applies to. */
|
|
54
|
+
const openAutoPopovers = [];
|
|
55
|
+
/** The focused element, followed down through shadow roots to the control that has focus. */
|
|
56
|
+
function deepActiveElement() {
|
|
57
|
+
var _a;
|
|
58
|
+
let active = document.activeElement;
|
|
59
|
+
while ((_a = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)
|
|
60
|
+
active = active.shadowRoot.activeElement;
|
|
61
|
+
return active;
|
|
62
|
+
}
|
|
53
63
|
const PdsPopover = class {
|
|
54
64
|
constructor(hostRef) {
|
|
55
65
|
index.registerInstance(this, hostRef);
|
|
@@ -81,7 +91,7 @@ const PdsPopover = class {
|
|
|
81
91
|
*/
|
|
82
92
|
this.triggerObserver = null;
|
|
83
93
|
/**
|
|
84
|
-
* Portal element
|
|
94
|
+
* Portal element: in document.body, or in the open dialog the popover is in
|
|
85
95
|
*/
|
|
86
96
|
this.portalEl = null;
|
|
87
97
|
/**
|
|
@@ -177,6 +187,26 @@ const PdsPopover = class {
|
|
|
177
187
|
break;
|
|
178
188
|
}
|
|
179
189
|
};
|
|
190
|
+
/** The focus style copied into a shadow root the panel opened in, which `document.head` can't reach. */
|
|
191
|
+
this.shadowFocusStyle = null;
|
|
192
|
+
/** The dialog the panel was opened in, closed along with it. */
|
|
193
|
+
this.watchedDialog = null;
|
|
194
|
+
this.onDialogClose = () => {
|
|
195
|
+
this.hide();
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Inside a dialog, the panel can sit in a form that wraps the dialog, which a body portal never
|
|
199
|
+
* did. A submit from the panel (a button in it, or Enter in a field in it) is stopped, so the
|
|
200
|
+
* panel behaves as it does in the body.
|
|
201
|
+
*/
|
|
202
|
+
this.handleSubmitFromPanel = (event) => {
|
|
203
|
+
if (!this.portalEl || !closest.containsComposed(event.target, this.portalEl))
|
|
204
|
+
return;
|
|
205
|
+
if (closest.containsComposed(this.portalEl, event.submitter) || closest.containsComposed(this.portalEl, deepActiveElement())) {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
180
210
|
}
|
|
181
211
|
restorePlacementDefault(newValue) {
|
|
182
212
|
this.placement = reflectedAttributeDefault.restoreReflectedDefault(this.el, 'placement', newValue, 'right');
|
|
@@ -239,19 +269,23 @@ const PdsPopover = class {
|
|
|
239
269
|
(_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.handleTriggerClick);
|
|
240
270
|
this.observeTrigger();
|
|
241
271
|
this.linkTriggerToPortal();
|
|
242
|
-
// Still open after a move: the trigger moved, so the portal follows it
|
|
272
|
+
// Still open after a move: the trigger moved, so the portal follows it, into the dialog it
|
|
273
|
+
// is in now, if any.
|
|
243
274
|
if (this.active) {
|
|
275
|
+
this.placePortal();
|
|
244
276
|
requestAnimationFrame(() => this.handlePopoverPositioning());
|
|
245
277
|
}
|
|
246
278
|
}
|
|
247
279
|
disconnectedCallback() {
|
|
248
280
|
var _a;
|
|
249
281
|
// Moved in one operation (a DOM morph, or a framework reparenting it), the popover is
|
|
250
|
-
// already back in the page. Its portal is
|
|
251
|
-
// where it was.
|
|
282
|
+
// already back in the page. Its portal is outside the popover, so it stays open, with focus
|
|
283
|
+
// where it was; connectedCallback moves it to follow the popover.
|
|
252
284
|
if (this.el.isConnected)
|
|
253
285
|
return;
|
|
254
286
|
this.isComponentMounted = false;
|
|
287
|
+
this.unwatchDialogClose();
|
|
288
|
+
this.forgetOpen();
|
|
255
289
|
// Open, the content sits in the portal; put it back before the portal goes. Only listeners on
|
|
256
290
|
// the popover itself hear the close event now, but those are the ones mirroring its state.
|
|
257
291
|
if (this.active) {
|
|
@@ -272,6 +306,7 @@ const PdsPopover = class {
|
|
|
272
306
|
// Clean up all event listeners
|
|
273
307
|
this.removeLightDismissListeners();
|
|
274
308
|
this.removeScrollAndResizeListeners();
|
|
309
|
+
document.removeEventListener('submit', this.handleSubmitFromPanel, true);
|
|
275
310
|
// Clear any pending debounce timers
|
|
276
311
|
this.clearDebounceTimers();
|
|
277
312
|
// Remove portal from DOM
|
|
@@ -316,9 +351,6 @@ const PdsPopover = class {
|
|
|
316
351
|
else {
|
|
317
352
|
this.portalEl.id = `${this.componentId}-portal`;
|
|
318
353
|
}
|
|
319
|
-
// Add accessibility attributes for screen readers
|
|
320
|
-
// Note: Native Popover API doesn't add a specific role, keeping semantic HTML
|
|
321
|
-
this.portalEl.setAttribute('aria-modal', 'false'); // Not a modal, can interact with rest of page
|
|
322
354
|
// Append to body
|
|
323
355
|
document.body.appendChild(this.portalEl);
|
|
324
356
|
// Add global focus styles to match Pine design system
|
|
@@ -402,7 +434,27 @@ const PdsPopover = class {
|
|
|
402
434
|
// Clear tracking array
|
|
403
435
|
this.movedNodes = [];
|
|
404
436
|
}
|
|
437
|
+
copyFocusStylesInto(root) {
|
|
438
|
+
var _a, _b, _c;
|
|
439
|
+
// Outside a shadow root the document's own style applies, so drop a copy left in one.
|
|
440
|
+
if (!(root instanceof ShadowRoot)) {
|
|
441
|
+
(_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
|
|
442
|
+
this.shadowFocusStyle = null;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (((_b = this.shadowFocusStyle) === null || _b === void 0 ? void 0 : _b.parentNode) === root || !this.portalEl)
|
|
446
|
+
return;
|
|
447
|
+
const source = document.querySelector(`style[data-pds-popover-focus="${cssEscape(this.portalEl.id)}"]`);
|
|
448
|
+
if (!source)
|
|
449
|
+
return;
|
|
450
|
+
(_c = this.shadowFocusStyle) === null || _c === void 0 ? void 0 : _c.remove();
|
|
451
|
+
this.shadowFocusStyle = source.cloneNode(true);
|
|
452
|
+
root.appendChild(this.shadowFocusStyle);
|
|
453
|
+
}
|
|
405
454
|
removePortal() {
|
|
455
|
+
var _a;
|
|
456
|
+
(_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
|
|
457
|
+
this.shadowFocusStyle = null;
|
|
406
458
|
if (this.portalEl) {
|
|
407
459
|
// Remove the portal element from DOM
|
|
408
460
|
if (this.portalEl.parentNode) {
|
|
@@ -427,9 +479,13 @@ const PdsPopover = class {
|
|
|
427
479
|
// Record open timestamp to prevent immediate close from opening click
|
|
428
480
|
this.openTimestamp = Date.now();
|
|
429
481
|
this.linkTriggerToPortal();
|
|
482
|
+
this.placePortal();
|
|
430
483
|
// Update portal content with latest slot content
|
|
431
484
|
this.updatePortalContent();
|
|
432
|
-
// Show portal
|
|
485
|
+
// Show portal. The attribute tells a modal around it that this popover takes Escape first; only
|
|
486
|
+
// an auto popover listens for Escape.
|
|
487
|
+
if (this.popoverType === 'auto')
|
|
488
|
+
this.portalEl.setAttribute('data-pds-popover-open', '');
|
|
433
489
|
this.portalEl.style.display = 'block';
|
|
434
490
|
this.portalEl.style.opacity = '1';
|
|
435
491
|
this.portalEl.style.visibility = 'visible';
|
|
@@ -441,8 +497,10 @@ const PdsPopover = class {
|
|
|
441
497
|
});
|
|
442
498
|
// Add scroll and resize listeners for repositioning (always)
|
|
443
499
|
this.addScrollAndResizeListeners();
|
|
500
|
+
document.addEventListener('submit', this.handleSubmitFromPanel, true);
|
|
444
501
|
// Add document listeners for light dismiss and escape key (auto type only)
|
|
445
502
|
if (this.popoverType === 'auto') {
|
|
503
|
+
openAutoPopovers.push(this);
|
|
446
504
|
this.addLightDismissListeners();
|
|
447
505
|
}
|
|
448
506
|
// Emit open event
|
|
@@ -458,12 +516,16 @@ const PdsPopover = class {
|
|
|
458
516
|
if (!this.active || !this.portalEl)
|
|
459
517
|
return;
|
|
460
518
|
this.active = false;
|
|
461
|
-
|
|
462
|
-
|
|
519
|
+
this.unwatchDialogClose();
|
|
520
|
+
// Restoring the content drops its focus to the body, so check where focus is first. In a
|
|
521
|
+
// shadow root, document.activeElement is only its host.
|
|
522
|
+
const focusWasInside = closest.containsComposed(this.portalEl, deepActiveElement());
|
|
463
523
|
this.linkTriggerToPortal();
|
|
464
524
|
// Restore content back to original slot positions
|
|
465
525
|
this.restorePortalContent();
|
|
466
|
-
// Hide portal
|
|
526
|
+
// Hide portal. The dark-mode border set in a shadow root is worked out again on the next open.
|
|
527
|
+
this.portalEl.style.border = '';
|
|
528
|
+
this.portalEl.removeAttribute('data-pds-popover-open');
|
|
467
529
|
this.portalEl.style.display = 'none';
|
|
468
530
|
this.portalEl.style.opacity = '0';
|
|
469
531
|
this.portalEl.style.visibility = 'hidden';
|
|
@@ -473,8 +535,10 @@ const PdsPopover = class {
|
|
|
473
535
|
this.returnFocusToTrigger();
|
|
474
536
|
}
|
|
475
537
|
// Remove all listeners
|
|
538
|
+
this.forgetOpen();
|
|
476
539
|
this.removeLightDismissListeners();
|
|
477
540
|
this.removeScrollAndResizeListeners();
|
|
541
|
+
document.removeEventListener('submit', this.handleSubmitFromPanel, true);
|
|
478
542
|
// Clear any pending timers
|
|
479
543
|
this.clearDebounceTimers();
|
|
480
544
|
// Emit close event
|
|
@@ -494,6 +558,59 @@ const PdsPopover = class {
|
|
|
494
558
|
this.show();
|
|
495
559
|
}
|
|
496
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Puts the panel where it shows above everything around the popover. In an open dialog, it goes
|
|
563
|
+
* inside it: a modal dialog sits in the top layer above the body and makes the rest of the page
|
|
564
|
+
* inert, and a non-modal one sits above it by z-index. Right after the popover, it's next in the
|
|
565
|
+
* Tab order. In another component's slot, though, it would have no slot to render in, and in a
|
|
566
|
+
* form its buttons would submit it, so there it goes at the end of the dialog.
|
|
567
|
+
*/
|
|
568
|
+
placePortal() {
|
|
569
|
+
var _a, _b;
|
|
570
|
+
if (!this.portalEl)
|
|
571
|
+
return;
|
|
572
|
+
const dialog = closest.closest('dialog[open]', this.el);
|
|
573
|
+
if (!dialog) {
|
|
574
|
+
this.unwatchDialogClose();
|
|
575
|
+
this.portalEl.style.border = '';
|
|
576
|
+
if (this.portalEl.parentElement !== document.body)
|
|
577
|
+
document.body.appendChild(this.portalEl);
|
|
578
|
+
this.copyFocusStylesInto(document);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const form = closest.closest('form', this.el);
|
|
582
|
+
const besidePopover = !((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.shadowRoot) && !(form && closest.containsComposed(dialog, form));
|
|
583
|
+
if (besidePopover) {
|
|
584
|
+
if (this.el.nextElementSibling !== this.portalEl)
|
|
585
|
+
(_b = this.el.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(this.portalEl, this.el.nextSibling);
|
|
586
|
+
}
|
|
587
|
+
else if (this.portalEl.parentNode !== dialog) {
|
|
588
|
+
dialog.appendChild(this.portalEl);
|
|
589
|
+
}
|
|
590
|
+
this.watchDialogClose(dialog);
|
|
591
|
+
const root = this.portalEl.getRootNode();
|
|
592
|
+
this.copyFocusStylesInto(root);
|
|
593
|
+
// In a shadow root, the copied dark-mode rule can't see a [data-theme="dark"] on the page.
|
|
594
|
+
this.portalEl.style.border = root instanceof ShadowRoot && closest.closest('[data-theme="dark"]', this.el) ? 'var(--pine-border)' : '';
|
|
595
|
+
}
|
|
596
|
+
forgetOpen() {
|
|
597
|
+
const index = openAutoPopovers.indexOf(this);
|
|
598
|
+
if (index >= 0)
|
|
599
|
+
openAutoPopovers.splice(index, 1);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Whether an Escape is this popover's. One from a dialog the popover isn't in belongs to that
|
|
603
|
+
* dialog when it's above it: a top-layer dialog or a pds-modal. A plain non-modal dialog inside
|
|
604
|
+
* the popover's modal doesn't close on Escape, so the popover takes it.
|
|
605
|
+
*/
|
|
606
|
+
takesEscapeFrom(event) {
|
|
607
|
+
var _a, _b;
|
|
608
|
+
const origin = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
|
|
609
|
+
const dialog = closest.closest('dialog[open]', origin);
|
|
610
|
+
if (!dialog || closest.containsComposed(dialog, this.portalEl))
|
|
611
|
+
return true;
|
|
612
|
+
return !(dialog.classList.contains('pds-modal__backdrop') || dialog.matches(':modal'));
|
|
613
|
+
}
|
|
497
614
|
/**
|
|
498
615
|
* Adds light dismiss listeners (click outside and escape key)
|
|
499
616
|
*/
|
|
@@ -502,6 +619,19 @@ const PdsPopover = class {
|
|
|
502
619
|
document.addEventListener('click', this.boundClickOutsideHandler, true);
|
|
503
620
|
document.addEventListener('keydown', this.boundEscapeKeyHandler);
|
|
504
621
|
}
|
|
622
|
+
/** Closes the popover when the dialog it opened in closes, so it isn't left open inside. */
|
|
623
|
+
watchDialogClose(dialog) {
|
|
624
|
+
if (this.watchedDialog === dialog)
|
|
625
|
+
return;
|
|
626
|
+
this.unwatchDialogClose();
|
|
627
|
+
this.watchedDialog = dialog;
|
|
628
|
+
dialog.addEventListener('close', this.onDialogClose);
|
|
629
|
+
}
|
|
630
|
+
unwatchDialogClose() {
|
|
631
|
+
var _a;
|
|
632
|
+
(_a = this.watchedDialog) === null || _a === void 0 ? void 0 : _a.removeEventListener('close', this.onDialogClose);
|
|
633
|
+
this.watchedDialog = null;
|
|
634
|
+
}
|
|
505
635
|
/**
|
|
506
636
|
* Removes light dismiss listeners
|
|
507
637
|
*/
|
|
@@ -527,7 +657,7 @@ const PdsPopover = class {
|
|
|
527
657
|
* Handles clicks outside the popover for light dismiss (auto type only)
|
|
528
658
|
*/
|
|
529
659
|
handleClickOutside(event) {
|
|
530
|
-
var _a, _b
|
|
660
|
+
var _a, _b;
|
|
531
661
|
// Ignore events that occurred at or before the popover opened
|
|
532
662
|
// This prevents the opening click from immediately closing the popover
|
|
533
663
|
// Convert event.timeStamp (DOMHighResTimeStamp from performance.now()) to epoch time
|
|
@@ -538,16 +668,18 @@ const PdsPopover = class {
|
|
|
538
668
|
if (eventTime <= this.openTimestamp) {
|
|
539
669
|
return;
|
|
540
670
|
}
|
|
541
|
-
|
|
671
|
+
// In a shadow root, event.target is only its host.
|
|
672
|
+
const target = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
|
|
542
673
|
// A datepicker's calendar is portaled to the body: a click in the calendar of a datepicker
|
|
543
|
-
// inside this popover, in its content or its trigger, isn't outside it.
|
|
544
|
-
|
|
674
|
+
// inside this popover, in its content or its trigger, isn't outside it. The target can be
|
|
675
|
+
// inside the calendar's shadow root, so look up across it.
|
|
676
|
+
const calendar = closest.closest('.pds-datepicker-calendar-portal', target);
|
|
545
677
|
const owner = calendar === null || calendar === void 0 ? void 0 : calendar.pdsDatepicker;
|
|
546
678
|
if (owner && (closest.containsComposed(this.portalEl, owner) || closest.containsComposed(this.triggerEl, owner)))
|
|
547
679
|
return;
|
|
548
680
|
// Check if click is outside both the popover portal and the trigger
|
|
549
|
-
const clickedInsidePopover = (
|
|
550
|
-
const clickedInsideTrigger = (
|
|
681
|
+
const clickedInsidePopover = closest.containsComposed(this.portalEl, target);
|
|
682
|
+
const clickedInsideTrigger = closest.containsComposed(this.triggerEl, target);
|
|
551
683
|
if (!clickedInsidePopover && !clickedInsideTrigger) {
|
|
552
684
|
this.hide();
|
|
553
685
|
}
|
|
@@ -568,14 +700,19 @@ const PdsPopover = class {
|
|
|
568
700
|
* Mirrors native Popover API: Escape closes and returns focus to trigger
|
|
569
701
|
*/
|
|
570
702
|
handleEscapeKey(event) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
703
|
+
// A control inside that closes on Escape (a datepicker, a combobox) has handled it already.
|
|
704
|
+
if (event.key !== 'Escape' || event.defaultPrevented)
|
|
705
|
+
return;
|
|
706
|
+
// Like the native Popover API, Escape closes only the top popover: the last one opened that
|
|
707
|
+
// it applies to. Every open popover hears it, so each checks whether it's that one.
|
|
708
|
+
const top = [...openAutoPopovers].reverse().find(popover => popover.takesEscapeFrom(event));
|
|
709
|
+
if (top !== this)
|
|
710
|
+
return;
|
|
711
|
+
// Take the key, so the dialog or modal around it doesn't close too.
|
|
712
|
+
event.preventDefault();
|
|
713
|
+
event.stopPropagation();
|
|
714
|
+
// Close popover and return focus to trigger
|
|
715
|
+
this.hide();
|
|
579
716
|
}
|
|
580
717
|
/**
|
|
581
718
|
* Handles scroll events to reposition the popover (debounced for performance)
|
|
@@ -680,13 +817,20 @@ const PdsPopover = class {
|
|
|
680
817
|
}
|
|
681
818
|
this.portalEl.style.top = `${top}px`;
|
|
682
819
|
this.portalEl.style.left = `${left}px`;
|
|
820
|
+
// `position: fixed` measures from the viewport unless an ancestor becomes its containing block,
|
|
821
|
+
// as a modal dialog does with its backdrop filter. Shift by however far it landed from the spot.
|
|
822
|
+
const placed = this.portalEl.getBoundingClientRect();
|
|
823
|
+
if (Math.abs(placed.top - top) > 0.5 || Math.abs(placed.left - left) > 0.5) {
|
|
824
|
+
this.portalEl.style.top = `${2 * top - placed.top}px`;
|
|
825
|
+
this.portalEl.style.left = `${2 * left - placed.left}px`;
|
|
826
|
+
}
|
|
683
827
|
// Reset the repositioning guard after a short delay
|
|
684
828
|
setTimeout(() => {
|
|
685
829
|
this.isRepositioning = false;
|
|
686
830
|
}, 16); // ~1 frame at 60fps
|
|
687
831
|
}
|
|
688
832
|
render() {
|
|
689
|
-
return (index.h(index.Host, { key: '
|
|
833
|
+
return (index.h(index.Host, { key: 'da8a0c715df1199231b1265085f3f9ae0234032d', id: this.componentId }, index.h("span", { key: 'f35023492399420829cec2e88556462f5ac1663e', class: "pds-popover__trigger-wrapper" }, index.h("slot", { key: '38ebe92eebb87665467b3f92ff94dc8095450063', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), index.h("div", { key: '8917930f4520544d14bbd68b4c7936dd60af19e3', class: "pds-popover__content-slot-wrapper" }, index.h("slot", { key: '28d3f9112256edd95721a79a7072a9e35a3712bf', onSlotchange: this.handleContentSlotChange }))));
|
|
690
834
|
}
|
|
691
835
|
get el() { return index.getElement(this); }
|
|
692
836
|
static get watchers() { return {
|