@ionic/core 8.7.17-dev.11767895575.16ea7cef → 8.7.17-dev.11767897190.1ef0f479

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 +96 -8
  2. package/components/ion-tab-bar.js +3 -23
  3. package/components/modal.js +213 -12
  4. package/components/popover.js +83 -11
  5. package/dist/cjs/ion-app_8.cjs.entry.js +107 -20
  6. package/dist/cjs/ion-modal.cjs.entry.js +213 -12
  7. package/dist/cjs/ion-popover.cjs.entry.js +83 -11
  8. package/dist/cjs/ion-tab-bar_2.cjs.entry.js +3 -23
  9. package/dist/collection/components/content/content.css +10 -0
  10. package/dist/collection/components/content/content.js +94 -6
  11. package/dist/collection/components/modal/gestures/sheet.js +3 -1
  12. package/dist/collection/components/modal/gestures/swipe-to-close.js +3 -1
  13. package/dist/collection/components/modal/modal.ios.css +0 -4
  14. package/dist/collection/components/modal/modal.js +205 -7
  15. package/dist/collection/components/modal/modal.md.css +0 -4
  16. package/dist/collection/components/popover/animations/ios.enter.js +21 -5
  17. package/dist/collection/components/popover/animations/md.enter.js +30 -5
  18. package/dist/collection/components/popover/utils.js +32 -1
  19. package/dist/collection/components/tab-bar/tab-bar.js +3 -23
  20. package/dist/docs.json +1 -1
  21. package/dist/esm/ion-app_8.entry.js +96 -9
  22. package/dist/esm/ion-modal.entry.js +213 -12
  23. package/dist/esm/ion-popover.entry.js +83 -11
  24. package/dist/esm/ion-tab-bar_2.entry.js +3 -23
  25. package/dist/ionic/ionic.esm.js +1 -1
  26. package/dist/ionic/p-7268efa5.entry.js +4 -0
  27. package/dist/ionic/p-968a55d1.entry.js +4 -0
  28. package/dist/ionic/p-d9fd799f.entry.js +4 -0
  29. package/dist/ionic/p-ec9ca3fe.entry.js +4 -0
  30. package/dist/types/components/content/content.d.ts +24 -0
  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 +45 -0
  34. package/dist/types/components/popover/utils.d.ts +2 -0
  35. package/dist/types/components/tab-bar/tab-bar.d.ts +0 -1
  36. package/hydrate/index.js +385 -52
  37. package/hydrate/index.mjs +385 -52
  38. package/package.json +1 -1
  39. package/dist/ionic/p-172a579f.entry.js +0 -4
  40. package/dist/ionic/p-732b2fd6.entry.js +0 -4
  41. package/dist/ionic/p-91840a80.entry.js +0 -4
  42. package/dist/ionic/p-f9061316.entry.js +0 -4
@@ -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';
@@ -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,10 @@ 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;
1506
+ // Track previous safe-area state to avoid redundant DOM writes
1507
+ this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
1500
1508
  this.presented = false;
1501
1509
  /** @internal */
1502
1510
  this.hasController = false;
@@ -1687,7 +1695,10 @@ const Modal = class {
1687
1695
  }
1688
1696
  }
1689
1697
  onWindowResize() {
1690
- // Only handle resize for iOS card modals when no custom animations are provided
1698
+ // Invalidate safe-area cache on resize (device rotation may change values)
1699
+ this.cachedSafeAreas = undefined;
1700
+ this.updateSafeAreaOverrides();
1701
+ // Only handle view transition for iOS card modals when no custom animations are provided
1691
1702
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
1692
1703
  return;
1693
1704
  }
@@ -1710,6 +1721,8 @@ const Modal = class {
1710
1721
  this.triggerController.removeClickListener();
1711
1722
  this.cleanupViewTransitionListener();
1712
1723
  this.cleanupParentRemovalObserver();
1724
+ // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
1725
+ this.resetSafeAreaState();
1713
1726
  }
