@ionic/core 8.7.18-dev.11770318664.15f47a5e → 8.7.18-dev.11770916502.174efd7f

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 (36) hide show
  1. package/components/ion-refresher.js +3 -32
  2. package/components/modal.js +326 -17
  3. package/components/popover.js +152 -30
  4. package/dist/cjs/ion-modal.cjs.entry.js +326 -17
  5. package/dist/cjs/ion-popover.cjs.entry.js +152 -30
  6. package/dist/cjs/ion-refresher_2.cjs.entry.js +3 -32
  7. package/dist/collection/components/modal/modal.ios.css +6 -5
  8. package/dist/collection/components/modal/modal.js +164 -15
  9. package/dist/collection/components/modal/modal.md.css +6 -5
  10. package/dist/collection/components/modal/safe-area-utils.js +165 -0
  11. package/dist/collection/components/popover/animations/ios.enter.js +33 -8
  12. package/dist/collection/components/popover/animations/md.enter.js +26 -6
  13. package/dist/collection/components/popover/utils.js +96 -19
  14. package/dist/collection/components/refresher/refresher.js +4 -70
  15. package/dist/docs.json +4 -49
  16. package/dist/esm/ion-modal.entry.js +326 -17
  17. package/dist/esm/ion-popover.entry.js +152 -30
  18. package/dist/esm/ion-refresher_2.entry.js +3 -32
  19. package/dist/html.html-data.json +1 -1
  20. package/dist/ionic/ionic.esm.js +1 -1
  21. package/dist/ionic/p-3e69999d.entry.js +4 -0
  22. package/dist/ionic/p-639dd543.entry.js +4 -0
  23. package/dist/ionic/p-cc3446d5.entry.js +4 -0
  24. package/dist/types/components/modal/modal.d.ts +35 -0
  25. package/dist/types/components/modal/safe-area-utils.d.ts +66 -0
  26. package/dist/types/components/popover/utils.d.ts +23 -1
  27. package/dist/types/components/refresher/refresher-interface.d.ts +0 -7
  28. package/dist/types/components/refresher/refresher.d.ts +3 -16
  29. package/dist/types/components.d.ts +5 -16
  30. package/dist/types/interface.d.ts +1 -1
  31. package/hydrate/index.js +481 -79
  32. package/hydrate/index.mjs +481 -79
  33. package/package.json +1 -1
  34. package/dist/ionic/p-90f2d3ff.entry.js +0 -4
  35. package/dist/ionic/p-91840a80.entry.js +0 -4
  36. package/dist/ionic/p-f9061316.entry.js +0 -4
@@ -16,6 +16,58 @@ require('./index-DkNv4J_i.js');
16
16
  require('./hardware-back-button-VCK4V3mG.js');
17
17
  require('./gesture-controller-dtqlP_q4.js');
18
18
 
19
+ /**
20
+ * Shared per-frame cache for safe-area insets. Avoids creating a temporary
21
+ * DOM element and forcing a synchronous reflow on every call within the same
22
+ * frame (e.g., multiple popovers presenting simultaneously). Invalidated
23
+ * after the next animation frame so values stay fresh across orientation
24
+ * changes and viewport resizes.
25
+ */
26
+ let cachedInsets = null;
27
+ let cacheInvalidationScheduled = false;
28
+ /**
29
+ * Resolves the current --ion-safe-area-* CSS custom property values
30
+ * to actual pixel numbers. A temporary element is needed because
31
+ * getComputedStyle on :root returns the declared value of custom
32
+ * properties (e.g. "env(safe-area-inset-top)") rather than a
33
+ * resolved number. By assigning them to standard padding properties,
34
+ * the browser resolves the value.
35
+ *
36
+ * Results are cached for the current frame to avoid repeated reflows.
37
+ */
38
+ const getSafeAreaInsets = (doc) => {
39
+ if (cachedInsets !== null) {
40
+ return cachedInsets;
41
+ }
42
+ if (doc.body === null) {
43
+ return { top: 0, bottom: 0, left: 0, right: 0 };
44
+ }
45
+ const el = doc.createElement('div');
46
+ el.style.cssText =
47
+ 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
48
+ 'padding-top:var(--ion-safe-area-top,0px);' +
49
+ 'padding-bottom:var(--ion-safe-area-bottom,0px);' +
50
+ 'padding-left:var(--ion-safe-area-left,0px);' +
51
+ 'padding-right:var(--ion-safe-area-right,0px);';
52
+ doc.body.appendChild(el);
53
+ const style = getComputedStyle(el);
54
+ const insets = {
55
+ top: parseFloat(style.paddingTop) || 0,
56
+ bottom: parseFloat(style.paddingBottom) || 0,
57
+ left: parseFloat(style.paddingLeft) || 0,
58
+ right: parseFloat(style.paddingRight) || 0,
59
+ };
60
+ el.remove();
61
+ cachedInsets = insets;
62
+ if (!cacheInvalidationScheduled) {
63
+ cacheInvalidationScheduled = true;
64
+ helpers.raf(() => {
65
+ cachedInsets = null;
66
+ cacheInvalidationScheduled = false;
67
+ });
68
+ }
69
+ return insets;
70
+ };
19
71
  /**
20
72
  * Returns the dimensions of the popover
21
73
  * arrow on `ios` mode. If arrow is disabled
@@ -651,8 +703,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
651
703
  * Adjusts popover positioning coordinates
652
704
  * such that popover does not appear offscreen
653
705
  * or overlapping safe area bounds.
706
+ *
707
+ * @internal - This is not part of the public API.
654
708
  */
