@ionic/core 8.7.17-dev.11767891829.1a63afa3 → 8.7.17-dev.11767895575.16ea7cef

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 (42) hide show
  1. package/components/content.js +8 -96
  2. package/components/ion-tab-bar.js +23 -3
  3. package/components/modal.js +12 -213
  4. package/components/popover.js +11 -83
  5. package/dist/cjs/ion-app_8.cjs.entry.js +20 -107
  6. package/dist/cjs/ion-modal.cjs.entry.js +12 -213
  7. package/dist/cjs/ion-popover.cjs.entry.js +11 -83
  8. package/dist/cjs/ion-tab-bar_2.cjs.entry.js +23 -3
  9. package/dist/collection/components/content/content.css +0 -10
  10. package/dist/collection/components/content/content.js +6 -94
  11. package/dist/collection/components/modal/gestures/sheet.js +1 -3
  12. package/dist/collection/components/modal/gestures/swipe-to-close.js +1 -3
  13. package/dist/collection/components/modal/modal.ios.css +4 -0
  14. package/dist/collection/components/modal/modal.js +7 -205
  15. package/dist/collection/components/modal/modal.md.css +4 -0
  16. package/dist/collection/components/popover/animations/ios.enter.js +5 -21
  17. package/dist/collection/components/popover/animations/md.enter.js +5 -30
  18. package/dist/collection/components/popover/utils.js +1 -32
  19. package/dist/collection/components/tab-bar/tab-bar.js +23 -3
  20. package/dist/docs.json +1 -1
  21. package/dist/esm/ion-app_8.entry.js +9 -96
  22. package/dist/esm/ion-modal.entry.js +12 -213
  23. package/dist/esm/ion-popover.entry.js +11 -83
  24. package/dist/esm/ion-tab-bar_2.entry.js +23 -3
  25. package/dist/ionic/ionic.esm.js +1 -1
  26. package/dist/ionic/p-172a579f.entry.js +4 -0
  27. package/dist/ionic/p-732b2fd6.entry.js +4 -0
  28. package/dist/ionic/p-91840a80.entry.js +4 -0
  29. package/dist/ionic/p-f9061316.entry.js +4 -0
  30. package/dist/types/components/content/content.d.ts +0 -24
  31. package/dist/types/components/modal/gestures/sheet.d.ts +1 -1
  32. package/dist/types/components/modal/gestures/swipe-to-close.d.ts +1 -1
  33. package/dist/types/components/modal/modal.d.ts +0 -45
  34. package/dist/types/components/popover/utils.d.ts +0 -2
  35. package/dist/types/components/tab-bar/tab-bar.d.ts +1 -0
  36. package/hydrate/index.js +52 -385
  37. package/hydrate/index.mjs +52 -385
  38. package/package.json +1 -1
  39. package/dist/ionic/p-7268efa5.entry.js +0 -4
  40. package/dist/ionic/p-968a55d1.entry.js +0 -4
  41. package/dist/ionic/p-d9fd799f.entry.js +0 -4
  42. package/dist/ionic/p-ec9ca3fe.entry.js +0 -4
@@ -2,7 +2,6 @@
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";
6
5
  import { componentOnReady, hasLazyBuild, inheritAriaAttributes } from "../../utils/helpers";
7
6
  import { isPlatform } from "../../utils/platform";
8
7
  import { isRTL } from "../../utils/rtl/index";
@@ -26,12 +25,6 @@ export class Content {
26
25
  this.isMainContent = true;
27
26
  this.resizeTimeout = null;
28
27
  this.inheritedAttributes = {};
29
- /**
30
- * Track whether this content has sibling header/footer elements.
31
- * When absent, we need to apply safe-area padding directly.
32
- */
33
- this.hasHeader = false;
34
- this.hasFooter = false;
35
28
  this.tabsElement = null;
36
29
  // Detail is used in a hot loop in the scroll event, by allocating it here
37
30
  // V8 will be able to inline any read/write to it since it's a monomorphic class.
@@ -86,13 +79,7 @@ export class Content {
86
79
  this.inheritedAttributes = inheritAriaAttributes(this.el);
87
80
  }
88
81
  connectedCallback() {
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;
94
- // Detect sibling header/footer for safe-area handling
95
- this.detectSiblingElements();
82
+ this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
96
83
  /**
97
84
  * The fullscreen content offsets need to be
98
85
  * computed after the tab bar has loaded. Since
@@ -123,86 +110,13 @@ export class Content {
123
110
  * bubbles, we can catch any instances of child tab bars loading by listening
124
111
  * on IonTabs.
125
112
  */
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
- };
113
+ this.tabsLoadCallback = () => this.resize();
132
114
  closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
133
115
  }
