@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.js CHANGED
@@ -11625,6 +11625,9 @@ class PdsCombobox {
11625
11625
  };
11626
11626
  // Handler for button trigger click
11627
11627
  this.onButtonTriggerClick = () => {
11628
+ // The button and chip triggers are divs, so a click reaches them even while disabled.
11629
+ if (this.disabled)
11630
+ return;
11628
11631
  this.isOpen = !this.isOpen;
11629
11632
  if (this.isOpen) {
11630
11633
  this.prepareExpandFilterListOnOpen();
@@ -11644,6 +11647,13 @@ class PdsCombobox {
11644
11647
  };
11645
11648
  // Handler for button trigger keyboard events
11646
11649
  this.onButtonTriggerKeyDown = (e) => {
11650
+ // Disabled takes it out of the tab order, but setFocus() or a click can still focus it.
11651
+ if (this.disabled) {
11652
+ // Keep Space from scrolling the page, as on a native disabled button.
11653
+ if (e.key === ' ')
11654
+ e.preventDefault();
11655
+ return;
11656
+ }
11647
11657
  // Escape closes an open group before the dropdown. Other keys reach the tree through handleKeyDown.
11648
11658
  if (this.isOpen && e.key === 'Escape' && this.handleGroupedPanelKey(e))
11649
11659
  return;
@@ -11664,8 +11674,9 @@ class PdsCombobox {
11664
11674
  }, 0);
11665
11675
  }
11666
11676
  else if (e.key === 'Escape') {
11667
- e.preventDefault();
11677
+ // Only an open dropdown claims Escape; a closed one leaves it to a modal or popover around it.
11668
11678
  if (this.isOpen) {
11679
+ e.preventDefault();
11669
11680
  this.isOpen = false;
11670
11681
  this.highlightedIndex = -1;
11671
11682
  this.clearExpandFilterList();
@@ -11910,6 +11921,22 @@ class PdsCombobox {
11910
11921
  if (this.isOpen && this.hidesPanelSearch && this.panelTreeEl)
11911
11922
  this.focusPanel(this.panelTreeEl);
11912
11923
  }
11924
+ disabledChanged(disabled) {
11925
+ var _a, _b, _c, _d, _e;
11926
+ if (!disabled || !this.isOpen)
11927
+ return;
11928
+ const shadowActive = (_b = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) !== null && _b !== void 0 ? _b : null;
11929
+ 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)));
11930
+ this.isOpen = false;
11931
+ this.highlightedIndex = -1;
11932
+ this.isArrowKeyNavigationMode = false;
11933
+ this.clearExpandFilterList();
11934
+ this.updateAriaActiveDescendant();
11935
+ // Closing removes the search field or option that held focus. A button or chip trigger can still
11936
+ // take it (aria-disabled), so focus doesn't fall to the page; a disabled input can't, as natively.
11937
+ if (focusInDropdown && this.trigger !== 'input')
11938
+ this.restoreFocusToTrigger();
11939
+ }
11913
11940
  isOpenChanged(isOpen) {
11914
11941
  if (!isOpen) {
11915
11942
  this.revealedGroupLabel = null;
@@ -13624,13 +13651,13 @@ class PdsCombobox {
13624
13651
  ]
13625
13652
  .filter(Boolean)
13626
13653
  .join(' ');
13627
- 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 => {
13654
+ 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 => {
13628
13655
  this.inputEl = el;
13629
13656
  this.triggerEl = el;
13630
13657
  }, class: {
13631
13658
  'pds-combobox__input': true,
13632
13659
  'pds-combobox__input--invalid': this.isInvalid(),
13633
- }, 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' && [
13660
+ }, 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' && [
13634
13661
  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 : ''),
13635
13662
  // Its own region: sharing one, revealing a row then opening it would leave the text the
13636
13663
  // same, and opening the group would go unannounced.
@@ -13649,6 +13676,9 @@ class PdsCombobox {
13649
13676
  "flyoutFallback": [{
13650
13677
  "flyoutFallbackChanged": 0
13651
13678
  }],
13679
+ "disabled": [{
13680
+ "disabledChanged": 0
13681
+ }],
13652
13682
  "isOpen": [{
13653
13683
  "isOpenChanged": 0
13654
13684
  }],
@@ -16082,22 +16112,24 @@ function unwrapReconnectedContent(container, selector, contentSelector) {
16082
16112
  }
16083
16113
  }
16084
16114
 
16085
- 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}`;
16115
+ 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}`;
16086
16116
 
16087
- // Known selectors for content Pine components portal to document.body rather
16088
- // than rendering in place — pds-popover, pds-tooltip, pds-combobox's dropdown.
16117
+ // Known selectors for content Pine components portal out of place rather than
16118
+ // rendering where they are — pds-tooltip and pds-combobox's dropdown in
16119
+ // document.body, pds-popover in the body or the open dialog it's in.
16089
16120
  // A pointerdown landing on one of these is not "outside" the drawer in the
16090
16121
  // sense light dismiss cares about: it belongs to an overlay something inside
16091
16122
  // the drawer opened, even though it is not a DOM descendant of the drawer.
16092
16123
  const PORTALED_OVERLAY_SELECTOR = '.pds-popover, .pds-tooltip, .pds-combobox-dropdown-portal';
16093
16124
  /**
16094
- * A non-modal side panel composed from `pds-modal`.
16125
+ * A side panel composed from `pds-modal`, non-modal by default.
16095
16126
  *
16096
- * Unlike `pds-modal`, the page stays interactive while a drawer is open: no
16097
- * dimming, no blur, no scroll lock, no click blocking. `pds-drawer` renders a
16098
- * `pds-modal` internally with `disableTopLayer` always on and its backdrop
16099
- * suppressed, and adds the edge, width and dismiss behavior a side panel
16100
- * needs on top. It does not reimplement focus, Escape or dialog semantics —
16127
+ * By default, unlike `pds-modal`, the page stays interactive while a drawer
16128
+ * is open: no dimming, no blur, no scroll lock, no click blocking.
16129
+ * `pds-drawer` renders a `pds-modal` internally with `disableTopLayer` on and
16130
+ * its backdrop suppressed, and adds the edge, width and dismiss behavior a
16131
+ * side panel needs on top. With `modal`, it opens that `pds-modal` in the top
16132
+ * layer instead and keeps its backdrop. It does not reimplement focus, Escape or dialog semantics —
16101
16133
  * those come from `pds-modal` unchanged.
16102
16134
  * @lightDom renders-markup
16103
16135
  */
