@ionic/core 8.7.18-dev.11770316772.1e7b64e5 → 8.7.18-dev.11770674094.18396f54

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 +248 -13
  3. package/components/popover.js +152 -30
  4. package/dist/cjs/ion-modal.cjs.entry.js +248 -13
  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 +0 -4
  8. package/dist/collection/components/modal/modal.js +127 -11
  9. package/dist/collection/components/modal/modal.md.css +0 -4
  10. package/dist/collection/components/modal/safe-area-utils.js +123 -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 +248 -13
  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-36d78f36.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 +23 -0
  25. package/dist/types/components/modal/safe-area-utils.d.ts +55 -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 +403 -75
  32. package/hydrate/index.mjs +403 -75
  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
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-02-05T18:41:36",
2
+ "timestamp": "2026-02-09T21:56:44",
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": "",
@@ -1472,9 +1472,129 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1472
1472
  };
1473
1473
  };
1474
1474
 
1475
- const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
1475
+ /**
1476
+ * These thresholds match the SCSS media query breakpoints in modal.vars.scss
1477
+ * that trigger the centered dialog layout (non-fullscreen modal).
1478
+ *
1479
+ * SCSS defines two height breakpoints: $modal-inset-min-height-small (600px)
1480
+ * and $modal-inset-min-height-large (768px). We use the smaller one because
1481
+ * that's the threshold where the modal transitions from fullscreen to centered
1482
+ * dialog — the larger breakpoint only increases the dialog's height.
1483
+ */
1484
+ const MODAL_INSET_MIN_WIDTH = 768;
1485
+ const MODAL_INSET_MIN_HEIGHT = 600;
1486
+ const EDGE_THRESHOLD = 5;
1487
+ /**
1488
+ * Determines if the current viewport meets the CSS media query conditions
1489
+ * that cause regular modals to render as centered dialogs instead of fullscreen.
1490
+ * Matches: @media (min-width: 768px) and (min-height: 600px)
1491
+ */
1492
+ const isCenteredDialogViewport = () => {
1493
+ if (!win)
1494
+ return false;
1495
+ return win.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
1496
+ .matches;
1497
+ };
1498
+ /**
1499
+ * Returns the initial safe-area configuration based on modal type.
1500
+ * This is called before animation starts and uses configuration-based prediction.
1501
+ *
1502
+ * @param context - Modal context information
1503
+ * @returns SafeAreaConfig with initial safe-area values
1504
+ */
1505
+ const getInitialSafeAreaConfig = (context) => {
1506
+ const { isSheetModal, isCardModal } = context;
1507
+ // Sheet modals use bottom safe-area, and top safe-area only when fully expanded
1508
+ if (isSheetModal) {
1509
+ return {
1510
+ top: context.currentBreakpoint === 1 ? 'inherit' : '0px',
1511
+ bottom: 'inherit',
1512
+ left: '0px',
1513
+ right: '0px',
1514
+ };
1515
+ }
1516
+ // Card modals need safe-area for height calculation.
1517
+ // Note: isCardModal is already gated on mode === 'ios' by the caller.
1518
+ if (isCardModal) {
1519
+ return {
1520
+ top: 'inherit',
1521
+ bottom: 'inherit',
1522
+ left: '0px',
1523
+ right: '0px',
1524
+ };
1525
+ }
1526
+ // On viewports that meet the centered dialog media query breakpoints,
1527
+ // regular modals render as centered dialogs (not fullscreen), so they
1528
+ // don't touch any screen edges and don't need safe-area insets.
1529
+ if (isCenteredDialogViewport()) {
1530
+ return {
1531
+ top: '0px',
1532
+ bottom: '0px',
1533
+ left: '0px',
1534
+ right: '0px',
1535
+ };
1536
+ }
1537
+ // Fullscreen modals on phone - inherit all safe areas
1538
+ return {
1539
+ top: 'inherit',
1540
+ bottom: 'inherit',
1541
+ left: 'inherit',
1542
+ right: 'inherit',
1543
+ };
1544
+ };
1545
+ /**
1546
+ * Returns safe-area configuration based on actual modal position.
1547
+ * Detects which edges the modal overlaps with and only applies safe-area to those edges.
1548
+ *
1549
+ * Note: On Android edge-to-edge (API 36+), getBoundingClientRect() may report
1550
+ * inconsistent values. Sheet and card modals avoid this by using configuration-based
1551
+ * prediction instead. Regular modals use coordinate detection which works reliably
1552
+ * on web and iOS; Android edge-to-edge may need a configuration-based fallback
1553
+ * once a reliable detection mechanism is available.
1554
+ *
1555
+ * @param wrapperEl - The modal wrapper element to measure
1556
+ * @returns SafeAreaConfig based on position
1557
+ */
1558
+ const getPositionBasedSafeAreaConfig = (wrapperEl) => {
1559
+ var _a, _b;
1560
+ const rect = wrapperEl.getBoundingClientRect();
1561
+ const vh = (_a = win === null || win === void 0 ? void 0 : win.innerHeight) !== null && _a !== void 0 ? _a : 0;
1562
+ const vw = (_b = win === null || win === void 0 ? void 0 : win.innerWidth) !== null && _b !== void 0 ? _b : 0;
1563
+ // Only apply safe-area to sides where modal overlaps with screen edge
1564
+ return {
1565
+ top: rect.top <= EDGE_THRESHOLD ? 'inherit' : '0px',
1566
+ bottom: rect.bottom >= vh - EDGE_THRESHOLD ? 'inherit' : '0px',
1567
+ left: rect.left <= EDGE_THRESHOLD ? 'inherit' : '0px',
1568
+ right: rect.right >= vw - EDGE_THRESHOLD ? 'inherit' : '0px',
1569
+ };
1570
+ };
1571
+ /**
1572
+ * Applies safe-area CSS custom property overrides to the modal host element.
1573
+ *
1574
+ * @param hostEl - The modal host element (ion-modal)
1575
+ * @param config - Safe-area configuration to apply
1576
+ */
1577
+ const applySafeAreaOverrides = (hostEl, config) => {
1578
+ hostEl.style.setProperty('--ion-safe-area-top', config.top);
1579
+ hostEl.style.setProperty('--ion-safe-area-bottom', config.bottom);
1580
+ hostEl.style.setProperty('--ion-safe-area-left', config.left);
1581
+ hostEl.style.setProperty('--ion-safe-area-right', config.right);
1582
+ };
1583
+ /**
1584
+ * Clears safe-area CSS custom property overrides from the modal host element.
1585
+ *
1586
+ * @param hostEl - The modal host element (ion-modal)
1587
+ */
1588
+ const clearSafeAreaOverrides = (hostEl) => {
1589
+ hostEl.style.removeProperty('--ion-safe-area-top');
1590
+ hostEl.style.removeProperty('--ion-safe-area-bottom');
1591
+ hostEl.style.removeProperty('--ion-safe-area-left');
1592
+ hostEl.style.removeProperty('--ion-safe-area-right');
1593
+ };
1594
+
1595
+ const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
1476
1596
 