1714
1727
  componentWillLoad() {
1715
1728
  var _a;
@@ -1869,6 +1882,8 @@ const Modal = class {
1869
1882
  else if (!this.keepContentsMounted) {
1870
1883
  await waitForMount();
1871
1884
  }
1885
+ // Predict safe-area needs based on modal configuration to avoid visual snap
1886
+ this.setInitialSafeAreaOverrides(presentingElement);
1872
1887
  writeTask(() => this.el.classList.add('show-modal'));
1873
1888
  const hasCardModal = presentingElement !== undefined;
1874
1889
  /**
@@ -1930,6 +1945,8 @@ const Modal = class {
1930
1945
  else if (hasCardModal) {
1931
1946
  this.initSwipeToClose();
1932
1947
  }
1948
+ // Now that animation is complete, update safe-area based on actual position
1949
+ this.updateSafeAreaOverrides();
1933
1950
  // Initialize view transition listener for iOS card modals
1934
1951
  this.initViewTransitionListener();
1935
1952
  // Initialize parent removal observer
@@ -1981,7 +1998,7 @@ const Modal = class {
1981
1998
  await this.dismiss(undefined, GESTURE);
1982
1999
  this.gestureAnimationDismissing = false;
1983
2000
  });
1984
- });
2001
+ }, () => this.updateSafeAreaOverrides());
1985
2002
  this.gesture.enable(true);
1986
2003
  }
1987
2004
  initSheetGesture() {
@@ -2002,7 +2019,8 @@ const Modal = class {
2002
2019
  this.currentBreakpoint = breakpoint;
2003
2020
  this.ionBreakpointDidChange.emit({ breakpoint });
2004
2021
  }
2005
- });
2022
+ this.updateSafeAreaOverrides();
2023
+ }, () => this.updateSafeAreaOverrides());
2006
2024
  this.gesture = gesture;
2007
2025
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
2008
2026
  this.gesture.enable(true);
@@ -2080,6 +2098,187 @@ const Modal = class {
2080
2098
  // Clear the cached reference
2081
2099
  this.cachedPageParent = undefined;
2082
2100
  }
2101
+ /**
2102
+ * Sets initial safe-area overrides based on modal configuration before
2103
+ * the modal becomes visible. This predicts whether the modal will touch
2104
+ * screen edges to avoid a visual snap after animation completes.
2105
+ */
2106
+ setInitialSafeAreaOverrides(presentingElement) {
2107
+ const style = this.el.style;
2108
+ const mode = getIonMode(this);
2109
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
2110
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
2111
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
2112
+ const isTablet = window.innerWidth >= 768;
2113
+ // Sheet modals always touch bottom edge, never top/left/right
2114
+ if (isSheetModal) {
2115
+ style.setProperty('--ion-safe-area-top', '0px');
2116
+ style.setProperty('--ion-safe-area-left', '0px');
2117
+ style.setProperty('--ion-safe-area-right', '0px');
2118
+ return;
2119
+ }
2120
+ // Card modals have rounded top corners
2121
+ if (isCardModal) {
2122
+ style.setProperty('--ion-safe-area-top', '0px');
2123
+ if (isTablet) {
2124
+ // On tablets, card modals are inset from all edges
2125
+ this.zeroAllSafeAreas();
2126
+ }
2127
+ else {
2128
+ // On phones, card modals still extend to the bottom edge
2129
+ style.setProperty('--ion-safe-area-left', '0px');
2130
+ style.setProperty('--ion-safe-area-right', '0px');
2131
+ this.applyFullscreenSafeArea();
2132
+ }
2133
+ return;
2134
+ }
2135
+ // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
2136
+ if (!isTablet) {
2137
+ this.applyFullscreenSafeArea();
2138
+ return;
2139
+ }
2140
+ // Check if tablet modal is fullscreen via CSS custom properties
2141
+ const computedStyle = getComputedStyle(this.el);
2142
+ const width = computedStyle.getPropertyValue('--width').trim();
2143
+ const height = computedStyle.getPropertyValue('--height').trim();
2144
+ const isFullscreen = width === '100%' && height === '100%';
2145
+ if (isFullscreen) {
2146
+ this.applyFullscreenSafeArea();
2147
+ }
2148
+ else {
2149
+ // Centered dialog doesn't touch edges
2150
+ this.zeroAllSafeAreas();
2151
+ }
2152
+ }
2153
+ /**
2154
+ * Applies safe-area handling for fullscreen modals.
2155
+ * Adds wrapper padding when no footer is present to prevent
2156
+ * content from overlapping system navigation areas.
2157
+ */
2158
+ applyFullscreenSafeArea() {
2159
+ this.skipSafeAreaCoordinateDetection = true;
2160
+ this.updateFooterPadding();
2161
+ // Watch for dynamic footer additions/removals (e.g., async data loading)
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) {
2165
+ this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
2166
+ this.footerObserver.observe(this.el, { childList: true, subtree: true });
2167
+ }
2168
+ }
2169
+ /**
2170
+ * Updates wrapper padding based on footer presence.
2171
+ * Called initially and when footer is dynamically added/removed.
2172
+ */
2173
+ updateFooterPadding() {
2174
+ if (!this.wrapperEl)
2175
+ return;
2176
+ const hasFooter = this.el.querySelector('ion-footer') !== null;
2177
+ if (hasFooter) {
2178
+ this.wrapperEl.style.removeProperty('padding-bottom');
2179
+ this.wrapperEl.style.removeProperty('box-sizing');
2180
+ }
2181
+ else {
2182
+ this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
2183
+ this.wrapperEl.style.setProperty('box-sizing', 'border-box');
2184
+ }
2185
+ }
2186
+ /**
2187
+ * Sets all safe-area CSS variables to 0px for modals that
2188
+ * don't touch screen edges.
2189
+ */
2190
+ zeroAllSafeAreas() {
2191
+ const style = this.el.style;
2192
+ style.setProperty('--ion-safe-area-top', '0px');
2193
+ style.setProperty('--ion-safe-area-bottom', '0px');
2194
+ style.setProperty('--ion-safe-area-left', '0px');
2195
+ style.setProperty('--ion-safe-area-right', '0px');
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
+ }
2221
+ /**
2222
+ * Gets the root safe-area values from the document element.
2223
+ * Uses cached values during gestures to avoid getComputedStyle calls.
2224
+ */
2225
+ getSafeAreaValues() {
2226
+ if (!this.cachedSafeAreas) {
2227
+ const rootStyle = getComputedStyle(document.documentElement);
2228
+ this.cachedSafeAreas = {
2229
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
2230
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
2231
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
2232
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
2233
+ };
2234
+ }
2235
+ return this.cachedSafeAreas;
2236
+ }
2237
+ /**
2238
+ * Updates safe-area CSS variable overrides based on whether the modal
2239
+ * extends into each safe-area region. Called after animation
2240
+ * and during gestures to handle dynamic position changes.
2241
+ *
2242
+ * Optimized to avoid redundant DOM writes by tracking previous state.
2243
+ */
2244
+ updateSafeAreaOverrides() {
2245
+ if (this.skipSafeAreaCoordinateDetection) {
2246
+ return;
2247
+ }
2248
+ const wrapper = this.wrapperEl;
2249
+ if (!wrapper) {
2250
+ return;
2251
+ }
2252
+ const rect = wrapper.getBoundingClientRect();
2253
+ const safeAreas = this.getSafeAreaValues();
2254
+ const extendsIntoTop = rect.top < safeAreas.top;
2255
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
2256
+ const extendsIntoLeft = rect.left < safeAreas.left;
2257
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
2258
+ // Only update DOM when state actually changes
2259
+ const prev = this.prevSafeAreaState;
2260
+ const style = this.el.style;
2261
+ if (extendsIntoTop !== prev.top) {
2262
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
2263
+ prev.top = extendsIntoTop;
2264
+ }
2265
+ if (extendsIntoBottom !== prev.bottom) {
2266
+ extendsIntoBottom
2267
+ ? style.removeProperty('--ion-safe-area-bottom')
2268
+ : style.setProperty('--ion-safe-area-bottom', '0px');
2269
+ prev.bottom = extendsIntoBottom;
2270
+ }
2271
+ if (extendsIntoLeft !== prev.left) {
2272
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
2273
+ prev.left = extendsIntoLeft;
2274
+ }
2275
+ if (extendsIntoRight !== prev.right) {
2276
+ extendsIntoRight
2277
+ ? style.removeProperty('--ion-safe-area-right')
2278
+ : style.setProperty('--ion-safe-area-right', '0px');
2279
+ prev.right = extendsIntoRight;
2280
+ }
2281
+ }
2083
2282
  sheetOnDismiss() {
2084
2283
  /**
2085
2284
  * While the gesture animation is finishing
@@ -2172,6 +2371,8 @@ const Modal = class {
2172
2371
  }
2173
2372
  this.currentBreakpoint = undefined;
2174
2373
  this.animation = undefined;
2374
+ // Reset safe-area state for potential re-presentation
2375
+ this.resetSafeAreaState();
2175
2376
  unlock();
2176
2377
  return dismissed;
2177
2378
  }
@@ -2421,20 +2622,20 @@ const Modal = class {
2421
2622
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2422
2623
  const isHandleCycle = handleBehavior === 'cycle';
2423
2624
  const isSheetModalWithHandle = isSheetModal && showHandle;
2424
- return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
2625
+ return (h(Host, Object.assign({ key: '44022099fcaf047b97d1c2cb45b9b51c930e707c', "no-router": true,
2425
2626
  // Allow the modal to be navigable when the handle is focusable
2426
2627
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2427
2628
  zIndex: `${20000 + this.overlayIndex}`,
2428
- }, 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',
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',
2429
2630
  /*
2430
2631
  role and aria-modal must be used on the
2431
2632
  same element. They must also be set inside the
2432
2633
  shadow DOM otherwise ion-button will not be highlighted
2433
2634
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2434
2635
  */
2435
- 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",
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",
2436
2637
  // Prevents the handle from receiving keyboard focus when it does not cycle
2437
- 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 }))));
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 }))));
2438
2639
  }
