@ionic/core 8.7.18-dev.11769790854.11895f8f → 8.7.18-dev.11770316329.126ef4af

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 (38) hide show
  1. package/components/ion-refresher.js +32 -3
  2. package/components/modal.js +12 -220
  3. package/components/popover.js +11 -83
  4. package/dist/cjs/ion-modal.cjs.entry.js +12 -220
  5. package/dist/cjs/ion-popover.cjs.entry.js +11 -83
  6. package/dist/cjs/ion-refresher_2.cjs.entry.js +32 -3
  7. package/dist/collection/components/modal/gestures/sheet.js +1 -3
  8. package/dist/collection/components/modal/gestures/swipe-to-close.js +1 -3
  9. package/dist/collection/components/modal/modal.ios.css +4 -0
  10. package/dist/collection/components/modal/modal.js +7 -215
  11. package/dist/collection/components/modal/modal.md.css +4 -0
  12. package/dist/collection/components/popover/animations/ios.enter.js +5 -21
  13. package/dist/collection/components/popover/animations/md.enter.js +5 -30
  14. package/dist/collection/components/popover/utils.js +1 -32
  15. package/dist/collection/components/refresher/refresher.js +70 -4
  16. package/dist/docs.json +49 -4
  17. package/dist/esm/ion-modal.entry.js +12 -220
  18. package/dist/esm/ion-popover.entry.js +11 -83
  19. package/dist/esm/ion-refresher_2.entry.js +32 -3
  20. package/dist/html.html-data.json +1 -1
  21. package/dist/ionic/ionic.esm.js +1 -1
  22. package/dist/ionic/p-90f2d3ff.entry.js +4 -0
  23. package/dist/ionic/p-91840a80.entry.js +4 -0
  24. package/dist/ionic/p-f9061316.entry.js +4 -0
  25. package/dist/types/components/modal/gestures/sheet.d.ts +1 -1
  26. package/dist/types/components/modal/gestures/swipe-to-close.d.ts +1 -1
  27. package/dist/types/components/modal/modal.d.ts +0 -47
  28. package/dist/types/components/popover/utils.d.ts +0 -2
  29. package/dist/types/components/refresher/refresher-interface.d.ts +7 -0
  30. package/dist/types/components/refresher/refresher.d.ts +16 -3
  31. package/dist/types/components.d.ts +16 -5
  32. package/dist/types/interface.d.ts +1 -1
  33. package/hydrate/index.js +54 -305
  34. package/hydrate/index.mjs +54 -305
  35. package/package.json +2 -4
  36. package/dist/ionic/p-639dd543.entry.js +0 -4
  37. package/dist/ionic/p-ec9ca3fe.entry.js +0 -4
  38. package/dist/ionic/p-ef4256eb.entry.js +0 -4
