@ionic/core 8.7.16-dev.11767111768.1552e96d → 8.7.16-dev.11767205737.13557925

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.
@@ -42,6 +42,8 @@ export class Modal {
42
42
  this.inline = false;
43
43
  // Whether or not modal is being dismissed via gesture
44
44
  this.gestureAnimationDismissing = false;
45
+ // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
46
+ this.skipSafeAreaCoordinateDetection = false;
45
47
  this.presented = false;
46
48
  /** @internal */
47
49
  this.hasController = false;
@@ -232,7 +234,9 @@ export class Modal {
232
234
  }
233
235
  }
234
236
  onWindowResize() {
235
- // Only handle resize for iOS card modals when no custom animations are provided
237
+ // Update safe-area overrides for all modal types on resize
238
+ this.updateSafeAreaOverrides();
239
+ // Only handle view transition for iOS card modals when no custom animations are provided
236
240
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
237
241
  return;
238
242
  }
@@ -414,6 +418,8 @@ export class Modal {
414
418
  else if (!this.keepContentsMounted) {
415
419
  await waitForMount();
416
420
  }
421
+ // Predict safe-area needs based on modal configuration to avoid visual snap
422
+ this.setInitialSafeAreaOverrides(presentingElement);
417
423
  writeTask(() => this.el.classList.add('show-modal'));
418
424
  const hasCardModal = presentingElement !== undefined;
419
425
  /**
@@ -475,6 +481,8 @@ export class Modal {
475
481
  else if (hasCardModal) {
476
482
  this.initSwipeToClose();
477
483
  }
484
+ // Now that animation is complete, update safe-area based on actual position
485
+ this.updateSafeAreaOverrides();
478
486
  // Initialize view transition listener for iOS card modals
479
487
  this.initViewTransitionListener();
480
488
  // Initialize parent removal observer
@@ -526,7 +534,7 @@ export class Modal {
526
534
  await this.dismiss(undefined, GESTURE);
527
535
  this.gestureAnimationDismissing = false;
528
536
  });
529
- });
537
+ }, () => this.updateSafeAreaOverrides());
530
538
  this.gesture.enable(true);
531
539
  }
532
540
  initSheetGesture() {
@@ -547,7 +555,8 @@ export class Modal {
547
555
  this.currentBreakpoint = breakpoint;
548
556
  this.ionBreakpointDidChange.emit({ breakpoint });
549
557
  }
550
- });
558
+ this.updateSafeAreaOverrides();
559
+ }, () => this.updateSafeAreaOverrides());
551
560
  this.gesture = gesture;
552
561
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
553
562
  this.gesture.enable(true);
@@ -625,6 +634,97 @@ export class Modal {
625
634
  // Clear the cached reference
626
635
  this.cachedPageParent = undefined;
627
636
  }
637
+ /**
638
+ * Sets initial safe-area overrides based on modal configuration before
639
+ * the modal becomes visible. This predicts whether the modal will touch
640
+ * screen edges to avoid a visual snap after animation completes.
641
+ */
642
+ setInitialSafeAreaOverrides(presentingElement) {
643
+ const style = this.el.style;
644
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
645
+ const isCardModal = presentingElement !== undefined;
646
+ const isTablet = window.innerWidth >= 768;
647
+ // Sheet modals always touch bottom edge, never top/left/right
648
+ if (isSheetModal) {
649
+ style.setProperty('--ion-safe-area-top', '0px');
650
+ style.setProperty('--ion-safe-area-left', '0px');
651
+ style.setProperty('--ion-safe-area-right', '0px');
652
+ return;
653
+ }
654
+ // Card modals are inset from all edges
655
+ if (isCardModal) {
656
+ this.zeroAllSafeAreas();
657
+ return;
658
+ }
659
+ // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
660
+ if (!isTablet) {
661
+ this.applyFullscreenSafeArea();
662
+ return;
663
+ }
664
+ // Check if tablet modal is fullscreen via CSS custom properties
665
+ const computedStyle = getComputedStyle(this.el);
666
+ const width = computedStyle.getPropertyValue('--width').trim();
667
+ const height = computedStyle.getPropertyValue('--height').trim();
668
+ const isFullscreen = width === '100%' && height === '100%';
669
+ if (isFullscreen) {
670
+ this.applyFullscreenSafeArea();
671
+ }
672
+ else {
673
+ // Centered dialog doesn't touch edges
674
+ this.zeroAllSafeAreas();
675
+ }
676
+ }
677
+ /**
678
+ * Applies safe-area handling for fullscreen modals.
679
+ * Adds wrapper padding when no footer is present to prevent
680
+ * content from overlapping system navigation areas.
681
+ */
682
+ applyFullscreenSafeArea() {
683
+ this.skipSafeAreaCoordinateDetection = true;
684
+ const hasFooter = this.el.querySelector('ion-footer') !== null;
685
+ if (!hasFooter && this.wrapperEl) {
686
+ this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
687
+ this.wrapperEl.style.setProperty('box-sizing', 'border-box');
688
+ }
689
+ }
690
+ /**
691
+ * Sets all safe-area CSS variables to 0px for modals that
692
+ * don't touch screen edges.
693
+ */
694
+ zeroAllSafeAreas() {
695
+ const style = this.el.style;
696
+ style.setProperty('--ion-safe-area-top', '0px');
697
+ style.setProperty('--ion-safe-area-bottom', '0px');
698
+ style.setProperty('--ion-safe-area-left', '0px');
699
+ style.setProperty('--ion-safe-area-right', '0px');
700
+ }
701
+ /**
702
+ * Updates safe-area CSS variable overrides based on whether the modal
703
+ * is touching each edge of the viewport. Called after animation
704
+ * and during gestures to handle dynamic position changes.
705
+ */
706
+ updateSafeAreaOverrides() {
707
+ if (this.skipSafeAreaCoordinateDetection) {
708
+ return;
709
+ }
710
+ const wrapper = this.wrapperEl;
711
+ if (!wrapper) {
712
+ return;
713
+ }
714
+ const rect = wrapper.getBoundingClientRect();
715
+ const threshold = 2;
716
+ const touchingTop = rect.top <= threshold;
717
+ const touchingBottom = rect.bottom >= window.innerHeight - threshold;
718
+ const touchingLeft = rect.left <= threshold;
719
+ const touchingRight = rect.right >= window.innerWidth - threshold;
720
+ const style = this.el.style;
721
+ touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
722
+ touchingBottom
723
+ ? style.removeProperty('--ion-safe-area-bottom')
724
+ : style.setProperty('--ion-safe-area-bottom', '0px');
725
+ touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
726
+ touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
727
+ }
628
728
  sheetOnDismiss() {
629
729
  /**
630
730
  * While the gesture animation is finishing
@@ -717,6 +817,8 @@ export class Modal {
717
817
  }
718
818
  this.currentBreakpoint = undefined;
719
819
  this.animation = undefined;
820
+ // Reset safe-area detection flag for potential re-presentation
821
+ this.skipSafeAreaCoordinateDetection = false;
720
822
  unlock();
721
823
  return dismissed;
722
824
  }
@@ -798,6 +900,10 @@ export class Modal {
798
900
  this.currentViewIsPortrait = window.innerWidth < 768;
799
901
  }
800
902
  handleViewTransition() {
903
+ // Only run view transitions when the modal is presented
904
+ if (!this.presented) {
905
+ return;
906
+ }
801
907
  const isPortrait = window.innerWidth < 768;
802
908
  // Only transition if view state actually changed
803
909
  if (this.currentViewIsPortrait === isPortrait) {
@@ -970,20 +1076,20 @@ export class Modal {
970
1076
  const isCardModal = presentingElement !== undefined && mode === 'ios';
971
1077
  const isHandleCycle = handleBehavior === 'cycle';
972
1078
  const isSheetModalWithHandle = isSheetModal && showHandle;
973
- return (h(Host, Object.assign({ key: '9a75095a13de0cfc96f1fa69fd92777d25da8daa', "no-router": true,
1079
+ return (h(Host, Object.assign({ key: 'b93c9d20985ca4800b34c0165957b12b0a9d6789', "no-router": true,
974
1080
  // Allow the modal to be navigable when the handle is focusable
975
1081
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
976
1082
  zIndex: `${20000 + this.overlayIndex}`,
977
- }, 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: 'd02612d8063ef20f59f173ff47795f71cdaaf63e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '708761b70a93e34c08faae079569f444c7416a4c', class: "modal-shadow" }), h("div", Object.assign({ key: 'a72226ff1a98229f9bfd9207b98fc57e02baa430',
1083
+ }, 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: '732b9355b3c0b051d19e1e0cedd7c49e3ad6fdda', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '874dcbb38da5a9feecafc5d3d1cb9f140f7fe48c', class: "modal-shadow" }), h("div", Object.assign({ key: '0d0c28581d49aeb0f764387a76ad54f68c35a6d5',
978
1084
  /*
979
1085
  role and aria-modal must be used on the
980
1086
  same element. They must also be set inside the
981
1087
  shadow DOM otherwise ion-button will not be highlighted
982
1088
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
983
1089
  */
984
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0547f32323882660221385d84d492929caa77c6b', class: "modal-handle",
1090
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a27a7ecc9857b4dc2f8a6f64bb01487ac578f5aa', class: "modal-handle",
985
1091
  // Prevents the handle from receiving keyboard focus when it does not cycle
986
- 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: 'fccbd64518b6fa22f9e874deb6b4ba55d8d89c3b', onSlotchange: this.onSlotChange }))));
1092
+ 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: '2ca0a826798dbc5cfe06e94b6a2d0ef375d018bc', onSlotchange: this.onSlotChange }))));
987
1093
  }