2439
2640
  get el() { return getElement(this); }
2440
2641
  static get watchers() { return {
@@ -658,6 +658,8 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
658
658
  let bottom;
659
659
  let originX = contentOriginX;
660
660
  let originY = contentOriginY;
661
+ let checkSafeAreaTop = false;
662
+ let checkSafeAreaBottom = false;
661
663
  let checkSafeAreaLeft = false;
662
664
  let checkSafeAreaRight = false;
663
665
  const triggerTop = triggerCoordinates
@@ -702,10 +704,18 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
702
704
  * We chose 12 here so that the popover position looks a bit nicer as
703
705
  * it is not right up against the edge of the screen.
704
706
  */
705
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
707
+ top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
706
708
  arrowTop = top + contentHeight;
707
709
  originY = 'bottom';
708
710
  addPopoverBottomClass = true;
711
+ /**
712
+ * If the popover is positioned near the top edge, account for safe area.
713
+ * This ensures the popover doesn't overlap with status bars or notches.
714
+ */
715
+ if (top <= bodyPadding + safeAreaMargin) {
716
+ checkSafeAreaTop = true;
717
+ top = bodyPadding;
718
+ }
709
719
  /**
710
720
  * If not enough room for popover to appear
711
721
  * above trigger, then cut it off.
@@ -713,14 +723,35 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
713
723
  }
714
724
  else {
715
725
  bottom = bodyPadding;
726
+ /**
727
+ * When the popover is pinned to the bottom, account for safe area.
728
+ * This ensures the popover doesn't overlap with home indicators
729
+ * or navigation bars (e.g., Android API 36+ edge-to-edge).
730
+ */
731
+ checkSafeAreaBottom = true;
716
732
  }
717
733
  }