package/hydrate/index.mjs CHANGED
@@ -21579,7 +21579,7 @@ const calculateSpringStep = (t) => {
21579
21579
  const SwipeToCloseDefaults = {
21580
21580
  MIN_PRESENTING_SCALE: 0.915,
21581
21581
  };
21582
- const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onGestureMove) => {
21582
+ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss) => {
21583
21583
  /**
21584
21584
  * The step value at which a card modal
21585
21585
  * is eligible for dismissing via gesture.
@@ -21736,8 +21736,6 @@ const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismiss, onG
21736
21736
  const processedStep = isAttemptingDismissWithCanDismiss ? calculateSpringStep(step / maxStep) : step;
21737
21737
  const clampedStep = clamp(0.0001, processedStep, maxStep);
21738
21738
  animation.progressStep(clampedStep);
21739
- // Notify modal of position change for safe-area updates
21740
- onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
21741
21739
  /**
21742
21740
  * When swiping down half way, the status bar style
21743
21741
  * should be reset to its default value.
@@ -22281,7 +22279,7 @@ const mdLeaveAnimation$2 = (baseEl, opts) => {
22281
22279
  return baseAnimation;
22282
22280
  };
22283
22281
 
22284
- const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange, onGestureMove) => {
22282
+ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, backdropBreakpoint, animation, breakpoints = [], expandToScroll, getCurrentBreakpoint, onDismiss, onBreakpointChange) => {
22285
22283
  // Defaults for the sheet swipe animation
22286
22284
  const defaultBackdrop = [
22287
22285
  { offset: 0, opacity: 'var(--backdrop-opacity)' },
@@ -22612,8 +22610,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
22612
22610
  : step;
22613
22611
  offset = clamp(0.0001, processedStep, maxStep);
22614
22612
  animation.progressStep(offset);
22615
- // Notify modal of position change for safe-area updates
22616
- onGestureMove === null || onGestureMove === void 0 ? void 0 : onGestureMove();
22617
22613
  };
22618
22614
  const onEnd = (detail) => {
22619
22615
  /**
@@ -22808,9 +22804,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
22808
22804
  };
22809
22805
  };
22810
22806
 
22811
- 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}";
22807
+ 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}";
22812
22808
 
22813
- 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}";
22809
+ 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}";
22814
22810
 
22815
22811
  // TODO(FW-2832): types
22816
22812
  /**
@@ -22843,10 +22839,6 @@ class Modal {
22843
22839
  this.inline = false;
22844
22840
  // Whether or not modal is being dismissed via gesture
22845
22841
  this.gestureAnimationDismissing = false;
22846
- // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
22847
- this.skipSafeAreaCoordinateDetection = false;
22848
- // Track previous safe-area state to avoid redundant DOM writes
22849
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
22850
22842
  this.presented = false;
22851
22843
  /** @internal */
22852
22844
  this.hasController = false;
@@ -23037,10 +23029,7 @@ class Modal {
23037
23029
  }
23038
23030
  }
