@ionic/core 8.7.17-dev.11767796972.148e4bc4 → 8.7.17-dev.11767891829.1a63afa3

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.
@@ -2,6 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Build, Host, forceUpdate, h, readTask } from "@stencil/core";
5
+ import { win } from "../../utils/browser/index";
5
6
  import { componentOnReady, hasLazyBuild, inheritAriaAttributes } from "../../utils/helpers";
6
7
  import { isPlatform } from "../../utils/platform";
7
8
  import { isRTL } from "../../utils/rtl/index";
@@ -85,7 +86,11 @@ export class Content {
85
86
  this.inheritedAttributes = inheritAriaAttributes(this.el);
86
87
  }
87
88
  connectedCallback() {
88
- this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
89
+ var _a;
90
+ // Content is "main" if not inside menu/popover/modal and not nested in another ion-content
91
+ this.isMainContent =
92
+ this.el.closest('ion-menu, ion-popover, ion-modal') === null &&
93
+ ((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest('ion-content')) === null;
89
94
  // Detect sibling header/footer for safe-area handling
90
95
  this.detectSiblingElements();
91
96
  /**
@@ -118,7 +123,12 @@ export class Content {
118
123
  * bubbles, we can catch any instances of child tab bars loading by listening
119
124
  * on IonTabs.
120
125
  */
121
- this.tabsLoadCallback = () => this.resize();
126
+ this.tabsLoadCallback = () => {
127
+ this.resize();
128
+ // Re-detect footer when tab bar loads (it may not exist during initial detection)
129
+ this.updateSiblingDetection();
130
+ forceUpdate(this);
131
+ };
122
132
  closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
123
133
  }
124
134
  }