655
- const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
709
+ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
656
710
  let arrowTop = coordArrowTop;
657
711
  const arrowLeft = coordArrowLeft;
658
712
  let left = coordLeft;
@@ -662,16 +716,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
662
716
  let originY = contentOriginY;
663
717
  let checkSafeAreaLeft = false;
664
718
  let checkSafeAreaRight = false;
719
+ let checkSafeAreaTop = false;
720
+ let checkSafeAreaBottom = false;
665
721
  const triggerTop = triggerCoordinates
666
722
  ? triggerCoordinates.top + triggerCoordinates.height
667
723
  : bodyHeight / 2 - contentHeight / 2;
668
724
  const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
669
725
  let addPopoverBottomClass = false;
726
+ const hideArrow = false;
670
727
  /**
671
728
  * Adjust popover so it does not
672
729
  * go off the left of the screen.
673
730
  */
674
- if (left < bodyPadding + safeAreaMargin) {
731
+ if (left < bodyPadding + safeArea.left) {
675
732
  left = bodyPadding;
676
733
  checkSafeAreaLeft = true;
677
734
  originX = 'left';
@@ -680,41 +737,58 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
680
737
  * go off the right of the screen.
681
738
  */
682
739
  }
683
- else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) {
740
+ else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
684
741
  checkSafeAreaRight = true;
685
742
  left = bodyWidth - contentWidth - bodyPadding;
686
743
  originX = 'right';
687
744
  }
688
745
  /**
689
746
  * Adjust popover so it does not
690
- * go off the top of the screen.
747
+ * go off the bottom of the screen
748
+ * or overlap the bottom safe area.
691
749
  * If popover is on the left or the right of
692
750
  * the trigger, then we should not adjust top
693
751
  * margins.
694
752
  */
695
- if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
696
- if (triggerTop - contentHeight > 0) {
753
+ if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
754
+ (side === 'top' || side === 'bottom')) {
755
+ /**
756
+ * Calculate where the popover top would be if flipped
757
+ * above the trigger. Check whether that position clears
758
+ * the top safe area with room for bodyPadding.
759
+ */
760
+ const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
761
+ if (idealFlipTop >= safeArea.top + bodyPadding) {
697
762
  /**
698
- * While we strive to align the popover with the trigger
699
- * on smaller screens this is not always possible. As a result,
700
- * we adjust the popover up so that it does not hang
701
- * off the bottom of the screen. However, we do not want to move
702
- * the popover up so much that it goes off the top of the screen.
703
- *
704
- * We chose 12 here so that the popover position looks a bit nicer as
705
- * it is not right up against the edge of the screen.
763
+ * Popover fits above the trigger without overlapping
764
+ * the top safe area. Use the ideal position directly —
765
+ * no safe-area CSS vars needed since it clears both edges.
706
766
  */
707
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
767
+ top = idealFlipTop;
708
768
  arrowTop = top + contentHeight;
709
769
  originY = 'bottom';
710
770
  addPopoverBottomClass = true;
711
- /**
712
- * If not enough room for popover to appear
713
- * above trigger, then cut it off.
714
- */
715
771
  }
716
772
  else {
773
+ /**
774
+ * Can't flip above the trigger. Constrain the bottom
775
+ * edge while keeping the top near the trigger. This
776
+ * creates a scrollable area anchored to the trigger.
777
+ */
717
778
  bottom = bodyPadding;
779
+ checkSafeAreaBottom = true;
780
+ /**
781
+ * When the trigger is near the bottom of the screen,
782
+ * the calculated top may be at or past the bottom
783
+ * constraint, leaving zero visible height. In that
784
+ * case, pull top up to the top safe area so the
785
+ * popover fills the available space between safe areas.
786
+ */
787
+ const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
788
+ if (top >= bottomEdge) {
789
+ top = safeArea.top + bodyPadding;
790
+ checkSafeAreaTop = true;
791
+ }
718
792
  }
719
793
  }