23039
23031
  onWindowResize() {
23040
- // Invalidate safe-area cache on resize (device rotation may change values)
23041
- this.cachedSafeAreas = undefined;
23042
- this.updateSafeAreaOverrides();
23043
- // Only handle view transition for iOS card modals when no custom animations are provided
23032
+ // Only handle resize for iOS card modals when no custom animations are provided
23044
23033
  if (getIonMode$1(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
23045
23034
  return;
23046
23035
  }
@@ -23063,8 +23052,6 @@ class Modal {
23063
23052
  this.triggerController.removeClickListener();
23064
23053
  this.cleanupViewTransitionListener();
23065
23054
  this.cleanupParentRemovalObserver();
23066
- // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
23067
- this.resetSafeAreaState();
23068
23055
  }
23069
23056
  componentWillLoad() {
23070
23057
  var _a;
@@ -23224,8 +23211,6 @@ class Modal {
23224
23211
  else if (!this.keepContentsMounted) {
23225
23212
  await waitForMount();
23226
23213
  }
23227
- // Predict safe-area needs based on modal configuration to avoid visual snap
23228
- this.setInitialSafeAreaOverrides(presentingElement);
23229
23214
  writeTask(() => this.el.classList.add('show-modal'));
23230
23215
  const hasCardModal = presentingElement !== undefined;
23231
23216
  /**
@@ -23287,8 +23272,6 @@ class Modal {
23287
23272
  else if (hasCardModal) {
23288
23273
  this.initSwipeToClose();
23289
23274
  }
23290
- // Now that animation is complete, update safe-area based on actual position
23291
- this.updateSafeAreaOverrides();
23292
23275
  // Initialize view transition listener for iOS card modals
23293
23276
  this.initViewTransitionListener();
23294
23277
  // Initialize parent removal observer
@@ -23340,7 +23323,7 @@ class Modal {
23340
23323
  await this.dismiss(undefined, GESTURE);
23341
23324
  this.gestureAnimationDismissing = false;
23342
23325
  });
23343
- }, () => this.updateSafeAreaOverrides());
23326
+ });
23344
23327
  this.gesture.enable(true);
23345
23328
  }
23346
23329
  initSheetGesture() {
@@ -23361,8 +23344,7 @@ class Modal {
23361
23344
  this.currentBreakpoint = breakpoint;
23362
23345
  this.ionBreakpointDidChange.emit({ breakpoint });
23363
23346
  }
23364
- this.updateSafeAreaOverrides();
23365
- }, () => this.updateSafeAreaOverrides());
23347
+ });
23366
23348
  this.gesture = gesture;
23367
23349
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
23368
23350
  this.gesture.enable(true);
@@ -23440,194 +23422,6 @@ class Modal {
23440
23422
  // Clear the cached reference
23441
23423
  this.cachedPageParent = undefined;
23442
23424
  }
23443
- /**
23444
- * Sets initial safe-area overrides based on modal configuration before
23445
- * the modal becomes visible. This predicts whether the modal will touch
23446
- * screen edges to avoid a visual snap after animation completes.
23447
- */
23448
- setInitialSafeAreaOverrides(presentingElement) {
23449
- const style = this.el.style;
23450
- const mode = getIonMode$1(this);
23451
- const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
23452
- // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
23453
- const isCardModal = presentingElement !== undefined && mode === 'ios';
23454
- const isTablet = window.innerWidth >= 768;
23455
- // Sheet modals always touch bottom edge, never top/left/right
23456
- if (isSheetModal) {
23457
- style.setProperty('--ion-safe-area-top', '0px');
23458
- style.setProperty('--ion-safe-area-left', '0px');
23459
- style.setProperty('--ion-safe-area-right', '0px');
23460
- return;
23461
- }
23462
- // Card modals have rounded top corners
23463
- if (isCardModal) {
23464
- style.setProperty('--ion-safe-area-top', '0px');
23465
- if (isTablet) {
23466
- // On tablets, card modals are inset from all edges
23467
- this.zeroAllSafeAreas();
23468
- }
23469
- else {
23470
- // On phones, card modals still extend to the bottom edge
23471
- style.setProperty('--ion-safe-area-left', '0px');
23472
- style.setProperty('--ion-safe-area-right', '0px');
23473
- this.applyFullscreenSafeArea();
23474
- }
23475
- return;
23476
- }
23477
- // Check if modal is fullscreen via CSS custom properties
23478
- // This applies to both phone and tablet sizes - custom modals may have
23479
- // non-fullscreen dimensions even on phones (e.g., --height: 70%)
23480
- const computedStyle = getComputedStyle(this.el);
23481
- const width = computedStyle.getPropertyValue('--width').trim();
23482
- const height = computedStyle.getPropertyValue('--height').trim();
23483
- const isFullscreen = width === '100%' && height === '100%';
23484
- if (isFullscreen) {
23485
- this.applyFullscreenSafeArea();
23486
- }
23487
- else if (isTablet) {
23488
- // Centered dialog on tablet doesn't touch edges
23489
- this.zeroAllSafeAreas();
23490
- }
23491
- else ;
23492
- }
23493
- /**
23494
- * Applies safe-area handling for fullscreen modals.
23495
- * Adds wrapper padding when no footer is present to prevent
23496
- * content from overlapping system navigation areas.
23497
- */
23498
- applyFullscreenSafeArea() {
23499
- this.skipSafeAreaCoordinateDetection = true;
23500
- this.updateFooterPadding();
23501
- // Watch for dynamic footer additions/removals (e.g., async data loading)
23502
- // Use subtree:true to support wrapped footers in framework components
23503
- // (e.g., <my-footer><ion-footer>...</ion-footer></my-footer>)
23504
- if (!this.footerObserver && win$1 !== undefined && 'MutationObserver' in win$1) {
23505
- this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
23506
- this.footerObserver.observe(this.el, { childList: true, subtree: true });
23507
- }
23508
- }
23509
- /**
23510
- * Updates wrapper and shadow padding based on footer presence.
23511
- * Called initially and when footer is dynamically added/removed.
23512
- * Both elements must be styled identically to prevent visual mismatches.
23513
- */
23514
- updateFooterPadding() {
23515
- if (!this.wrapperEl)
23516
- return;
23517
- const hasFooter = this.el.querySelector('ion-footer') !== null;
23518
- // Apply to both wrapper and shadow to keep them in sync
23519
- const elements = [this.wrapperEl, this.shadowEl].filter(Boolean);
23520
- if (hasFooter) {
23521
- elements.forEach((el) => {
23522
- el.style.removeProperty('padding-bottom');
23523
- el.style.removeProperty('box-sizing');
23524
- });
23525
- }
23526
- else {
23527
- elements.forEach((el) => {
23528
- el.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
23529
- el.style.setProperty('box-sizing', 'border-box');
23530
- });
23531
- }
23532
- }
23533
- /**
23534
- * Sets all safe-area CSS variables to 0px for modals that
23535
- * don't touch screen edges.
23536
- */
23537
- zeroAllSafeAreas() {
23538
- const style = this.el.style;
23539
- style.setProperty('--ion-safe-area-top', '0px');
23540
- style.setProperty('--ion-safe-area-bottom', '0px');
23541
- style.setProperty('--ion-safe-area-left', '0px');
23542
- style.setProperty('--ion-safe-area-right', '0px');
23543
- }
23544
- /**
23545
- * Resets all safe-area related state and styles.
23546
- * Called during dismiss and disconnectedCallback to ensure clean state
23547
- * for re-presentation of inline modals.
23548
- */
23549
- resetSafeAreaState() {
23550
- var _a;
23551
- this.skipSafeAreaCoordinateDetection = false;
23552
- this.cachedSafeAreas = undefined;
23553
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
23554
- (_a = this.footerObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
23555
- this.footerObserver = undefined;
23556
- // Clear wrapper and shadow styles that may have been set for safe-area handling
23557
- [this.wrapperEl, this.shadowEl].forEach((el) => {
23558
- if (el) {
23559
- el.style.removeProperty('padding-bottom');
23560
- el.style.removeProperty('box-sizing');
23561
- }
23562
- });
23563
- // Clear safe-area CSS variable overrides
23564
- const style = this.el.style;
23565
- style.removeProperty('--ion-safe-area-top');
23566
- style.removeProperty('--ion-safe-area-bottom');
23567
- style.removeProperty('--ion-safe-area-left');
23568
- style.removeProperty('--ion-safe-area-right');
23569
- }
23570
- /**
23571
- * Gets the root safe-area values from the document element.
23572
- * Uses cached values during gestures to avoid getComputedStyle calls.
23573
- */
23574
- getSafeAreaValues() {
23575
- if (!this.cachedSafeAreas) {
23576
- const rootStyle = getComputedStyle(document.documentElement);
23577
- this.cachedSafeAreas = {
23578
- top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
23579
- bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
23580
- left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
23581
- right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
23582
- };
23583
- }
23584
- return this.cachedSafeAreas;
23585
- }
23586
- /**
23587
- * Updates safe-area CSS variable overrides based on whether the modal
23588
- * extends into each safe-area region. Called after animation
23589
- * and during gestures to handle dynamic position changes.
23590
- *
23591
- * Optimized to avoid redundant DOM writes by tracking previous state.
23592
- */
23593
- updateSafeAreaOverrides() {
23594
- if (this.skipSafeAreaCoordinateDetection) {
23595
- return;
23596
- }
23597
- const wrapper = this.wrapperEl;
23598
- if (!wrapper) {
23599
- return;
23600
- }
23601
- const rect = wrapper.getBoundingClientRect();
23602
- const safeAreas = this.getSafeAreaValues();
23603
- const extendsIntoTop = rect.top < safeAreas.top;
23604
- const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
23605
- const extendsIntoLeft = rect.left < safeAreas.left;
23606
- const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
23607
- // Only update DOM when state actually changes
23608
- const prev = this.prevSafeAreaState;
23609
- const style = this.el.style;
23610
- if (extendsIntoTop !== prev.top) {
23611
- extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
23612
- prev.top = extendsIntoTop;
23613
- }
23614
- if (extendsIntoBottom !== prev.bottom) {
23615
- extendsIntoBottom
23616
- ? style.removeProperty('--ion-safe-area-bottom')
23617
- : style.setProperty('--ion-safe-area-bottom', '0px');
23618
- prev.bottom = extendsIntoBottom;
23619
- }
23620
- if (extendsIntoLeft !== prev.left) {
23621
- extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
23622
- prev.left = extendsIntoLeft;
23623
- }
23624
- if (extendsIntoRight !== prev.right) {
23625
- extendsIntoRight
23626
- ? style.removeProperty('--ion-safe-area-right')
23627
- : style.setProperty('--ion-safe-area-right', '0px');
23628
- prev.right = extendsIntoRight;
23629
- }
23630
- }
23631
23425
  sheetOnDismiss() {
23632
23426
  /**
23633
23427
  * While the gesture animation is finishing
@@ -23720,8 +23514,6 @@ class Modal {
23720
23514
  }
23721
23515
  this.currentBreakpoint = undefined;
23722
23516
  this.animation = undefined;
23723
- // Reset safe-area state for potential re-presentation
23724
- this.resetSafeAreaState();
23725
23517
  unlock();
23726
23518
  return dismissed;
23727
23519
  }
@@ -23971,20 +23763,20 @@ class Modal {
23971
23763
  const isCardModal = presentingElement !== undefined && mode === 'ios';
23972
23764
  const isHandleCycle = handleBehavior === 'cycle';
23973
23765
  const isSheetModalWithHandle = isSheetModal && showHandle;
23974
- return (hAsync(Host, Object.assign({ key: '8f42c71ec9c9270b4218ca7eec57ca998871ac07', "no-router": true,
23766
+ return (hAsync(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
23975
23767
  // Allow the modal to be navigable when the handle is focusable
23976
23768
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
23977
23769
  zIndex: `${20000 + this.overlayIndex}`,
23978
- }, 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 }), hAsync("ion-backdrop", { key: '2f7b08b019c66cbfceacafa9a68b91f8da1f4084', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: '6b0d1ae90e8483332f74371be5dcbd0a7b2661ba', class: "modal-shadow", ref: (el) => (this.shadowEl = el) }), hAsync("div", Object.assign({ key: 'c96f671fb25e5de45231ea65ac52500547b2e262',
23770
+ }, 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 }), hAsync("ion-backdrop", { key: 'ee94ff8e09b691dd4ad4e4db1720f06bc3c5a469', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: 'bffd69b4635c22d9f249725bd952c1e93d5615c7', class: "modal-shadow" }), hAsync("div", Object.assign({ key: '1d394d3c68916e464ff1fbf5242419f4a3d3cca1',
23979
23771
  /*
23980
23772
  role and aria-modal must be used on the
23981
23773
  same element. They must also be set inside the
23982
23774
  shadow DOM otherwise ion-button will not be highlighted
23983
23775
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
23984
23776
  */
23985
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: 'e07419b01a588c0ba62ed9607c843cac7bd26337', class: "modal-handle",
23777
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '2dcf58792018e557e0c323baad2d672bc99c0bb1', class: "modal-handle",
23986
23778
  // Prevents the handle from receiving keyboard focus when it does not cycle
23987
- 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) })), hAsync("slot", { key: '65f35079df8eb8b6cd262e3a08297866715ce7ea', onSlotchange: this.onSlotChange }))));
23779
+ 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) })), hAsync("slot", { key: '44164b1e8710c3895400ad9f44ecd99873874ad5', onSlotchange: this.onSlotChange }))));
23988
23780
  }