1477
- const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
1597
+ const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";
1478
1598
 
1479
1599
  const Modal = class {
1480
1600
  constructor(hostRef) {
@@ -1710,6 +1830,9 @@ const Modal = class {
1710
1830
  this.triggerController.removeClickListener();
1711
1831
  this.cleanupViewTransitionListener();
1712
1832
  this.cleanupParentRemovalObserver();
1833
+ // Also called in dismiss() — intentional dual cleanup covers both
1834
+ // dismiss-then-remove and direct DOM removal without dismiss.
1835
+ this.cleanupSafeAreaOverrides();
1713
1836
  }
1714
1837
  componentWillLoad() {
1715
1838
  var _a;
@@ -1870,6 +1993,11 @@ const Modal = class {
1870
1993
  await waitForMount();
1871
1994
  }
1872
1995
  writeTask(() => this.el.classList.add('show-modal'));
1996
+ // Recalculate isSheetModal before safe-area setup because framework
1997
+ // bindings (e.g., Angular) may not have been applied when componentWillLoad ran.
1998
+ this.isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
1999
+ // Set initial safe-area overrides before animation
2000
+ this.setInitialSafeAreaOverrides();
1873
2001
  const hasCardModal = presentingElement !== undefined;
1874
2002
  /**
1875
2003
  * We need to change the status bar at the
@@ -1887,6 +2015,10 @@ const Modal = class {
1887
2015
  backdropBreakpoint: this.backdropBreakpoint,
1888
2016
  expandToScroll: this.expandToScroll,
1889
2017
  });
2018
+ // Update safe-area based on actual position after animation
2019
+ this.updateSafeAreaOverrides();
2020
+ // Apply fullscreen safe-area padding if needed
2021
+ this.applyFullscreenSafeArea();
1890
2022
  /* tslint:disable-next-line */
1891
2023
  if (typeof window !== 'undefined') {
1892
2024
  /**
@@ -1918,13 +2050,7 @@ const Modal = class {
1918
2050
  };
1919
2051
  window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
1920
2052
  }
1921
- /**
1922
- * Recalculate isSheetModal because framework bindings (e.g., Angular)
1923
- * may not have been applied when componentWillLoad ran.
1924
- */
1925
- const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
1926
- this.isSheetModal = isSheetModal;
1927
- if (isSheetModal) {
2053
+ if (this.isSheetModal) {
1928
2054
  this.initSheetGesture();
1929
2055
  }
1930
2056
  else if (hasCardModal) {
@@ -2001,6 +2127,8 @@ const Modal = class {
2001
2127
  if (this.currentBreakpoint !== breakpoint) {
2002
2128
  this.currentBreakpoint = breakpoint;
2003
2129
  this.ionBreakpointDidChange.emit({ breakpoint });
2130
+ // Update safe-area overrides based on new position
2131
+ this.updateSafeAreaOverrides();
2004
2132
  }
2005
2133
  });
2006
2134
  this.gesture = gesture;
@@ -2168,6 +2296,7 @@ const Modal = class {
2168
2296
  }
2169
2297
  this.cleanupViewTransitionListener();
2170
2298
  this.cleanupParentRemovalObserver();
2299
+ this.cleanupSafeAreaOverrides();
2171
2300
  this.cleanupChildRoutePassthrough();
2172
2301
  }
2173
2302
  this.currentBreakpoint = undefined;
@@ -2288,6 +2417,9 @@ const Modal = class {
2288
2417
  this.viewTransitionAnimation = transitionAnimation;
2289
2418
  transitionAnimation.play().then(() => {
2290
2419
  this.viewTransitionAnimation = undefined;
2420
+ // Wait for a layout pass after the transition so getBoundingClientRect()
2421
+ // in getPositionBasedSafeAreaConfig() reflects the new dimensions.
2422
+ raf(() => this.updateSafeAreaOverrides());
2291
2423
  // After orientation transition, recreate the swipe-to-close gesture
2292
2424
  // with updated animation that reflects the new presenting element state
2293
2425
  this.reinitSwipeToClose();
@@ -2414,6 +2546,109 @@ const Modal = class {
2414
2546
  (_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
2415
2547
  this.parentRemovalObserver = undefined;
2416
2548
  }
2549
+ /**
2550
+ * Creates the context object for safe-area utilities.
2551
+ */
2552
+ getSafeAreaContext() {
2553
+ return {
2554
+ isSheetModal: this.isSheetModal,
2555
+ isCardModal: this.presentingElement !== undefined && getIonMode(this) === 'ios',
2556
+ presentingElement: this.presentingElement,
2557
+ breakpoints: this.breakpoints,
2558
+ currentBreakpoint: this.currentBreakpoint,
2559
+ };
2560
+ }
2561
+ /**
2562
+ * Sets initial safe-area overrides before modal animation.
2563
+ * Called in present() before animation starts.
2564
+ */
2565
+ setInitialSafeAreaOverrides() {
2566
+ const context = this.getSafeAreaContext();
2567
+ const safeAreaConfig = getInitialSafeAreaConfig(context);
2568
+ applySafeAreaOverrides(this.el, safeAreaConfig);
2569
+ }
2570
+ /**
2571
+ * Updates safe-area overrides during dynamic state changes.
2572
+ * Called after animations, during gestures, and on orientation changes.
2573
+ */
2574
+ updateSafeAreaOverrides() {
2575
+ const { wrapperEl, el } = this;
2576
+ const context = this.getSafeAreaContext();
2577
+ // Sheet modals: the wrapper extends beyond the viewport and is translated
2578
+ // via breakpoint gestures, making getBoundingClientRect unreliable for
2579
+ // edge detection. Instead, use breakpoint value to determine top safe-area.
2580
+ if (context.isSheetModal) {
2581
+ const needsTopSafeArea = context.currentBreakpoint === 1;
2582
+ applySafeAreaOverrides(el, {
2583
+ top: needsTopSafeArea ? 'inherit' : '0px',
2584
+ bottom: 'inherit',
2585
+ left: '0px',
2586
+ right: '0px',
2587
+ });
2588
+ return;
2589
+ }
2590
+ // Card modals have fixed safe-area requirements set by initial prediction.
2591
+ if (context.isCardModal)
2592
+ return;
2593
+ // wrapperEl is required for position-based detection below
2594
+ if (!wrapperEl)
2595
+ return;
2596
+ // Regular modals: use position-based detection to correctly handle both
2597
+ // fullscreen modals and centered dialogs with custom dimensions.
2598
+ const safeAreaConfig = getPositionBasedSafeAreaConfig(wrapperEl);
2599
+ applySafeAreaOverrides(el, safeAreaConfig);
2600
+ }
2601
+ /**
2602
+ * Applies padding-bottom to fullscreen modal wrapper to prevent
2603
+ * content from overlapping system navigation bar.
2604
+ */
2605
+ applyFullscreenSafeArea() {
2606
+ const { wrapperEl, el } = this;
2607
+ if (!wrapperEl)
2608
+ return;
2609
+ const context = this.getSafeAreaContext();
2610
+ if (context.isSheetModal || context.isCardModal)
2611
+ return;
2612
+ // Check for standard Ionic layout children (ion-content, ion-footer),
2613
+ // searching one level deep for wrapped components (e.g.,
2614
+ // <app-footer><ion-footer>...</ion-footer></app-footer>).
2615
+ let hasContent = false;
2616
+ let hasFooter = false;
2617
+ for (const child of Array.from(el.children)) {
2618
+ if (child.tagName === 'ION-CONTENT')
2619
+ hasContent = true;
2620
+ if (child.tagName === 'ION-FOOTER')
2621
+ hasFooter = true;
2622
+ for (const grandchild of Array.from(child.children)) {
2623
+ if (grandchild.tagName === 'ION-CONTENT')
2624
+ hasContent = true;
2625
+ if (grandchild.tagName === 'ION-FOOTER')
2626
+ hasFooter = true;
2627
+ }
2628
+ }
2629
+ // Only apply wrapper padding for standard Ionic layouts (has ion-content
2630
+ // but no ion-footer). Custom modals with raw HTML are fully
2631
+ // developer-controlled and should not be modified.
2632
+ if (!hasContent || hasFooter)
2633
+ return;
2634
+ // Reduce wrapper height by safe-area and add equivalent padding so the
2635
+ // total visual size stays the same but the flex content area shrinks.
2636
+ // Using height + padding instead of box-sizing: border-box avoids
2637
+ // breaking custom modals that set --border-width (border-box would
2638
+ // include the border inside the height, changing the layout).
2639
+ wrapperEl.style.setProperty('height', 'calc(var(--height) - var(--ion-safe-area-bottom, 0px))');
2640
+ wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
2641
+ }
2642
+ /**
2643
+ * Clears all safe-area overrides and padding from wrapper.
2644
+ */
2645
+ cleanupSafeAreaOverrides() {
2646
+ clearSafeAreaOverrides(this.el);
2647
+ if (this.wrapperEl) {
2648
+ this.wrapperEl.style.removeProperty('height');
2649
+ this.wrapperEl.style.removeProperty('padding-bottom');
2650
+ }
2651
+ }
2417
2652
  render() {
2418
2653
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
2419
2654
  const showHandle = handle !== false && isSheetModal;
@@ -2421,20 +2656,20 @@ const Modal = class {
2421
2656
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2422
2657
  const isHandleCycle = handleBehavior === 'cycle';
2423
2658
  const isSheetModalWithHandle = isSheetModal && showHandle;
2424
- return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
2659
+ return (h(Host, Object.assign({ key: 'f6c1245b5458693f19a040b362a98140c571297f', "no-router": true,
2425
2660
  // Allow the modal to be navigable when the handle is focusable
2426
2661
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2427
2662
  zIndex: `${20000 + this.overlayIndex}`,
2428
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'ee94ff8e09b691dd4ad4e4db1720f06bc3c5a469', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'bffd69b4635c22d9f249725bd952c1e93d5615c7', class: "modal-shadow" }), h("div", Object.assign({ key: '1d394d3c68916e464ff1fbf5242419f4a3d3cca1',
2663
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '6866fb2a265d40c761ed6bfb661a217948c571ef', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '00788de9267f1c064f16aa55f35250de16522ae6', class: "modal-shadow" }), h("div", Object.assign({ key: 'a575e1755bd25a7996cdb04b014a91d538e7bc94',
2429
2664
  /*
2430
2665
  role and aria-modal must be used on the
2431
2666
  same element. They must also be set inside the
2432
2667
  shadow DOM otherwise ion-button will not be highlighted
2433
2668
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2434
2669
  */
2435
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2dcf58792018e557e0c323baad2d672bc99c0bb1', class: "modal-handle",
2670
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'b99c64717037e9241479ea2f5bb84e79a466d918', class: "modal-handle",
2436
2671
  // Prevents the handle from receiving keyboard focus when it does not cycle
2437
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '44164b1e8710c3895400ad9f44ecd99873874ad5', onSlotchange: this.onSlotChange }))));
2672
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'ee672afd537b281aeeb73a55d61ceeb5dfae212a', onSlotchange: this.onSlotChange }))));
2438
2673
  }
2439
2674
  get el() { return getElement(this); }
2440
2675
  static get watchers() { return {