134
116
  }
135
117
  }
136
- /**
137
- * Detects sibling ion-header and ion-footer elements and sets up
138
- * a mutation observer to handle dynamic changes (e.g., conditional rendering).
139
- */
140
- detectSiblingElements() {
141
- this.updateSiblingDetection();
142
- // Watch for dynamic header/footer changes (common in React conditional rendering)
143
- const parent = this.el.parentElement;
144
- if (parent && !this.parentMutationObserver && win !== undefined && 'MutationObserver' in win) {
145
- this.parentMutationObserver = new MutationObserver(() => {
146
- const prevHasHeader = this.hasHeader;
147
- const prevHasFooter = this.hasFooter;
148
- this.updateSiblingDetection();
149
- // Only trigger re-render if header/footer detection actually changed
150
- if (prevHasHeader !== this.hasHeader || prevHasFooter !== this.hasFooter) {
151
- forceUpdate(this);
152
- }
153
- });
154
- this.parentMutationObserver.observe(parent, { childList: true });
155
- }
156
- }
157
- /**
158
- * Updates hasHeader/hasFooter based on current DOM state.
159
- * Checks both direct siblings and elements wrapped in custom components
160
- * (e.g., <my-header><ion-header>...</ion-header></my-header>).
161
- */
162
- updateSiblingDetection() {
163
- const parent = this.el.parentElement;
164
- if (parent) {
165
- // First check for direct ion-header/ion-footer siblings
166
- this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
167
- this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
168
- // If not found, check if any sibling contains them (wrapped components)
169
- if (!this.hasHeader) {
170
- this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
171
- }
172
- if (!this.hasFooter) {
173
- this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
174
- }
175
- }
176
- // If no footer found, check if we're inside ion-tabs which has ion-tab-bar
177
- if (!this.hasFooter) {
178
- const tabs = this.el.closest('ion-tabs');
179
- if (tabs) {
180
- this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
181
- }
182
- }
183
- }
184
- /**
185
- * Checks if any sibling element of ion-content contains the specified element.
186
- * Only searches one level deep to avoid finding elements in nested pages.
187
- */
188
- siblingContainsElement(parent, tagName) {
189
- for (const sibling of parent.children) {
190
- // Skip ion-content itself
191
- if (sibling === this.el)
192
- continue;
193
- // Check if this sibling contains the target element as an immediate child
194
- if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
195
- return true;
196
- }
197
- }
198
- return false;
199
- }
200
118
  disconnectedCallback() {
201
- var _a;
202
119
  this.onScrollEnd();
203
- // Clean up mutation observer to prevent memory leaks
204
- (_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
205
- this.parentMutationObserver = undefined;
206
120
  if (hasLazyBuild(this.el)) {
207
121
  /**
208
122
  * The event listener and tabs caches need to
@@ -449,28 +363,26 @@ export class Content {
449
363
  }
450
364
  }
451
365
  render() {
452
- const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
366
+ const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
453
367
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
454
368
  const mode = getIonMode(this);
455
369
  const forceOverscroll = this.shouldForceOverscroll();
456
370
  const transitionShadow = mode === 'ios';
457
371
  this.resize();
458
- return (h(Host, Object.assign({ key: 'f7218f733e4022a30875441bd949747537d28aa1', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
372
+ return (h(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
459
373
  [mode]: true,
460
374
  'content-sizing': hostContext('ion-popover', this.el),
461
375
  overscroll: forceOverscroll,
462
376
  [`content-${rtl}`]: true,
463
- 'safe-area-top': isMainContent && !hasHeader,
464
- 'safe-area-bottom': isMainContent && !hasFooter,
465
377
  }), style: {
466
378
  '--offset-top': `${this.cTop}px`,
467
379
  '--offset-bottom': `${this.cBottom}px`,
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: {
380
+ } }, inheritedAttributes), h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
469
381
  'inner-scroll': true,
470
382
  'scroll-x': scrollX,
471
383
  'scroll-y': scrollY,
472
384
  overscroll: (scrollX || scrollY) && forceOverscroll,
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));
385
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
474
386
  }
475
387
  static get is() { return "ion-content"; }
476
388
  static get encapsulation() { return "shadow"; }
@@ -7,7 +7,7 @@ import { clamp, getElementRoot, raf } from "../../../utils/helpers";
7
7
  import { FOCUS_TRAP_DISABLE_CLASS } from "../../../utils/overlays";
8
8
  import { getBackdropValueForSheet } from "../utils";
9
9
  import { calculateSpringStep, handleCanDismiss } from "./utils";
10
- export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange, onGestureMove) => {
10
+ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
11
11
  // Defaults for the sheet swipe animation
12
12
  const defaultBackdrop = [
13
13
  { offset: 0, opacity: 'var(--backdrop-opacity)' },
@@ -338,8 +338,6 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
338
338
  : step;
339
339
  offset = clamp(0.0001, processedStep, maxStep);
340
340
  animation.progressStep(offset);
341
- // Notify modal of position change for safe-area updates
342
- onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
343
341
  };
344
342
  const onEnd = (detail) => {
345
343
  /**
@@ -12,7 +12,7 @@ import { calculateSpringStep, handleCanDismiss } from "./utils";
12
12
  export const SwipeToCloseDefaults = {
13
13
  MIN_PRESENTING_SCALE: 0.915,
14
14
  };
15
- export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onGestureMove) => {
15
+ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
16
16
  /**
17
17
  * The step value at which a card modal
18
18
  * is eligible for dismissing via gesture.
@@ -169,8 +169,6 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
169
169
  const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
170
170
  const clampedStep = clamp(0.0001, processedStep, maxStep);
171
171
  animation.progressStep(clampedStep);
172
- // Notify modal of position change for safe-area updates
173
- onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
174
172
  /**
175
173
  * When swiping down half way, the status bar style
176
174
  * should be reset to its default value.
@@ -135,6 +135,10 @@ ion-backdrop {
135
135
  :host {
136
136
  --width: 600px;
137
137
  --height: 500px;
138
+ --ion-safe-area-top: 0px;
139
+ --ion-safe-area-bottom: 0px;
140
+ --ion-safe-area-right: 0px;
141
+ --ion-safe-area-left: 0px;
138
142
  }
139
143
  }
140
144
  @media only screen and (min-width: 768px) and (min-height: 768px) {
@@ -2,7 +2,6 @@
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";
6
5
  import { findIonContent, printIonContentErrorMsg } from "../../utils/content/index";
7
6
  import { CoreDelegate, attachComponent, detachComponent } from "../../utils/framework-delegate";
8
7
  import { raf, inheritAttributes, hasLazyBuild, getElementRoot } from "../../utils/helpers";
@@ -43,10 +42,6 @@ export class Modal {
43
42
  this.inline = false;
44
43
  // Whether or not modal is being dismissed via gesture
45
44
  this.gestureAnimationDismissing = false;
46
- // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
47
- this.skipSafeAreaCoordinateDetection = false;
48
- // Track previous safe-area state to avoid redundant DOM writes
49
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
50
45
  this.presented = false;
51
46
  /** @internal */
52
47
  this.hasController = false;
@@ -237,10 +232,7 @@ export class Modal {
237
232
  }
238
233
  }
239
234
  onWindowResize() {
240
- // Invalidate safe-area cache on resize (device rotation may change values)
241
- this.cachedSafeAreas = undefined;
242
- this.updateSafeAreaOverrides();
243
- // Only handle view transition for iOS card modals when no custom animations are provided
235
+ // Only handle resize for iOS card modals when no custom animations are provided
244
236
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
245
237
  return;
246
238
  }
@@ -263,8 +255,6 @@ export class Modal {
263
255
  this.triggerController.removeClickListener();
264
256
  this.cleanupViewTransitionListener();
265
257
  this.cleanupParentRemovalObserver();
266
- // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
267
- this.resetSafeAreaState();
268
258
  }
269
259
  componentWillLoad() {
270
260
  var _a;
@@ -424,8 +414,6 @@ export class Modal {
424
414
  else if (!this.keepContentsMounted) {
425
415
  await waitForMount();
426
416
  }
427
- // Predict safe-area needs based on modal configuration to avoid visual snap
428
- this.setInitialSafeAreaOverrides(presentingElement);
429
417
  writeTask(() => this.el.classList.add('show-modal'));
430
418
  const hasCardModal = presentingElement !== undefined;
431
419
  /**
@@ -487,8 +475,6 @@ export class Modal {
487
475
  else if (hasCardModal) {
488
476
  this.initSwipeToClose();
489
477
  }
490
- // Now that animation is complete, update safe-area based on actual position
491
- this.updateSafeAreaOverrides();
492
478
  // Initialize view transition listener for iOS card modals
493
479
  this.initViewTransitionListener();
494
480
  // Initialize parent removal observer
@@ -540,7 +526,7 @@ export class Modal {
540
526
  await this.dismiss(undefined, GESTURE);
541
527
  this.gestureAnimationDismissing = false;
542
528
  });
543
- }, () => this.updateSafeAreaOverrides());
529
+ });
544
530
  this.gesture.enable(true);
545
531
  }
546
532
  initSheetGesture() {
@@ -561,8 +547,7 @@ export class Modal {
561
547
  this.currentBreakpoint = breakpoint;
562
548
  this.ionBreakpointDidChange.emit({ breakpoint });
563
549
  }
564
- this.updateSafeAreaOverrides();
565
- }, () => this.updateSafeAreaOverrides());
550
+ });
566
551
  this.gesture = gesture;
567
552
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
568
553
  this.gesture.enable(true);
@@ -640,187 +625,6 @@ export class Modal {
640
625
  // Clear the cached reference
641
626
  this.cachedPageParent = undefined;
642
627
  }
643
- /**
644
- * Sets initial safe-area overrides based on modal configuration before
645
- * the modal becomes visible. This predicts whether the modal will touch
646
- * screen edges to avoid a visual snap after animation completes.
647
- */
648
- setInitialSafeAreaOverrides(presentingElement) {
649
- const style = this.el.style;
650
- const mode = getIonMode(this);
651
- const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
652
- // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
653
- const isCardModal = presentingElement !== undefined && mode === 'ios';
654
- const isTablet = window.innerWidth >= 768;
655
- // Sheet modals always touch bottom edge, never top/left/right
656
- if (isSheetModal) {
657
- style.setProperty('--ion-safe-area-top', '0px');
658
- style.setProperty('--ion-safe-area-left', '0px');
659
- style.setProperty('--ion-safe-area-right', '0px');
660
- return;
661
- }
662
- // Card modals have rounded top corners
663
- if (isCardModal) {
664
- style.setProperty('--ion-safe-area-top', '0px');
665
- if (isTablet) {
666
- // On tablets, card modals are inset from all edges
667
- this.zeroAllSafeAreas();
668
- }
669
- else {
670
- // On phones, card modals still extend to the bottom edge
671
- style.setProperty('--ion-safe-area-left', '0px');
672
- style.setProperty('--ion-safe-area-right', '0px');
673
- this.applyFullscreenSafeArea();
674
- }
675
- return;
676
- }
677
- // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
678
- if (!isTablet) {
679
- this.applyFullscreenSafeArea();
680
- return;
681
- }
682
- // Check if tablet modal is fullscreen via CSS custom properties
683
- const computedStyle = getComputedStyle(this.el);
684
- const width = computedStyle.getPropertyValue('--width').trim();
685
- const height = computedStyle.getPropertyValue('--height').trim();
686
- const isFullscreen = width === '100%' && height === '100%';
687
- if (isFullscreen) {
688
- this.applyFullscreenSafeArea();
689
- }
690
- else {
691
- // Centered dialog doesn't touch edges
692
- this.zeroAllSafeAreas();
693
- }
694
- }
695
- /**
696
- * Applies safe-area handling for fullscreen modals.
697
- * Adds wrapper padding when no footer is present to prevent
698
- * content from overlapping system navigation areas.
699
- */
700
- applyFullscreenSafeArea() {
701
- this.skipSafeAreaCoordinateDetection = true;
702
- this.updateFooterPadding();
703
- // Watch for dynamic footer additions/removals (e.g., async data loading)
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) {
707
- this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
708
- this.footerObserver.observe(this.el, { childList: true, subtree: true });
709
- }
710
- }
711
- /**
712
- * Updates wrapper padding based on footer presence.
713
- * Called initially and when footer is dynamically added/removed.
714
- */
715
- updateFooterPadding() {
716
- if (!this.wrapperEl)
717
- return;
718
- const hasFooter = this.el.querySelector('ion-footer') !== null;
719
- if (hasFooter) {
720
- this.wrapperEl.style.removeProperty('padding-bottom');
721
- this.wrapperEl.style.removeProperty('box-sizing');
722
- }
723
- else {
724
- this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
725
- this.wrapperEl.style.setProperty('box-sizing', 'border-box');
726
- }
727
- }
728
- /**
729
- * Sets all safe-area CSS variables to 0px for modals that
730
- * don't touch screen edges.
731
- */
732
- zeroAllSafeAreas() {
733
- const style = this.el.style;
734
- style.setProperty('--ion-safe-area-top', '0px');
735
- style.setProperty('--ion-safe-area-bottom', '0px');
736
- style.setProperty('--ion-safe-area-left', '0px');
737
- style.setProperty('--ion-safe-area-right', '0px');
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
- }
763
- /**
764
- * Gets the root safe-area values from the document element.
765
- * Uses cached values during gestures to avoid getComputedStyle calls.
766
- */
767
- getSafeAreaValues() {
768
- if (!this.cachedSafeAreas) {
769
- const rootStyle = getComputedStyle(document.documentElement);
770
- this.cachedSafeAreas = {
771
- top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
772
- bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
773
- left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
774
- right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
775
- };
776
- }
777
- return this.cachedSafeAreas;
778
- }
779
- /**
780
- * Updates safe-area CSS variable overrides based on whether the modal
781
- * extends into each safe-area region. Called after animation
782
- * and during gestures to handle dynamic position changes.
783
- *
784
- * Optimized to avoid redundant DOM writes by tracking previous state.
785
- */
786
- updateSafeAreaOverrides() {
787
- if (this.skipSafeAreaCoordinateDetection) {
788
- return;
789
- }
790
- const wrapper = this.wrapperEl;
791
- if (!wrapper) {
792
- return;
793
- }
794
- const rect = wrapper.getBoundingClientRect();
795
- const safeAreas = this.getSafeAreaValues();
796
- const extendsIntoTop = rect.top < safeAreas.top;
797
- const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
798
- const extendsIntoLeft = rect.left < safeAreas.left;
799
- const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
800
- // Only update DOM when state actually changes
801
- const prev = this.prevSafeAreaState;
802
- const style = this.el.style;
803
- if (extendsIntoTop !== prev.top) {
804
- extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
805
- prev.top = extendsIntoTop;
806
- }
807
- if (extendsIntoBottom !== prev.bottom) {
808
- extendsIntoBottom
809
- ? style.removeProperty('--ion-safe-area-bottom')
810
- : style.setProperty('--ion-safe-area-bottom', '0px');
811
- prev.bottom = extendsIntoBottom;
812
- }
813
- if (extendsIntoLeft !== prev.left) {
814
- extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
815
- prev.left = extendsIntoLeft;
816
- }
817
- if (extendsIntoRight !== prev.right) {
818
- extendsIntoRight
819
- ? style.removeProperty('--ion-safe-area-right')
820
- : style.setProperty('--ion-safe-area-right', '0px');
821
- prev.right = extendsIntoRight;
822
- }
823
- }
824
628
  sheetOnDismiss() {
825
629
  /**
826
630
  * While the gesture animation is finishing
@@ -913,8 +717,6 @@ export class Modal {
913
717
  }
914
718
  this.currentBreakpoint = undefined;
915
719
  this.animation = undefined;
916
- // Reset safe-area state for potential re-presentation
917
- this.resetSafeAreaState();
918
720
  unlock();
919
721
  return dismissed;
920
722
  }
@@ -1172,20 +974,20 @@ export class Modal {
1172
974
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1173
975
  const isHandleCycle = handleBehavior === 'cycle';
1174
976
  const isSheetModalWithHandle = isSheetModal && showHandle;
1175
- return (h(Host, Object.assign({ key: '44022099fcaf047b97d1c2cb45b9b51c930e707c', "no-router": true,
977
+ return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
1176
978
  // Allow the modal to be navigable when the handle is focusable
1177
979
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1178
980
  zIndex: `${20000 + this.overlayIndex}`,
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',
981
+ }, 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: 'ee94ff8e09b691dd4ad4e4db1720f06bc3c5a469', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'bffd69b4635c22d9f249725bd952c1e93d5615c7', class: "modal-shadow" }), h("div", Object.assign({ key: '1d394d3c68916e464ff1fbf5242419f4a3d3cca1',
1180
982
  /*
1181
983
  role and aria-modal must be used on the
1182
984
  same element. They must also be set inside the
1183
985
  shadow DOM otherwise ion-button will not be highlighted
1184
986
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1185
987
  */
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",
988
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2dcf58792018e557e0c323baad2d672bc99c0bb1', class: "modal-handle",
1187
989
  // Prevents the handle from receiving keyboard focus when it does not cycle
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 }))));
990
+ 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: '44164b1e8710c3895400ad9f44ecd99873874ad5', onSlotchange: this.onSlotChange }))));
1189
991
  }