23989
23781
  get el() { return getElement(this); }
23990
23782
  static get watchers() { return {
@@ -27562,8 +27354,6 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
27562
27354
  let bottom;
27563
27355
  let originX = contentOriginX;
27564
27356
  let originY = contentOriginY;
27565
- let checkSafeAreaTop = false;
27566
- let checkSafeAreaBottom = false;
27567
27357
  let checkSafeAreaLeft = false;
27568
27358
  let checkSafeAreaRight = false;
27569
27359
  const triggerTop = triggerCoordinates
@@ -27608,18 +27398,10 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
27608
27398
  * We chose 12 here so that the popover position looks a bit nicer as
27609
27399
  * it is not right up against the edge of the screen.
27610
27400
  */
27611
- top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
27401
+ top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
27612
27402
  arrowTop = top + contentHeight;
27613
27403
  originY = 'bottom';
27614
27404
  addPopoverBottomClass = true;
27615
- /**
27616
- * If the popover is positioned near the top edge, account for safe area.
27617
- * This ensures the popover doesn't overlap with status bars or notches.
27618
- */
27619
- if (top <= bodyPadding + safeAreaMargin) {
27620
- checkSafeAreaTop = true;
27621
- top = bodyPadding;
27622
- }
27623
27405
  /**
27624
27406
  * If not enough room for popover to appear
27625
27407
  * above trigger, then cut it off.
@@ -27627,35 +27409,14 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
27627
27409
  }
27628
27410
  else {
27629
27411
  bottom = bodyPadding;
27630
- /**
27631
- * When the popover is pinned to the bottom, account for safe area.
27632
- * This ensures the popover doesn't overlap with home indicators
27633
- * or navigation bars (e.g., Android API 36+ edge-to-edge).
27634
- */
27635
- checkSafeAreaBottom = true;
27636
27412
  }
27637
27413
  }
