@ionic/core 8.7.17-dev.11767639327.19c404a6 → 8.7.17-dev.11767647939.17c197c2

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.
@@ -2103,8 +2103,10 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2103
2103
  */
2104
2104
  setInitialSafeAreaOverrides(presentingElement) {
2105
2105
  const style = this.el.style;
2106
+ const mode = getIonMode(this);
2106
2107
  const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
2107
- const isCardModal = presentingElement !== undefined;
2108
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
2109
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
2108
2110
  const isTablet = window.innerWidth >= 768;
2109
2111
  // Sheet modals always touch bottom edge, never top/left/right
2110
2112
  if (isSheetModal) {
@@ -2170,9 +2172,22 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2170
2172
  style.setProperty('--ion-safe-area-left', '0px');
2171
2173
  style.setProperty('--ion-safe-area-right', '0px');
2172
2174
  }
2175
+ /**
2176
+ * Gets the root safe-area values from the document element.
2177
+ * These represent the actual device safe areas before any overlay overrides.
2178
+ */
2179
+ getRootSafeAreaValues() {
2180
+ const rootStyle = getComputedStyle(document.documentElement);
2181
+ return {
2182
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
2183
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
2184
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
2185
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
2186
+ };
2187
+ }
2173
2188
  /**
2174
2189
  * Updates safe-area CSS variable overrides based on whether the modal
2175
- * is touching each edge of the viewport. Called after animation
2190
+ * extends into each safe-area region. Called after animation
2176
2191
  * and during gestures to handle dynamic position changes.
2177
2192
  */
2178
2193
  updateSafeAreaOverrides() {
@@ -2184,18 +2199,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2184
2199
  return;
2185
2200
  }
2186
2201
  const rect = wrapper.getBoundingClientRect();
2187
- const threshold = 2;
2188
- const touchingTop = rect.top <= threshold;
2189
- const touchingBottom = rect.bottom >= window.innerHeight - threshold;
2190
- const touchingLeft = rect.left <= threshold;
2191
- const touchingRight = rect.right >= window.innerWidth - threshold;
2202
+ const safeAreas = this.getRootSafeAreaValues();
2203
+ const extendsIntoTop = rect.top < safeAreas.top;
2204
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
2205
+ const extendsIntoLeft = rect.left < safeAreas.left;
2206
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
2192
2207
  const style = this.el.style;
2193
- touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2194
- touchingBottom
2208
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2209
+ extendsIntoBottom
2195
2210
  ? style.removeProperty('--ion-safe-area-bottom')
2196
2211
  : style.setProperty('--ion-safe-area-bottom', '0px');
2197
- touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2198
- touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
2212
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2213
+ extendsIntoRight
2214
+ ? style.removeProperty('--ion-safe-area-right')
2215
+ : style.setProperty('--ion-safe-area-right', '0px');
2199
2216
  }
2200
2217
  sheetOnDismiss() {
2201
2218
  /**
@@ -2540,20 +2557,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2540
2557
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2541
2558
  const isHandleCycle = handleBehavior === 'cycle';
2542
2559
  const isSheetModalWithHandle = isSheetModal && showHandle;
2543
- return (h(Host, Object.assign({ key: '6642b8a32dad449f26ac4ed60814d69afc97bf30', "no-router": true,
2560
+ return (h(Host, Object.assign({ key: '07ebca6a70eb99f8a2236e1d66a03097a7bb67d8', "no-router": true,
2544
2561
  // Allow the modal to be navigable when the handle is focusable
2545
2562
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2546
2563
  zIndex: `${20000 + this.overlayIndex}`,
2547
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '8adf79ea72c0f622190ff366980621067c35795b', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '34f14fcd4970c04aee3b7a5305f9f220ff377f37', class: "modal-shadow" }), h("div", Object.assign({ key: 'a3a8e3df6a1cfe061bbd99503655015ca8cd416d',
2564
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '1b6850d9b9f6e8f3865b49e0a14399e2ef43a5d6', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'eab52c0ebccb820781e92392dc6fa90db93525d5', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab9448cabdf03a633319999771ce1ca1edce5699',
2548
2565
  /*
2549
2566
  role and aria-modal must be used on the
2550
2567
  same element. They must also be set inside the
2551
2568
  shadow DOM otherwise ion-button will not be highlighted
2552
2569
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2553
2570
  */
2554
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a4ad2f6def8c20a36a9731553dfa5a98392151ef', class: "modal-handle",
2571
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'b2b8c0a8d8add0d43e928dd3d3519f184551e62b', class: "modal-handle",
2555
2572
  // Prevents the handle from receiving keyboard focus when it does not cycle
2556
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '46fb6d94814dae4a9bcdf99387d57a17c6a6a95c', onSlotchange: this.onSlotChange }))));
2573
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'b994f206765f7b340971d378f00999c0da334102', onSlotchange: this.onSlotChange }))));
2557
2574
  }