@@ -16128,8 +16160,18 @@ class PdsDrawer {
16128
16160
  */
16129
16161
  this.scrollable = true;
16130
16162
  /**
16131
- * Whether the drawer can be dismissed with a pointerdown outside it.
16132
- * Replaces `pds-modal`'s `backdropDismiss` — there is no backdrop to click.
16163
+ * Whether the drawer is modal. A modal drawer dims the page behind it with a
16164
+ * backdrop, makes the page inert, and keeps focus inside the drawer, like
16165
+ * `pds-modal`. Use it for a task the page should wait on. Leave it off for a
16166
+ * panel people work alongside the page. Read when the drawer opens; changing
16167
+ * it while the drawer is open is not supported.
16168
+ * @default false
16169
+ */
16170
+ this.modal = false;
16171
+ /**
16172
+ * Whether the drawer can be dismissed with a pointerdown outside it (in a
16173
+ * `modal` drawer, a click on its backdrop).
16174
+ * Replaces `pds-modal`'s `backdropDismiss`.
16133
16175
  * This also gates Escape, matching how `backdropDismiss` gates Escape on
16134
16176
  * `pds-modal` today: setting this to `false` means the close button is the
16135
16177
  * only way out.
@@ -16148,7 +16190,8 @@ class PdsDrawer {
16148
16190
  * background event while the user is mid-task elsewhere on the page —
16149
16191
  * moving focus there would interrupt them, and the page stays live under
16150
16192
  * a non-modal drawer, so it is a genuine data-entry risk, not just an
16151
- * annoyance.
16193
+ * annoyance. A `modal` drawer ignores `none`: the page behind it is inert,
16194
+ * so focus starts in the drawer.
16152
16195
  * @default 'auto'
16153
16196
  */
16154
16197
  this.initialFocus = 'auto';
@@ -16194,7 +16237,8 @@ class PdsDrawer {
16194
16237
  // page underneath stays genuinely clickable, which means a click there
16195
16238
  // never reaches pds-modal's backdrop element at all.
16196
16239
  handleOutsidePointerDown(e) {
16197
- if (!this.lightDismiss || !this.open)
16240
+ // A modal drawer's backdrop takes the click instead, through pds-modal.
16241
+ if (this.modal || !this.lightDismiss || !this.open)
16198
16242
  return;
16199
16243
  const target = e.target;
16200
16244
  if (!target)
@@ -16204,7 +16248,7 @@ class PdsDrawer {
16204
16248
  if (target.closest(PORTALED_OVERLAY_SELECTOR))
16205
16249
  return;
16206
16250
  // A datepicker's portaled calendar counts as inside only when its datepicker is in the drawer,
16207
- // or in an overlay portaled from it (a pds-popover in the drawer puts its content on the body).
16251
+ // or in an overlay portaled from it (a pds-popover in the drawer puts its content beside it).
16208
16252
  const calendar = target.closest('.pds-datepicker-calendar-portal');
16209
16253
  const owner = calendar === null || calendar === void 0 ? void 0 : calendar.pdsDatepicker;
16210
16254
  if (owner && (containsComposed$1(this.el, owner) || closest$1(PORTALED_OVERLAY_SELECTOR, owner)))
@@ -16220,11 +16264,11 @@ class PdsDrawer {
16220
16264
  this.open = false;
16221
16265
  }
16222
16266
  render() {
16223
- return (hAsync(Host, { key: 'ae7e67ed196ef95a39c2b729e859712ce5c41144', class: {
16267
+ return (hAsync(Host, { key: '287bf043c991209488eb1b617031acffb59ff9af', class: {
16224
16268
  'pds-drawer': true,
16225
16269
  [`pds-drawer--${this.side}`]: true,
16226
16270
  [`pds-drawer--${this.size}`]: true,
16227
- } }, 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' }))));
16271
+ } }, 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' }))));
16228
16272
  }
16229
16273
  get el() { return getElement(this); }
16230
16274
  static get style() { return pdsDrawerCss(); }
@@ -16237,6 +16281,7 @@ class PdsDrawer {
16237
16281
  "side": [1],
16238
16282
  "size": [1],
16239
16283
  "scrollable": [4],
16284
+ "modal": [4],
16240
16285
  "lightDismiss": [4, "light-dismiss"],
16241
16286
  "initialFocus": [1, "initial-focus"]
16242
16287
  },
@@ -16588,15 +16633,19 @@ class PdsDropdownMenu {
16588
16633
  this.focusItemByIndex(prevIndex);
16589
16634
  }
16590
16635
  }
16636
+ // Escape is taken in document's capture phase, before a modal around the menu hears it on
16637
+ // document and closes too.
16638
+ handleEscapeKeyDown(event) {
16639
+ if (!this.isOpen || event.key !== 'Escape' || event.defaultPrevented)
16640
+ return;
16641
+ event.preventDefault();
16642
+ this.closeDropdown();
16643
+ }
16591
16644
  // Handle keyboard events for the dropdown
16592
16645
  handleKeyDown(event) {
16593
16646
  if (!this.isOpen)
16594
16647
  return;
16595
16648
  switch (event.key) {
16596
- case 'Escape':
16597
- event.preventDefault();
16598
- this.closeDropdown();
16599
- break;
16600
16649
  case 'ArrowDown':
16601
16650
  event.preventDefault();
16602
16651
  this.focusNextItem();
@@ -16688,7 +16737,7 @@ class PdsDropdownMenu {
16688
16737
  }
16689
16738
  }
16690
16739
  render() {
16691
- 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 })))));
16740
+ 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 })))));
16692
16741
  }
16693
16742
  get host() { return getElement(this); }
16694
16743
  static get style() { return pdsDropdownMenuCss(); }
@@ -16701,7 +16750,7 @@ class PdsDropdownMenu {
16701
16750
  "currentFocusIndex": [32],
16702
16751
  "hasHeader": [32]
16703
16752
  },
16704
- "$listeners$": [[8, "keydown", "handleKeyDown"], [8, "click", "handleWindowClick"]],
16753
+ "$listeners$": [[6, "keydown", "handleEscapeKeyDown"], [8, "keydown", "handleKeyDown"], [8, "click", "handleWindowClick"]],
16705
16754
  "$lazyBundleId$": "-",
16706
16755
  "$attrsToReflect$": []
16707
16756
  }; }