720
794
  return {
@@ -725,9 +799,12 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
725
799
  originY,
726
800
  checkSafeAreaLeft,
727
801
  checkSafeAreaRight,
802
+ checkSafeAreaTop,
803
+ checkSafeAreaBottom,
728
804
  arrowTop,
729
805
  arrowLeft,
730
806
  addPopoverBottomClass,
807
+ hideArrow,
731
808
  };
732
809
  };
733
810
  const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
@@ -757,6 +834,15 @@ const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
757
834
  };
758
835
 
759
836
  const POPOVER_IOS_BODY_PADDING = 5;
837
+ /**
838
+ * Minimum edge margin for iOS popovers ensures visual spacing from screen
839
+ * edges on devices without safe areas (e.g., older iPhones without notches).
840
+ * Previously this was a hardcoded `safeAreaMargin = 25` that served dual
841
+ * purpose: safe-area avoidance AND visual spacing. Now that actual safe-area
842
+ * insets are read dynamically, this floor preserves the visual spacing when
843
+ * safe-area values are 0.
844
+ */
845
+ const POPOVER_IOS_MIN_EDGE_MARGIN = 25;
760
846
  /**
761
847
  * iOS Popover Enter Animation
762
848
  */
@@ -782,8 +868,16 @@ const iosEnterAnimation = (baseEl, opts) => {
782
868
  };
783
869
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
784
870
  const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
785
- const margin = size === 'cover' ? 0 : 25;
786
- 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);
871
+ const rawSafeArea = getSafeAreaInsets(doc);
872
+ const safeArea = size === 'cover'
873
+ ? { top: 0, bottom: 0, left: 0, right: 0 }
874
+ : {
875
+ top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN),
876
+ bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN),
877
+ left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN),
878
+ right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN),
879
+ };
880
+ const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass} = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
787
881
  const baseAnimation = animation.createAnimation();
788
882
  const backdropAnimation = animation.createAnimation();
789
883
  const contentAnimation = animation.createAnimation();
@@ -814,10 +908,14 @@ const iosEnterAnimation = (baseEl, opts) => {
814
908
  baseEl.classList.add('popover-bottom');
815
909
  }
816
910
  if (bottom !== undefined) {
817
- contentEl.style.setProperty('bottom', `${bottom}px`);
911
+ let bottomValue = `${bottom}px`;
912
+ if (checkSafeAreaBottom) {
913
+ bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
914
+ }
915
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
818
916
  }
819
- const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
820
- const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
917
+ const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)';
918
+ const safeAreaRight = ' - var(--ion-safe-area-right, 0px)';
821
919
  let leftValue = `${left}px`;
822
920
  if (checkSafeAreaLeft) {
823
921
  leftValue = `${left}px${safeAreaLeft}`;
@@ -825,7 +923,11 @@ const iosEnterAnimation = (baseEl, opts) => {
825
923
  if (checkSafeAreaRight) {
826
924
  leftValue = `${left}px${safeAreaRight}`;
827
925
  }
828
- contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
926
+ let topValue = `${top}px`;
927
+ if (checkSafeAreaTop) {
928
+ topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
929
+ }
930
+ contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
829
931
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
830
932
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
831
933
  if (arrowEl !== null) {
@@ -901,7 +1003,23 @@ const mdEnterAnimation = (baseEl, opts) => {
901
1003
  };
902
1004
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
903
1005
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
904
- const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
1006
+ // MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas).
1007
+ // This is needed for Android edge-to-edge (API 36+) where system bars overlap content.
1008
+ const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc);
1009
+ const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates);
1010
+ const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)';
1011
+ const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)';
1012
+ let leftValue = `${left}px`;
1013
+ if (checkSafeAreaLeft) {
1014
+ leftValue = `${left}px${safeAreaLeftCalc}`;
1015
+ }
1016
+ if (checkSafeAreaRight) {
1017
+ leftValue = `${left}px${safeAreaRightCalc}`;
1018
+ }
1019
+ let topValue = `${top}px`;
1020
+ if (checkSafeAreaTop) {
1021
+ topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
1022
+ }
905
1023
  const baseAnimation = animation.createAnimation();