2558
2575
  get el() { return this; }
2559
2576
  static get watchers() { return {
@@ -730,6 +730,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
730
730
  checkSafeAreaBottom = true;
731
731
  }
732
732
  }
733
+ /**
734
+ * Final check: If the popover extends into any safe-area region,
735
+ * ensure the corresponding flag is set regardless of side.
736
+ * This handles cases where a side-positioned popover (left/right)
737
+ * still needs bottom safe-area padding because it extends into that region.
738
+ */
739
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
740
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
741
+ checkSafeAreaBottom = true;
742
+ }
743
+ if (top < safeAreaMargin) {
744
+ checkSafeAreaTop = true;
745
+ }
733
746
  return {
734
747
  top,
735
748
  left,
@@ -2102,8 +2102,10 @@ const Modal = class {
2102
2102
  */
2103
2103
  setInitialSafeAreaOverrides(presentingElement) {
2104
2104
  const style = this.el.style;
2105
+ const mode = ionicGlobal.getIonMode(this);
2105
2106
  const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
2106
- const isCardModal = presentingElement !== undefined;
2107
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
2108
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
2107
2109
  const isTablet = window.innerWidth >= 768;
2108
2110
  // Sheet modals always touch bottom edge, never top/left/right
2109
2111
  if (isSheetModal) {
@@ -2169,9 +2171,22 @@ const Modal = class {
2169
2171
  style.setProperty('--ion-safe-area-left', '0px');
2170
2172
  style.setProperty('--ion-safe-area-right', '0px');
2171
2173
  }
2174
+ /**
2175
+ * Gets the root safe-area values from the document element.
2176
+ * These represent the actual device safe areas before any overlay overrides.
2177
+ */
2178
+ getRootSafeAreaValues() {
2179
+ const rootStyle = getComputedStyle(document.documentElement);
2180
+ return {
2181
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
2182
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
2183
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
2184
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
2185
+ };
2186
+ }
2172
2187
  /**
2173
2188
  * Updates safe-area CSS variable overrides based on whether the modal
2174
- * is touching each edge of the viewport. Called after animation
2189
+ * extends into each safe-area region. Called after animation
2175
2190
  * and during gestures to handle dynamic position changes.
2176
2191
  */
2177
2192
  updateSafeAreaOverrides() {
@@ -2183,18 +2198,20 @@ const Modal = class {
2183
2198
  return;
2184
2199
  }
2185
2200
  const rect = wrapper.getBoundingClientRect();
2186
- const threshold = 2;
2187
- const touchingTop = rect.top <= threshold;
2188
- const touchingBottom = rect.bottom >= window.innerHeight - threshold;
2189
- const touchingLeft = rect.left <= threshold;
2190
- const touchingRight = rect.right >= window.innerWidth - threshold;
2201
+ const safeAreas = this.getRootSafeAreaValues();
2202
+ const extendsIntoTop = rect.top < safeAreas.top;
2203
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
2204
+ const extendsIntoLeft = rect.left < safeAreas.left;
2205
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
2191
2206
  const style = this.el.style;
2192
- touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2193
- touchingBottom
2207
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2208
+ extendsIntoBottom
2194
2209
  ? style.removeProperty('--ion-safe-area-bottom')
2195
2210
  : style.setProperty('--ion-safe-area-bottom', '0px');
2196
- touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2197
- touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
2211
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2212
+ extendsIntoRight
2213
+ ? style.removeProperty('--ion-safe-area-right')
2214
+ : style.setProperty('--ion-safe-area-right', '0px');
2198
2215
  }
2199
2216
  sheetOnDismiss() {
2200
2217
  /**
@@ -2539,20 +2556,20 @@ const Modal = class {
2539
2556
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2540
2557
  const isHandleCycle = handleBehavior === 'cycle';
2541
2558
  const isSheetModalWithHandle = isSheetModal && showHandle;
2542
- return (index$3.h(index$3.Host, Object.assign({ key: '6642b8a32dad449f26ac4ed60814d69afc97bf30', "no-router": true,
2559
+ return (index$3.h(index$3.Host, Object.assign({ key: '07ebca6a70eb99f8a2236e1d66a03097a7bb67d8', "no-router": true,
2543
2560
  // Allow the modal to be navigable when the handle is focusable
2544
2561
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2545
2562
  zIndex: `${20000 + this.overlayIndex}`,
2546
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '8adf79ea72c0f622190ff366980621067c35795b', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '34f14fcd4970c04aee3b7a5305f9f220ff377f37', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: 'a3a8e3df6a1cfe061bbd99503655015ca8cd416d',
2563
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '1b6850d9b9f6e8f3865b49e0a14399e2ef43a5d6', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: 'eab52c0ebccb820781e92392dc6fa90db93525d5', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: 'ab9448cabdf03a633319999771ce1ca1edce5699',
2547
2564
  /*
2548
2565
  role and aria-modal must be used on the
2549
2566
  same element. They must also be set inside the
2550
2567
  shadow DOM otherwise ion-button will not be highlighted
2551
2568
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2552
2569
  */
2553
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: 'a4ad2f6def8c20a36a9731553dfa5a98392151ef', class: "modal-handle",
2570
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: 'b2b8c0a8d8add0d43e928dd3d3519f184551e62b', class: "modal-handle",
2554
2571
  // Prevents the handle from receiving keyboard focus when it does not cycle
2555
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: '46fb6d94814dae4a9bcdf99387d57a17c6a6a95c', onSlotchange: this.onSlotChange }))));
2572
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: 'b994f206765f7b340971d378f00999c0da334102', onSlotchange: this.onSlotChange }))));
2556
2573
  }