27638
- /**
27639
- * Final check: If the popover extends into any safe-area region,
27640
- * ensure the corresponding flag is set regardless of side.
27641
- * This handles cases where a side-positioned popover (left/right)
27642
- * still needs bottom safe-area padding because it extends into that region.
27643
- */
27644
- const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
27645
- if (popoverBottom + safeAreaMargin > bodyHeight) {
27646
- checkSafeAreaBottom = true;
27647
- }
27648
- if (top < safeAreaMargin) {
27649
- checkSafeAreaTop = true;
27650
- }
27651
27414
  return {
27652
27415
  top,
27653
27416
  left,
27654
27417
  bottom,
27655
27418
  originX,
27656
27419
  originY,
27657
- checkSafeAreaTop,
27658
- checkSafeAreaBottom,
27659
27420
  checkSafeAreaLeft,
27660
27421
  checkSafeAreaRight,
27661
27422
  arrowTop,
@@ -27716,7 +27477,7 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
27716
27477
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
27717
27478
  const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
27718
27479
  const margin = size === 'cover' ? 0 : 25;
27719
- 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);
27480
+ 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);
27720
27481
  const baseAnimation = createAnimation();
27721
27482
  const backdropAnimation = createAnimation();
27722
27483
  const contentAnimation = createAnimation();