988
1094
  static get is() { return "ion-modal"; }
989
1095
  static get encapsulation() { return "shadow"; }
@@ -135,10 +135,6 @@ 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;
142
138
  }
143
139
  }
144
140
  @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, 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, 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);
35
35
  const baseAnimation = createAnimation();
36
36
  const backdropAnimation = createAnimation();
37
37
  const contentAnimation = createAnimation();
@@ -61,19 +61,35 @@ export const iosEnterAnimation = (baseEl, opts) => {
61
61
  if (addPopoverBottomClass) {
62
62
  baseEl.classList.add('popover-bottom');
63
63
  }
64
- if (bottom !== undefined) {
65
- contentEl.style.setProperty('bottom', `${bottom}px`);
66
- }
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)';
67
72
  const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
68
73
  const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
74
+ let topValue = `${top}px`;
75
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
69
76
  let leftValue = `${left}px`;
77
+ if (checkSafeAreaTop) {
78
+ topValue = `${top}px${safeAreaTop}`;
79
+ }
80
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
81
+ bottomValue = `${bottom}px${safeAreaBottom}`;
82
+ }
70
83
  if (checkSafeAreaLeft) {
71
84
  leftValue = `${left}px${safeAreaLeft}`;
72
85
  }
73
86
  if (checkSafeAreaRight) {
74
87
  leftValue = `${left}px${safeAreaRight}`;
75
88
  }