2557
2574
  get el() { return index$3.getElement(this); }
2558
2575
  static get watchers() { return {
@@ -733,6 +733,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
733
733
  checkSafeAreaBottom = true;
734
734
  }
735
735
  }
736
+ /**
737
+ * Final check: If the popover extends into any safe-area region,
738
+ * ensure the corresponding flag is set regardless of side.
739
+ * This handles cases where a side-positioned popover (left/right)
740
+ * still needs bottom safe-area padding because it extends into that region.
741
+ */
742
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
743
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
744
+ checkSafeAreaBottom = true;
745
+ }
746
+ if (top < safeAreaMargin) {
747
+ checkSafeAreaTop = true;
748
+ }
736
749
  return {
737
750
  top,
738
751
  left,
@@ -641,8 +641,10 @@ export class Modal {
641
641
  */
642
642
  setInitialSafeAreaOverrides(presentingElement) {
643
643
  const style = this.el.style;
644
+ const mode = getIonMode(this);
644
645
  const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
645
- const isCardModal = presentingElement !== undefined;
646
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
647
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
646
648
  const isTablet = window.innerWidth >= 768;
647
649
  // Sheet modals always touch bottom edge, never top/left/right
648
650
  if (isSheetModal) {
@@ -708,9 +710,22 @@ export class Modal {
708
710
  style.setProperty('--ion-safe-area-left', '0px');
709
711
  style.setProperty('--ion-safe-area-right', '0px');
710
712
  }
713
+ /**
714
+ * Gets the root safe-area values from the document element.
715
+ * These represent the actual device safe areas before any overlay overrides.
716
+ */
717
+ getRootSafeAreaValues() {
718
+ const rootStyle = getComputedStyle(document.documentElement);
719
+ return {
720
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
721
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
722
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
723
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
724
+ };
725
+ }
711
726
  /**
712
727
  * Updates safe-area CSS variable overrides based on whether the modal
713
- * is touching each edge of the viewport. Called after animation
728
+ * extends into each safe-area region. Called after animation
714
729
  * and during gestures to handle dynamic position changes.
715
730
  */
716
731
  updateSafeAreaOverrides() {
@@ -722,18 +737,20 @@ export class Modal {
722
737
  return;
723
738
  }
724
739
  const rect = wrapper.getBoundingClientRect();
725
- const threshold = 2;
726
- const touchingTop = rect.top <= threshold;
727
- const touchingBottom = rect.bottom >= window.innerHeight - threshold;
728
- const touchingLeft = rect.left <= threshold;
729
- const touchingRight = rect.right >= window.innerWidth - threshold;
740
+ const safeAreas = this.getRootSafeAreaValues();
741
+ const extendsIntoTop = rect.top < safeAreas.top;
742
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
743
+ const extendsIntoLeft = rect.left < safeAreas.left;
744
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
730
745
  const style = this.el.style;
731
- touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
732
- touchingBottom
746
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
747
+ extendsIntoBottom
733
748
  ? style.removeProperty('--ion-safe-area-bottom')
734
749
  : style.setProperty('--ion-safe-area-bottom', '0px');
735
- touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
736
- touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
750
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
751
+ extendsIntoRight
752
+ ? style.removeProperty('--ion-safe-area-right')
753
+ : style.setProperty('--ion-safe-area-right', '0px');
737
754
  }
738
755
  sheetOnDismiss() {
739
756
  /**
@@ -1086,20 +1103,20 @@ export class Modal {
1086
1103
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1087
1104
  const isHandleCycle = handleBehavior === 'cycle';
1088
1105
  const isSheetModalWithHandle = isSheetModal && showHandle;
1089
- return (h(Host, Object.assign({ key: '6642b8a32dad449f26ac4ed60814d69afc97bf30', "no-router": true,
1106
+ return (h(Host, Object.assign({ key: '07ebca6a70eb99f8a2236e1d66a03097a7bb67d8', "no-router": true,
1090
1107
  // Allow the modal to be navigable when the handle is focusable
1091
1108
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1092
1109
  zIndex: `${20000 + this.overlayIndex}`,
1093
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '8adf79ea72c0f622190ff366980621067c35795b', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '34f14fcd4970c04aee3b7a5305f9f220ff377f37', class: "modal-shadow" }), h("div", Object.assign({ key: 'a3a8e3df6a1cfe061bbd99503655015ca8cd416d',
1110
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '1b6850d9b9f6e8f3865b49e0a14399e2ef43a5d6', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'eab52c0ebccb820781e92392dc6fa90db93525d5', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab9448cabdf03a633319999771ce1ca1edce5699',
1094
1111
  /*
1095
1112
  role and aria-modal must be used on the
1096
1113
  same element. They must also be set inside the
1097
1114
  shadow DOM otherwise ion-button will not be highlighted
1098
1115
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1099
1116
  */
1100
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a4ad2f6def8c20a36a9731553dfa5a98392151ef', class: "modal-handle",
1117
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'b2b8c0a8d8add0d43e928dd3d3519f184551e62b', class: "modal-handle",
1101
1118
  // Prevents the handle from receiving keyboard focus when it does not cycle
1102
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '46fb6d94814dae4a9bcdf99387d57a17c6a6a95c', onSlotchange: this.onSlotChange }))));
1119
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'b994f206765f7b340971d378f00999c0da334102', onSlotchange: this.onSlotChange }))));
1103
1120
  }