734
+ /**
735
+ * Final check: If the popover extends into any safe-area region,
736
+ * ensure the corresponding flag is set regardless of side.
737
+ * This handles cases where a side-positioned popover (left/right)
738
+ * still needs bottom safe-area padding because it extends into that region.
739
+ */
740
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
741
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
742
+ checkSafeAreaBottom = true;
743
+ }
744
+ if (top < safeAreaMargin) {
745
+ checkSafeAreaTop = true;
746
+ }
718
747
  return {
719
748
  top,
720
749
  left,
721
750
  bottom,
722
751
  originX,
723
752
  originY,
753
+ checkSafeAreaTop,
754
+ checkSafeAreaBottom,
724
755
  checkSafeAreaLeft,
725
756
  checkSafeAreaRight,
726
757
  arrowTop,
@@ -781,7 +812,7 @@ const iosEnterAnimation = (baseEl, opts) => {
781
812
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
782
813
  const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
783
814
  const margin = size === 'cover' ? 0 : 25;
784
- 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);
815
+ 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);
785
816
  const baseAnimation = createAnimation();
786
817
  const backdropAnimation = createAnimation();
787
818
  const contentAnimation = createAnimation();
@@ -811,19 +842,35 @@ const iosEnterAnimation = (baseEl, opts) => {
811
842
  if (addPopoverBottomClass) {
812
843
  baseEl.classList.add('popover-bottom');
813
844
  }
814
- if (bottom !== undefined) {
815
- contentEl.style.setProperty('bottom', `${bottom}px`);
816
- }
845
+ /**
846
+ * Safe area CSS variable adjustments.
847
+ * When the popover is positioned near an edge, we add the corresponding
848
+ * safe-area inset to ensure the popover doesn't overlap with system UI
849
+ * (status bars, home indicators, navigation bars on Android API 36+, etc.)
850
+ */
851
+ const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
852
+ const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
817
853
  const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
818
854
  const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
855
+ let topValue = `${top}px`;
856
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
819
857
  let leftValue = `${left}px`;
858
+ if (checkSafeAreaTop) {
859
+ topValue = `${top}px${safeAreaTop}`;
860
+ }
861
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
862
+ bottomValue = `${bottom}px${safeAreaBottom}`;
863
+ }
820
864
  if (checkSafeAreaLeft) {
821
865
  leftValue = `${left}px${safeAreaLeft}`;
822
866
  }