76
- contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
89
+ if (bottomValue !== undefined) {
90
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
91
+ }
92
+ contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
77
93
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
78
94
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
79
95
  if (arrowEl !== null) {
@@ -28,7 +28,23 @@ 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 } = 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 } = 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
+ let topValue = `${top}px`;
41
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
42
+ if (checkSafeAreaTop) {
43
+ topValue = `${top}px${safeAreaTop}`;
44
+ }
45
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
46
+ bottomValue = `${bottom}px${safeAreaBottom}`;
47
+ }
32
48
  const baseAnimation = createAnimation();
33
49
  const backdropAnimation = createAnimation();
34
50
  const wrapperAnimation = createAnimation();
@@ -45,13 +61,13 @@ export const mdEnterAnimation = (baseEl, opts) => {
45
61
  contentAnimation
46
62
  .addElement(contentEl)
47
63
  .beforeStyles({
48
- top: `calc(${top}px + var(--offset-y, 0px))`,
64
+ top: `calc(${topValue} + var(--offset-y, 0px))`,
49
65
  left: `calc(${left}px + var(--offset-x, 0px))`,
50
66
  'transform-origin': `${originY} ${originX}`,
51
67
  })
52
68
  .beforeAddWrite(() => {
53
- if (bottom !== undefined) {
54
- contentEl.style.setProperty('bottom', `${bottom}px`);
69
+ if (bottomValue !== undefined) {
70
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
55
71
  }
56
72
  })
57
73
  .fromTo('transform', 'scale(0.8)', 'scale(1)');
@@ -648,6 +648,8 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
648
648
  let bottom;
649
649
  let originX = contentOriginX;
650
650
  let originY = contentOriginY;
651
+ let checkSafeAreaTop = false;
652
+ let checkSafeAreaBottom = false;
651
653
  let checkSafeAreaLeft = false;
652
654
  let checkSafeAreaRight = false;
653
655
  const triggerTop = triggerCoordinates
@@ -692,10 +694,18 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
692
694
  * We chose 12 here so that the popover position looks a bit nicer as
693
695
  * it is not right up against the edge of the screen.
694
696
  */
695
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
697
+ top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
696
698
  arrowTop = top + contentHeight;
697
699
  originY = 'bottom';
698
700
  addPopoverBottomClass = true;
701
+ /**
702
+ * If the popover is positioned near the top edge, account for safe area.
703
+ * This ensures the popover doesn't overlap with status bars or notches.
704
+ */
705
+ if (top <= bodyPadding + safeAreaMargin) {
706
+ checkSafeAreaTop = true;
707
+ top = bodyPadding;
708
+ }
699
709
  /**
700
710
  * If not enough room for popover to appear
701
711
  * above trigger, then cut it off.
@@ -703,6 +713,12 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
703
713
  }
704
714
  else {
705
715
  bottom = bodyPadding;
716
+ /**
717
+ * When the popover is pinned to the bottom, account for safe area.
718
+ * This ensures the popover doesn't overlap with home indicators
719
+ * or navigation bars (e.g., Android API 36+ edge-to-edge).
720
+ */
721
+ checkSafeAreaBottom = true;
706
722
  }
707
723
  }