@@ -18570,8 +18619,36 @@ function containsComposed(ancestor, node) {
18570
18619
  }
18571
18620
  return false;
18572
18621
  }
18622
+ /**
18623
+ * Every open modal, with the order it opened in, which ranks top-layer modals and the modals
18624
+ * inside them. Kept here rather than queried from the document, which can't see a modal inside
18625
+ * another component's shadow root.
18626
+ */
18627
+ const openModals = new Map();
18628
+ let openSequence = 0;
18629
+ /** Whether an open auto popover's panel sits in `root`, including inside shadow roots under it. */
18630
+ function hasOpenPopover(root) {
18631
+ if (root.querySelector('[data-pds-popover-open]'))
18632
+ return true;
18633
+ return Array.from(root.querySelectorAll('*')).some(el => !!el.shadowRoot && hasOpenPopover(el.shadowRoot));
18634
+ }
18635
+ /**
18636
+ * Whether `a` comes before `b` in the page. `compareDocumentPosition` is arbitrary across a shadow
18637
+ * boundary, so each side climbs to its host until both are in the same tree.
18638
+ */
18639
+ function precedesComposed(a, b) {
18640
+ // A document has no host, which ends each climb.
18641
+ for (let x = a; x; x = x.getRootNode().host) {
18642
+ for (let y = b; y; y = y.getRootNode().host) {
18643
+ if (x.getRootNode() === y.getRootNode()) {
18644
+ return !!(x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING);
18645
+ }
18646
+ }
18647
+ }
18648
+ return false;
18649
+ }
18573
18650
  /** The focused element, followed down through shadow roots to the control that has focus. */