823
867
  if (checkSafeAreaRight) {
824
868
  leftValue = `${left}px${safeAreaRight}`;
825
869
  }
826
- contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
870
+ if (bottomValue !== undefined) {
871
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
872
+ }
873
+ contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
827
874
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
828
875
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
829
876
  if (arrowEl !== null) {
@@ -899,7 +946,32 @@ const mdEnterAnimation = (baseEl, opts) => {
899
946
  };
900
947
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
901
948
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
902
- const { originX, originY, top, left, bottom } = 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
+ /**
951
+ * Safe area CSS variable adjustments.
952
+ * When the popover is positioned near an edge, we add the corresponding
953
+ * safe-area inset to ensure the popover doesn't overlap with system UI
954
+ * (status bars, home indicators, navigation bars on Android API 36+, etc.)
955
+ */
956
+ const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
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)';
960
+ let topValue = `${top}px`;
961
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
962
+ let leftValue = `${left}px`;
963
+ if (checkSafeAreaTop) {
964
+ topValue = `${top}px${safeAreaTop}`;
965
+ }
966
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
967
+ bottomValue = `${bottom}px${safeAreaBottom}`;
968
+ }
969
+ if (checkSafeAreaLeft) {
970
+ leftValue = `${left}px${safeAreaLeft}`;
971
+ }
972
+ if (checkSafeAreaRight) {
973
+ leftValue = `${left}px${safeAreaRight}`;
974
+ }
903
975
  const baseAnimation = createAnimation();