708
724
  return {
@@ -711,6 +727,8 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
711
727
  bottom,
712
728
  originX,
713
729
  originY,
730
+ checkSafeAreaTop,
731
+ checkSafeAreaBottom,
714
732
  checkSafeAreaLeft,
715
733
  checkSafeAreaRight,
716
734
  arrowTop,
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-12-30T16:24:36",
2
+ "timestamp": "2025-12-31T18:30:46",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -247,7 +247,7 @@ const calculateSpringStep = (t) => {
247
247
  const SwipeToCloseDefaults = {
248
248
  MIN_PRESENTING_SCALE: 0.915,
249
249
  };
250
- const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
250
+ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onGestureMove) => {
251
251
  /**
252
252
  * The step value at which a card modal
253
253
  * is eligible for dismissing via gesture.
@@ -404,6 +404,8 @@ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) =>
404
404
  const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
405
405
  const clampedStep = clamp(0.0001, processedStep, maxStep);
406
406
  animation.progressStep(clampedStep);
407
+ // Notify modal of position change for safe-area updates
408
+ onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
407
409
  /**
408
410
  * When swiping down half way, the status bar style
409
411
  * should be reset to its default value.
@@ -947,7 +949,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
947
949
  return baseAnimation;
948
950
  };
949
951
 
950
- const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
952
+ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange, onGestureMove) => {
951
953
  // Defaults for the sheet swipe animation
952
954
  const defaultBackdrop = [
953
955
  { offset: 0, opacity: 'var(--backdrop-opacity)' },
@@ -1278,6 +1280,8 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1278
1280
  : step;
1279
1281
  offset = clamp(0.0001, processedStep, maxStep);
1280
1282
  animation.progressStep(offset);
1283
+ // Notify modal of position change for safe-area updates
1284
+ onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
1281
1285
  };
1282
1286
  const onEnd = (detail) => {
1283
1287
  /**
@@ -1472,9 +1476,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1472
1476
  };
1473
1477
  };
1474
1478
 
1475
- const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
1479
+ const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
1476
1480
 
1477
- const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
1481
+ const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
1478
1482
 
1479
1483
  const Modal = class {
1480
1484
  constructor(hostRef) {
@@ -1497,6 +1501,8 @@ const Modal = class {
1497
1501
  this.inline = false;
1498
1502
  // Whether or not modal is being dismissed via gesture
1499
1503
  this.gestureAnimationDismissing = false;
1504
+ // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
1505
+ this.skipSafeAreaCoordinateDetection = false;
1500
1506
  this.presented = false;
1501
1507
  /** @internal */
1502
1508
  this.hasController = false;
@@ -1687,7 +1693,9 @@ const Modal = class {
1687
1693
  }
1688
1694
  }
1689
1695
  onWindowResize() {
1690
- // Only handle resize for iOS card modals when no custom animations are provided
1696
+ // Update safe-area overrides for all modal types on resize
1697
+ this.updateSafeAreaOverrides();
1698
+ // Only handle view transition for iOS card modals when no custom animations are provided
1691
1699
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
1692
1700
  return;
1693
1701
  }
@@ -1869,6 +1877,8 @@ const Modal = class {
1869
1877
  else if (!this.keepContentsMounted) {
1870
1878
  await waitForMount();
1871
1879
  }
1880
+ // Predict safe-area needs based on modal configuration to avoid visual snap
1881
+ this.setInitialSafeAreaOverrides(presentingElement);
1872
1882
  writeTask(() => this.el.classList.add('show-modal'));
1873
1883
  const hasCardModal = presentingElement !== undefined;
1874
1884
  /**
@@ -1930,6 +1940,8 @@ const Modal = class {
1930
1940
  else if (hasCardModal) {
1931
1941
  this.initSwipeToClose();
1932
1942
  }
1943
+ // Now that animation is complete, update safe-area based on actual position
1944
+ this.updateSafeAreaOverrides();
1933
1945
  // Initialize view transition listener for iOS card modals
1934
1946
  this.initViewTransitionListener();
1935
1947
  // Initialize parent removal observer
@@ -1981,7 +1993,7 @@ const Modal = class {
1981
1993
  await this.dismiss(undefined, GESTURE);
1982
1994
  this.gestureAnimationDismissing = false;
1983
1995
  });
1984
- });
1996
+ }, () => this.updateSafeAreaOverrides());
1985
1997
  this.gesture.enable(true);
1986
1998
  }
1987
1999
  initSheetGesture() {
@@ -2002,7 +2014,8 @@ const Modal = class {
2002
2014
  this.currentBreakpoint = breakpoint;
2003
2015
  this.ionBreakpointDidChange.emit({ breakpoint });
2004
2016
  }
2005
- });
2017
+ this.updateSafeAreaOverrides();
2018
+ }, () => this.updateSafeAreaOverrides());
2006
2019
  this.gesture = gesture;
2007
2020
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
2008
2021
  this.gesture.enable(true);
@@ -2080,6 +2093,97 @@ const Modal = class {
2080
2093
  // Clear the cached reference
2081
2094
  this.cachedPageParent = undefined;
2082
2095
  }
2096
+ /**
2097
+ * Sets initial safe-area overrides based on modal configuration before
2098
+ * the modal becomes visible. This predicts whether the modal will touch
2099
+ * screen edges to avoid a visual snap after animation completes.
2100
+ */
2101
+ setInitialSafeAreaOverrides(presentingElement) {
2102
+ const style = this.el.style;
2103
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
2104
+ const isCardModal = presentingElement !== undefined;
2105
+ const isTablet = window.innerWidth >= 768;
2106
+ // Sheet modals always touch bottom edge, never top/left/right
2107
+ if (isSheetModal) {
2108
+ style.setProperty('--ion-safe-area-top', '0px');
2109
+ style.setProperty('--ion-safe-area-left', '0px');
2110
+ style.setProperty('--ion-safe-area-right', '0px');
2111
+ return;
2112
+ }
2113
+ // Card modals are inset from all edges
2114
+ if (isCardModal) {
2115
+ this.zeroAllSafeAreas();
2116
+ return;
2117
+ }
2118
+ // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
2119
+ if (!isTablet) {
2120
+ this.applyFullscreenSafeArea();
2121
+ return;
2122
+ }
2123
+ // Check if tablet modal is fullscreen via CSS custom properties
2124
+ const computedStyle = getComputedStyle(this.el);
2125
+ const width = computedStyle.getPropertyValue('--width').trim();
2126
+ const height = computedStyle.getPropertyValue('--height').trim();
2127
+ const isFullscreen = width === '100%' && height === '100%';
2128
+ if (isFullscreen) {
2129
+ this.applyFullscreenSafeArea();
2130
+ }
2131
+ else {
2132
+ // Centered dialog doesn't touch edges
2133
+ this.zeroAllSafeAreas();
2134
+ }
2135
+ }
2136
+ /**
2137
+ * Applies safe-area handling for fullscreen modals.
2138
+ * Adds wrapper padding when no footer is present to prevent
2139
+ * content from overlapping system navigation areas.
2140
+ */
2141
+ applyFullscreenSafeArea() {
2142
+ this.skipSafeAreaCoordinateDetection = true;
2143
+ const hasFooter = this.el.querySelector('ion-footer') !== null;
2144
+ if (!hasFooter && this.wrapperEl) {
2145
+ this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
2146
+ this.wrapperEl.style.setProperty('box-sizing', 'border-box');
2147
+ }
2148
+ }
2149
+ /**
2150
+ * Sets all safe-area CSS variables to 0px for modals that
2151
+ * don't touch screen edges.
2152
+ */
2153
+ zeroAllSafeAreas() {
2154
+ const style = this.el.style;
2155
+ style.setProperty('--ion-safe-area-top', '0px');
2156
+ style.setProperty('--ion-safe-area-bottom', '0px');
2157
+ style.setProperty('--ion-safe-area-left', '0px');
2158
+ style.setProperty('--ion-safe-area-right', '0px');
2159
+ }
2160
+ /**
2161
+ * Updates safe-area CSS variable overrides based on whether the modal
2162
+ * is touching each edge of the viewport. Called after animation
2163
+ * and during gestures to handle dynamic position changes.
2164
+ */
2165
+ updateSafeAreaOverrides() {
2166
+ if (this.skipSafeAreaCoordinateDetection) {
2167
+ return;
2168
+ }
2169
+ const wrapper = this.wrapperEl;
2170
+ if (!wrapper) {
2171
+ return;
2172
+ }
2173
+ const rect = wrapper.getBoundingClientRect();
2174
+ const threshold = 2;
2175
+ const touchingTop = rect.top <= threshold;
2176
+ const touchingBottom = rect.bottom >= window.innerHeight - threshold;
2177
+ const touchingLeft = rect.left <= threshold;
2178
+ const touchingRight = rect.right >= window.innerWidth - threshold;
2179
+ const style = this.el.style;
2180
+ touchingTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2181
+ touchingBottom
2182
+ ? style.removeProperty('--ion-safe-area-bottom')
2183
+ : style.setProperty('--ion-safe-area-bottom', '0px');
2184
+ touchingLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2185
+ touchingRight ? style.removeProperty('--ion-safe-area-right') : style.setProperty('--ion-safe-area-right', '0px');
2186
+ }
2083
2187
  sheetOnDismiss() {
2084
2188
  /**
2085
2189
  * While the gesture animation is finishing
@@ -2172,6 +2276,8 @@ const Modal = class {
2172
2276
  }
2173
2277
  this.currentBreakpoint = undefined;
2174
2278
  this.animation = undefined;
2279
+ // Reset safe-area detection flag for potential re-presentation
2280
+ this.skipSafeAreaCoordinateDetection = false;
2175
2281
  unlock();
2176
2282
  return dismissed;
2177
2283
  }
@@ -2253,6 +2359,10 @@ const Modal = class {
2253
2359
  this.currentViewIsPortrait = window.innerWidth < 768;
2254
2360
  }
2255
2361
  handleViewTransition() {
2362
+ // Only run view transitions when the modal is presented
2363
+ if (!this.presented) {
2364
+ return;
2365
+ }
2256
2366
  const isPortrait = window.innerWidth < 768;
2257
2367
  // Only transition if view state actually changed
2258
2368
  if (this.currentViewIsPortrait === isPortrait) {
@@ -2417,20 +2527,20 @@ const Modal = class {
2417
2527
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2418
2528
  const isHandleCycle = handleBehavior === 'cycle';
2419
2529
  const isSheetModalWithHandle = isSheetModal && showHandle;
2420
- return (h(Host, Object.assign({ key: '9a75095a13de0cfc96f1fa69fd92777d25da8daa', "no-router": true,
2530
+ return (h(Host, Object.assign({ key: 'b93c9d20985ca4800b34c0165957b12b0a9d6789', "no-router": true,
2421
2531
  // Allow the modal to be navigable when the handle is focusable
2422
2532
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2423
2533
  zIndex: `${20000 + this.overlayIndex}`,
2424
- }, 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: 'd02612d8063ef20f59f173ff47795f71cdaaf63e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '708761b70a93e34c08faae079569f444c7416a4c', class: "modal-shadow" }), h("div", Object.assign({ key: 'a72226ff1a98229f9bfd9207b98fc57e02baa430',
2534
+ }, 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: '732b9355b3c0b051d19e1e0cedd7c49e3ad6fdda', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '874dcbb38da5a9feecafc5d3d1cb9f140f7fe48c', class: "modal-shadow" }), h("div", Object.assign({ key: '0d0c28581d49aeb0f764387a76ad54f68c35a6d5',
2425
2535
  /*
2426
2536
  role and aria-modal must be used on the
2427
2537
  same element. They must also be set inside the
2428
2538
  shadow DOM otherwise ion-button will not be highlighted
2429
2539
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2430
2540
  */
2431
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0547f32323882660221385d84d492929caa77c6b', class: "modal-handle",
2541
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a27a7ecc9857b4dc2f8a6f64bb01487ac578f5aa', class: "modal-handle",
2432
2542
  // Prevents the handle from receiving keyboard focus when it does not cycle
2433
- 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: 'fccbd64518b6fa22f9e874deb6b4ba55d8d89c3b', onSlotchange: this.onSlotChange }))));
2543
+ 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: '2ca0a826798dbc5cfe06e94b6a2d0ef375d018bc', onSlotchange: this.onSlotChange }))));
2434
2544
  }
2435
2545
  get el() { return getElement(this); }
2436
2546
  static get watchers() { return {