@@ -27746,35 +27507,19 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
27746
27507
  if (addPopoverBottomClass) {
27747
27508
  baseEl.classList.add('popover-bottom');
27748
27509
  }
27749
- /**
27750
- * Safe area CSS variable adjustments.
27751
- * When the popover is positioned near an edge, we add the corresponding
27752
- * safe-area inset to ensure the popover doesn't overlap with system UI
27753
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
27754
- */
27755
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
27756
- const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
27510
+ if (bottom !== undefined) {
27511
+ contentEl.style.setProperty('bottom', `${bottom}px`);
27512
+ }
27757
27513
  const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
27758
27514
  const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
27759
- let topValue = `${top}px`;
27760
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
27761
27515
  let leftValue = `${left}px`;
27762
- if (checkSafeAreaTop) {
27763
- topValue = `${top}px${safeAreaTop}`;
27764
- }
27765
- if (checkSafeAreaBottom && bottomValue !== undefined) {
27766
- bottomValue = `${bottom}px${safeAreaBottom}`;
27767
- }
27768
27516
  if (checkSafeAreaLeft) {
27769
27517
  leftValue = `${left}px${safeAreaLeft}`;
27770
27518
  }
27771
27519
  if (checkSafeAreaRight) {
27772
27520
  leftValue = `${left}px${safeAreaRight}`;
27773
27521
  }