@@ -131,10 +141,15 @@ export class Content {
131
141
  this.updateSiblingDetection();
132
142
  // Watch for dynamic header/footer changes (common in React conditional rendering)
133
143
  const parent = this.el.parentElement;
134
- if (parent && !this.parentMutationObserver) {
144
+ if (parent && !this.parentMutationObserver && win !== undefined && 'MutationObserver' in win) {
135
145
  this.parentMutationObserver = new MutationObserver(() => {
146
+ const prevHasHeader = this.hasHeader;
147
+ const prevHasFooter = this.hasFooter;
136
148
  this.updateSiblingDetection();
137
- forceUpdate(this);
149
+ // Only trigger re-render if header/footer detection actually changed
150
+ if (prevHasHeader !== this.hasHeader || prevHasFooter !== this.hasFooter) {
151
+ forceUpdate(this);
152
+ }
138
153
  });
139
154
  this.parentMutationObserver.observe(parent, { childList: true });
140
155
  }
@@ -440,7 +455,7 @@ export class Content {
440
455
  const forceOverscroll = this.shouldForceOverscroll();
441
456
  const transitionShadow = mode === 'ios';
442
457
  this.resize();
443
- return (h(Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
458
+ return (h(Host, Object.assign({ key: 'f7218f733e4022a30875441bd949747537d28aa1', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
444
459
  [mode]: true,
445
460
  'content-sizing': hostContext('ion-popover', this.el),
446
461
  overscroll: forceOverscroll,
@@ -450,12 +465,12 @@ export class Content {
450
465
  }), style: {
451
466
  '--offset-top': `${this.cTop}px`,
452
467
  '--offset-bottom': `${this.cBottom}px`,
453
- } }, inheritedAttributes), h("div", { key: '4c0482cda885348eea9eb66d7f076af6b38c52e5', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '6fbb39bf7ab7120009c56aea9340de45c934eeed', class: {
468
+ } }, inheritedAttributes), h("div", { key: 'b735ec68c18c0b99c3595bb194029830e6542cde', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'e76c00d030342d44ade6648c3f9e32ca990787ba', class: {
454
469
  'inner-scroll': true,
455
470
  'scroll-x': scrollX,
456
471
  'scroll-y': scrollY,
457
472
  overscroll: (scrollX || scrollY) && forceOverscroll,
458
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6425bc84edbc0c5b1f2764a1d611df1b46628274' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
473
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '9049be4cea9b5da5ec1e1012248b05286fddeb7a' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
459
474
  }
460
475
  static get is() { return "ion-content"; }
461
476
  static get encapsulation() { return "shadow"; }
@@ -2,6 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Host, h, writeTask } from "@stencil/core";
5
+ import { win } from "../../utils/browser/index";
5
6
  import { findIonContent, printIonContentErrorMsg } from "../../utils/content/index";
6
7
  import { CoreDelegate, attachComponent, detachComponent } from "../../utils/framework-delegate";
7
8
  import { raf, inheritAttributes, hasLazyBuild, getElementRoot } from "../../utils/helpers";
@@ -262,6 +263,8 @@ export class Modal {
262
263
  this.triggerController.removeClickListener();
263
264
  this.cleanupViewTransitionListener();
264
265
  this.cleanupParentRemovalObserver();
266
+ // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
267
+ this.resetSafeAreaState();
265
268
  }
266
269
  componentWillLoad() {
267
270
  var _a;
@@ -698,7 +701,9 @@ export class Modal {
698
701
  this.skipSafeAreaCoordinateDetection = true;
699
702
  this.updateFooterPadding();
700
703
  // Watch for dynamic footer additions/removals (e.g., async data loading)
701
- if (!this.footerObserver) {
704
+ // Use subtree:true to support wrapped footers in framework components
705
+ // (e.g., <my-footer><ion-footer>...</ion-footer></my-footer>)
706
+ if (!this.footerObserver && win !== undefined && 'MutationObserver' in win) {
702
707
  this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
703
708
  this.footerObserver.observe(this.el, { childList: true, subtree: true });
704
709
  }
@@ -731,6 +736,30 @@ export class Modal {
731
736
  style.setProperty('--ion-safe-area-left', '0px');
732
737
  style.setProperty('--ion-safe-area-right', '0px');
733
738
  }
739
+ /**
740
+ * Resets all safe-area related state and styles.
741
+ * Called during dismiss and disconnectedCallback to ensure clean state
742
+ * for re-presentation of inline modals.
743
+ */
744
+ resetSafeAreaState() {
745
+ var _a;
746
+ this.skipSafeAreaCoordinateDetection = false;
747
+ this.cachedSafeAreas = undefined;
748
+ this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
749
+ (_a = this.footerObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
750
+ this.footerObserver = undefined;
751
+ // Clear wrapper styles that may have been set for safe-area handling
752
+ if (this.wrapperEl) {
753
+ this.wrapperEl.style.removeProperty('padding-bottom');
754
+ this.wrapperEl.style.removeProperty('box-sizing');
755
+ }
756
+ // Clear safe-area CSS variable overrides
757
+ const style = this.el.style;
758
+ style.removeProperty('--ion-safe-area-top');
759
+ style.removeProperty('--ion-safe-area-bottom');
760
+ style.removeProperty('--ion-safe-area-left');
761
+ style.removeProperty('--ion-safe-area-right');
762
+ }
734
763
  /**
735
764
  * Gets the root safe-area values from the document element.
736
765
  * Uses cached values during gestures to avoid getComputedStyle calls.
@@ -822,7 +851,7 @@ export class Modal {
822
851
  * For example, `cancel` or `backdrop`.
823
852
  */
824
853
  async dismiss(data, role) {
825
- var _a, _b;
854
+ var _a;
826
855
  if (this.gestureAnimationDismissing && role !== GESTURE) {
827
856
  return false;
828
857
  }
@@ -885,22 +914,7 @@ export class Modal {
885
914
  this.currentBreakpoint = undefined;
886
915
  this.animation = undefined;
887
916
  // Reset safe-area state for potential re-presentation
888
- this.skipSafeAreaCoordinateDetection = false;
889
- this.cachedSafeAreas = undefined;
890
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
891
- (_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
892
- this.footerObserver = undefined;
893
- // Clear styles that may have been set for safe-area handling
894
- if (this.wrapperEl) {
895
- this.wrapperEl.style.removeProperty('padding-bottom');
896
- this.wrapperEl.style.removeProperty('box-sizing');
897
- }
898
- // Clear safe-area CSS variable overrides
899
- const style = this.el.style;
900
- style.removeProperty('--ion-safe-area-top');
901
- style.removeProperty('--ion-safe-area-bottom');
902
- style.removeProperty('--ion-safe-area-left');
903
- style.removeProperty('--ion-safe-area-right');
917
+ this.resetSafeAreaState();
904
918
  unlock();
905
919
  return dismissed;
906
920
  }
@@ -1158,20 +1172,20 @@ export class Modal {
1158
1172
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1159
1173
  const isHandleCycle = handleBehavior === 'cycle';
1160
1174
  const isSheetModalWithHandle = isSheetModal && showHandle;
1161
- return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
1175
+ return (h(Host, Object.assign({ key: '44022099fcaf047b97d1c2cb45b9b51c930e707c', "no-router": true,
1162
1176
  // Allow the modal to be navigable when the handle is focusable
1163
1177
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1164
1178
  zIndex: `${20000 + this.overlayIndex}`,
1165
- }, 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: '125658fbb071960da3905854668078e15bce56da', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '4a63815ef165e5806fef85ef48bf509813ff55c9', class: "modal-shadow" }), h("div", Object.assign({ key: 'cfc4b20354cbf2c0f873f6aee91c9b8f553de61d',
1179
+ }, 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: 'ddd7e4f6eef51ac1f62ac70e0af10fb01e707f07', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '58620980e3e4ec273c6787bde026e1c010b904b7', class: "modal-shadow" }), h("div", Object.assign({ key: '3fb7f6218644ba898fc504467775593eb89426a0',
1166
1180
  /*
1167
1181
  role and aria-modal must be used on the
1168
1182
  same element. They must also be set inside the
1169
1183
  shadow DOM otherwise ion-button will not be highlighted
1170
1184
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1171
1185
  */
1172
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '33a4ee89bb8b6512883cb8756641c1e27fdb0ebc', class: "modal-handle",
1186
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '9745cd590fdaa9d023a14b487ec2c87ddbafd7f7', class: "modal-handle",
1173
1187
  // Prevents the handle from receiving keyboard focus when it does not cycle
1174
- 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: '986fafb71234591c96e927f92b7330bb5c76fc2e', onSlotchange: this.onSlotChange }))));
1188
+ 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: 'b9a8b5d2d3d3c9b06f99179f496c9f08907d0bad', onSlotchange: this.onSlotChange }))));
1175
1189
  }
1176
1190
  static get is() { return "ion-modal"; }
1177
1191
  static get encapsulation() { return "shadow"; }
@@ -28,7 +28,7 @@ export const mdEnterAnimation = (baseEl, opts) => {
28
28
  };
29
29
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
30
30
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
31
- const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
31
+ const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
32
32
  /**
33
33
  * Safe area CSS variable adjustments.
34
34
  * When the popover is positioned near an edge, we add the corresponding
@@ -37,14 +37,23 @@ export const mdEnterAnimation = (baseEl, opts) => {
37
37
  */
38
38
  const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
39
39
  const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
40
+ const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
41
+ const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
40
42
  let topValue = `${top}px`;
41
43
  let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
44
+ let leftValue = `${left}px`;
42
45
  if (checkSafeAreaTop) {
43
46
  topValue = `${top}px${safeAreaTop}`;
44
47
  }
45
48
  if (checkSafeAreaBottom && bottomValue !== undefined) {
46
49
  bottomValue = `${bottom}px${safeAreaBottom}`;
47
50
  }
51
+ if (checkSafeAreaLeft) {
52
+ leftValue = `${left}px${safeAreaLeft}`;
53
+ }
54
+ if (checkSafeAreaRight) {
55
+ leftValue = `${left}px${safeAreaRight}`;
56
+ }
48
57
  const baseAnimation = createAnimation();
49
58
  const backdropAnimation = createAnimation();
50
59
  const wrapperAnimation = createAnimation();
@@ -62,7 +71,7 @@ export const mdEnterAnimation = (baseEl, opts) => {
62
71
  .addElement(contentEl)
63
72
  .beforeStyles({
64
73
  top: `calc(${topValue} + var(--offset-y, 0px))`,
65
- left: `calc(${left}px + var(--offset-x, 0px))`,
74
+ left: `calc(${leftValue} + var(--offset-x, 0px))`,
66
75
  'transform-origin': `${originY} ${originX}`,
67
76
  })
68
77
  .beforeAddWrite(() => {
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-07T14:44:35",
2
+ "timestamp": "2026-01-08T17:05:41",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -4,6 +4,7 @@
4
4
  import { r as registerInstance, h, e as config, d as Host, g as getElement, f as printIonWarning, c as createEvent, i as forceUpdate, a as readTask, w as writeTask, j as printIonError } from './index-C8IsBmNU.js';
5
5
  import { shouldUseCloseWatcher } from './hardware-back-button-CPLxO-Ev.js';
6
6
  import { b as getIonMode, a as isPlatform } from './ionic-global-CDrldh-5.js';
7
+ import { w as win } from './index-ZjP4CjeZ.js';
7
8
  import { i as inheritAriaAttributes, h as hasLazyBuild, c as componentOnReady, e as clamp, s as shallowEqualStringMap } from './helpers-DEn3pfjm.js';
8
9
  import { i as isRTL } from './dir-C53feagD.js';
9
10
  import { c as createColorClasses, h as hostContext } from './theme-DiVJyqlX.js';
@@ -13,7 +14,6 @@ import { g as getTimeGivenProgression } from './cubic-bezier-hHmYLOfE.js';
13
14
  import { a as attachComponent, d as detachComponent } from './framework-delegate-BYawdMXj.js';
14
15
  import { c as createLockController } from './lock-controller-B-hirT0v.js';
15
16
  import { t as transition } from './index-r2D9DEro.js';
16
- import './index-ZjP4CjeZ.js';
17
17
  import './keyboard-CUw4ekVy.js';
18
18
  import './capacitor-CFERIeaU.js';
19
19
 
@@ -229,7 +229,11 @@ const Content = class {
229
229
  this.inheritedAttributes = inheritAriaAttributes(this.el);
230
230
  }
231
231
  connectedCallback() {
232
- this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
232
+ var _a;
233
+ // Content is "main" if not inside menu/popover/modal and not nested in another ion-content
234
+ this.isMainContent =
235
+ this.el.closest('ion-menu, ion-popover, ion-modal') === null &&
236
+ ((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest('ion-content')) === null;
233
237
  // Detect sibling header/footer for safe-area handling
234
238
  this.detectSiblingElements();
235
239
  /**
@@ -262,7 +266,12 @@ const Content = class {
262
266
  * bubbles, we can catch any instances of child tab bars loading by listening
263
267
  * on IonTabs.
264
268
  */
265
- this.tabsLoadCallback = () => this.resize();
269
+ this.tabsLoadCallback = () => {
270
+ this.resize();
271
+ // Re-detect footer when tab bar loads (it may not exist during initial detection)
272
+ this.updateSiblingDetection();
273
+ forceUpdate(this);
274
+ };
266
275
  closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
267
276
  }
268
277
  }
@@ -275,10 +284,15 @@ const Content = class {
275
284
  this.updateSiblingDetection();
276
285
  // Watch for dynamic header/footer changes (common in React conditional rendering)
277
286
  const parent = this.el.parentElement;
278
- if (parent && !this.parentMutationObserver) {
287
+ if (parent && !this.parentMutationObserver && win !== undefined && 'MutationObserver' in win) {
279
288
  this.parentMutationObserver = new MutationObserver(() => {
289
+ const prevHasHeader = this.hasHeader;
290
+ const prevHasFooter = this.hasFooter;
280
291
  this.updateSiblingDetection();
281
- forceUpdate(this);
292
+ // Only trigger re-render if header/footer detection actually changed
293
+ if (prevHasHeader !== this.hasHeader || prevHasFooter !== this.hasFooter) {
294
+ forceUpdate(this);
295
+ }
282
296
  });
283
297
  this.parentMutationObserver.observe(parent, { childList: true });
284
298
  }
@@ -584,7 +598,7 @@ const Content = class {
584
598
  const forceOverscroll = this.shouldForceOverscroll();
585
599
  const transitionShadow = mode === 'ios';
586
600
  this.resize();
587
- return (h(Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
601
+ return (h(Host, Object.assign({ key: 'f7218f733e4022a30875441bd949747537d28aa1', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
588
602
  [mode]: true,
589
603
  'content-sizing': hostContext('ion-popover', this.el),
590
604
  overscroll: forceOverscroll,
@@ -594,12 +608,12 @@ const Content = class {
594
608
  }), style: {
595
609
  '--offset-top': `${this.cTop}px`,
596
610
  '--offset-bottom': `${this.cBottom}px`,
597
- } }, inheritedAttributes), h("div", { key: '4c0482cda885348eea9eb66d7f076af6b38c52e5', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '6fbb39bf7ab7120009c56aea9340de45c934eeed', class: {
611
+ } }, inheritedAttributes), h("div", { key: 'b735ec68c18c0b99c3595bb194029830e6542cde', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'e76c00d030342d44ade6648c3f9e32ca990787ba', class: {
598
612
  'inner-scroll': true,
599
613
  'scroll-x': scrollX,
600
614
  'scroll-y': scrollY,
601
615
  overscroll: (scrollX || scrollY) && forceOverscroll,
602
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6425bc84edbc0c5b1f2764a1d611df1b46628274' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
616
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '9049be4cea9b5da5ec1e1012248b05286fddeb7a' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
603
617
  }
604
618
  get el() { return getElement(this); }
605
619
  };
@@ -2,6 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { r as registerInstance, c as createEvent, f as printIonWarning, w as writeTask, e as config, h, d as Host, g as getElement } from './index-C8IsBmNU.js';
5
+ import { w as win } from './index-ZjP4CjeZ.js';
5
6
  import { f as findClosestIonContent, i as isIonContent, d as disableContentScrollY, r as resetContentScrollY, a as findIonContent, p as printIonContentErrorMsg } from './index-Bs3kT4bc.js';
6
7
  import { C as CoreDelegate, a as attachComponent, d as detachComponent } from './framework-delegate-BYawdMXj.js';
7
8
  import { e as clamp, g as getElementRoot, r as raf, b as inheritAttributes, h as hasLazyBuild } from './helpers-DEn3pfjm.js';
@@ -15,7 +16,6 @@ import { KEYBOARD_DID_OPEN } from './keyboard-ywgs5efA.js';
15
16
  import { c as createAnimation } from './animation-Dt8bGnA-.js';
16
17
  import { g as getTimeGivenProgression } from './cubic-bezier-hHmYLOfE.js';
17
18
  import { createGesture } from './index-CfgBF1SE.js';
18
- import { w as win } from './index-ZjP4CjeZ.js';
19
19
  import './hardware-back-button-CPLxO-Ev.js';
20
20
  import './gesture-controller-BTEOs1at.js';
21
21
  import './keyboard-CUw4ekVy.js';
@@ -1721,6 +1721,8 @@ const Modal = class {
1721
1721
  this.triggerController.removeClickListener();
1722
1722
  this.cleanupViewTransitionListener();
1723
1723
  this.cleanupParentRemovalObserver();
1724
+ // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
1725
+ this.resetSafeAreaState();
1724
1726
  }
1725
1727
  componentWillLoad() {
1726
1728
  var _a;
@@ -2157,7 +2159,9 @@ const Modal = class {
2157
2159
  this.skipSafeAreaCoordinateDetection = true;
2158
2160
  this.updateFooterPadding();
2159
2161
  // Watch for dynamic footer additions/removals (e.g., async data loading)
2160
- if (!this.footerObserver) {
2162
+ // Use subtree:true to support wrapped footers in framework components
2163
+ // (e.g., <my-footer><ion-footer>...</ion-footer></my-footer>)
2164
+ if (!this.footerObserver && win !== undefined && 'MutationObserver' in win) {
2161
2165
  this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
2162
2166
  this.footerObserver.observe(this.el, { childList: true, subtree: true });
2163
2167
  }
@@ -2190,6 +2194,30 @@ const Modal = class {
2190
2194
  style.setProperty('--ion-safe-area-left', '0px');
2191
2195
  style.setProperty('--ion-safe-area-right', '0px');
2192
2196
  }
2197
+ /**
2198
+ * Resets all safe-area related state and styles.
2199
+ * Called during dismiss and disconnectedCallback to ensure clean state
2200
+ * for re-presentation of inline modals.
2201
+ */
2202
+ resetSafeAreaState() {
2203
+ var _a;
2204
+ this.skipSafeAreaCoordinateDetection = false;
2205
+ this.cachedSafeAreas = undefined;
2206
+ this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
2207
+ (_a = this.footerObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
2208
+ this.footerObserver = undefined;
2209
+ // Clear wrapper styles that may have been set for safe-area handling
2210
+ if (this.wrapperEl) {
2211
+ this.wrapperEl.style.removeProperty('padding-bottom');
2212
+ this.wrapperEl.style.removeProperty('box-sizing');
2213
+ }
2214
+ // Clear safe-area CSS variable overrides
2215
+ const style = this.el.style;
2216
+ style.removeProperty('--ion-safe-area-top');
2217
+ style.removeProperty('--ion-safe-area-bottom');
2218
+ style.removeProperty('--ion-safe-area-left');
2219
+ style.removeProperty('--ion-safe-area-right');
2220
+ }
2193
2221
  /**
2194
2222
  * Gets the root safe-area values from the document element.
2195
2223
  * Uses cached values during gestures to avoid getComputedStyle calls.
@@ -2281,7 +2309,7 @@ const Modal = class {
2281
2309
  * For example, `cancel` or `backdrop`.
2282
2310
  */
2283
2311
  async dismiss(data, role) {
2284
- var _a, _b;
2312
+ var _a;
2285
2313
  if (this.gestureAnimationDismissing && role !== GESTURE) {
2286
2314
  return false;
2287
2315
  }
@@ -2344,22 +2372,7 @@ const Modal = class {
2344
2372
  this.currentBreakpoint = undefined;
2345
2373
  this.animation = undefined;
2346
2374
  // Reset safe-area state for potential re-presentation
2347
- this.skipSafeAreaCoordinateDetection = false;
2348
- this.cachedSafeAreas = undefined;
2349
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
2350
- (_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
2351
- this.footerObserver = undefined;
2352
- // Clear styles that may have been set for safe-area handling
2353
- if (this.wrapperEl) {
2354
- this.wrapperEl.style.removeProperty('padding-bottom');
2355
- this.wrapperEl.style.removeProperty('box-sizing');
2356
- }
2357
- // Clear safe-area CSS variable overrides
2358
- const style = this.el.style;
2359
- style.removeProperty('--ion-safe-area-top');
2360
- style.removeProperty('--ion-safe-area-bottom');
2361
- style.removeProperty('--ion-safe-area-left');
2362
- style.removeProperty('--ion-safe-area-right');
2375
+ this.resetSafeAreaState();
2363
2376
  unlock();
2364
2377
  return dismissed;
2365
2378
  }
@@ -2609,20 +2622,20 @@ const Modal = class {
2609
2622
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2610
2623
  const isHandleCycle = handleBehavior === 'cycle';
2611
2624
  const isSheetModalWithHandle = isSheetModal && showHandle;
2612
- return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
2625
+ return (h(Host, Object.assign({ key: '44022099fcaf047b97d1c2cb45b9b51c930e707c', "no-router": true,
2613
2626
  // Allow the modal to be navigable when the handle is focusable
2614
2627
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2615
2628
  zIndex: `${20000 + this.overlayIndex}`,
2616
- }, 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: '125658fbb071960da3905854668078e15bce56da', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '4a63815ef165e5806fef85ef48bf509813ff55c9', class: "modal-shadow" }), h("div", Object.assign({ key: 'cfc4b20354cbf2c0f873f6aee91c9b8f553de61d',
2629
+ }, 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: 'ddd7e4f6eef51ac1f62ac70e0af10fb01e707f07', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '58620980e3e4ec273c6787bde026e1c010b904b7', class: "modal-shadow" }), h("div", Object.assign({ key: '3fb7f6218644ba898fc504467775593eb89426a0',
2617
2630
  /*
2618
2631
  role and aria-modal must be used on the
2619
2632
  same element. They must also be set inside the
2620
2633
  shadow DOM otherwise ion-button will not be highlighted
2621
2634
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2622
2635
  */
2623
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '33a4ee89bb8b6512883cb8756641c1e27fdb0ebc', class: "modal-handle",
2636
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '9745cd590fdaa9d023a14b487ec2c87ddbafd7f7', class: "modal-handle",
2624
2637
  // Prevents the handle from receiving keyboard focus when it does not cycle
2625
- 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: '986fafb71234591c96e927f92b7330bb5c76fc2e', onSlotchange: this.onSlotChange }))));
2638
+ 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: 'b9a8b5d2d3d3c9b06f99179f496c9f08907d0bad', onSlotchange: this.onSlotChange }))));
2626
2639
  }
2627
2640
  get el() { return getElement(this); }
2628
2641
  static get watchers() { return {
@@ -946,7 +946,7 @@ const mdEnterAnimation = (baseEl, opts) => {
946
946
  };
947
947
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
948
948
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
949
- const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
949
+ const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
950
950
  /**
951
951
  * Safe area CSS variable adjustments.
952
952
  * When the popover is positioned near an edge, we add the corresponding
@@ -955,14 +955,23 @@ const mdEnterAnimation = (baseEl, opts) => {
955
955
  */
956
956
  const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
957
957
  const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
958
+ const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
959
+ const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
958
960
  let topValue = `${top}px`;
959
961
  let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
962
+ let leftValue = `${left}px`;
960
963
  if (checkSafeAreaTop) {
961
964
  topValue = `${top}px${safeAreaTop}`;
962
965
  }
963
966
  if (checkSafeAreaBottom && bottomValue !== undefined) {
964
967
  bottomValue = `${bottom}px${safeAreaBottom}`;
965
968
  }
969
+ if (checkSafeAreaLeft) {
970
+ leftValue = `${left}px${safeAreaLeft}`;
971
+ }
972
+ if (checkSafeAreaRight) {
973
+ leftValue = `${left}px${safeAreaRight}`;
974
+ }
966
975
  const baseAnimation = createAnimation();
967
976
  const backdropAnimation = createAnimation();
968
977
  const wrapperAnimation = createAnimation();
@@ -980,7 +989,7 @@ const mdEnterAnimation = (baseEl, opts) => {
980
989
  .addElement(contentEl)
981
990
  .beforeStyles({
982
991
  top: `calc(${topValue} + var(--offset-y, 0px))`,
983
- left: `calc(${left}px + var(--offset-x, 0px))`,
992
+ left: `calc(${leftValue} + var(--offset-x, 0px))`,
984
993
  'transform-origin': `${originY} ${originX}`,
985
994
  })
986
995
  .beforeAddWrite(() => {