906
1024
  const backdropAnimation = animation.createAnimation();
907
1025
  const wrapperAnimation = animation.createAnimation();
@@ -918,13 +1036,17 @@ const mdEnterAnimation = (baseEl, opts) => {
918
1036
  contentAnimation
919
1037
  .addElement(contentEl)
920
1038
  .beforeStyles({
921
- top: `calc(${top}px + var(--offset-y, 0px))`,
922
- left: `calc(${left}px + var(--offset-x, 0px))`,
1039
+ top: `calc(${topValue} + var(--offset-y, 0px))`,
1040
+ left: `calc(${leftValue} + var(--offset-x, 0px))`,
923
1041
  'transform-origin': `${originY} ${originX}`,
924
1042
  })
925
1043
  .beforeAddWrite(() => {
926
1044
  if (bottom !== undefined) {
927
- contentEl.style.setProperty('bottom', `${bottom}px`);
1045
+ let bottomValue = `${bottom}px`;
1046
+ if (checkSafeAreaBottom) {
1047
+ bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
1048
+ }
1049
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
928
1050
  }
929
1051
  })
930
1052
  .fromTo('transform', 'scale(0.8)', 'scale(1)');
@@ -936,7 +1058,7 @@ const mdEnterAnimation = (baseEl, opts) => {
936
1058
  if (size === 'cover') {
937
1059
  baseEl.style.setProperty('--width', `${contentWidth}px`);
938
1060
  }
939
- if (originY === 'bottom') {
1061
+ if (addPopoverBottomClass) {
940
1062
  baseEl.classList.add('popover-bottom');
941
1063
  }
942
1064
  })
@@ -214,8 +214,6 @@ const Refresher = class {
214
214
  this.ionRefresh = index.createEvent(this, "ionRefresh", 7);
215
215
  this.ionPull = index.createEvent(this, "ionPull", 7);
216
216
  this.ionStart = index.createEvent(this, "ionStart", 7);
217
- this.ionPullStart = index.createEvent(this, "ionPullStart", 7);
218
- this.ionPullEnd = index.createEvent(this, "ionPullEnd", 7);
219
217
  this.appliedStyles = false;
220
218
  this.didStart = false;
221
219
  this.progress = 0;
@@ -270,8 +268,8 @@ const Refresher = class {
270
268
  * than `1`. The default value is `1` which is equal to the speed of the cursor.
271
269
  * If a negative value is passed in, the factor will be `1` instead.
272
270
  *
273
- * For example, If the value passed is `1.2` and the content is dragged by
274
- * `10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels
271
+ * For example: If the value passed is `1.2` and the content is dragged by
272
+ * `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
275
273
  * (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
276
274
  * will be `8` pixels, less than the amount the cursor has moved.
277
275
  *
@@ -321,9 +319,6 @@ const Refresher = class {
321
319
  this.animations = [];
322
320
  this.progress = 0;
323
321
  this.state = 1 /* RefresherState.Inactive */;
324
- this.ionPullEnd.emit({
325
- reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
326
- });
327
322
  }
328
323
  async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
329
324
  this.elementToTransform = this.scrollEl;
@@ -356,7 +351,6 @@ const Refresher = class {
356
351
  if (!this.didStart) {
357
352
  this.didStart = true;
358
353
  this.ionStart.emit();
359
- this.ionPullStart.emit();
360
354
  }
361
355
  // emit "pulling" on every move
362
356
  if (this.pointerDown) {
@@ -432,7 +426,6 @@ const Refresher = class {
432
426
  this.lastVelocityY = ev.velocityY;
433
427
  },
434
428
  onEnd: () => {
435
- const hadStarted = this.didStart;
436
429
  this.pointerDown = false;
437
430
  this.didStart = false;
438
431
  if (this.needsCompletion) {
@@ -442,13 +435,6 @@ const Refresher = class {
442
435
  else if (this.didRefresh) {
443
436
  index.readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
444
437
  }
445
- else if (hadStarted) {
446
- /**
447
- * User started pulling but released before reaching the refresh threshold.
448
- * Emit ionPullEnd to complete the event pair.
449
- */
450
- this.ionPullEnd.emit({ reason: 'cancel' });
451
- }
452
438
  },
453
439
  });
454
440
  this.disabledChanged();
@@ -495,7 +481,6 @@ const Refresher = class {
495
481
  ev.data.animation = animation;
496
482
  animation.progressStart(false, 0);
497
483
  this.ionStart.emit();
498
- this.ionPullStart.emit();
499
484
  this.animations.push(animation);
500
485
  return;
501
486
  }
@@ -518,7 +503,6 @@ const Refresher = class {
518
503
  this.animations = [];
519
504
  this.gesture.enable(true);
520
505
  this.state = 1 /* RefresherState.Inactive */;
521
- this.ionPullEnd.emit({ reason: 'cancel' });
522
506
  });
523
507
  return;
524
508
  }
@@ -763,7 +747,6 @@ const Refresher = class {
763
747
  if (!this.didStart) {
764
748
  this.didStart = true;
765
749
  this.ionStart.emit();
766
- this.ionPullStart.emit();
767
750
  }
768
751
  // emit "pulling" on every move
769
752
  this.ionPull.emit();
@@ -807,15 +790,6 @@ const Refresher = class {
807
790
  * available right away.
808
791
  */
809
792
  this.restoreOverflowStyle();
810
- /**
811
- * If ionPullStart was emitted, we need to emit ionPullEnd
812
- * even though the gesture was aborted before reaching the
813
- * pulling threshold.
814
- */
815
- if (this.didStart) {
816
- this.didStart = false;
817
- this.ionPullEnd.emit({ reason: 'cancel' });
818
- }
819
793
  }
820
794
  }
821
795
  beginRefresh() {
@@ -862,9 +836,6 @@ const Refresher = class {
862
836
  if (this.contentFullscreen && this.backgroundContentEl) {
863
837
  (_a = this.backgroundContentEl) === null || _a === void 0 ? void 0 : _a.style.removeProperty('--offset-top');
864
838
  }
865
- this.ionPullEnd.emit({
866
- reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
867
- });
868
839
  }, 600);
869
840
  // reset the styles on the scroll element
870
841
  // set that the refresh is actively cancelling/completing
@@ -918,7 +889,7 @@ const Refresher = class {
918
889
  }
919
890
  render() {
920
891
  const mode = ionicGlobal.getIonMode(this);
921
- return (index.h(index.Host, { key: 'bed0c2f44c32ecac24b7d8326ac7c7ef1d12c44f', slot: "fixed", class: {
892
+ return (index.h(index.Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
922
893
  [mode]: true,
923
894
  // Used internally for styling
924
895
  [`refresher-${mode}`]: true,
@@ -135,10 +135,6 @@ ion-backdrop {
135
135
  :host {
136
136
  --width: 600px;
137
137
  --height: 500px;
138
- --ion-safe-area-top: 0px;
139
- --ion-safe-area-bottom: 0px;
140
- --ion-safe-area-right: 0px;
141
- --ion-safe-area-left: 0px;
142
138
  }
143
139
  }
144
140
  @media only screen and (min-width: 768px) and (min-height: 768px) {
@@ -190,9 +186,14 @@ ion-backdrop {
190
186
  /**
191
187
  * Ensure that the sheet modal does not
192
188
  * completely cover the content.
189
+ *
190
+ * --ion-modal-offset-top is an internal property set by modal.tsx
191
+ * with the resolved root safe-area-top pixel value. This decouples
192
+ * the height calculation from --ion-safe-area-top (which is zeroed
193
+ * for sheet modals to prevent header double-padding).
193
194
  */
194
195
  :host(.modal-sheet) {
195
- --height: calc(100% - (var(--ion-safe-area-top) + 10px));
196
+ --height: calc(100% - (var(--ion-modal-offset-top, 0px) + 10px));
196
197
  }
197
198
 
198
199
  :host(.modal-sheet) .modal-wrapper,