@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
@@ -2,6 +2,58 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { getElementRoot, raf } from "../../utils/helpers";
5
+ /**
6
+ * Shared per-frame cache for safe-area insets. Avoids creating a temporary
7
+ * DOM element and forcing a synchronous reflow on every call within the same
8
+ * frame (e.g., multiple popovers presenting simultaneously). Invalidated
9
+ * after the next animation frame so values stay fresh across orientation
10
+ * changes and viewport resizes.
11
+ */
12
+ let cachedInsets = null;
13
+ let cacheInvalidationScheduled = false;
14
+ /**
15
+ * Resolves the current --ion-safe-area-* CSS custom property values
16
+ * to actual pixel numbers. A temporary element is needed because
17
+ * getComputedStyle on :root returns the declared value of custom
18
+ * properties (e.g. "env(safe-area-inset-top)") rather than a
19
+ * resolved number. By assigning them to standard padding properties,
20
+ * the browser resolves the value.
21
+ *
22
+ * Results are cached for the current frame to avoid repeated reflows.
23
+ */
24
+ export const getSafeAreaInsets = (doc) => {
25
+ if (cachedInsets !== null) {
26
+ return cachedInsets;
27
+ }
28
+ if (doc.body === null) {
29
+ return { top: 0, bottom: 0, left: 0, right: 0 };
30
+ }
31
+ const el = doc.createElement('div');
32
+ el.style.cssText =
33
+ 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
34
+ 'padding-top:var(--ion-safe-area-top,0px);' +
35
+ 'padding-bottom:var(--ion-safe-area-bottom,0px);' +
36
+ 'padding-left:var(--ion-safe-area-left,0px);' +
37
+ 'padding-right:var(--ion-safe-area-right,0px);';
38
+ doc.body.appendChild(el);
39
+ const style = getComputedStyle(el);
40
+ const insets = {
41
+ top: parseFloat(style.paddingTop) || 0,
42
+ bottom: parseFloat(style.paddingBottom) || 0,
43
+ left: parseFloat(style.paddingLeft) || 0,
44
+ right: parseFloat(style.paddingRight) || 0,
45
+ };
46
+ el.remove();
47
+ cachedInsets = insets;
48
+ if (!cacheInvalidationScheduled) {
49
+ cacheInvalidationScheduled = true;
50
+ raf(() => {
51
+ cachedInsets = null;
52
+ cacheInvalidationScheduled = false;
53
+ });
54
+ }
55
+ return insets;
56
+ };
5
57
  /**
6
58
  * Returns the dimensions of the popover
7
59
  * arrow on `ios` mode. If arrow is disabled
@@ -639,8 +691,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
639
691
  * Adjusts popover positioning coordinates
640
692
  * such that popover does not appear offscreen
641
693
  * or overlapping safe area bounds.
694
+ *
695
+ * @internal - This is not part of the public API.
642
696
  */
643
- export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
697
+ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
644
698
  let arrowTop = coordArrowTop;
645
699
  const arrowLeft = coordArrowLeft;
646
700
  let left = coordLeft;
@@ -650,16 +704,19 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
650
704
  let originY = contentOriginY;
651
705
  let checkSafeAreaLeft = false;
652
706
  let checkSafeAreaRight = false;
707
+ let checkSafeAreaTop = false;
708
+ let checkSafeAreaBottom = false;
653
709
  const triggerTop = triggerCoordinates
654
710
  ? triggerCoordinates.top + triggerCoordinates.height
655
711
  : bodyHeight / 2 - contentHeight / 2;
656
712
  const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
657
713
  let addPopoverBottomClass = false;
714
+ const hideArrow = false;
658
715
  /**
659
716
  * Adjust popover so it does not
660
717
  * go off the left of the screen.
661
718
  */
662
- if (left < bodyPadding + safeAreaMargin) {
719
+ if (left < bodyPadding + safeArea.left) {
663
720
  left = bodyPadding;
664
721
  checkSafeAreaLeft = true;
665
722
  originX = 'left';
@@ -668,41 +725,58 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
668
725
  * go off the right of the screen.
669
726
  */
670
727
  }
671
- else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) {
728
+ else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
672
729
  checkSafeAreaRight = true;
673
730
  left = bodyWidth - contentWidth - bodyPadding;
674
731
  originX = 'right';
675
732
  }
676
733
  /**
677
734
  * Adjust popover so it does not
678
- * go off the top of the screen.
735
+ * go off the bottom of the screen
736
+ * or overlap the bottom safe area.
679
737
  * If popover is on the left or the right of
680
738
  * the trigger, then we should not adjust top
681
739
  * margins.
682
740
  */