1104
1121
  static get is() { return "ion-modal"; }
1105
1122
  static get encapsulation() { return "shadow"; }
@@ -721,6 +721,19 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
721
721
  checkSafeAreaBottom = true;
722
722
  }
723
723
  }
724
+ /**
725
+ * Final check: If the popover extends into any safe-area region,
726
+ * ensure the corresponding flag is set regardless of side.
727
+ * This handles cases where a side-positioned popover (left/right)
728
+ * still needs bottom safe-area padding because it extends into that region.
729
+ */
730
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
731
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
732
+ checkSafeAreaBottom = true;
733
+ }
734
+ if (top < safeAreaMargin) {
735
+ checkSafeAreaTop = true;
736
+ }
724
737
  return {
725
738
  top,
726
739
  left,
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-05T18:57:24",
2
+ "timestamp": "2026-01-05T21:20:51",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -2100,8 +2100,10 @@ const Modal = class {
2100
2100
  */
2101
2101
  setInitialSafeAreaOverrides(presentingElement) {
2102
2102
  const style = this.el.style;
2103
+ const mode = getIonMode(this);
2103
2104
  const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
2104
- const isCardModal = presentingElement !== undefined;
2105
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
2106
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
2105
2107
  const isTablet = window.innerWidth >= 768;
2106
2108
  // Sheet modals always touch bottom edge, never top/left/right
2107
2109
  if (isSheetModal) {
@@ -2167,9 +2169,22 @@ const Modal = class {
2167
2169
  style.setProperty('--ion-safe-area-left', '0px');
2168
2170
  style.setProperty('--ion-safe-area-right', '0px');
2169
2171
  }
2172
+ /**
2173
+ * Gets the root safe-area values from the document element.
2174
+ * These represent the actual device safe areas before any overlay overrides.
2175
+ */
2176
+ getRootSafeAreaValues() {
2177
+ const rootStyle = getComputedStyle(document.documentElement);
2178
+ return {
2179
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
2180
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
2181
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
2182
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
2183
+ };
2184
+ }
2170
2185
  /**
2171
2186
  * Updates safe-area CSS variable overrides based on whether the modal
2172
- * is touching each edge of the viewport. Called after animation
2187
+ * extends into each safe-area region. Called after animation
2173
2188
  * and during gestures to handle dynamic position changes.
2174
2189
  */
2175
2190
  updateSafeAreaOverrides() {
@@ -2181,18 +2196,20 @@ const Modal = class {
2181
2196
  return;
2182
2197
  }
2183
2198
  const rect = wrapper.getBoundingClientRect();
2184
- const threshold = 2;
2185
- const touchingTop = rect.top <= threshold;
2186
- const touchingBottom = rect.bottom >= window.innerHeight - threshold;
2187
- const touchingLeft = rect.left <= threshold;
2188
- const touchingRight = rect.right >= window.innerWidth - threshold;
2199
+ const safeAreas = this.getRootSafeAreaValues();
2200
+ const extendsIntoTop = rect.top < safeAreas.top;
2201
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
2202
+ const extendsIntoLeft = rect.left < safeAreas.left;
2203
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
2189
2204
  const style = this.el.style;
2190
- touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2191
- touchingBottom
2205
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2206
+ extendsIntoBottom
2192
2207
  ? style.removeProperty('--ion-safe-area-bottom')
2193
2208
  : style.setProperty('--ion-safe-area-bottom', '0px');
2194
- touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2195
- touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
2209
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2210
+ extendsIntoRight
2211
+ ? style.removeProperty('--ion-safe-area-right')
2212
+ : style.setProperty('--ion-safe-area-right', '0px');
2196
2213
  }
2197
2214
  sheetOnDismiss() {
2198
2215
  /**
@@ -2537,20 +2554,20 @@ const Modal = class {
2537
2554
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2538
2555
  const isHandleCycle = handleBehavior === 'cycle';
2539
2556
  const isSheetModalWithHandle = isSheetModal && showHandle;
2540
- return (h(Host, Object.assign({ key: '6642b8a32dad449f26ac4ed60814d69afc97bf30', "no-router": true,
2557
+ return (h(Host, Object.assign({ key: '07ebca6a70eb99f8a2236e1d66a03097a7bb67d8', "no-router": true,
2541
2558
  // Allow the modal to be navigable when the handle is focusable
2542
2559
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2543
2560
  zIndex: `${20000 + this.overlayIndex}`,
2544
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '8adf79ea72c0f622190ff366980621067c35795b', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '34f14fcd4970c04aee3b7a5305f9f220ff377f37', class: "modal-shadow" }), h("div", Object.assign({ key: 'a3a8e3df6a1cfe061bbd99503655015ca8cd416d',
2561
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '1b6850d9b9f6e8f3865b49e0a14399e2ef43a5d6', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'eab52c0ebccb820781e92392dc6fa90db93525d5', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab9448cabdf03a633319999771ce1ca1edce5699',
2545
2562
  /*
2546
2563
  role and aria-modal must be used on the
2547
2564
  same element. They must also be set inside the
2548
2565
  shadow DOM otherwise ion-button will not be highlighted
2549
2566
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2550
2567
  */
2551
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a4ad2f6def8c20a36a9731553dfa5a98392151ef', class: "modal-handle",
2568
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'b2b8c0a8d8add0d43e928dd3d3519f184551e62b', class: "modal-handle",
2552
2569
  // Prevents the handle from receiving keyboard focus when it does not cycle
2553
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '46fb6d94814dae4a9bcdf99387d57a17c6a6a95c', onSlotchange: this.onSlotChange }))));
2570
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'b994f206765f7b340971d378f00999c0da334102', onSlotchange: this.onSlotChange }))));
2554
2571
  }
2555
2572
  get el() { return getElement(this); }
2556
2573
  static get watchers() { return {
@@ -731,6 +731,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
731
731
  checkSafeAreaBottom = true;
732
732
  }
733
733
  }
734
+ /**
735
+ * Final check: If the popover extends into any safe-area region,
736
+ * ensure the corresponding flag is set regardless of side.
737
+ * This handles cases where a side-positioned popover (left/right)
738
+ * still needs bottom safe-area padding because it extends into that region.
739
+ */
740
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
741
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
742
+ checkSafeAreaBottom = true;
743
+ }
744
+ if (top < safeAreaMargin) {
745
+ checkSafeAreaTop = true;
746
+ }
734
747
  return {
735
748
  top,
736
749
  left,