18574
- function deepActiveElement() {
18651
+ function deepActiveElement$1() {
18575
18652
  var _a;
18576
18653
  let active = document.activeElement;
18577
18654
  while ((_a = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)
@@ -18588,7 +18665,8 @@ class PdsModal {
18588
18665
  this.pdsModalClose = createEvent(this, "pdsModalClose");
18589
18666
  this.focusableElements = [];
18590
18667
  /**
18591
- * Whether the modal can be dismissed by clicking the backdrop
18668
+ * Whether the modal can be dismissed by clicking the backdrop, pressing Escape, or the browser's
18669
+ * own close request (such as a back gesture)
18592
18670
  * @default true
18593
18671
  */
18594
18672
  this.backdropDismiss = true;
@@ -18636,19 +18714,33 @@ class PdsModal {
18636
18714
  * Stores the list of focusable elements in the modal
18637
18715
  */
18638
18716
  this.focusableElementsArray = [];
18717
+ // The browser's own close request (an Escape nothing prevented, a back gesture) would close the
18718
+ // dialog without updating `open`, so close it through hideModal() instead, or not at all.
18719
+ this.handleCancel = (e) => {
18720
+ e.preventDefault();
18721
+ if (!this.open)
18722
+ return;
18723
+ // The request reaches the top-layer dialog even when a disableTopLayer modal inside it is on
18724
+ // top; that one closes first, as it would on Escape.
18725
+ const top = this.innermostOpenModal();
18726
+ const target = (top && containsComposed(this.el, top) ? top : this.el);
18727
+ if (target.backdropDismiss)
18728
+ target.hideModal();
18729
+ };
18639
18730
  this.handleBackdropClick = (e) => {
18640
18731
  if (!this.backdropDismiss || !this.open)
18641
18732
  return;
18642
- if (e.target.classList.contains('pds-modal__backdrop')) {
18733
+ // This modal's own backdrop; a nested modal's backdrop handles its own clicks.
18734
+ if (e.target === this.modalRef) {
18643
18735
  e.stopPropagation();
18644
18736
  // Only close if this is the innermost modal
18645
- if (this.isInnermostModal()) {
18737
+ if (this.innermostOpenModal() === this.el) {
18646
18738
  this.hideModal();
18647
18739
  }
18648
18740
  }
18649
18741
  };
18650
18742
  this.handleKeyDown = (e) => {
18651
- var _a, _b, _c;
18743
+ var _a, _b, _c, _d;
18652
18744
  // If the modal is not open, don't handle any keyboard events
18653
18745
  if (!this.open)
18654
18746
  return;
@@ -18671,24 +18763,39 @@ class PdsModal {
18671
18763
  if (!('__pdsActiveElementAtEscape' in eventSnapshot)) {
18672
18764
  const activeAtDispatch = document.activeElement;
18673
18765
  eventSnapshot.__pdsActiveElementAtEscape = activeAtDispatch;
18766
+ // Followed into shadow roots: for a modal inside one, document.activeElement is only the host.
18767
+ eventSnapshot.__pdsDeepActiveElementAtEscape = deepActiveElement$1();
18674
18768
  const topLayerDialog = (_a = activeAtDispatch === null || activeAtDispatch === void 0 ? void 0 : activeAtDispatch.closest('dialog')) !== null && _a !== void 0 ? _a : null;
18675
18769
  eventSnapshot.__pdsTopLayerDialogAtEscape = topLayerDialog;
18676
18770
  eventSnapshot.__pdsTopLayerDialogWasModalAtEscape = !!(topLayerDialog && topLayerDialog.matches(':modal'));
18771
+ // A control that closed on this Escape (a combobox on its own element, a dropdown menu or
18772
+ // multiselect in document's capture phase) has prevented it before this bubble listener.
18773
+ // Read before any modal prevents it below.
18774
+ eventSnapshot.__pdsEscapeHandledInside = e.defaultPrevented;
18775
+ // Which modal is innermost is read once too: the inner one closing first would make its
18776
+ // outer modal the innermost by the time that modal's listener runs.
18777
+ eventSnapshot.__pdsInnermostModalAtEscape = this.innermostOpenModal();
18677
18778
  }
18779
+ if (eventSnapshot.__pdsEscapeHandledInside)
18780
+ return;
18678
18781
  // Leave Escape to a genuinely stacked-above overlay (disableTopLayer's
18679
18782
  // reason for existing) — not just to anything outside this modal.
18680
18783
  const active = eventSnapshot.__pdsActiveElementAtEscape;
18681
18784
  if (this.disableTopLayer &&
18682
18785
  active &&
18683
18786
  active !== document.body &&
18684
- !this.el.contains(active) &&
18685
- this.isStackedAboveOverlay(active, (_b = eventSnapshot.__pdsTopLayerDialogAtEscape) !== null && _b !== void 0 ? _b : null, (_c = eventSnapshot.__pdsTopLayerDialogWasModalAtEscape) !== null && _c !== void 0 ? _c : false)) {
18787
+ !containsComposed(this.el, (_b = eventSnapshot.__pdsDeepActiveElementAtEscape) !== null && _b !== void 0 ? _b : active) &&
18788
+ this.isStackedAboveOverlay(active, (_c = eventSnapshot.__pdsTopLayerDialogAtEscape) !== null && _c !== void 0 ? _c : null, (_d = eventSnapshot.__pdsTopLayerDialogWasModalAtEscape) !== null && _d !== void 0 ? _d : false)) {
18686
18789
  return;
18687
18790
  }
18791
+ // An open popover inside takes Escape from its own document listener, which runs after this
18792
+ // one, so leave the key for it. Its preventDefault() keeps the native dialog open.
18793
+ if (this.modalRef && hasOpenPopover(this.modalRef))
18794
+ return;
18688
18795
  // Always prevent native dialog close behavior
18689
18796
  e.preventDefault();
18690
18797
  // Only close if backdropDismiss is enabled and this is the innermost modal
18691
- if (this.backdropDismiss && this.isInnermostModal()) {
18798
+ if (this.backdropDismiss && eventSnapshot.__pdsInnermostModalAtEscape === this.el) {
18692
18799
  this.hideModal();
18693
18800
  }
18694
18801
  return;
@@ -18708,7 +18815,7 @@ class PdsModal {
18708
18815
  // Every open modal hears this keydown. When focus is in a modal dialog opened above this one,
18709
18816
  // that dialog traps Tab: this modal's list also holds its controls, so it would wrap first. A
18710
18817
  // non-modal dialog inside (a disableTopLayer modal) doesn't trap, so this one still does.
18711
- const activeElement = deepActiveElement();
18818
+ const activeElement = deepActiveElement$1();
18712
18819
  if (!activeElement || this.modalDialogAbove(activeElement))
18713
18820
  return;
18714
18821
  // Content can change while the modal is open (rows fetched in, sections shown), so the
@@ -18748,8 +18855,6 @@ class PdsModal {
18748
18855
  }
18749
18856
  componentDidLoad() {
18750
18857
  this.modalRef = this.el.querySelector('.pds-modal__backdrop');
18751
- // Add keyboard event listener
18752
- document.addEventListener('keydown', this.handleKeyDown);
18753
18858
  // @Watch('open') only fires on a later change, not the prop's initial
18754
18859
  // value — a modal mounted with `open` already true (static markup, or a
18755
18860
  // framework passing it on first render) would otherwise never call
@@ -18765,9 +18870,18 @@ class PdsModal {
18765
18870
  componentDidRender() {
18766
18871
  unwrapReconnectedContent(this.el.querySelector('.pds-modal'), 'dialog.pds-modal__backdrop', '.pds-modal');
18767
18872
  }
18873
+ // Here rather than in componentDidLoad, which runs once: a modal taken out of the page and put
18874
+ // back (a morph or a framework moving it) gets its keyboard handling, and its place among the
18875
+ // open modals, back too. Adding the same listener twice is a no-op.
18876
+ connectedCallback() {
18877
+ document.addEventListener('keydown', this.handleKeyDown);
18878
+ if (this.open && !openModals.has(this.el))
18879
+ openModals.set(this.el, ++openSequence);
18880
+ }
18768
18881
  disconnectedCallback() {
18769
18882
  // Clean up event listener
18770
18883
  document.removeEventListener('keydown', this.handleKeyDown);
18884
+ openModals.delete(this.el);
18771
18885
  }
18772
18886
  handleOpenChange(newValue) {
18773
18887
  if (newValue) {
@@ -18832,6 +18946,9 @@ class PdsModal {
18832
18946
  this.modalRef.showModal();
18833
18947
  }
18834
18948
  this.open = true;
18949
+ // Showing an open modal again doesn't move it above one opened after it.
18950
+ if (!openModals.has(this.el))
18951
+ openModals.set(this.el, ++openSequence);
18835
18952
  // Update focusable elements and set initial focus
18836
18953
  // Using a longer timeout to ensure all components are fully rendered
18837
18954
  setTimeout(() => {
@@ -18894,6 +19011,7 @@ class PdsModal {
18894
19011
  const focusAlreadyMovedElsewhere = activeElement !== null && activeElement !== document.body && !this.modalRef.contains(activeElement);
18895
19012
  this.modalRef.close();
18896
19013
  this.open = false;
19014
+ openModals.delete(this.el);
18897
19015
  // Restore focus to the element that was focused before the modal was opened
18898
19016
  if (!focusAlreadyMovedElsewhere &&
18899
19017
  this.previousActiveElement &&
@@ -18912,7 +19030,9 @@ class PdsModal {
18912
19030
  */
18913
19031
  getBackdropZIndex(modal) {
18914
19032
  const backdrop = modal.querySelector('.pds-modal__backdrop');
18915
- return backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;
19033
+ // `auto` (no z-index token loaded) parses to NaN, which no comparison can rank.
19034
+ const zIndex = backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;
19035
+ return Number.isNaN(zIndex) ? 0 : zIndex;
18916
19036
  }
18917
19037
  /**
18918
19038
  * Whether `active` sits inside a surface actually stacked above this modal.
@@ -18939,7 +19059,8 @@ class PdsModal {
18939
19059
  * lose that evidence depending on listener order.
18940
19060
  */
18941
19061
  isStackedAboveOverlay(active, topLayerDialog, topLayerDialogWasModal) {
18942
- if (topLayerDialog && topLayerDialog !== this.modalRef && topLayerDialogWasModal) {
19062
+ // A top-layer dialog this modal sits inside is underneath it, not an overlay above.
19063
+ if (topLayerDialog && topLayerDialog !== this.modalRef && topLayerDialogWasModal && !containsComposed(topLayerDialog, this.el)) {
18943
19064
  return true;
18944
19065
  }
18945
19066
  const ownZIndex = this.getBackdropZIndex(this.el);
@@ -18955,24 +19076,37 @@ class PdsModal {
18955
19076
  return false;
18956
19077
  }
18957
19078
  /**
18958
- * Checks if this modal is the innermost (highest z-index) modal
19079
+ * The open modal that takes Escape or a backdrop click, among those with no other open modal
19080
+ * inside them (a nested modal's backdrop can share its outer modal's z-index). A top-layer dialog
19081
+ * (or a modal inside one) paints above anything outside it, and within the top layer the last
19082
+ * opened is on top. Modals in the same layer (the page, or one top-layer dialog) then rank by
19083
+ * the highest backdrop, then the one later in the page.
18959
19084
  */
18960
- isInnermostModal() {
18961
- // Find all open modals
18962
- const openModals = Array.from(document.querySelectorAll('pds-modal')).filter(modal => modal.open);
18963
- if (openModals.length === 0)
18964
- return false;
18965
- // Get this modal's backdrop element
18966
- const thisBackdrop = this.el.querySelector('.pds-modal__backdrop');
18967
- if (!thisBackdrop)
18968
- return false;
18969
- // Get computed z-index of all open modal backdrops
18970
- const modalZIndexes = openModals.map(modal => this.getBackdropZIndex(modal));
18971
- // Get the highest z-index
18972
- const maxZIndex = Math.max(...modalZIndexes);
18973
- // Check if this modal's backdrop has the highest z-index
18974
- const thisZIndex = this.getBackdropZIndex(this.el);
18975
- return thisZIndex === maxZIndex;
19085
+ innermostOpenModal() {
19086
+ const open = Array.from(openModals.keys()).filter(modal => modal.isConnected);
19087
+ const innermost = open.filter(modal => !open.some(other => other !== modal && containsComposed(modal, other)));
19088
+ const byDocumentOrder = [...innermost].sort((a, b) => (precedesComposed(a, b) ? -1 : 1));
19089
+ // A modal inside a top-layer dialog paints in that dialog's place in the top layer, even when it
19090
+ // isn't in the top layer itself (disableTopLayer), so that dialog's open order ranks it first.
19091
+ const rank = (modal) => {
19092
+ var _a;
19093
+ const backdrop = modal.querySelector('.pds-modal__backdrop');
19094
+ const topLayerDialog = backdrop ? closest$1('dialog:modal', backdrop) : null;
19095
+ const topLayerModal = topLayerDialog === null || topLayerDialog === void 0 ? void 0 : topLayerDialog.closest('pds-modal');
19096
+ const layer = topLayerDialog ? ((_a = (topLayerModal && openModals.get(topLayerModal))) !== null && _a !== void 0 ? _a : 0) : 0;
19097
+ return [topLayerDialog ? 1 : 0, layer, this.getBackdropZIndex(modal), byDocumentOrder.indexOf(modal)];
19098
+ };
19099
+ let top = null;
19100
+ let topRank = [-1, -1, -1, -1];
19101
+ for (const modal of innermost) {
19102
+ const modalRank = rank(modal);
19103
+ const above = modalRank.findIndex((value, i) => value !== topRank[i]);
19104
+ if (above >= 0 && modalRank[above] > topRank[above]) {
19105
+ top = modal;
19106
+ topRank = modalRank;
19107
+ }
19108
+ }
19109
+ return top;
18976
19110
  }
18977
19111
  /** Whether a modal dialog other than this one sits between the focused element and this modal. */
18978
19112
  modalDialogAbove(active) {
@@ -19012,14 +19146,14 @@ class PdsModal {
19012
19146
  }
19013
19147
  render() {
19014
19148
  const [sizeClass = 'md', customSize] = this.resolveSize();
19015
- return (hAsync("dialog", { key: '12ea38928ea287f16ffa33e246caa2bfde6fc130', class: {
19149
+ return (hAsync("dialog", { key: '0633d4e8a7cb81f2f9280e754564960a00a4d60b', class: {
19016
19150
  'pds-modal__backdrop': true,
19017
19151
  'open': this.open
19018
- }, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick }, hAsync("div", { key: 'b9d83cabda56c315d9115f7030e3bdbcf923ec08', class: {
19152
+ }, "aria-modal": this.disableTopLayer ? 'false' : 'true', "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick, onCancel: this.handleCancel }, hAsync("div", { key: '466f8a3476cf066817554f47ec7d3a0e3ab10b89', class: {
19019
19153
  'pds-modal': true,
19020
19154
  [`pds-modal--${sizeClass}`]: true,
19021
19155
  'pds-modal--scrollable': this.scrollable
19022
- }, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, hAsync("slot", { key: '3c5edad1482cb124e503197adf411890573b076d' }))));
19156
+ }, style: customSize !== undefined ? { '--pds-modal-max-width': customSize } : undefined, part: "modal" }, hAsync("slot", { key: '01ca45c96540416216a1082bad52b137720f0f4d' }))));
19023
19157
  }
19024
19158
  get el() { return getElement(this); }
19025
19159
  static get watchers() { return {
@@ -19852,11 +19986,12 @@ class PdsMultiselect {
19852
19986
  }
19853
19987
  /**
19854
19988
  * Handle global keyboard events for accessibility.
19855
- * Closes dropdown on Escape key press regardless of focus location.
19989
+ * Closes dropdown on Escape key press regardless of focus location. Taken in document's capture
19990
+ * phase, before a modal around the multiselect hears it on document and closes too.
19856
19991
  */
19857
19992
  handleWindowKeyDown(event) {
19858
19993
  var _a;
19859
- if (!this.isOpen)
19994
+ if (!this.isOpen || event.defaultPrevented)
19860
19995
  return;
19861
19996
  if (event.key === 'Escape') {
19862
19997
  event.preventDefault();
@@ -20546,10 +20681,10 @@ class PdsMultiselect {
20546
20681
  }
20547
20682
  render() {
20548
20683
  const hasSelections = this.selectedItems.length > 0;
20549
- 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: {
20684
+ 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: {
20550
20685
  'pds-multiselect__label': true,
20551
20686
  'visually-hidden': this.hideLabel,
20552
- } }, 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: {
20687
+ } }, 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: {
20553
20688
  'pds-multiselect__trigger': true,
20554
20689
  'pds-multiselect__trigger--open': this.isOpen,
20555
20690
  'pds-multiselect__trigger--disabled': this.disabled,
@@ -20565,7 +20700,7 @@ class PdsMultiselect {
20565
20700
  }, 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: {
20566
20701
  'pds-multiselect__trigger-text': true,
20567
20702
  'pds-multiselect__trigger-text--placeholder': !hasSelections || (this.selectedDisplay === 'pill' && this.pillPosition === 'below'),
20568
- } }, 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' })))));
20703
+ } }, 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' })))));
20569
20704
  }
20570
20705
  static get formAssociated() { return true; }
20571
20706
  get el() { return getElement(this); }
@@ -20655,7 +20790,7 @@ class PdsMultiselect {
20655
20790
  "clear": [64],
20656
20791
  "refreshOptions": [64]
20657
20792
  },
20658
- "$listeners$": [[8, "keydown", "handleWindowKeyDown"]],
20793
+ "$listeners$": [[6, "keydown", "handleWindowKeyDown"]],
20659
20794
  "$lazyBundleId$": "-",
20660
20795
  "$attrsToReflect$": []
20661
20796
  }; }
@@ -20705,6 +20840,16 @@ const cssEscape = (value) => {
20705
20840
 
20706
20841
  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}`;
20707
20842
 
20843
+ /** Open auto popovers, in the order they opened: Escape goes to the last one it applies to. */
20844
+ const openAutoPopovers = [];
20845
+ /** The focused element, followed down through shadow roots to the control that has focus. */
20846
+ function deepActiveElement() {
20847
+ var _a;
20848
+ let active = document.activeElement;
20849
+ while ((_a = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)
20850
+ active = active.shadowRoot.activeElement;
20851
+ return active;
20852
+ }
20708
20853
  /**
20709
20854
  * @slot trigger - The trigger element for the popover
20710
20855
  * @slot (default) - The content to display inside the popover
@@ -20740,7 +20885,7 @@ class PdsPopover {
20740
20885
  */
20741
20886
  this.triggerObserver = null;
20742
20887
  /**
20743
- * Portal element rendered in document.body
20888
+ * Portal element: in document.body, or in the open dialog the popover is in
20744
20889
  */
20745
20890
  this.portalEl = null;
20746
20891
  /**
@@ -20836,6 +20981,26 @@ class PdsPopover {
20836
20981
  break;
20837
20982
  }
20838
20983
  };
20984
+ /** The focus style copied into a shadow root the panel opened in, which `document.head` can't reach. */
20985
+ this.shadowFocusStyle = null;
20986
+ /** The dialog the panel was opened in, closed along with it. */
20987
+ this.watchedDialog = null;
20988
+ this.onDialogClose = () => {
20989
+ this.hide();
20990
+ };
20991
+ /**
20992
+ * Inside a dialog, the panel can sit in a form that wraps the dialog, which a body portal never
20993
+ * did. A submit from the panel (a button in it, or Enter in a field in it) is stopped, so the
20994
+ * panel behaves as it does in the body.
20995
+ */
20996
+ this.handleSubmitFromPanel = (event) => {
20997
+ if (!this.portalEl || !containsComposed$1(event.target, this.portalEl))
20998
+ return;
20999
+ if (containsComposed$1(this.portalEl, event.submitter) || containsComposed$1(this.portalEl, deepActiveElement())) {
21000
+ event.preventDefault();
21001
+ event.stopPropagation();
21002
+ }
21003
+ };
20839
21004
  }
20840
21005
  restorePlacementDefault(newValue) {
20841
21006
  this.placement = restoreReflectedDefault(this.el, 'placement', newValue, 'right');
@@ -20898,19 +21063,23 @@ class PdsPopover {
20898
21063
  (_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.handleTriggerClick);
20899
21064
  this.observeTrigger();
20900
21065
  this.linkTriggerToPortal();
20901
- // Still open after a move: the trigger moved, so the portal follows it.
21066
+ // Still open after a move: the trigger moved, so the portal follows it, into the dialog it
21067
+ // is in now, if any.
20902
21068
  if (this.active) {
21069
+ this.placePortal();
20903
21070
  requestAnimationFrame(() => this.handlePopoverPositioning());
20904
21071
  }
20905
21072
  }
20906
21073
  disconnectedCallback() {
20907
21074
  var _a;
20908
21075
  // Moved in one operation (a DOM morph, or a framework reparenting it), the popover is
20909
- // already back in the page. Its portal is in <body>, untouched, so it stays open, with focus
20910
- // where it was.
21076
+ // already back in the page. Its portal is outside the popover, so it stays open, with focus
21077
+ // where it was; connectedCallback moves it to follow the popover.
20911
21078
  if (this.el.isConnected)
20912
21079
  return;
20913
21080
  this.isComponentMounted = false;
21081
+ this.unwatchDialogClose();
21082
+ this.forgetOpen();
20914
21083
  // Open, the content sits in the portal; put it back before the portal goes. Only listeners on
20915
21084
  // the popover itself hear the close event now, but those are the ones mirroring its state.
20916
21085
  if (this.active) {
@@ -20931,6 +21100,7 @@ class PdsPopover {
20931
21100
  // Clean up all event listeners
20932
21101
  this.removeLightDismissListeners();
20933
21102
  this.removeScrollAndResizeListeners();
21103
+ document.removeEventListener('submit', this.handleSubmitFromPanel, true);
20934
21104
  // Clear any pending debounce timers
20935
21105
  this.clearDebounceTimers();
20936
21106
  // Remove portal from DOM
@@ -20975,9 +21145,6 @@ class PdsPopover {
20975
21145
  else {
20976
21146
  this.portalEl.id = `${this.componentId}-portal`;
20977
21147
  }
20978
- // Add accessibility attributes for screen readers
20979
- // Note: Native Popover API doesn't add a specific role, keeping semantic HTML
20980
- this.portalEl.setAttribute('aria-modal', 'false'); // Not a modal, can interact with rest of page
20981
21148
  // Append to body
20982
21149
  document.body.appendChild(this.portalEl);
20983
21150
  // Add global focus styles to match Pine design system
@@ -21061,7 +21228,27 @@ class PdsPopover {
21061
21228
  // Clear tracking array
21062
21229
  this.movedNodes = [];
21063
21230
  }
21231
+ copyFocusStylesInto(root) {
21232
+ var _a, _b, _c;
21233
+ // Outside a shadow root the document's own style applies, so drop a copy left in one.
21234
+ if (!(root instanceof ShadowRoot)) {
21235
+ (_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
21236
+ this.shadowFocusStyle = null;
21237
+ return;
21238
+ }
21239
+ if (((_b = this.shadowFocusStyle) === null || _b === void 0 ? void 0 : _b.parentNode) === root || !this.portalEl)
21240
+ return;
21241
+ const source = document.querySelector(`style[data-pds-popover-focus="${cssEscape(this.portalEl.id)}"]`);
21242
+ if (!source)
21243
+ return;
21244
+ (_c = this.shadowFocusStyle) === null || _c === void 0 ? void 0 : _c.remove();
21245
+ this.shadowFocusStyle = source.cloneNode(true);
21246
+ root.appendChild(this.shadowFocusStyle);
21247
+ }
21064
21248
  removePortal() {
21249
+ var _a;
21250
+ (_a = this.shadowFocusStyle) === null || _a === void 0 ? void 0 : _a.remove();
21251
+ this.shadowFocusStyle = null;
21065
21252
  if (this.portalEl) {
21066
21253
  // Remove the portal element from DOM
21067
21254
  if (this.portalEl.parentNode) {
@@ -21086,9 +21273,13 @@ class PdsPopover {
21086
21273
  // Record open timestamp to prevent immediate close from opening click
21087
21274
  this.openTimestamp = Date.now();
21088
21275
  this.linkTriggerToPortal();
21276
+ this.placePortal();
21089
21277
  // Update portal content with latest slot content
21090
21278
  this.updatePortalContent();
21091
- // Show portal
21279
+ // Show portal. The attribute tells a modal around it that this popover takes Escape first; only
21280
+ // an auto popover listens for Escape.
21281
+ if (this.popoverType === 'auto')
21282
+ this.portalEl.setAttribute('data-pds-popover-open', '');
21092
21283
  this.portalEl.style.display = 'block';
21093
21284
  this.portalEl.style.opacity = '1';
21094
21285
  this.portalEl.style.visibility = 'visible';
@@ -21100,8 +21291,10 @@ class PdsPopover {
21100
21291
  });
21101
21292
  // Add scroll and resize listeners for repositioning (always)
21102
21293
  this.addScrollAndResizeListeners();
21294
+ document.addEventListener('submit', this.handleSubmitFromPanel, true);
21103
21295
  // Add document listeners for light dismiss and escape key (auto type only)
21104
21296
  if (this.popoverType === 'auto') {
21297
+ openAutoPopovers.push(this);
21105
21298
  this.addLightDismissListeners();
21106
21299
  }
21107
21300
  // Emit open event
@@ -21117,12 +21310,16 @@ class PdsPopover {
21117
21310
  if (!this.active || !this.portalEl)
21118
21311
  return;
21119
21312
  this.active = false;
21120
- // Restoring the content drops its focus to the body, so check where focus is first.
21121
- const focusWasInside = this.portalEl.contains(document.activeElement);
21313
+ this.unwatchDialogClose();
21314
+ // Restoring the content drops its focus to the body, so check where focus is first. In a
21315
+ // shadow root, document.activeElement is only its host.
21316
+ const focusWasInside = containsComposed$1(this.portalEl, deepActiveElement());
21122
21317
  this.linkTriggerToPortal();
21123
21318
  // Restore content back to original slot positions
21124
21319
  this.restorePortalContent();
21125
- // Hide portal
21320
+ // Hide portal. The dark-mode border set in a shadow root is worked out again on the next open.
21321
+ this.portalEl.style.border = '';
21322
+ this.portalEl.removeAttribute('data-pds-popover-open');
21126
21323
  this.portalEl.style.display = 'none';
21127
21324
  this.portalEl.style.opacity = '0';
21128
21325
  this.portalEl.style.visibility = 'hidden';
@@ -21132,8 +21329,10 @@ class PdsPopover {
21132
21329
  this.returnFocusToTrigger();
21133
21330
  }
21134
21331
  // Remove all listeners
21332
+ this.forgetOpen();
21135
21333
  this.removeLightDismissListeners();
21136
21334
  this.removeScrollAndResizeListeners();
21335
+ document.removeEventListener('submit', this.handleSubmitFromPanel, true);
21137
21336
  // Clear any pending timers
21138
21337
  this.clearDebounceTimers();
21139
21338
  // Emit close event
@@ -21153,6 +21352,59 @@ class PdsPopover {
21153
21352
  this.show();
21154
21353
  }
21155
21354
  }
21355
+ /**
21356
+ * Puts the panel where it shows above everything around the popover. In an open dialog, it goes
21357
+ * inside it: a modal dialog sits in the top layer above the body and makes the rest of the page
21358
+ * inert, and a non-modal one sits above it by z-index. Right after the popover, it's next in the
21359
+ * Tab order. In another component's slot, though, it would have no slot to render in, and in a
21360
+ * form its buttons would submit it, so there it goes at the end of the dialog.
21361
+ */
21362
+ placePortal() {
21363
+ var _a, _b;
21364
+ if (!this.portalEl)
21365
+ return;
21366
+ const dialog = closest$1('dialog[open]', this.el);
21367
+ if (!dialog) {
21368
+ this.unwatchDialogClose();
21369
+ this.portalEl.style.border = '';
21370
+ if (this.portalEl.parentElement !== document.body)
21371
+ document.body.appendChild(this.portalEl);
21372
+ this.copyFocusStylesInto(document);
21373
+ return;
21374
+ }
21375
+ const form = closest$1('form', this.el);
21376
+ const besidePopover = !((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.shadowRoot) && !(form && containsComposed$1(dialog, form));
21377
+ if (besidePopover) {
21378
+ if (this.el.nextElementSibling !== this.portalEl)
21379
+ (_b = this.el.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(this.portalEl, this.el.nextSibling);
21380
+ }
21381
+ else if (this.portalEl.parentNode !== dialog) {
21382
+ dialog.appendChild(this.portalEl);
21383
+ }
21384
+ this.watchDialogClose(dialog);
21385
+ const root = this.portalEl.getRootNode();
21386
+ this.copyFocusStylesInto(root);
21387
+ // In a shadow root, the copied dark-mode rule can't see a [data-theme="dark"] on the page.
21388
+ this.portalEl.style.border = root instanceof ShadowRoot && closest$1('[data-theme="dark"]', this.el) ? 'var(--pine-border)' : '';
21389
+ }
21390
+ forgetOpen() {
21391
+ const index = openAutoPopovers.indexOf(this);
21392
+ if (index >= 0)
21393
+ openAutoPopovers.splice(index, 1);
21394
+ }
21395
+ /**
21396
+ * Whether an Escape is this popover's. One from a dialog the popover isn't in belongs to that
21397
+ * dialog when it's above it: a top-layer dialog or a pds-modal. A plain non-modal dialog inside
21398
+ * the popover's modal doesn't close on Escape, so the popover takes it.
21399
+ */
21400
+ takesEscapeFrom(event) {
21401
+ var _a, _b;
21402
+ const origin = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
21403
+ const dialog = closest$1('dialog[open]', origin);
21404
+ if (!dialog || containsComposed$1(dialog, this.portalEl))
21405
+ return true;
21406
+ return !(dialog.classList.contains('pds-modal__backdrop') || dialog.matches(':modal'));
21407
+ }
21156
21408
  /**
21157
21409
  * Adds light dismiss listeners (click outside and escape key)
21158
21410
  */
@@ -21161,6 +21413,19 @@ class PdsPopover {
21161
21413
  document.addEventListener('click', this.boundClickOutsideHandler, true);
21162
21414
  document.addEventListener('keydown', this.boundEscapeKeyHandler);
21163
21415
  }
21416
+ /** Closes the popover when the dialog it opened in closes, so it isn't left open inside. */
21417
+ watchDialogClose(dialog) {
21418
+ if (this.watchedDialog === dialog)
21419
+ return;
21420
+ this.unwatchDialogClose();
21421
+ this.watchedDialog = dialog;
21422
+ dialog.addEventListener('close', this.onDialogClose);
21423
+ }
21424
+ unwatchDialogClose() {
21425
+ var _a;
21426
+ (_a = this.watchedDialog) === null || _a === void 0 ? void 0 : _a.removeEventListener('close', this.onDialogClose);
21427
+ this.watchedDialog = null;
21428
+ }
21164
21429
  /**
21165
21430
  * Removes light dismiss listeners
21166
21431
  */
@@ -21186,7 +21451,7 @@ class PdsPopover {
21186
21451
  * Handles clicks outside the popover for light dismiss (auto type only)
21187
21452
  */
21188
21453
  handleClickOutside(event) {
21189
- var _a, _b, _c, _d;
21454
+ var _a, _b;
21190
21455
  // Ignore events that occurred at or before the popover opened
21191
21456
  // This prevents the opening click from immediately closing the popover
21192
21457
  // Convert event.timeStamp (DOMHighResTimeStamp from performance.now()) to epoch time
@@ -21197,16 +21462,18 @@ class PdsPopover {
21197
21462
  if (eventTime <= this.openTimestamp) {
21198
21463
  return;
21199
21464
  }
21200
- const target = event.target;
21465
+ // In a shadow root, event.target is only its host.
21466
+ const target = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
21201
21467
  // A datepicker's calendar is portaled to the body: a click in the calendar of a datepicker
21202
- // inside this popover, in its content or its trigger, isn't outside it.
21203
- const calendar = (_b = (_a = target).closest) === null || _b === void 0 ? void 0 : _b.call(_a, '.pds-datepicker-calendar-portal');
21468
+ // inside this popover, in its content or its trigger, isn't outside it. The target can be
21469
+ // inside the calendar's shadow root, so look up across it.
21470
+ const calendar = closest$1('.pds-datepicker-calendar-portal', target);
21204
21471
  const owner = calendar === null || calendar === void 0 ? void 0 : calendar.pdsDatepicker;
21205
21472
  if (owner && (containsComposed$1(this.portalEl, owner) || containsComposed$1(this.triggerEl, owner)))
21206
21473
  return;
21207
21474
  // Check if click is outside both the popover portal and the trigger
21208
- const clickedInsidePopover = (_c = this.portalEl) === null || _c === void 0 ? void 0 : _c.contains(target);
21209
- const clickedInsideTrigger = (_d = this.triggerEl) === null || _d === void 0 ? void 0 : _d.contains(target);
21475
+ const clickedInsidePopover = containsComposed$1(this.portalEl, target);
21476
+ const clickedInsideTrigger = containsComposed$1(this.triggerEl, target);
21210
21477
  if (!clickedInsidePopover && !clickedInsideTrigger) {
21211
21478
  this.hide();
21212
21479
  }
@@ -21227,14 +21494,19 @@ class PdsPopover {
21227
21494
  * Mirrors native Popover API: Escape closes and returns focus to trigger
21228
21495
  */
21229
21496
  handleEscapeKey(event) {
21230
- if (event.key === 'Escape') {
21231
- // Prevent default Escape behavior and stop propagation
21232
- // This mirrors native Popover API which handles Escape exclusively
21233
- event.preventDefault();
21234
- event.stopPropagation();
21235
- // Close popover and return focus to trigger
21236
- this.hide();
21237
- }
21497
+ // A control inside that closes on Escape (a datepicker, a combobox) has handled it already.
21498
+ if (event.key !== 'Escape' || event.defaultPrevented)
21499
+ return;
21500
+ // Like the native Popover API, Escape closes only the top popover: the last one opened that
21501
+ // it applies to. Every open popover hears it, so each checks whether it's that one.
21502
+ const top = [...openAutoPopovers].reverse().find(popover => popover.takesEscapeFrom(event));
21503
+ if (top !== this)
21504
+ return;
21505
+ // Take the key, so the dialog or modal around it doesn't close too.
21506
+ event.preventDefault();
21507
+ event.stopPropagation();
21508
+ // Close popover and return focus to trigger
21509
+ this.hide();
21238
21510
  }
21239
21511
  /**
21240
21512
  * Handles scroll events to reposition the popover (debounced for performance)
@@ -21339,13 +21611,20 @@ class PdsPopover {
21339
21611
  }
21340
21612
  this.portalEl.style.top = `${top}px`;
21341
21613
  this.portalEl.style.left = `${left}px`;
21614
+ // `position: fixed` measures from the viewport unless an ancestor becomes its containing block,
21615
+ // as a modal dialog does with its backdrop filter. Shift by however far it landed from the spot.
21616
+ const placed = this.portalEl.getBoundingClientRect();
21617
+ if (Math.abs(placed.top - top) > 0.5 || Math.abs(placed.left - left) > 0.5) {
21618
+ this.portalEl.style.top = `${2 * top - placed.top}px`;
21619
+ this.portalEl.style.left = `${2 * left - placed.left}px`;
21620
+ }
21342
21621
  // Reset the repositioning guard after a short delay
21343
21622
  setTimeout(() => {
21344
21623
  this.isRepositioning = false;
21345
21624
  }, 16); // ~1 frame at 60fps
21346
21625
  }
21347
21626
  render() {
21348
- 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 }))));
21627
+ 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 }))));
21349
21628
  }
21350
21629
  get el() { return getElement(this); }
21351
21630
  static get watchers() { return {