@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.
Files changed (74) hide show
  1. package/components/pds-combobox.js +1 -1
  2. package/components/pds-drawer.js +1 -1
  3. package/components/pds-dropdown-menu.js +1 -1
  4. package/components/pds-modal2.js +1 -1
  5. package/components/pds-multiselect.js +1 -1
  6. package/components/pds-popover.js +1 -1
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/cjs/pds-combobox.cjs.entry.js +33 -3
  9. package/dist/cjs/pds-drawer.cjs.entry.js +23 -10
  10. package/dist/cjs/pds-dropdown-menu.cjs.entry.js +9 -5
  11. package/dist/cjs/pds-modal.cjs.entry.js +116 -31
  12. package/dist/cjs/pds-multiselect.cjs.entry.js +6 -5
  13. package/dist/cjs/pds-popover.cjs.entry.js +170 -26
  14. package/dist/cjs/pine-core.cjs.js +1 -1
  15. package/dist/collection/components/pds-combobox/pds-combobox.js +33 -3
  16. package/dist/collection/components/pds-drawer/pds-drawer.css +4 -1
  17. package/dist/collection/components/pds-drawer/pds-drawer.js +54 -17
  18. package/dist/collection/components/pds-drawer/stories/pds-drawer.stories.js +6 -1
  19. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +15 -5
  20. package/dist/collection/components/pds-modal/pds-modal.js +117 -32
  21. package/dist/collection/components/pds-multiselect/pds-multiselect.js +8 -7
  22. package/dist/collection/components/pds-popover/pds-popover.js +171 -27
  23. package/dist/docs.json +42 -7
  24. package/dist/esm/loader.js +1 -1
  25. package/dist/esm/pds-combobox.entry.js +33 -3
  26. package/dist/esm/pds-drawer.entry.js +23 -10
  27. package/dist/esm/pds-dropdown-menu.entry.js +9 -5
  28. package/dist/esm/pds-modal.entry.js +116 -31
  29. package/dist/esm/pds-multiselect.entry.js +6 -5
  30. package/dist/esm/pds-popover.entry.js +171 -27
  31. package/dist/esm/pine-core.js +1 -1
  32. package/dist/esm-es5/loader.js +1 -1
  33. package/dist/esm-es5/pds-combobox.entry.js +1 -1
  34. package/dist/esm-es5/pds-drawer.entry.js +1 -1
  35. package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
  36. package/dist/esm-es5/pds-modal.entry.js +1 -1
  37. package/dist/esm-es5/pds-multiselect.entry.js +1 -1
  38. package/dist/esm-es5/pds-popover.entry.js +1 -1
  39. package/dist/esm-es5/pine-core.js +1 -1
  40. package/dist/pine-core/p-153ba77b.entry.js +1 -0
  41. package/dist/pine-core/p-21fe6ee1.system.entry.js +1 -0
  42. package/dist/pine-core/p-24fbbb74.system.entry.js +1 -0
  43. package/dist/pine-core/{p-df539c42.system.entry.js → p-5a10784e.system.entry.js} +1 -1
  44. package/dist/pine-core/p-74a05bcc.entry.js +1 -0
  45. package/dist/pine-core/p-92ae6082.entry.js +1 -0
  46. package/dist/pine-core/p-945fb0b1.entry.js +1 -0
  47. package/dist/pine-core/p-CuSNnuFu.system.js +1 -1
  48. package/dist/pine-core/p-a0344e30.entry.js +2 -0
  49. package/dist/pine-core/p-a787b34a.system.entry.js +1 -0
  50. package/dist/pine-core/{p-646afbca.system.entry.js → p-b60ac7eb.system.entry.js} +1 -1
  51. package/dist/pine-core/p-bf5887e8.system.entry.js +1 -0
  52. package/dist/pine-core/p-e21ff692.entry.js +1 -0
  53. package/dist/pine-core/pine-core.esm.js +1 -1
  54. package/dist/types/components/pds-combobox/pds-combobox.d.ts +1 -0
  55. package/dist/types/components/pds-drawer/pds-drawer.d.ts +21 -9
  56. package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +1 -0
  57. package/dist/types/components/pds-modal/pds-modal.d.ts +10 -3
  58. package/dist/types/components/pds-multiselect/pds-multiselect.d.ts +2 -1
  59. package/dist/types/components/pds-popover/pds-popover.d.ts +31 -1
  60. package/dist/types/components.d.ts +45 -30
  61. package/dist/vscode.html-data.json +11 -4
  62. package/hydrate/index.js +369 -90
  63. package/hydrate/index.mjs +369 -90
  64. package/package.json +2 -2
  65. package/dist/pine-core/p-01c9b8f3.entry.js +0 -1
  66. package/dist/pine-core/p-3c27088f.system.entry.js +0 -1
  67. package/dist/pine-core/p-73eb47fa.entry.js +0 -1
  68. package/dist/pine-core/p-7f367db4.system.entry.js +0 -1
  69. package/dist/pine-core/p-9a0083dd.system.entry.js +0 -1
  70. package/dist/pine-core/p-a3735199.entry.js +0 -1
  71. package/dist/pine-core/p-da103a54.system.entry.js +0 -1
  72. package/dist/pine-core/p-ec5428fb.entry.js +0 -1
  73. package/dist/pine-core/p-ed4b979e.entry.js +0 -1
  74. package/dist/pine-core/p-f1f17940.entry.js +0 -2
package/hydrate/index.mjs CHANGED
@@ -11623,6 +11623,9 @@ class PdsCombobox {
11623
11623
  };
11624
11624
  // Handler for button trigger click