27774
- if (bottomValue !== undefined) {
27775
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
27776
- }
27777
- contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
27522
+ contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
27778
27523
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
27779
27524
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
27780
27525
  if (arrowEl !== null) {
@@ -27850,32 +27595,7 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
27850
27595
  };
27851
27596
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
27852
27597
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
27853
- 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);
27854
- /**
27855
- * Safe area CSS variable adjustments.
27856
- * When the popover is positioned near an edge, we add the corresponding
27857
- * safe-area inset to ensure the popover doesn't overlap with system UI
27858
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
27859
- */
27860
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
27861
- const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
27862
- const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
27863
- const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
27864
- let topValue = `${top}px`;
27865
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
27866
- let leftValue = `${left}px`;
27867
- if (checkSafeAreaTop) {
27868
- topValue = `${top}px${safeAreaTop}`;
27869
- }
27870
- if (checkSafeAreaBottom && bottomValue !== undefined) {
27871
- bottomValue = `${bottom}px${safeAreaBottom}`;
27872
- }
27873
- if (checkSafeAreaLeft) {
27874
- leftValue = `${left}px${safeAreaLeft}`;
27875
- }
27876
- if (checkSafeAreaRight) {
27877
- leftValue = `${left}px${safeAreaRight}`;
27878
- }
27598
+ const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
27879
27599
  const baseAnimation = createAnimation();
27880
27600
  const backdropAnimation = createAnimation();
27881
27601
  const wrapperAnimation = createAnimation();
@@ -27892,13 +27612,13 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
27892
27612
  contentAnimation
27893
27613
  .addElement(contentEl)
27894
27614
  .beforeStyles({
27895
- top: `calc(${topValue} + var(--offset-y, 0px))`,
27896
- left: `calc(${leftValue} + var(--offset-x, 0px))`,
27615
+ top: `calc(${top}px + var(--offset-y, 0px))`,
27616
+ left: `calc(${left}px + var(--offset-x, 0px))`,
27897
27617
  'transform-origin': `${originY} ${originX}`,
27898
27618
  })