1190
992
  static get is() { return "ion-modal"; }
1191
993
  static get encapsulation() { return "shadow"; }
@@ -135,6 +135,10 @@ ion-backdrop {
135
135
  :host {
136
136
  --width: 600px;
137
137
  --height: 500px;
138
+ --ion-safe-area-top: 0px;
139
+ --ion-safe-area-bottom: 0px;
140
+ --ion-safe-area-right: 0px;
141
+ --ion-safe-area-left: 0px;
138
142
  }
139
143
  }
140
144
  @media only screen and (min-width: 768px) and (min-height: 768px) {
@@ -31,7 +31,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
31
31
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
32
32
  const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
33
33
  const margin = size === 'cover' ? 0 : 25;
34
- const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
34
+ const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
35
35
  const baseAnimation = createAnimation();
36
36
  const backdropAnimation = createAnimation();
37
37
  const contentAnimation = createAnimation();
@@ -61,35 +61,19 @@ export const iosEnterAnimation = (baseEl, opts) => {
61
61
  if (addPopoverBottomClass) {
62
62
  baseEl.classList.add('popover-bottom');
63
63
  }
64
- /**
65
- * Safe area CSS variable adjustments.
66
- * When the popover is positioned near an edge, we add the corresponding
67
- * safe-area inset to ensure the popover doesn't overlap with system UI
68
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
69
- */
70
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
71
- const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
64
+ if (bottom !== undefined) {
65
+ contentEl.style.setProperty('bottom', `${bottom}px`);
66
+ }
72
67
  const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
73
68
  const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
74
- let topValue = `${top}px`;
75
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
76
69
  let leftValue = `${left}px`;
77
- if (checkSafeAreaTop) {
78
- topValue = `${top}px${safeAreaTop}`;
79
- }
80
- if (checkSafeAreaBottom && bottomValue !== undefined) {
81
- bottomValue = `${bottom}px${safeAreaBottom}`;
82
- }
83
70
  if (checkSafeAreaLeft) {
84
71
  leftValue = `${left}px${safeAreaLeft}`;
85
72
  }
86
73
  if (checkSafeAreaRight) {
87
74
  leftValue = `${left}px${safeAreaRight}`;
88
75
  }
89
- if (bottomValue !== undefined) {
90
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
91
- }
92
- contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
76
+ contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
93
77
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
94
78
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
95
79
  if (arrowEl !== null) {
@@ -28,32 +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, checkSafeAreaLeft, checkSafeAreaRight, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
32
- /**
33
- * Safe area CSS variable adjustments.
34
- * When the popover is positioned near an edge, we add the corresponding
35
- * safe-area inset to ensure the popover doesn't overlap with system UI
36
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
37
- */
38
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
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)';
42
- let topValue = `${top}px`;
43
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
44
- let leftValue = `${left}px`;
45
- if (checkSafeAreaTop) {
46
- topValue = `${top}px${safeAreaTop}`;
47
- }
48
- if (checkSafeAreaBottom && bottomValue !== undefined) {
49
- bottomValue = `${bottom}px${safeAreaBottom}`;
50
- }
51
- if (checkSafeAreaLeft) {
52
- leftValue = `${left}px${safeAreaLeft}`;
53
- }
54
- if (checkSafeAreaRight) {
55
- leftValue = `${left}px${safeAreaRight}`;
56
- }
31
+ const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
57
32
  const baseAnimation = createAnimation();
58
33
  const backdropAnimation = createAnimation();
59
34
  const wrapperAnimation = createAnimation();
@@ -70,13 +45,13 @@ export const mdEnterAnimation = (baseEl, opts) => {
70
45
  contentAnimation
71
46
  .addElement(contentEl)
72
47
  .beforeStyles({
73
- top: `calc(${topValue} + var(--offset-y, 0px))`,
74
- left: `calc(${leftValue} + var(--offset-x, 0px))`,
48
+ top: `calc(${top}px + var(--offset-y, 0px))`,
49
+ left: `calc(${left}px + var(--offset-x, 0px))`,
75
50
  'transform-origin': `${originY} ${originX}`,
76
51
  })
77
52
  .beforeAddWrite(() => {
78
- if (bottomValue !== undefined) {
79
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
53
+ if (bottom !== undefined) {
54
+ contentEl.style.setProperty('bottom', `${bottom}px`);
80
55
  }
81
56
  })
82
57
  .fromTo('transform', 'scale(0.8)', 'scale(1)');