683
- if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
684
- if (triggerTop - contentHeight > 0) {
741
+ if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
742
+ (side === 'top' || side === 'bottom')) {
743
+ /**
744
+ * Calculate where the popover top would be if flipped
745
+ * above the trigger. Check whether that position clears
746
+ * the top safe area with room for bodyPadding.
747
+ */
748
+ const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
749
+ if (idealFlipTop >= safeArea.top + bodyPadding) {
685
750
  /**
686
- * While we strive to align the popover with the trigger
687
- * on smaller screens this is not always possible. As a result,
688
- * we adjust the popover up so that it does not hang
689
- * off the bottom of the screen. However, we do not want to move
690
- * the popover up so much that it goes off the top of the screen.
691
- *
692
- * We chose 12 here so that the popover position looks a bit nicer as
693
- * it is not right up against the edge of the screen.
751
+ * Popover fits above the trigger without overlapping
752
+ * the top safe area. Use the ideal position directly —
753
+ * no safe-area CSS vars needed since it clears both edges.
694
754
  */
695
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
755
+ top = idealFlipTop;
696
756
  arrowTop = top + contentHeight;
697
757
  originY = 'bottom';
698
758
  addPopoverBottomClass = true;
699
- /**
700
- * If not enough room for popover to appear
701
- * above trigger, then cut it off.
702
- */
703
759
  }
704
760
  else {
761
+ /**
762
+ * Can't flip above the trigger. Constrain the bottom
763
+ * edge while keeping the top near the trigger. This
764
+ * creates a scrollable area anchored to the trigger.
765
+ */
705
766
  bottom = bodyPadding;
767
+ checkSafeAreaBottom = true;
768
+ /**
769
+ * When the trigger is near the bottom of the screen,
770
+ * the calculated top may be at or past the bottom
771
+ * constraint, leaving zero visible height. In that
772
+ * case, pull top up to the top safe area so the
773
+ * popover fills the available space between safe areas.
774
+ */
775
+ const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
776
+ if (top >= bottomEdge) {
777
+ top = safeArea.top + bodyPadding;
778
+ checkSafeAreaTop = true;
779
+ }
706
780
  }
707
781
  }
708
782
  return {
@@ -713,9 +787,12 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
713
787
  originY,
714
788
  checkSafeAreaLeft,
715
789
  checkSafeAreaRight,
790
+ checkSafeAreaTop,
791
+ checkSafeAreaBottom,
716
792
  arrowTop,
717
793
  arrowLeft,
718
794
  addPopoverBottomClass,
795
+ hideArrow,
719
796
  };
720
797
  };
721
798
  export const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