11625
11625
  this.onButtonTriggerClick = () => {
11626
+ // The button and chip triggers are divs, so a click reaches them even while disabled.
11627
+ if (this.disabled)
11628
+ return;
11626
11629
  this.isOpen = !this.isOpen;
11627
11630
  if (this.isOpen) {
11628
11631
  this.prepareExpandFilterListOnOpen();
@@ -11642,6 +11645,13 @@ class PdsCombobox {
11642
11645
  };
11643
11646
  // Handler for button trigger keyboard events
11644
11647
  this.onButtonTriggerKeyDown = (e) => {
11648
+ // Disabled takes it out of the tab order, but setFocus() or a click can still focus it.
11649
+ if (this.disabled) {
11650
+ // Keep Space from scrolling the page, as on a native disabled button.
11651
+ if (e.key === ' ')
11652
+ e.preventDefault();
11653
+ return;
11654
+ }
11645
11655
  // Escape closes an open group before the dropdown. Other keys reach the tree through handleKeyDown.
11646
11656
  if (this.isOpen && e.key === 'Escape' && this.handleGroupedPanelKey(e))
11647
11657
  return;
@@ -11662,8 +11672,9 @@ class PdsCombobox {
11662
11672
  }, 0);
11663
11673
  }
11664
11674
  else if (e.key === 'Escape') {
11665
- e.preventDefault();
11675
+ // Only an open dropdown claims Escape; a closed one leaves it to a modal or popover around it.
11666
11676
  if (this.isOpen) {
11677
+ e.preventDefault();
11667
11678
  this.isOpen = false;
11668
11679
  this.highlightedIndex = -1;
11669
11680
  this.clearExpandFilterList();
@@ -11908,6 +11919,22 @@ class PdsCombobox {
11908
11919
  if (this.isOpen && this.hidesPanelSearch && this.panelTreeEl)
11909
11920
  this.focusPanel(this.panelTreeEl);
11910
11921
  }
11922
+ disabledChanged(disabled) {
11923
+ var _a, _b, _c, _d, _e;
11924
+ if (!disabled || !this.isOpen)
11925
+ return;
11926
+ const shadowActive = (_b = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) !== null && _b !== void 0 ? _b : null;
11927
+ const focusInDropdown = !!((_d = (_c = this.portalEl) === null || _c === void 0 ? void 0 : _c.shadowRoot) === null || _d === void 0 ? void 0 : _d.activeElement) || (!!shadowActive && !!((_e = this.listboxEl) === null || _e === void 0 ? void 0 : _e.contains(shadowActive)));
11928
+ this.isOpen = false;
11929
+ this.highlightedIndex = -1;
11930
+ this.isArrowKeyNavigationMode = false;
11931
+ this.clearExpandFilterList();
11932
+ this.updateAriaActiveDescendant();
11933
+ // Closing removes the search field or option that held focus. A button or chip trigger can still
11934
+ // take it (aria-disabled), so focus doesn't fall to the page; a disabled input can't, as natively.
11935
+ if (focusInDropdown && this.trigger !== 'input')
11936
+ this.restoreFocusToTrigger();
11937
+ }
11911
11938
  isOpenChanged(isOpen) {
11912
11939
  if (!isOpen) {
11913
11940
  this.revealedGroupLabel = null;
@@ -13622,13 +13649,13 @@ class PdsCombobox {
13622
13649
  ]
13623
13650
  .filter(Boolean)
13624
13651
  .join(' ');
13625
- return (hAsync(Host, { key: 'e934b937916e45f715e6fb54d23a2a5e7c8ce4f6', "aria-disabled": this.disabled ? 'true' : null }, hAsync("div", { key: '79d282907c4f218e124b4f9250e0aedb0e9ea51d', class: "pds-combobox", tabIndex: -1, onFocusout: this.onComboboxFocusOut, part: "combobox" }, this.hasVisibleLabel && (hAsync("label", { key: 'd05a4a8fe5362b2b0a684f36115c127689c35c2f', htmlFor: this.trigger === 'input' ? this.componentId : undefined, id: this.labelId, class: "pds-combobox__label", onClick: this.onLabelClick }, this.label)), this.trigger === 'input' ? (hAsync("div", { class: "pds-combobox__input-wrapper", style: { width: this.triggerWidth } }, hAsync("input", { ref: el => {
13652
+ return (hAsync(Host, { key: 'c3bda161c67bab75f306c7f38435d279e5a88756', "aria-disabled": this.disabled ? 'true' : null }, hAsync("div", { key: '92b3f83c14a7296b3b2671eaef538a15743f695f', class: "pds-combobox", tabIndex: -1, onFocusout: this.onComboboxFocusOut, part: "combobox" }, this.hasVisibleLabel && (hAsync("label", { key: '4a2dba09dd6db9893e21346b861b4c6cb891112b', htmlFor: this.trigger === 'input' ? this.componentId : undefined, id: this.labelId, class: "pds-combobox__label", onClick: this.onLabelClick }, this.label)), this.trigger === 'input' ? (hAsync("div", { class: "pds-combobox__input-wrapper", style: { width: this.triggerWidth } }, hAsync("input", { ref: el => {
13626
13653
  this.inputEl = el;
13627
13654
  this.triggerEl = el;
13628
13655
  }, class: {
13629
13656
  'pds-combobox__input': true,
13630
13657
  'pds-combobox__input--invalid': this.isInvalid(),
13631
- }, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-haspopup": this.inputPopupRole === 'tree' ? 'tree' : undefined, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, value: this.displayText, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput, onClick: this.handleInputClick, onKeyDown: this.handleKeyDown, autocomplete: "off", part: "input" }), hAsync("pds-icon", { icon: "enlarge", "aria-hidden": "true", class: "pds-combobox__input-icon" }))) : this.trigger === 'chip' ? (hAsync("div", { class: this.getChipTriggerClass(), style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "chip-trigger" }, this.renderChipTriggerContent())) : (hAsync("div", { class: triggerClass, style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "button-trigger" }, this.renderButtonTriggerContent())), this.renderMessages(), hAsync("div", { key: '38d9c018d59472ef8b7eb3facb47bea5c73f5f1e', style: { display: 'none' } }, hAsync("slot", { key: '26acb32e5f301b568485d52e29b68e526ef1d525', onSlotchange: () => this.updateOptions() })), this.renderDropdown(), this.groupDisplay !== 'inline' && [
13658
+ }, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-haspopup": this.inputPopupRole === 'tree' ? 'tree' : undefined, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, value: this.displayText, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput, onClick: this.handleInputClick, onKeyDown: this.handleKeyDown, autocomplete: "off", part: "input" }), hAsync("pds-icon", { icon: "enlarge", "aria-hidden": "true", class: "pds-combobox__input-icon" }))) : this.trigger === 'chip' ? (hAsync("div", { class: this.getChipTriggerClass(), style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "chip-trigger" }, this.renderChipTriggerContent())) : (hAsync("div", { class: triggerClass, style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "button-trigger" }, this.renderButtonTriggerContent())), this.renderMessages(), hAsync("div", { key: 'b8d358339bb26037b6c3c464bd4e13fe978f7857', style: { display: 'none' } }, hAsync("slot", { key: '4aa4c2b8f2b57e13d1e958cc745c61e39e800529', onSlotchange: () => this.updateOptions() })), this.renderDropdown(), this.groupDisplay !== 'inline' && [
13632
13659
  hAsync("span", { key: "drill-status", class: "visually-hidden", role: "status", "aria-live": "polite" }, this.isOpen && this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null ? this.openGroup.label : ''),
13633
13660
  // Its own region: sharing one, revealing a row then opening it would leave the text the
13634
13661
  // same, and opening the group would go unannounced.
@@ -13647,6 +13674,9 @@ class PdsCombobox {
13647
13674
  "flyoutFallback": [{
13648
13675
  "flyoutFallbackChanged": 0
13649
13676
  }],
13677
+ "disabled": [{
13678
+ "disabledChanged": 0
13679
+ }],
13650
13680
  "isOpen": [{
13651
13681
  "isOpenChanged": 0
13652
13682
  }],
@@ -16080,22 +16110,24 @@ function unwrapReconnectedContent(container, selector, contentSelector) {
16080
16110
  }
16081
16111
  }
16082
16112
 
16083
- const pdsDrawerCss = () => `.pds-drawer__modal>.pds-modal__backdrop{-ms-flex-align:stretch;align-items:stretch;background-color:transparent;pointer-events:none}@supports ((-webkit-backdrop-filter: blur(3px)) or (backdrop-filter: blur(3px))){.pds-drawer__modal>.pds-modal__backdrop{-webkit-backdrop-filter:none;backdrop-filter:none}}.pds-drawer__modal>.pds-modal__backdrop[aria-modal]::before{content:none}.pds-drawer__modal>.pds-modal__backdrop>.pds-modal{border-radius:0;height:100%;margin:0;max-height:100%;max-width:100%;pointer-events:auto;width:var(--pds-drawer-width, 500px)}.pds-drawer{display:contents}.pds-drawer.pds-drawer--end .pds-drawer__modal>.pds-modal__backdrop{-ms-flex-pack:end;justify-content:flex-end}.pds-drawer.pds-drawer--start .pds-drawer__modal>.pds-modal__backdrop{-ms-flex-pack:start;justify-content:flex-start}.pds-drawer.pds-drawer--sm{--pds-drawer-width:360px}.pds-drawer.pds-drawer--md{--pds-drawer-width:500px}`;
16113
+ const pdsDrawerCss = () => `.pds-drawer__modal>.pds-modal__backdrop{-ms-flex-align:stretch;align-items:stretch}.pds-drawer__modal:not(.pds-drawer__modal--modal)>.pds-modal__backdrop{background-color:transparent;pointer-events:none}@supports ((-webkit-backdrop-filter: blur(3px)) or (backdrop-filter: blur(3px))){.pds-drawer__modal:not(.pds-drawer__modal--modal)>.pds-modal__backdrop{-webkit-backdrop-filter:none;backdrop-filter:none}}.pds-drawer__modal>.pds-modal__backdrop[aria-modal]::before{content:none}.pds-drawer__modal>.pds-modal__backdrop>.pds-modal{border-radius:0;height:100%;margin:0;max-height:100%;max-width:100%;pointer-events:auto;width:var(--pds-drawer-width, 500px)}.pds-drawer{display:contents}.pds-drawer.pds-drawer--end .pds-drawer__modal>.pds-modal__backdrop{-ms-flex-pack:end;justify-content:flex-end}.pds-drawer.pds-drawer--start .pds-drawer__modal>.pds-modal__backdrop{-ms-flex-pack:start;justify-content:flex-start}.pds-drawer.pds-drawer--sm{--pds-drawer-width:360px}.pds-drawer.pds-drawer--md{--pds-drawer-width:500px}`;
16084
16114
 
16085
- // Known selectors for content Pine components portal to document.body rather
16086
- // than rendering in place — pds-popover, pds-tooltip, pds-combobox's dropdown.
16115
+ // Known selectors for content Pine components portal out of place rather than
16116
+ // rendering where they are — pds-tooltip and pds-combobox's dropdown in
16117
+ // document.body, pds-popover in the body or the open dialog it's in.
16087
16118
  // A pointerdown landing on one of these is not "outside" the drawer in the
16088
16119
  // sense light dismiss cares about: it belongs to an overlay something inside
16089
16120
  // the drawer opened, even though it is not a DOM descendant of the drawer.
16090
16121
  const PORTALED_OVERLAY_SELECTOR = '.pds-popover, .pds-tooltip, .pds-combobox-dropdown-portal';
16091
16122
  /**
16092
- * A non-modal side panel composed from `pds-modal`.
16123
+ * A side panel composed from `pds-modal`, non-modal by default.
16093
16124
  *
16094
- * Unlike `pds-modal`, the page stays interactive while a drawer is open: no
16095
- * dimming, no blur, no scroll lock, no click blocking. `pds-drawer` renders a
16096
- * `pds-modal` internally with `disableTopLayer` always on and its backdrop
16097
- * suppressed, and adds the edge, width and dismiss behavior a side panel
16098
- * needs on top. It does not reimplement focus, Escape or dialog semantics —
16125
+ * By default, unlike `pds-modal`, the page stays interactive while a drawer
16126
+ * is open: no dimming, no blur, no scroll lock, no click blocking.
16127
+ * `pds-drawer` renders a `pds-modal` internally with `disableTopLayer` on and
16128
+ * its backdrop suppressed, and adds the edge, width and dismiss behavior a
16129
+ * side panel needs on top. With `modal`, it opens that `pds-modal` in the top
16130
+ * layer instead and keeps its backdrop. It does not reimplement focus, Escape or dialog semantics —
16099
16131
  * those come from `pds-modal` unchanged.
16100
16132
  * @lightDom renders-markup
16101
16133
  */
@@ -16126,8 +16158,18 @@ class PdsDrawer {
16126
16158
  */
16127
16159
  this.scrollable = true;
16128
16160
  /**
16129
- * Whether the drawer can be dismissed with a pointerdown outside it.
16130
- * Replaces `pds-modal`'s `backdropDismiss` — there is no backdrop to click.
16161
+ * Whether the drawer is modal. A modal drawer dims the page behind it with a
16162
+ * backdrop, makes the page inert, and keeps focus inside the drawer, like
16163
+ * `pds-modal`. Use it for a task the page should wait on. Leave it off for a
16164
+ * panel people work alongside the page. Read when the drawer opens; changing
16165
+ * it while the drawer is open is not supported.
16166
+ * @default false
16167
+ */
16168
+ this.modal = false;
16169
+ /**
16170
+ * Whether the drawer can be dismissed with a pointerdown outside it (in a
16171
+ * `modal` drawer, a click on its backdrop).
16172
+ * Replaces `pds-modal`'s `backdropDismiss`.
16131
16173
  * This also gates Escape, matching how `backdropDismiss` gates Escape on
16132
16174
  * `pds-modal` today: setting this to `false` means the close button is the
16133
16175
  * only way out.
@@ -16146,7 +16188,8 @@ class PdsDrawer {
16146
16188
  * background event while the user is mid-task elsewhere on the page —
16147
16189
  * moving focus there would interrupt them, and the page stays live under
16148
16190
  * a non-modal drawer, so it is a genuine data-entry risk, not just an
16149
- * annoyance.
16191
+ * annoyance. A `modal` drawer ignores `none`: the page behind it is inert,
16192
+ * so focus starts in the drawer.
16150
16193
  * @default 'auto'
16151
16194
  */
16152
16195
  this.initialFocus = 'auto';
@@ -16192,7 +16235,8 @@ class PdsDrawer {
16192
16235
  // page underneath stays genuinely clickable, which means a click there
16193
16236
  // never reaches pds-modal's backdrop element at all.
16194
16237
  handleOutsidePointerDown(e) {
16195
- if (!this.lightDismiss || !this.open)
16238
+ // A modal drawer's backdrop takes the click instead, through pds-modal.
16239
+ if (this.modal || !this.lightDismiss || !this.open)
16196
16240
  return;
16197
16241
  const target = e.target;
16198
16242
  if (!target)
@@ -16202,7 +16246,7 @@ class PdsDrawer {
16202
16246
  if (target.closest(PORTALED_OVERLAY_SELECTOR))
16203
16247
  return;
16204
16248
  // A datepicker's portaled calendar counts as inside only when its datepicker is in the drawer,
16205
- // or in an overlay portaled from it (a pds-popover in the drawer puts its content on the body).
16249
+ // or in an overlay portaled from it (a pds-popover in the drawer puts its content beside it).
16206
16250
  const calendar = target.closest('.pds-datepicker-calendar-portal');
16207
16251
  const owner = calendar === null || calendar === void 0 ? void 0 : calendar.pdsDatepicker;
16208
16252
  if (owner && (containsComposed$1(this.el, owner) || closest$1(PORTALED_OVERLAY_SELECTOR, owner)))
@@ -16218,11 +16262,11 @@ class PdsDrawer {
16218
16262
  this.open = false;
16219
16263
  }
16220
16264
  render() {
16221
- return (hAsync(Host, { key: 'ae7e67ed196ef95a39c2b729e859712ce5c41144', class: {
16265
+ return (hAsync(Host, { key: '287bf043c991209488eb1b617031acffb59ff9af', class: {
16222
16266
  'pds-drawer': true,
16223
16267
  [`pds-drawer--${this.side}`]: true,
16224
16268
  [`pds-drawer--${this.size}`]: true,
16225
- } }, hAsync("pds-modal", { key: '9b35ce377c84612a844a561d5540e98b08c0e499', class: "pds-drawer__modal", ref: (el) => (this.modalEl = el), componentId: this.componentId, open: this.open, scrollable: this.scrollable, disableTopLayer: true, backdropDismiss: this.lightDismiss, disableInitialFocus: this.initialFocus === 'none' }, hAsync("slot", { key: 'dda01aa40dc41b3d17073238a8cad8d816571e18' }))));
16269
+ } }, hAsync("pds-modal", { key: 'cd86fb552353f5cc12fb36fc956033432a91847c', class: { 'pds-drawer__modal': true, 'pds-drawer__modal--modal': this.modal }, ref: (el) => (this.modalEl = el), componentId: this.componentId, open: this.open, scrollable: this.scrollable, disableTopLayer: !this.modal, backdropDismiss: this.lightDismiss, disableInitialFocus: !this.modal && this.initialFocus === 'none' }, hAsync("slot", { key: '56bbe9b5c26b10b07c0f503ba147899a57a5de85' }))));
16226
16270
  }
16227
16271
  get el() { return getElement(this); }
16228
16272
  static get style() { return pdsDrawerCss(); }
@@ -16235,6 +16279,7 @@ class PdsDrawer {
16235
16279
  "side": [1],
16236
16280
  "size": [1],
16237
16281
  "scrollable": [4],
16282
+ "modal": [4],
16238
16283
  "lightDismiss": [4, "light-dismiss"],
16239
16284
  "initialFocus": [1, "initial-focus"]
16240
16285
  },
@@ -16586,15 +16631,19 @@ class PdsDropdownMenu {
16586
16631
  this.focusItemByIndex(prevIndex);
16587
16632
  }
16588
16633
  }
16634
+ // Escape is taken in document's capture phase, before a modal around the menu hears it on
16635
+ // document and closes too.
16636
+ handleEscapeKeyDown(event) {
16637
+ if (!this.isOpen || event.key !== 'Escape' || event.defaultPrevented)
16638
+ return;
16639
+ event.preventDefault();
16640
+ this.closeDropdown();
16641
+ }
16589
16642
  // Handle keyboard events for the dropdown
16590
16643
  handleKeyDown(event) {
16591
16644
  if (!this.isOpen)
16592
16645
  return;
16593
16646
  switch (event.key) {
16594
- case 'Escape':
16595
- event.preventDefault();
16596
- this.closeDropdown();
16597
- break;
16598
16647
  case 'ArrowDown':
16599
16648
  event.preventDefault();
16600
16649
  this.focusNextItem();
@@ -16686,7 +16735,7 @@ class PdsDropdownMenu {
16686
16735
  }
16687
16736
  }
16688
16737
  render() {
16689
- return (hAsync(Host, { key: '0dd585168e8fa86378c4812ce54fd09108861d5b', id: this.componentId }, hAsync("slot", { key: 'c7144f8d6aaebe37b585c6c6230102c013230a9e', name: "trigger", onSlotchange: this.handleTriggerSlotChange }), hAsync("pds-box", { key: '82292e051337141bc9fd83b90292b188b0bb3489', "border-radius": "sm", display: "flex", direction: "column", class: "pds-dropdown-menu--panel is-hidden", shadow: "100", part: "menu-panel" }, hAsync("div", { key: 'c0f49dca14df0ba3885865c51562c380155bf8a8', class: { 'pds-dropdown-menu__header': true, 'is-hidden': !this.hasHeader } }, hAsync("slot", { key: 'dc80c97a8dbb79ab4563524b3dea68d144e8570c', name: "header", onSlotchange: this.handleHeaderSlotChange })), hAsync("div", { key: '29cb56bdf28a0438296f33e0fa86c9da7b51902c', class: "pds-dropdown-menu__items", role: "menu", "aria-orientation": "vertical" }, hAsync("slot", { key: '8f91ceeb1eedc4acc79f09032dedc69a2ba1f99a', onSlotchange: this.handleSlotChange })))));
16738
+ return (hAsync(Host, { key: '18dac4d59ea236fe171f4c16f6571cb1547059c6', id: this.componentId }, hAsync("slot", { key: '95201a75a33ccb70dbd51a5cf9d78c76ebf26f65', name: "trigger", onSlotchange: this.handleTriggerSlotChange }), hAsync("pds-box", { key: '7fc588f40f607ad81ad70bf74a525d078d32a80b', "border-radius": "sm", display: "flex", direction: "column", class: "pds-dropdown-menu--panel is-hidden", shadow: "100", part: "menu-panel" }, hAsync("div", { key: '73d13d94edccb68db7b703f930b50f729334efa8', class: { 'pds-dropdown-menu__header': true, 'is-hidden': !this.hasHeader } }, hAsync("slot", { key: '7066f3a6f71c8961326f38f0509f1c0cc17098ae', name: "header", onSlotchange: this.handleHeaderSlotChange })), hAsync("div", { key: '9f07ec488b688c58c6422b575015ccd364075fe8', class: "pds-dropdown-menu__items", role: "menu", "aria-orientation": "vertical" }, hAsync("slot", { key: 'd0aad3f9ac3b58779669411523adbb8878dec61c', onSlotchange: this.handleSlotChange })))));
16690
16739
  }
16691
16740
  get host() { return getElement(this); }
16692
16741
  static get style() { return pdsDropdownMenuCss(); }
@@ -16699,7 +16748,7 @@ class PdsDropdownMenu {
16699
16748
  "currentFocusIndex": [32],
16700
16749
  "hasHeader": [32]
16701
16750
  },
16702
- "$listeners$": [[8, "keydown", "handleKeyDown"], [8, "click", "handleWindowClick"]],
16751
+ "$listeners$": [[6, "keydown", "handleEscapeKeyDown"], [8, "keydown", "handleKeyDown"], [8, "click", "handleWindowClick"]],
16703
16752
  "$lazyBundleId$": "-",
16704
16753
  "$attrsToReflect$": []
16705
16754
  }; }
@@ -18568,8 +18617,36 @@ function containsComposed(ancestor, node) {
18568
18617
  }
18569
18618
  return false;
18570
18619
  }
18620
+ /**
18621
+ * Every open modal, with the order it opened in, which ranks top-layer modals and the modals
18622
+ * inside them. Kept here rather than queried from the document, which can't see a modal inside
18623
+ * another component's shadow root.
18624
+ */
18625
+ const openModals = new Map();
18626
+ let openSequence = 0;
18627
+ /** Whether an open auto popover's panel sits in `root`, including inside shadow roots under it. */
18628
+ function hasOpenPopover(root) {
18629
+ if (root.querySelector('[data-pds-popover-open]'))
18630
+ return true;
18631
+ return Array.from(root.querySelectorAll('*')).some(el => !!el.shadowRoot && hasOpenPopover(el.shadowRoot));
18632
+ }
18633
+ /**
18634
+ * Whether `a` comes before `b` in the page. `compareDocumentPosition` is arbitrary across a shadow
18635
+ * boundary, so each side climbs to its host until both are in the same tree.
18636
+ */
18637
+ function precedesComposed(a, b) {
18638
+ // A document has no host, which ends each climb.
18639
+ for (let x = a; x; x = x.getRootNode().host) {
18640
+ for (let y = b; y; y = y.getRootNode().host) {
18641
+ if (x.getRootNode() === y.getRootNode()) {
18642
+ return !!(x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING);
18643
+ }
18644
+ }
18645
+ }
18646
+ return false;
18647
+ }
18571
18648
  /** The focused element, followed down through shadow roots to the control that has focus. */
18572
- function deepActiveElement() {
18649
+ function deepActiveElement$1() {
18573
18650
  var _a;
18574
18651
  let active = document.activeElement;
18575
18652
  while ((_a = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)
@@ -18586,7 +18663,8 @@ class PdsModal {
18586
18663
  this.pdsModalClose = createEvent(this, "pdsModalClose");
18587
18664
  this.focusableElements = [];
18588
18665
  /**
18589
- * Whether the modal can be dismissed by clicking the backdrop
18666
+ * Whether the modal can be dismissed by clicking the backdrop, pressing Escape, or the browser's
18667
+ * own close request (such as a back gesture)
18590
18668
  * @default true
18591
18669
  */
18592
18670
  this.backdropDismiss = true;
@@ -18634,19 +18712,33 @@ class PdsModal {
18634
18712
  * Stores the list of focusable elements in the modal
18635
18713
  */
18636
18714
  this.focusableElementsArray = [];
18715
+ // The browser's own close request (an Escape nothing prevented, a back gesture) would close the
18716
+ // dialog without updating `open`, so close it through hideModal() instead, or not at all.
18717
+ this.handleCancel = (e) => {
18718
+ e.preventDefault();
18719
+ if (!this.open)
18720
+ return;
18721
+ // The request reaches the top-layer dialog even when a disableTopLayer modal inside it is on
18722
+ // top; that one closes first, as it would on Escape.
18723
+ const top = this.innermostOpenModal();
18724
+ const target = (top && containsComposed(this.el, top) ? top : this.el);
18725
+ if (target.backdropDismiss)
18726
+ target.hideModal();
18727
+ };
18637
18728
  this.handleBackdropClick = (e) => {
18638
18729
  if (!this.backdropDismiss || !this.open)
18639
18730
  return;
18640
- if (e.target.classList.contains('pds-modal__backdrop')) {
18731
+ // This modal's own backdrop; a nested modal's backdrop handles its own clicks.
18732
+ if (e.target === this.modalRef) {
18641
18733
  e.stopPropagation();
18642
18734
  // Only close if this is the innermost modal
18643
- if (this.isInnermostModal()) {
18735
+ if (this.innermostOpenModal() === this.el) {
18644
18736
  this.hideModal();
18645
18737
  }
18646
18738
  }
18647
18739
  };
18648
18740
  this.handleKeyDown = (e) => {
18649
- var _a, _b, _c;
18741
+ var _a, _b, _c, _d;
18650
18742
  // If the modal is not open, don't handle any keyboard events
18651
18743
  if (!this.open)
18652
18744
  return;
@@ -18669,24 +18761,39 @@ class PdsModal {
18669
18761
  if (!('__pdsActiveElementAtEscape' in eventSnapshot)) {
18670
18762
  const activeAtDispatch = document.activeElement;
18671
18763
  eventSnapshot.__pdsActiveElementAtEscape = activeAtDispatch;
18764
+ // Followed into shadow roots: for a modal inside one, document.activeElement is only the host.
18765
+ eventSnapshot.__pdsDeepActiveElementAtEscape = deepActiveElement$1();
18672
18766
  const topLayerDialog = (_a = activeAtDispatch === null || activeAtDispatch === void 0 ? void 0 : activeAtDispatch.closest('dialog')) !== null && _a !== void 0 ? _a : null;
18673
18767
  eventSnapshot.__pdsTopLayerDialogAtEscape = topLayerDialog;
18674
18768
  eventSnapshot.__pdsTopLayerDialogWasModalAtEscape = !!(topLayerDialog && topLayerDialog.matches(':modal'));
18769
+ // A control that closed on this Escape (a combobox on its own element, a dropdown menu or
18770
+ // multiselect in document's capture phase) has prevented it before this bubble listener.
18771
+ // Read before any modal prevents it below.
18772
+ eventSnapshot.__pdsEscapeHandledInside = e.defaultPrevented;
18773
+ // Which modal is innermost is read once too: the inner one closing first would make its
18774
+ // outer modal the innermost by the time that modal's listener runs.
18775
+ eventSnapshot.__pdsInnermostModalAtEscape = this.innermostOpenModal();
18675
18776
  }
18777
+ if (eventSnapshot.__pdsEscapeHandledInside)
18778
+ return;
18676
18779
  // Leave Escape to a genuinely stacked-above overlay (disableTopLayer's
18677
18780
  // reason for existing) — not just to anything outside this modal.
18678
18781
  const active = eventSnapshot.__pdsActiveElementAtEscape;
18679
18782
  if (this.disableTopLayer &&
18680
18783
  active &&
18681
18784
  active !== document.body &&
18682
- !this.el.contains(active) &&
18683
- this.isStackedAboveOverlay(active, (_b = eventSnapshot.__pdsTopLayerDialogAtEscape) !== null && _b !== void 0 ? _b : null, (_c = eventSnapshot.__pdsTopLayerDialogWasModalAtEscape) !== null && _c !== void 0 ? _c : false)) {
18785
+ !containsComposed(this.el, (_b = eventSnapshot.__pdsDeepActiveElementAtEscape) !== null && _b !== void 0 ? _b : active) &&
18786
+ this.isStackedAboveOverlay(active, (_c = eventSnapshot.__pdsTopLayerDialogAtEscape) !== null && _c !== void 0 ? _c : null, (_d = eventSnapshot.__pdsTopLayerDialogWasModalAtEscape) !== null && _d !== void 0 ? _d : false)) {
18684
18787
  return;
18685
18788
  }
18789
+ // An open popover inside takes Escape from its own document listener, which runs after this
18790
+ // one, so leave the key for it. Its preventDefault() keeps the native dialog open.
18791
+ if (this.modalRef && hasOpenPopover(this.modalRef))
18792
+ return;
18686
18793
  // Always prevent native dialog close behavior
18687
18794
  e.preventDefault();
18688
18795
  // Only close if backdropDismiss is enabled and this is the innermost modal
18689
- if (this.backdropDismiss && this.isInnermostModal()) {
18796
+ if (this.backdropDismiss && eventSnapshot.__pdsInnermostModalAtEscape === this.el) {
18690
18797
  this.hideModal();
18691
18798
  }
18692
18799
  return;
@@ -18706,7 +18813,7 @@ class PdsModal {
18706
18813
  // Every open modal hears this keydown. When focus is in a modal dialog opened above this one,
18707
18814
  // that dialog traps Tab: this modal's list also holds its controls, so it would wrap first. A
18708
18815
  // non-modal dialog inside (a disableTopLayer modal) doesn't trap, so this one still does.
18709
- const activeElement = deepActiveElement();
18816
+ const activeElement = deepActiveElement$1();
18710
18817
  if (!activeElement || this.modalDialogAbove(activeElement))
18711
18818
  return;
18712
18819
  // Content can change while the modal is open (rows fetched in, sections shown), so the
@@ -18746,8 +18853,6 @@ class PdsModal {
18746
18853
  }
18747
18854
  componentDidLoad() {
18748
18855
  this.modalRef = this.el.querySelector('.pds-modal__backdrop');
18749
- // Add keyboard event listener
18750
- document.addEventListener('keydown', this.handleKeyDown);
18751
18856
  // @Watch('open') only fires on a later change, not the prop's initial
18752
18857
  // value — a modal mounted with `open` already true (static markup, or a
18753
18858
  // framework passing it on first render) would otherwise never call
@@ -18763,9 +18868,18 @@ class PdsModal {
18763
18868
  componentDidRender() {
18764
18869
  unwrapReconnectedContent(this.el.querySelector('.pds-modal'), 'dialog.pds-modal__backdrop', '.pds-modal');
18765
18870
  }
18871
+ // Here rather than in componentDidLoad, which runs once: a modal taken out of the page and put
18872
+ // back (a morph or a framework moving it) gets its keyboard handling, and its place among the
18873
+ // open modals, back too. Adding the same listener twice is a no-op.
18874
+ connectedCallback() {
18875
+ document.addEventListener('keydown', this.handleKeyDown);
18876
+ if (this.open && !openModals.has(this.el))
18877
+ openModals.set(this.el, ++openSequence);
18878
+ }
18766
18879
  disconnectedCallback() {
18767
18880
  // Clean up event listener
18768
18881
  document.removeEventListener('keydown', this.handleKeyDown);
18882
+ openModals.delete(this.el);
18769
18883
  }
18770
18884
  handleOpenChange(newValue) {
18771
18885
  if (newValue) {
@@ -18830,6 +18944,9 @@ class PdsModal {
18830
18944
  this.modalRef.showModal();
18831
18945
  }
18832
18946
  this.open = true;
18947
+ // Showing an open modal again doesn't move it above one opened after it.
18948
+ if (!openModals.has(this.el))
18949
+ openModals.set(this.el, ++openSequence);
18833
18950
  // Update focusable elements and set initial focus
18834
18951
  // Using a longer timeout to ensure all components are fully rendered
18835
18952
  setTimeout(() => {
@@ -18892,6 +19009,7 @@ class PdsModal {
18892
19009
  const focusAlreadyMovedElsewhere = activeElement !== null && activeElement !== document.body && !this.modalRef.contains(activeElement);
18893
19010
  this.modalRef.close();
18894
19011
  this.open = false;
19012
+ openModals.delete(this.el);
18895
19013
  // Restore focus to the element that was focused before the modal was opened
18896
19014
  if (!focusAlreadyMovedElsewhere &&
18897
19015
  this.previousActiveElement &&
@@ -18910,7 +19028,9 @@ class PdsModal {
18910
19028
  */
18911
19029
  getBackdropZIndex(modal) {
18912
19030
  const backdrop = modal.querySelector('.pds-modal__backdrop');
18913
- return backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;
19031
+ // `auto` (no z-index token loaded) parses to NaN, which no comparison can rank.
19032
+ const zIndex = backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;
19033
+ return Number.isNaN(zIndex) ? 0 : zIndex;
18914
19034
  }
18915
19035
  /**
18916
19036
  * Whether `active` sits inside a surface actually stacked above this modal.
@@ -18937,7 +19057,8 @@ class PdsModal {
18937
19057
  * lose that evidence depending on listener order.
18938
19058
  */
18939
19059
  isStackedAboveOverlay(active, topLayerDialog, topLayerDialogWasModal) {
18940
- if (topLayerDialog && topLayerDialog !== this.modalRef && topLayerDialogWasModal) {
19060
+ // A top-layer dialog this modal sits inside is underneath it, not an overlay above.
19061
+ if (topLayerDialog && topLayerDialog !== this.modalRef && topLayerDialogWasModal && !containsComposed(topLayerDialog, this.el)) {
18941
19062
  return true;
18942
19063
  }
18943
19064
  const ownZIndex = this.getBackdropZIndex(this.el);
@@ -18953,24 +19074,37 @@ class PdsModal {
18953
19074
  return false;
18954
19075
  }
18955
19076
  /**
18956
- * Checks if this modal is the innermost (highest z-index) modal
19077
+ * The open modal that takes Escape or a backdrop click, among those with no other open modal
19078
+ * inside them (a nested modal's backdrop can share its outer modal's z-index). A top-layer dialog
19079
+ * (or a modal inside one) paints above anything outside it, and within the top layer the last
19080
+ * opened is on top. Modals in the same layer (the page, or one top-layer dialog) then rank by
19081
+ * the highest backdrop, then the one later in the page.
18957
19082
  */
18958
- isInnermostModal() {
18959
- // Find all open modals
18960
- const openModals = Array.from(document.querySelectorAll('pds-modal')).filter(modal => modal.open);
18961
- if (openModals.length === 0)
18962
- return false;
18963
- // Get this modal's backdrop element
18964
- const thisBackdrop = this.el.querySelector('.pds-modal__backdrop');
18965
- if (!thisBackdrop)
18966
- return false;
18967
- // Get computed z-index of all open modal backdrops
18968
- const modalZIndexes = openModals.map(modal => this.getBackdropZIndex(modal));
18969
- // Get the highest z-index
18970
- const maxZIndex = Math.max(...modalZIndexes);
18971
- // Check if this modal's backdrop has the highest z-index
18972
- const thisZIndex = this.getBackdropZIndex(this.el);
18973
- return thisZIndex === maxZIndex;
19083
+ innermostOpenModal() {
19084
+ const open = Array.from(openModals.keys()).filter(modal => modal.isConnected);
19085
+ const innermost = open.filter(modal => !open.some(other => other !== modal && containsComposed(modal, other)));
19086
+ const byDocumentOrder = [...innermost].sort((a, b) => (precedesComposed(a, b) ? -1 : 1));
19087
+ // A modal inside a top-layer dialog paints in that dialog's place in the top layer, even when it
19088
+ // isn't in the top layer itself (disableTopLayer), so that dialog's open order ranks it first.
19089
+ const rank = (modal) => {
19090
+ var _a;
19091
+ const backdrop = modal.querySelector('.pds-modal__backdrop');
19092
+ const topLayerDialog = backdrop ? closest$1('dialog:modal', backdrop) : null;
19093
+ const topLayerModal = topLayerDialog === null || topLayerDialog === void 0 ? void 0 : topLayerDialog.closest('pds-modal');
19094
+ const layer = topLayerDialog ? ((_a = (topLayerModal && openModals.get(topLayerModal))) !== null && _a !== void 0 ? _a : 0) : 0;
19095
+ return [topLayerDialog ? 1 : 0, layer, this.getBackdropZIndex(modal), byDocumentOrder.indexOf(modal)];
19096
+ };
19097
+ let top = null;
19098
+ let topRank = [-1, -1, -1, -1];
19099
+ for (const modal of innermost) {
19100
+ const modalRank = rank(modal);
19101
+ const above = modalRank.findIndex((value, i) => value !== topRank[i]);
19102
+ if (above >= 0 && modalRank[above] > topRank[above]) {
19103
+ top = modal;
19104
+ topRank = modalRank;
19105
+ }
19106
+ }
19107
+ return top;
18974
19108
  }
18975
19109
  /** Whether a modal dialog other than this one sits between the focused element and this modal. */
18976
19110
  modalDialogAbove(active) {
@@ -19010,14 +19144,14 @@ class PdsModal {
19010
19144
  }
19011
19145
  render() {
19012
19146
  const [sizeClass = 'md', customSize] = this.resolveSize();
19013
- return (hAsync("dialog", { key: '12ea38928ea287f16ffa33e246caa2bfde6fc130', class: {
19147
+ return (hAsync("dialog", { key: '0633d4e8a7cb81f2f9280e754564960a00a4d60b', class: {
19014
19148
  'pds-modal__backdrop': true,
19015
19149
  'open': this.open
19016
- }, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick }, hAsync("div", { key: 'b9d83cabda56c315d9115f7030e3bdbcf923ec08', class: {
19150
+ }, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick, onCancel: this.handleCancel }, hAsync("div", { key: '466f8a3476cf066817554f47ec7d3a0e3ab10b89', class: {
19017
19151
  'pds-modal': true,
19018
19152
  [`pds-modal--${sizeClass}`]: true,
19019
19153
  'pds-modal--scrollable': this.scrollable
19020
- }, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, hAsync("slot", { key: '3c5edad1482cb124e503197adf411890573b076d' }))));
19154
+ }, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, hAsync("slot", { key: '01ca45c96540416216a1082bad52b137720f0f4d' }))));
19021
19155
  }
19022
19156
  get el() { return getElement(this); }
19023
19157
  static get watchers() { return {
@@ -19850,11 +19984,12 @@ class PdsMultiselect {
19850
19984
  }
19851
19985
  /**
19852
19986
  * Handle global keyboard events for accessibility.
19853
- * Closes dropdown on Escape key press regardless of focus location.
19987
+ * Closes dropdown on Escape key press regardless of focus location. Taken in document's capture
19988
+ * phase, before a modal around the multiselect hears it on document and closes too.
19854
19989
  */
19855
19990
  handleWindowKeyDown(event) {
19856
19991
  var _a;
19857
- if (!this.isOpen)
19992
+ if (!this.isOpen || event.defaultPrevented)
19858
19993
  return;
19859
19994
  if (event.key === 'Escape') {
19860
19995
  event.preventDefault();
@@ -20544,10 +20679,10 @@ class PdsMultiselect {
20544
20679
  }
20545
20680
  render() {
20546
20681
  const hasSelections = this.selectedItems.length > 0;
20547
- return (hAsync(Host, { key: '0ef6791666063e0d951d28f980b3bca89cf24287', "aria-disabled": this.disabled ? 'true' : null }, hAsync("div", { key: 'f0ccbabfbd2dcf38f6df52f73f2152eb4bd2828e', class: "pds-multiselect", onFocusout: this.handleContainerFocusOut }, this.label && (hAsync("label", { key: '88e01c744644ce1f75c8d5b71dfaeeb79d1ef7df', id: `${this.componentId}-label`, htmlFor: this.componentId, class: {
20682
+ return (hAsync(Host, { key: 'fcc35a82c16d5cf35a49ce997f19618104f9cc45', "aria-disabled": this.disabled ? 'true' : null }, hAsync("div", { key: '72d1fdf759d9173f54bac80ec50aff651fd2b357', class: "pds-multiselect", onFocusout: this.handleContainerFocusOut }, this.label && (hAsync("label", { key: 'e28f02c2beb23dc2c0deefadbd0645b20d938d91', id: `${this.componentId}-label`, htmlFor: this.componentId, class: {
20548
20683
  'pds-multiselect__label': true,
20549
20684
  'visually-hidden': this.hideLabel,
20550
- } }, this.label)), hAsync("div", { key: '0bf4a3b1378ff18e6d6b449507ebbae1bfb279e4', class: "pds-multiselect__wrapper", ref: el => (this.containerEl = el), style: { width: this.triggerWidth } }, this.slottedTriggerEl ? (hAsync("slot", { name: "trigger" })) : this.selectedDisplay === 'pill' && this.pillPosition === 'inline' ? (hAsync("div", { ref: el => (this.pillInlineTriggerEl = el || undefined), part: "trigger", class: {
20685
+ } }, this.label)), hAsync("div", { key: '06539bf2c4b8da2889505a9f042d5cd6721b1853', class: "pds-multiselect__wrapper", ref: el => (this.containerEl = el), style: { width: this.triggerWidth } }, this.slottedTriggerEl ? (hAsync("slot", { name: "trigger" })) : this.selectedDisplay === 'pill' && this.pillPosition === 'inline' ? (hAsync("div", { ref: el => (this.pillInlineTriggerEl = el || undefined), part: "trigger", class: {
20551
20686
  'pds-multiselect__trigger': true,
20552
20687
  'pds-multiselect__trigger--open': this.isOpen,
20553
20688
  'pds-multiselect__trigger--disabled': this.disabled,
@@ -20563,7 +20698,7 @@ class PdsMultiselect {
20563
20698
  }, id: this.componentId, disabled: this.disabled, "aria-required": this.required ? 'true' : undefined, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-haspopup": "listbox", "aria-describedby": 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 }, hAsync("span", { class: {
20564
20699
  'pds-multiselect__trigger-text': true,
20565
20700
  'pds-multiselect__trigger-text--placeholder': !hasSelections || (this.selectedDisplay === 'pill' && this.pillPosition === 'below'),
20566
- } }, this.getTriggerText()), hAsync("pds-icon", { "aria-hidden": "true", class: "pds-multiselect__icon", icon: enlarge }))), this.renderDropdown()), this.selectedDisplay === 'pill' && this.pillPosition === 'below' && this.renderBelowPills(), hAsync("span", { key: '5202a263b219d06891bda739f98db0162bf33c48', class: "visually-hidden", "aria-live": "polite", "aria-atomic": "true" }, this.removalAnnouncement), this.helperMessage && !(this.errorMessage && this.errorMessage.length > 0) && (hAsync("p", { key: '47f7b55863c5b72d41c4ef1893b9364988fc0b56', class: "pds-multiselect__helper", id: messageId(this.componentId, 'helper') }, this.helperMessage)), this.errorMessage && (hAsync("p", { key: '3930dc30b2df7179d178873c470aa1da0f6f258c', class: "pds-multiselect__error", id: messageId(this.componentId, 'error') }, hAsync("pds-icon", { key: 'e31d55db3fd0173c56faa23e34e0be92371983d4', icon: danger, size: "small" }), this.errorMessage)), hAsync("div", { key: 'b3326cc1a5e5886be05327a066e0a7ddd36ccefc', style: { display: 'none' } }, hAsync("slot", { key: '91559cb9510abce6084348a17516460fd8545ef7' })))));
20701
+ } }, this.getTriggerText()), hAsync("pds-icon", { "aria-hidden": "true", class: "pds-multiselect__icon", icon: enlarge }))), this.renderDropdown()), this.selectedDisplay === 'pill' && this.pillPosition === 'below' && this.renderBelowPills(), hAsync("span", { key: '3364fc00bc744fcd599436a9cb6dc563564d89d9', class: "visually-hidden", "aria-live": "polite", "aria-atomic": "true" }, this.removalAnnouncement), this.helperMessage && !(this.errorMessage && this.errorMessage.length > 0) && (hAsync("p", { key: 'bf712c65b9acb66e2998a43686fed408fc1ff48a', class: "pds-multiselect__helper", id: messageId(this.componentId, 'helper') }, this.helperMessage)), this.errorMessage && (hAsync("p", { key: 'b485db6c2c3ae312cce2b90db60e850ad6dfdcf5', class: "pds-multiselect__error", id: messageId(this.componentId, 'error') }, hAsync("pds-icon", { key: '20daf861cf9584f33f5a9574e9581acc487e7dfe', icon: danger, size: "small" }), this.errorMessage)), hAsync("div", { key: '018d333254771695a8c191f1cea40af6e78427a2', style: { display: 'none' } }, hAsync("slot", { key: '2ef4a01fbe5d294a9ead5cc1183c28341834539b' })))));
20567
20702
  }
20568
20703
  static get formAssociated() { return true; }
20569
20704
  get el() { return getElement(this); }
@@ -20653,7 +20788,7 @@ class PdsMultiselect {
20653
20788
  "clear": [64],
20654
20789
  "refreshOptions": [64]
20655
20790
  },
20656
- "$listeners$": [[8, "keydown", "handleWindowKeyDown"]],
20791
+ "$listeners$": [[6, "keydown", "handleWindowKeyDown"]],
20657
20792
  "$lazyBundleId$": "-",
20658
20793
  "$attrsToReflect$": []
20659
20794
  }; }
@@ -20703,6 +20838,16 @@ const cssEscape = (value) => {
20703
20838
 
20704
20839
  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}`;
20705
20840
 
20841
+ /** Open auto popovers, in the order they opened: Escape goes to the last one it applies to. */
20842
+ const openAutoPopovers = [];
20843
+ /** The focused element, followed down through shadow roots to the control that has focus. */
20844
+ function deepActiveElement() {
20845
+ var _a;
20846
+ let active = document.activeElement;
20847
+ while ((_a = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)
20848
+ active = active.shadowRoot.activeElement;
20849
+ return active;
20850
+ }
20706
20851
  /**
20707
20852
  * @slot trigger - The trigger element for the popover
20708
20853
  * @slot (default) - The content to display inside the popover
@@ -20738,7 +20883,7 @@ class PdsPopover {
20738
20883
  */
20739
20884
  this.triggerObserver = null;
20740
20885
  /**
20741
- * Portal element rendered in document.body
20886
+ * Portal element: in document.body, or in the open dialog the popover is in
20742
20887
  */
20743
20888
  this.portalEl = null;
20744
20889
  /**
@@ -20834,6 +20979,26 @@ class PdsPopover {
20834
20979
  break;
20835
20980
  }
20836
20981
  };
20982
+ /** The focus style copied into a shadow root the panel opened in, which `document.head` can't reach. */
20983
+ this.shadowFocusStyle = null;
20984
+ /** The dialog the panel was opened in, closed along with it. */
20985
+ this.watchedDialog = null;
20986
+ this.onDialogClose = () => {
20987
+ this.hide();
20988
+ };
20989
+ /**
20990
+ * Inside a dialog, the panel can sit in a form that wraps the dialog, which a body portal never
20991
+ * did. A submit from the panel (a button in it, or Enter in a field in it) is stopped, so the
20992
+ * panel behaves as it does in the body.
20993
+ */
20994
+ this.handleSubmitFromPanel = (event) => {
20995
+ if (!this.portalEl || !containsComposed$1(event.target, this.portalEl))
20996
+ return;
20997
+ if (containsComposed$1(this.portalEl, event.submitter) || containsComposed$1(this.portalEl, deepActiveElement())) {
20998
+ event.preventDefault();
20999
+ event.stopPropagation();
21000
+ }
21001
+ };
20837
21002
  }
20838
21003
  restorePlacementDefault(newValue) {
20839
21004
  this.placement = restoreReflectedDefault(this.el, 'placement', newValue, 'right');
@@ -20896,19 +21061,23 @@ class PdsPopover {
20896
21061
  (_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.handleTriggerClick);
20897
21062
  this.observeTrigger();
20898
21063
  this.linkTriggerToPortal();
20899
- // Still open after a move: the trigger moved, so the portal follows it.
21064
+ // Still open after a move: the trigger moved, so the portal follows it, into the dialog it
21065
+ // is in now, if any.
20900
21066
  if (this.active) {
21067
+ this.placePortal();
20901
21068
  requestAnimationFrame(() => this.handlePopoverPositioning());
20902
21069
  }
20903
21070
  }
20904
21071
  disconnectedCallback() {
20905
21072
  var _a;
20906
21073
  // Moved in one operation (a DOM morph, or a framework reparenting it), the popover is
20907
- // already back in the page. Its portal is in <body>, untouched, so it stays open, with focus
20908
- // where it was.
21074
+ // already back in the page. Its portal is outside the popover, so it stays open, with focus
21075
+ // where it was; connectedCallback moves it to follow the popover.
20909
21076
  if (this.el.isConnected)
20910
21077
  return;
20911
21078
  this.isComponentMounted = false;
21079
+ this.unwatchDialogClose();
21080
+ this.forgetOpen();
20912
21081
  // Open, the content sits in the portal; put it back before the portal goes. Only listeners on
20913
21082
  // the popover itself hear the close event now, but those are the ones mirroring its state.
20914
21083
  if (this.active) {
@@ -20929,6 +21098,7 @@ class PdsPopover {
20929
21098
  // Clean up all event listeners
20930
21099
  this.removeLightDismissListeners();
20931
21100
  this.removeScrollAndResizeListeners();
21101
+ document.removeEventListener('submit', this.handleSubmitFromPanel, true);
20932
21102
  // Clear any pending debounce timers
20933
21103
  this.clearDebounceTimers();
20934
21104
  // Remove portal from DOM
@@ -20973,9 +21143,6 @@ class PdsPopover {
20973
21143
  else {
20974
21144
  this.portalEl.id = `${this.componentId}-portal`;
20975
21145
  }
20976
- // Add accessibility attributes for screen readers
20977
- // Note: Native Popover API doesn't add a specific role, keeping semantic HTML
20978
- this.portalEl.setAttribute('aria-modal', 'false'); // Not a modal, can interact with rest of page
20979
21146
  // Append to body
20980
21147
  document.body.appendChild(this.portalEl);
20981
21148
  // Add global focus styles to match Pine design system
@@ -21059,7 +21226,27 @@ class PdsPopover {
21059
21226
  // Clear tracking array
21060
21227
  this.movedNodes = [];
21061
21228
  }
21229
+ copyFocusStylesInto(root) {
21230
+ var _a, _b, _c;
21231
+ // Outside a shadow root the document's own style applies, so drop a copy left in one.
21232
+ if (!(root instanceof ShadowRoot)) {
21233
+ (_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
21234
+ this.shadowFocusStyle = null;
21235
+ return;
21236
+ }
21237
+ if (((_b = this.shadowFocusStyle) === null || _b === void 0 ? void 0 : _b.parentNode) === root || !this.portalEl)
21238
+ return;
21239
+ const source = document.querySelector(`style[data-pds-popover-focus="${cssEscape(this.portalEl.id)}"]`);
21240
+ if (!source)
21241
+ return;
21242
+ (_c = this.shadowFocusStyle) === null || _c === void 0 ? void 0 : _c.remove();
21243
+ this.shadowFocusStyle = source.cloneNode(true);
21244
+ root.appendChild(this.shadowFocusStyle);
21245
+ }
21062
21246
  removePortal() {
21247
+ var _a;
21248
+ (_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
21249
+ this.shadowFocusStyle = null;
21063
21250
  if (this.portalEl) {
21064
21251
  // Remove the portal element from DOM
21065
21252
  if (this.portalEl.parentNode) {
@@ -21084,9 +21271,13 @@ class PdsPopover {
21084
21271
  // Record open timestamp to prevent immediate close from opening click
21085
21272
  this.openTimestamp = Date.now();
21086
21273
  this.linkTriggerToPortal();
21274
+ this.placePortal();
21087
21275
  // Update portal content with latest slot content
21088
21276
  this.updatePortalContent();
21089
- // Show portal
21277
+ // Show portal. The attribute tells a modal around it that this popover takes Escape first; only
21278
+ // an auto popover listens for Escape.
21279
+ if (this.popoverType === 'auto')
21280
+ this.portalEl.setAttribute('data-pds-popover-open', '');
21090
21281
  this.portalEl.style.display = 'block';
21091
21282
  this.portalEl.style.opacity = '1';
21092
21283
  this.portalEl.style.visibility = 'visible';
@@ -21098,8 +21289,10 @@ class PdsPopover {
21098
21289
  });
21099
21290
  // Add scroll and resize listeners for repositioning (always)
21100
21291
  this.addScrollAndResizeListeners();
21292
+ document.addEventListener('submit', this.handleSubmitFromPanel, true);
21101
21293
  // Add document listeners for light dismiss and escape key (auto type only)
21102
21294
  if (this.popoverType === 'auto') {
21295
+ openAutoPopovers.push(this);
21103
21296
  this.addLightDismissListeners();
21104
21297
  }
21105
21298
  // Emit open event
@@ -21115,12 +21308,16 @@ class PdsPopover {
21115
21308
  if (!this.active || !this.portalEl)
21116
21309
  return;
21117
21310
  this.active = false;
21118
- // Restoring the content drops its focus to the body, so check where focus is first.
21119
- const focusWasInside = this.portalEl.contains(document.activeElement);
21311
+ this.unwatchDialogClose();
21312
+ // Restoring the content drops its focus to the body, so check where focus is first. In a
21313
+ // shadow root, document.activeElement is only its host.
21314
+ const focusWasInside = containsComposed$1(this.portalEl, deepActiveElement());
21120
21315
  this.linkTriggerToPortal();
21121
21316
  // Restore content back to original slot positions
21122
21317
  this.restorePortalContent();
21123
- // Hide portal
21318
+ // Hide portal. The dark-mode border set in a shadow root is worked out again on the next open.
21319
+ this.portalEl.style.border = '';
21320
+ this.portalEl.removeAttribute('data-pds-popover-open');
21124
21321
  this.portalEl.style.display = 'none';
21125
21322
  this.portalEl.style.opacity = '0';
21126
21323
  this.portalEl.style.visibility = 'hidden';
@@ -21130,8 +21327,10 @@ class PdsPopover {
21130
21327
  this.returnFocusToTrigger();
21131
21328
  }
21132
21329
  // Remove all listeners
21330
+ this.forgetOpen();
21133
21331
  this.removeLightDismissListeners();
21134
21332
  this.removeScrollAndResizeListeners();
21333
+ document.removeEventListener('submit', this.handleSubmitFromPanel, true);
21135
21334
  // Clear any pending timers
21136
21335
  this.clearDebounceTimers();
21137
21336
  // Emit close event
@@ -21151,6 +21350,59 @@ class PdsPopover {
21151
21350
  this.show();
21152
21351
  }
21153
21352
  }
21353
+ /**
21354
+ * Puts the panel where it shows above everything around the popover. In an open dialog, it goes
21355
+ * inside it: a modal dialog sits in the top layer above the body and makes the rest of the page
21356
+ * inert, and a non-modal one sits above it by z-index. Right after the popover, it's next in the
21357
+ * Tab order. In another component's slot, though, it would have no slot to render in, and in a
21358
+ * form its buttons would submit it, so there it goes at the end of the dialog.
21359
+ */
21360
+ placePortal() {
21361
+ var _a, _b;
21362
+ if (!this.portalEl)
21363
+ return;
21364
+ const dialog = closest$1('dialog[open]', this.el);
21365
+ if (!dialog) {
21366
+ this.unwatchDialogClose();
21367
+ this.portalEl.style.border = '';
21368
+ if (this.portalEl.parentElement !== document.body)
21369
+ document.body.appendChild(this.portalEl);
21370
+ this.copyFocusStylesInto(document);
21371
+ return;
21372
+ }
21373
+ const form = closest$1('form', this.el);
21374
+ const besidePopover = !((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.shadowRoot) && !(form && containsComposed$1(dialog, form));
21375
+ if (besidePopover) {
21376
+ if (this.el.nextElementSibling !== this.portalEl)
21377
+ (_b = this.el.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(this.portalEl, this.el.nextSibling);
21378
+ }
21379
+ else if (this.portalEl.parentNode !== dialog) {
21380
+ dialog.appendChild(this.portalEl);
21381
+ }
21382
+ this.watchDialogClose(dialog);
21383
+ const root = this.portalEl.getRootNode();
21384
+ this.copyFocusStylesInto(root);
21385
+ // In a shadow root, the copied dark-mode rule can't see a [data-theme="dark"] on the page.
21386
+ this.portalEl.style.border = root instanceof ShadowRoot && closest$1('[data-theme="dark"]', this.el) ? 'var(--pine-border)' : '';
21387
+ }
21388
+ forgetOpen() {
21389
+ const index = openAutoPopovers.indexOf(this);
21390
+ if (index >= 0)
21391
+ openAutoPopovers.splice(index, 1);
21392
+ }
21393
+ /**
21394
+ * Whether an Escape is this popover's. One from a dialog the popover isn't in belongs to that
21395
+ * dialog when it's above it: a top-layer dialog or a pds-modal. A plain non-modal dialog inside
21396
+ * the popover's modal doesn't close on Escape, so the popover takes it.
21397
+ */
21398
+ takesEscapeFrom(event) {
21399
+ var _a, _b;
21400
+ const origin = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
21401
+ const dialog = closest$1('dialog[open]', origin);
21402
+ if (!dialog || containsComposed$1(dialog, this.portalEl))
21403
+ return true;
21404
+ return !(dialog.classList.contains('pds-modal__backdrop') || dialog.matches(':modal'));
21405
+ }
21154
21406
  /**
21155
21407
  * Adds light dismiss listeners (click outside and escape key)
21156
21408
  */
@@ -21159,6 +21411,19 @@ class PdsPopover {
21159
21411
  document.addEventListener('click', this.boundClickOutsideHandler, true);
21160
21412
  document.addEventListener('keydown', this.boundEscapeKeyHandler);
21161
21413
  }
21414
+ /** Closes the popover when the dialog it opened in closes, so it isn't left open inside. */
21415
+ watchDialogClose(dialog) {
21416
+ if (this.watchedDialog === dialog)
21417
+ return;
21418
+ this.unwatchDialogClose();
21419
+ this.watchedDialog = dialog;
21420
+ dialog.addEventListener('close', this.onDialogClose);
21421
+ }
21422
+ unwatchDialogClose() {
21423
+ var _a;
21424
+ (_a = this.watchedDialog) === null || _a === void 0 ? void 0 : _a.removeEventListener('close', this.onDialogClose);
21425
+ this.watchedDialog = null;
21426
+ }
21162
21427
  /**
21163
21428
  * Removes light dismiss listeners
21164
21429
  */
@@ -21184,7 +21449,7 @@ class PdsPopover {
21184
21449
  * Handles clicks outside the popover for light dismiss (auto type only)
21185
21450
  */
21186
21451
  handleClickOutside(event) {
21187
- var _a, _b, _c, _d;
21452
+ var _a, _b;
21188
21453
  // Ignore events that occurred at or before the popover opened
21189
21454
  // This prevents the opening click from immediately closing the popover
21190
21455
  // Convert event.timeStamp (DOMHighResTimeStamp from performance.now()) to epoch time
@@ -21195,16 +21460,18 @@ class PdsPopover {
21195
21460
  if (eventTime <= this.openTimestamp) {
21196
21461
  return;
21197
21462
  }
21198
- const target = event.target;
21463
+ // In a shadow root, event.target is only its host.
21464
+ const target = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
21199
21465
  // A datepicker's calendar is portaled to the body: a click in the calendar of a datepicker
21200
- // inside this popover, in its content or its trigger, isn't outside it.
21201
- const calendar = (_b = (_a = target).closest) === null || _b === void 0 ? void 0 : _b.call(_a, '.pds-datepicker-calendar-portal');
21466
+ // inside this popover, in its content or its trigger, isn't outside it. The target can be
21467
+ // inside the calendar's shadow root, so look up across it.
21468
+ const calendar = closest$1('.pds-datepicker-calendar-portal', target);
21202
21469
  const owner = calendar === null || calendar === void 0 ? void 0 : calendar.pdsDatepicker;
21203
21470
  if (owner && (containsComposed$1(this.portalEl, owner) || containsComposed$1(this.triggerEl, owner)))
21204
21471
  return;
21205
21472
  // Check if click is outside both the popover portal and the trigger
21206
- const clickedInsidePopover = (_c = this.portalEl) === null || _c === void 0 ? void 0 : _c.contains(target);
21207
- const clickedInsideTrigger = (_d = this.triggerEl) === null || _d === void 0 ? void 0 : _d.contains(target);
21473
+ const clickedInsidePopover = containsComposed$1(this.portalEl, target);
21474
+ const clickedInsideTrigger = containsComposed$1(this.triggerEl, target);
21208
21475
  if (!clickedInsidePopover && !clickedInsideTrigger) {
21209
21476
  this.hide();
21210
21477
  }
@@ -21225,14 +21492,19 @@ class PdsPopover {
21225
21492
  * Mirrors native Popover API: Escape closes and returns focus to trigger
21226
21493
  */
21227
21494
  handleEscapeKey(event) {
21228
- if (event.key === 'Escape') {
21229
- // Prevent default Escape behavior and stop propagation
21230
- // This mirrors native Popover API which handles Escape exclusively
21231
- event.preventDefault();
21232
- event.stopPropagation();
21233
- // Close popover and return focus to trigger
21234
- this.hide();
21235
- }
21495
+ // A control inside that closes on Escape (a datepicker, a combobox) has handled it already.
21496
+ if (event.key !== 'Escape' || event.defaultPrevented)
21497
+ return;
21498
+ // Like the native Popover API, Escape closes only the top popover: the last one opened that
21499
+ // it applies to. Every open popover hears it, so each checks whether it's that one.
21500
+ const top = [...openAutoPopovers].reverse().find(popover => popover.takesEscapeFrom(event));
21501
+ if (top !== this)
21502
+ return;
21503
+ // Take the key, so the dialog or modal around it doesn't close too.
21504
+ event.preventDefault();
21505
+ event.stopPropagation();
21506
+ // Close popover and return focus to trigger
21507
+ this.hide();
21236
21508
  }
21237
21509
  /**
21238
21510
  * Handles scroll events to reposition the popover (debounced for performance)
@@ -21337,13 +21609,20 @@ class PdsPopover {
21337
21609
  }
21338
21610
  this.portalEl.style.top = `${top}px`;
21339
21611
  this.portalEl.style.left = `${left}px`;
21612
+ // `position: fixed` measures from the viewport unless an ancestor becomes its containing block,
21613
+ // as a modal dialog does with its backdrop filter. Shift by however far it landed from the spot.
21614
+ const placed = this.portalEl.getBoundingClientRect();
21615
+ if (Math.abs(placed.top - top) > 0.5 || Math.abs(placed.left - left) > 0.5) {
21616
+ this.portalEl.style.top = `${2 * top - placed.top}px`;
21617
+ this.portalEl.style.left = `${2 * left - placed.left}px`;
21618
+ }
21340
21619
  // Reset the repositioning guard after a short delay
21341
21620
  setTimeout(() => {
21342
21621
  this.isRepositioning = false;
21343
21622
  }, 16); // ~1 frame at 60fps
21344
21623
  }
21345
21624
  render() {
21346
- return (hAsync(Host, { key: 'd804db8c73e0dc2acf0d1bbca20edb77993ff7fd', id: this.componentId }, hAsync("span", { key: '5408d1856c9b13b327ea13b43cffc23af925a82f', class: "pds-popover__trigger-wrapper" }, hAsync("slot", { key: '9d0ff01292c67bdaa88d49114162c3c65ff8a20d', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), hAsync("div", { key: 'cdd38016453ea1838d5718fb8e190cd4304ac8cc', class: "pds-popover__content-slot-wrapper" }, hAsync("slot", { key: '9faf60d7be7a2ff3c5fe6c3e4de374bafadf848a', onSlotchange: this.handleContentSlotChange }))));
21625
+ return (hAsync(Host, { key: 'da8a0c715df1199231b1265085f3f9ae0234032d', id: this.componentId }, hAsync("span", { key: 'f35023492399420829cec2e88556462f5ac1663e', class: "pds-popover__trigger-wrapper" }, hAsync("slot", { key: '38ebe92eebb87665467b3f92ff94dc8095450063', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), hAsync("div", { key: '8917930f4520544d14bbd68b4c7936dd60af19e3', class: "pds-popover__content-slot-wrapper" }, hAsync("slot", { key: '28d3f9112256edd95721a79a7072a9e35a3712bf', onSlotchange: this.handleContentSlotChange }))));
21347
21626
  }
21348
21627
  get el() { return getElement(this); }
21349
21628
  static get watchers() { return {