27899
27619
  .beforeAddWrite(() => {
27900
- if (bottomValue !== undefined) {
27901
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
27620
+ if (bottom !== undefined) {
27621
+ contentEl.style.setProperty('bottom', `${bottom}px`);
27902
27622
  }
27903
27623
  })
27904
27624
  .fromTo('transform', 'scale(0.8)', 'scale(1)');
@@ -30015,6 +29735,8 @@ class Refresher {
30015
29735
  this.ionRefresh = createEvent(this, "ionRefresh", 7);
30016
29736
  this.ionPull = createEvent(this, "ionPull", 7);
30017
29737
  this.ionStart = createEvent(this, "ionStart", 7);
29738
+ this.ionPullStart = createEvent(this, "ionPullStart", 7);
29739
+ this.ionPullEnd = createEvent(this, "ionPullEnd", 7);
30018
29740
  this.appliedStyles = false;
30019
29741
  this.didStart = false;
30020
29742
  this.progress = 0;
@@ -30069,8 +29791,8 @@ class Refresher {
30069
29791
  * than `1`. The default value is `1` which is equal to the speed of the cursor.
30070
29792
  * If a negative value is passed in, the factor will be `1` instead.
30071
29793
  *
30072
- * For example: If the value passed is `1.2` and the content is dragged by
30073
- * `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
29794
+ * For example, If the value passed is `1.2` and the content is dragged by
29795
+ * `10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels
30074
29796
  * (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
30075
29797
  * will be `8` pixels, less than the amount the cursor has moved.
30076
29798
  *
@@ -30120,6 +29842,9 @@ class Refresher {
30120
29842
  this.animations = [];
30121
29843
  this.progress = 0;
30122
29844
  this.state = 1 /* RefresherState.Inactive */;
29845
+ this.ionPullEnd.emit({
29846
+ reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
29847
+ });
30123
29848
  }
30124
29849
  async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
30125
29850
  this.elementToTransform = this.scrollEl;
@@ -30152,6 +29877,7 @@ class Refresher {
30152
29877
  if (!this.didStart) {
30153
29878
  this.didStart = true;
30154
29879
  this.ionStart.emit();
29880
+ this.ionPullStart.emit();
30155
29881
  }
30156
29882
  // emit "pulling" on every move
30157
29883
  if (this.pointerDown) {
@@ -30227,6 +29953,7 @@ class Refresher {
30227
29953
  this.lastVelocityY = ev.velocityY;
30228
29954
  },
30229
29955
  onEnd: () => {
29956
+ const hadStarted = this.didStart;
30230
29957
  this.pointerDown = false;
30231
29958
  this.didStart = false;
30232
29959
  if (this.needsCompletion) {
@@ -30236,6 +29963,13 @@ class Refresher {
30236
29963
  else if (this.didRefresh) {
30237
29964
  readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
30238
29965
  }
29966
+ else if (hadStarted) {
29967
+ /**
29968
+ * User started pulling but released before reaching the refresh threshold.
29969
+ * Emit ionPullEnd to complete the event pair.
29970
+ */
29971
+ this.ionPullEnd.emit({ reason: 'cancel' });
29972
+ }
30239
29973
  },
30240
29974
  });
30241
29975
  this.disabledChanged();
@@ -30282,6 +30016,7 @@ class Refresher {
30282
30016
  ev.data.animation = animation;
30283
30017
  animation.progressStart(false, 0);
30284
30018
  this.ionStart.emit();
30019
+ this.ionPullStart.emit();
30285
30020
  this.animations.push(animation);
30286
30021
  return;
30287
30022
  }
@@ -30304,6 +30039,7 @@ class Refresher {
30304
30039
  this.animations = [];
30305
30040
  this.gesture.enable(true);
30306
30041
  this.state = 1 /* RefresherState.Inactive */;
30042
+ this.ionPullEnd.emit({ reason: 'cancel' });
30307
30043
  });
30308
30044
  return;
30309
30045
  }
@@ -30548,6 +30284,7 @@ class Refresher {
30548
30284
  if (!this.didStart) {
30549
30285
  this.didStart = true;
30550
30286
  this.ionStart.emit();
30287
+ this.ionPullStart.emit();
30551
30288
  }
30552
30289
  // emit "pulling" on every move
30553
30290
  this.ionPull.emit();
@@ -30591,6 +30328,15 @@ class Refresher {
30591
30328
  * available right away.
30592
30329
  */
30593
30330
  this.restoreOverflowStyle();
30331
+ /**
30332
+ * If ionPullStart was emitted, we need to emit ionPullEnd
30333
+ * even though the gesture was aborted before reaching the
30334
+ * pulling threshold.
30335
+ */
30336
+ if (this.didStart) {
30337
+ this.didStart = false;
30338
+ this.ionPullEnd.emit({ reason: 'cancel' });
30339
+ }
30594
30340
  }
30595
30341
  }
30596
30342
  beginRefresh() {
@@ -30637,6 +30383,9 @@ class Refresher {
30637
30383
  if (this.contentFullscreen && this.backgroundContentEl) {
30638
30384
  (_a = this.backgroundContentEl) === null || _a === void 0 ? void 0 : _a.style.removeProperty('--offset-top');
30639
30385
  }
30386
+ this.ionPullEnd.emit({
30387
+ reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
30388
+ });
30640
30389
  }, 600);
30641
30390
  // reset the styles on the scroll element
30642
30391
  // set that the refresh is actively cancelling/completing
@@ -30690,7 +30439,7 @@ class Refresher {
30690
30439
  }
30691
30440
  render() {
30692
30441
  const mode = getIonMode$1(this);
30693
- return (hAsync(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
30442
+ return (hAsync(Host, { key: 'bed0c2f44c32ecac24b7d8326ac7c7ef1d12c44f', slot: "fixed", class: {
30694
30443
  [mode]: true,
30695
30444
  // Used internally for styling
30696
30445
  [`refresher-${mode}`]: true,