904
976
  const backdropAnimation = createAnimation();
905
977
  const wrapperAnimation = createAnimation();
@@ -916,13 +988,13 @@ const mdEnterAnimation = (baseEl, opts) => {
916
988
  contentAnimation
917
989
  .addElement(contentEl)
918
990
  .beforeStyles({
919
- top: `calc(${top}px + var(--offset-y, 0px))`,
920
- left: `calc(${left}px + var(--offset-x, 0px))`,
991
+ top: `calc(${topValue} + var(--offset-y, 0px))`,
992
+ left: `calc(${leftValue} + var(--offset-x, 0px))`,
921
993
  'transform-origin': `${originY} ${originX}`,
922
994
  })
923
995
  .beforeAddWrite(() => {
924
- if (bottom !== undefined) {
925
- contentEl.style.setProperty('bottom', `${bottom}px`);
996
+ if (bottomValue !== undefined) {
997
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
926
998
  }
927
999
  })
928
1000
  .fromTo('transform', 'scale(0.8)', 'scale(1)');
@@ -20,7 +20,6 @@ const TabBar = class {
20
20
  this.ionTabBarChanged = createEvent(this, "ionTabBarChanged", 7);
21
21
  this.ionTabBarLoaded = createEvent(this, "ionTabBarLoaded", 7);
22
22
  this.keyboardCtrl = null;
23
- this.keyboardCtrlPromise = null;
24
23
  this.didLoad = false;
25
24
  this.keyboardVisible = false;
26
25
  /**
@@ -56,7 +55,7 @@ const TabBar = class {
56
55
  }
57
56
  }
58
57
  async connectedCallback() {
59
- const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
58
+ this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
60
59
  /**
61
60
  * If the keyboard is hiding, then we need to wait
62
61
  * for the webview to resize. Otherwise, the tab bar
@@ -67,40 +66,21 @@ const TabBar = class {
67
66
  }
68
67
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
69
68
  });
70
- this.keyboardCtrlPromise = promise;
71
- const keyboardCtrl = await promise;
72
- /**
73
- * Only assign if this is still the current promise.
74
- * Otherwise, a new connectedCallback has started or
75
- * disconnectedCallback was called, so destroy this instance.
76
- */
77
- if (this.keyboardCtrlPromise === promise) {
78
- this.keyboardCtrl = keyboardCtrl;
79
- this.keyboardCtrlPromise = null;
80
- }
81
- else {
82
- keyboardCtrl.destroy();
83
- }
84
69
  }
85
70
  disconnectedCallback() {
86
- if (this.keyboardCtrlPromise) {
87
- this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
88
- this.keyboardCtrlPromise = null;
89
- }
90
71
  if (this.keyboardCtrl) {
91
72
  this.keyboardCtrl.destroy();
92
- this.keyboardCtrl = null;
93
73
  }
94
74
  }
95
75
  render() {
96
76
  const { color, translucent, keyboardVisible } = this;
97
77
  const mode = getIonMode(this);
98
78
  const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
99
- return (h(Host, { key: '9daf4e2acaff6e3ce3878cf9dd5109fb1afbbebe', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
79
+ return (h(Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
100
80
  [mode]: true,
101
81
  'tab-bar-translucent': translucent,
102
82
  'tab-bar-hidden': shouldHide,
103
- }) }, h("slot", { key: '1d15aa2da8501e8e7eff11ad4a491478be845c43' })));
83
+ }) }, h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
104
84
  }
105
85
  get el() { return getElement(this); }
106
86
  static get watchers() { return {