@@ -68,8 +68,8 @@ export class Refresher {
68
68
  * than `1`. The default value is `1` which is equal to the speed of the cursor.
69
69
  * If a negative value is passed in, the factor will be `1` instead.
70
70
  *
71
- * For example, If the value passed is `1.2` and the content is dragged by
72
- * `10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels
71
+ * For example: If the value passed is `1.2` and the content is dragged by
72
+ * `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
73
73
  * (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
74
74
  * will be `8` pixels, less than the amount the cursor has moved.
75
75
  *
@@ -119,9 +119,6 @@ export class Refresher {
119
119
  this.animations = [];
120
120
  this.progress = 0;
121
121
  this.state = 1 /* RefresherState.Inactive */;
122
- this.ionPullEnd.emit({
123
- reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
124
- });
125
122
  }
126
123
  async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
127
124
  this.elementToTransform = this.scrollEl;
@@ -154,7 +151,6 @@ export class Refresher {
154
151
  if (!this.didStart) {
155
152
  this.didStart = true;
156
153
  this.ionStart.emit();
157
- this.ionPullStart.emit();
158
154
  }
159
155
  // emit "pulling" on every move
160
156
  if (this.pointerDown) {
@@ -230,7 +226,6 @@ export class Refresher {
230
226
  this.lastVelocityY = ev.velocityY;
231
227
  },
232
228
  onEnd: () => {
233
- const hadStarted = this.didStart;
234
229
  this.pointerDown = false;
235
230
  this.didStart = false;
236
231
  if (this.needsCompletion) {
@@ -240,13 +235,6 @@ export class Refresher {
240
235
  else if (this.didRefresh) {
241
236
  readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
242
237
  }
243
- else if (hadStarted) {
244
- /**
245
- * User started pulling but released before reaching the refresh threshold.
246
- * Emit ionPullEnd to complete the event pair.
247
- */
248
- this.ionPullEnd.emit({ reason: 'cancel' });
249
- }
250
238
  },
251
239
  });
252
240
  this.disabledChanged();
@@ -293,7 +281,6 @@ export class Refresher {
293
281
  ev.data.animation = animation;
294
282
  animation.progressStart(false, 0);
295
283
  this.ionStart.emit();
296
- this.ionPullStart.emit();
297
284
  this.animations.push(animation);
298
285
  return;
299
286
  }
@@ -316,7 +303,6 @@ export class Refresher {
316
303
  this.animations = [];
317
304
  this.gesture.enable(true);
318
305
  this.state = 1 /* RefresherState.Inactive */;
319
- this.ionPullEnd.emit({ reason: 'cancel' });
320
306
  });
321
307
  return;
322
308
  }
@@ -561,7 +547,6 @@ export class Refresher {
561
547
  if (!this.didStart) {
562
548
  this.didStart = true;
563
549
  this.ionStart.emit();
564
- this.ionPullStart.emit();
565
550
  }
566
551
  // emit "pulling" on every move
567
552
  this.ionPull.emit();
@@ -605,15 +590,6 @@ export class Refresher {
605
590
  * available right away.
606
591
  */
607
592
  this.restoreOverflowStyle();
608
- /**
609
- * If ionPullStart was emitted, we need to emit ionPullEnd
610
- * even though the gesture was aborted before reaching the
611
- * pulling threshold.
612
- */
613
- if (this.didStart) {
614
- this.didStart = false;
615
- this.ionPullEnd.emit({ reason: 'cancel' });
616
- }
617
593
  }
618
594
  }
619
595
  beginRefresh() {
@@ -660,9 +636,6 @@ export class Refresher {
660
636
  if (this.contentFullscreen && this.backgroundContentEl) {
661
637
  (_a = this.backgroundContentEl) === null || _a === void 0 ? void 0 : _a.style.removeProperty('--offset-top');
662
638
  }
663
- this.ionPullEnd.emit({
664
- reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
665
- });
666
639
  }, 600);
667
640
  // reset the styles on the scroll element
668
641
  // set that the refresh is actively cancelling/completing
@@ -716,7 +689,7 @@ export class Refresher {
716
689
  }
717
690
  render() {
718
691
  const mode = getIonMode(this);
719
- return (h(Host, { key: 'bed0c2f44c32ecac24b7d8326ac7c7ef1d12c44f', slot: "fixed", class: {
692
+ return (h(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
720
693
  [mode]: true,
721
694
  // Used internally for styling
722
695
  [`refresher-${mode}`]: true,
@@ -836,7 +809,7 @@ export class Refresher {
836
809
  "optional": false,
837
810
  "docs": {
838
811
  "tags": [],
839
- "text": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example, If the value passed is `1.2` and the content is dragged by\n`10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels\n(an increase of 20 percent). If the value passed is `0.8`, the dragged amount\nwill be `8` pixels, less than the amount the cursor has moved.\n\nDoes not apply when the refresher content uses a spinner,\nenabling the native refresher."
812
+ "text": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example: If the value passed is `1.2` and the content is dragged by\n`10` pixels, instead of `10` pixels the content will be pulled by `12` pixels\n(an increase of 20 percent). If the value passed is `0.8`, the dragged amount\nwill be `8` pixels, less than the amount the cursor has moved.\n\nDoes not apply when the refresher content uses a spinner,\nenabling the native refresher."
840
813
  },
841
814
  "getter": false,
842
815
  "setter": false,
@@ -915,24 +888,6 @@ export class Refresher {
915
888
  "bubbles": true,
916
889
  "cancelable": true,
917
890
  "composed": true,
918
- "docs": {
919
- "tags": [{
920
- "name": "deprecated",
921
- "text": "Use `ionPullStart` instead."
922
- }],
923
- "text": "Emitted when the user begins to start pulling down.\nTODO(FW-7044): Remove this in a major release"
924
- },
925
- "complexType": {
926
- "original": "void",
927
- "resolved": "void",
928
- "references": {}
929
- }
930
- }, {
931
- "method": "ionPullStart",
932
- "name": "ionPullStart",
933
- "bubbles": true,
934
- "cancelable": true,
935
- "composed": true,
936
891
  "docs": {
937
892
  "tags": [],
938
893
  "text": "Emitted when the user begins to start pulling down."
@@ -942,27 +897,6 @@ export class Refresher {
942
897
  "resolved": "void",
943
898
  "references": {}
944
899
  }
945
- }, {
946
- "method": "ionPullEnd",
947
- "name": "ionPullEnd",
948
- "bubbles": true,
949
- "cancelable": true,
950
- "composed": true,
951
- "docs": {
952
- "tags": [],
953
- "text": "Emitted when the refresher has returned to the inactive state\nafter a pull gesture. This fires whether the refresh completed\nsuccessfully or was canceled."
954
- },
955
- "complexType": {
956
- "original": "RefresherPullEndEventDetail",
957
- "resolved": "RefresherPullEndEventDetail",
958
- "references": {
959
- "RefresherPullEndEventDetail": {
960
- "location": "import",
961
- "path": "./refresher-interface",
962
- "id": "src/components/refresher/refresher-interface.ts::RefresherPullEndEventDetail"
963
- }
964
- }
965
- }
966
900
  }];
967
901
  }
968
902
  static get methods() {
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-02-05T19:12:51",
2
+ "timestamp": "2026-02-12T17:17:06",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -26514,7 +26514,7 @@
26514
26514
  "mutable": false,
26515
26515
  "attr": "pull-factor",
26516
26516
  "reflectToAttr": false,
26517
- "docs": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example, If the value passed is `1.2` and the content is dragged by\n`10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels\n(an increase of 20 percent). If the value passed is `0.8`, the dragged amount\nwill be `8` pixels, less than the amount the cursor has moved.\n\nDoes not apply when the refresher content uses a spinner,\nenabling the native refresher.",
26517
+ "docs": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example: If the value passed is `1.2` and the content is dragged by\n`10` pixels, instead of `10` pixels the content will be pulled by `12` pixels\n(an increase of 20 percent). If the value passed is `0.8`, the dragged amount\nwill be `8` pixels, less than the amount the cursor has moved.\n\nDoes not apply when the refresher content uses a spinner,\nenabling the native refresher.",
26518
26518
  "docsTags": [
26519
26519
  {
26520
26520
  "name": "default",
@@ -26703,40 +26703,6 @@
26703
26703
  "docs": "Emitted while the user is pulling down the content and exposing the refresher.",
26704
26704
  "docsTags": []
26705
26705
  },
26706
- {
26707
- "event": "ionPullEnd",
26708
- "detail": "RefresherPullEndEventDetail",
26709
- "bubbles": true,
26710
- "complexType": {
26711
- "original": "RefresherPullEndEventDetail",
26712
- "resolved": "RefresherPullEndEventDetail",
26713
- "references": {
26714
- "RefresherPullEndEventDetail": {
26715
- "location": "import",
26716
- "path": "./refresher-interface",
26717
- "id": "src/components/refresher/refresher-interface.ts::RefresherPullEndEventDetail"
26718
- }
26719
- }
26720
- },
26721
- "cancelable": true,
26722
- "composed": true,
26723
- "docs": "Emitted when the refresher has returned to the inactive state\nafter a pull gesture. This fires whether the refresh completed\nsuccessfully or was canceled.",
26724
- "docsTags": []
26725
- },
26726
- {
26727
- "event": "ionPullStart",
26728
- "detail": "void",
26729
- "bubbles": true,
26730
- "complexType": {
26731
- "original": "void",
26732
- "resolved": "void",
26733
- "references": {}
26734
- },
26735
- "cancelable": true,
26736
- "composed": true,
26737
- "docs": "Emitted when the user begins to start pulling down.",
26738
- "docsTags": []
26739
- },
26740
26706
  {
26741
26707
  "event": "ionRefresh",
26742
26708
  "detail": "RefresherEventDetail",
@@ -26768,14 +26734,8 @@
26768
26734
  },
26769
26735
  "cancelable": true,
26770
26736
  "composed": true,
26771
- "docs": "Emitted when the user begins to start pulling down.\nTODO(FW-7044): Remove this in a major release",
26772
- "docsTags": [
26773
- {
26774
- "name": "deprecated",
26775
- "text": "Use `ionPullStart` instead."
26776
- }
26777
- ],
26778
- "deprecation": "Use `ionPullStart` instead."
26737
+ "docs": "Emitted when the user begins to start pulling down.",
26738
+ "docsTags": []
26779
26739
  }
26780
26740
  ],
26781
26741
  "listeners": [],
@@ -37591,11 +37551,6 @@
37591
37551
  "docstring": "",
37592
37552
  "path": "src/components/refresher/refresher-interface.ts"
37593
37553
  },
37594
- "src/components/refresher/refresher-interface.ts::RefresherPullEndEventDetail": {
37595
- "declaration": "export interface RefresherPullEndEventDetail {\n reason: 'complete' | 'cancel';\n}",
37596
- "docstring": "",
37597
- "path": "src/components/refresher/refresher-interface.ts"
37598
- },
37599
37554
  "src/components/reorder-group/reorder-group-interface.ts::ItemReorderEventDetail": {
37600
37555
  "declaration": "export interface ItemReorderEventDetail {\n from: number;\n to: number;\n complete: (data?: boolean | any[]) => any;\n}",
37601
37556
  "docstring": "",