@ionic/core 8.7.18-dev.11770674094.18396f54 → 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.
- package/components/modal.js +100 -26
- package/dist/cjs/ion-modal.cjs.entry.js +100 -26
- package/dist/collection/components/modal/modal.ios.css +6 -1
- package/dist/collection/components/modal/modal.js +56 -23
- package/dist/collection/components/modal/modal.md.css +6 -1
- package/dist/collection/components/modal/safe-area-utils.js +44 -2
- package/dist/docs.json +1 -1
- package/dist/esm/ion-modal.entry.js +100 -26
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-3e69999d.entry.js +4 -0
- package/dist/types/components/modal/modal.d.ts +12 -0
- package/dist/types/components/modal/safe-area-utils.d.ts +16 -5
- package/hydrate/index.js +100 -26
- package/hydrate/index.mjs +100 -26
- package/package.json +1 -1
- package/dist/ionic/p-36d78f36.entry.js +0 -4
package/components/modal.js
CHANGED
|
@@ -1483,6 +1483,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1483
1483
|
const MODAL_INSET_MIN_WIDTH = 768;
|
|
1484
1484
|
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
1485
1485
|
const EDGE_THRESHOLD = 5;
|
|
1486
|
+
/**
|
|
1487
|
+
* Cache for resolved root safe-area-top value, invalidated once per frame.
|
|
1488
|
+
*/
|
|
1489
|
+
let cachedRootSafeAreaTop = null;
|
|
1490
|
+
let cacheInvalidationScheduled = false;
|
|
1486
1491
|
/**
|
|
1487
1492
|
* Determines if the current viewport meets the CSS media query conditions
|
|
1488
1493
|
* that cause regular modals to render as centered dialogs instead of fullscreen.
|
|
@@ -1494,6 +1499,38 @@ const isCenteredDialogViewport = () => {
|
|
|
1494
1499
|
return win.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
|
|
1495
1500
|
.matches;
|
|
1496
1501
|
};
|
|
1502
|
+
/**
|
|
1503
|
+
* Resolves the current root --ion-safe-area-top value to pixels.
|
|
1504
|
+
* Uses a temporary element because getComputedStyle on :root returns
|
|
1505
|
+
* the declared value of custom properties (e.g. "env(safe-area-inset-top)")
|
|
1506
|
+
* rather than a resolved number.
|
|
1507
|
+
*
|
|
1508
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
1509
|
+
*/
|
|
1510
|
+
const getRootSafeAreaTop = () => {
|
|
1511
|
+
if (cachedRootSafeAreaTop !== null) {
|
|
1512
|
+
return cachedRootSafeAreaTop;
|
|
1513
|
+
}
|
|
1514
|
+
const doc = win === null || win === void 0 ? void 0 : win.document;
|
|
1515
|
+
if (!(doc === null || doc === void 0 ? void 0 : doc.body)) {
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1518
|
+
const el = doc.createElement('div');
|
|
1519
|
+
el.style.cssText =
|
|
1520
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + 'padding-top:var(--ion-safe-area-top,0px);';
|
|
1521
|
+
doc.body.appendChild(el);
|
|
1522
|
+
const value = parseFloat(getComputedStyle(el).paddingTop) || 0;
|
|
1523
|
+
el.remove();
|
|
1524
|
+
cachedRootSafeAreaTop = value;
|
|
1525
|
+
if (!cacheInvalidationScheduled) {
|
|
1526
|
+
cacheInvalidationScheduled = true;
|
|
1527
|
+
raf(() => {
|
|
1528
|
+
cachedRootSafeAreaTop = null;
|
|
1529
|
+
cacheInvalidationScheduled = false;
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
return value;
|
|
1533
|
+
};
|
|
1497
1534
|
/**
|
|
1498
1535
|
* Returns the initial safe-area configuration based on modal type.
|
|
1499
1536
|
* This is called before animation starts and uses configuration-based prediction.
|
|
@@ -1503,10 +1540,14 @@ const isCenteredDialogViewport = () => {
|
|
|
1503
1540
|
*/
|
|
1504
1541
|
const getInitialSafeAreaConfig = (context) => {
|
|
1505
1542
|
const { isSheetModal, isCardModal } = context;
|
|
1506
|
-
// Sheet modals
|
|
1543
|
+
// Sheet modals always zero top safe-area. The sheet height offset from the
|
|
1544
|
+
// top edge is handled by --ion-modal-offset-top (set in modal.tsx) using
|
|
1545
|
+
// the resolved root value, so --ion-safe-area-top is never needed for
|
|
1546
|
+
// height calculation. Keeping it at 0px prevents header content from
|
|
1547
|
+
// getting double-offset padding.
|
|
1507
1548
|
if (isSheetModal) {
|
|
1508
1549
|
return {
|
|
1509
|
-
top:
|
|
1550
|
+
top: '0px',
|
|
1510
1551
|
bottom: 'inherit',
|
|
1511
1552
|
left: '0px',
|
|
1512
1553
|
right: '0px',
|
|
@@ -1591,9 +1632,9 @@ const clearSafeAreaOverrides = (hostEl) => {
|
|
|
1591
1632
|
hostEl.style.removeProperty('--ion-safe-area-right');
|
|
1592
1633
|
};
|
|
1593
1634
|
|
|
1594
|
-
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-
|
|
1635
|
+
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-modal-offset-top, 0px) + 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}";
|
|
1595
1636
|
|
|
1596
|
-
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-
|
|
1637
|
+
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-modal-offset-top, 0px) + 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
1638
|
|
|
1598
1639
|
const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
1599
1640
|
constructor(registerHost) {
|
|
@@ -1810,13 +1851,31 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1810
1851
|
}
|
|
1811
1852
|
}
|
|
1812
1853
|
onWindowResize() {
|
|
1813
|
-
|
|
1814
|
-
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
1854
|
+
if (!this.presented)
|
|
1815
1855
|
return;
|
|
1816
|
-
}
|
|
1817
1856
|
clearTimeout(this.resizeTimeout);
|
|
1818
1857
|
this.resizeTimeout = setTimeout(() => {
|
|
1819
|
-
this.
|
|
1858
|
+
const context = this.getSafeAreaContext();
|
|
1859
|
+
// iOS card modals: handle portrait/landscape view transitions
|
|
1860
|
+
if (context.isCardModal && !this.enterAnimation && !this.leaveAnimation) {
|
|
1861
|
+
this.handleViewTransition();
|
|
1862
|
+
}
|
|
1863
|
+
// Sheet modals: re-compute the internal offset property since safe-area
|
|
1864
|
+
// values may change on device rotation (e.g., portrait notch vs landscape).
|
|
1865
|
+
if (context.isSheetModal) {
|
|
1866
|
+
this.updateSheetOffsetTop();
|
|
1867
|
+
}
|
|
1868
|
+
// Regular (non-sheet, non-card) modals: update safe-area overrides
|
|
1869
|
+
// since the viewport may have crossed the centered-dialog breakpoint.
|
|
1870
|
+
if (!context.isSheetModal && !context.isCardModal) {
|
|
1871
|
+
this.updateSafeAreaOverrides();
|
|
1872
|
+
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply
|
|
1873
|
+
if (this.wrapperEl) {
|
|
1874
|
+
this.wrapperEl.style.removeProperty('height');
|
|
1875
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
1876
|
+
}
|
|
1877
|
+
this.applyFullscreenSafeArea();
|
|
1878
|
+
}
|
|
1820
1879
|
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
1821
1880
|
}
|
|
1822
1881
|
breakpointsChanged(breakpoints) {
|
|
@@ -2130,8 +2189,6 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2130
2189
|
if (this.currentBreakpoint !== breakpoint) {
|
|
2131
2190
|
this.currentBreakpoint = breakpoint;
|
|
2132
2191
|
this.ionBreakpointDidChange.emit({ breakpoint });
|
|
2133
|
-
// Update safe-area overrides based on new position
|
|
2134
|
-
this.updateSafeAreaOverrides();
|
|
2135
2192
|
}
|
|
2136
2193
|
});
|
|
2137
2194
|
this.gesture = gesture;
|
|
@@ -2245,6 +2302,9 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2245
2302
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
2246
2303
|
return false;
|
|
2247
2304
|
}
|
|
2305
|
+
// Cancel any pending resize timeout to prevent stale updates during dismiss
|
|
2306
|
+
clearTimeout(this.resizeTimeout);
|
|
2307
|
+
this.resizeTimeout = undefined;
|
|
2248
2308
|
/**
|
|
2249
2309
|
* Because the canDismiss check below is async,
|
|
2250
2310
|
* we need to claim a lock before the check happens,
|
|
@@ -2564,11 +2624,30 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2564
2624
|
/**
|
|
2565
2625
|
* Sets initial safe-area overrides before modal animation.
|
|
2566
2626
|
* Called in present() before animation starts.
|
|
2627
|
+
*
|
|
2628
|
+
* For sheet modals, the SCSS --height formula uses --ion-modal-offset-top
|
|
2629
|
+
* (an internal property) instead of --ion-safe-area-top. We resolve the
|
|
2630
|
+
* root safe-area-top to pixels and set --ion-modal-offset-top, decoupling
|
|
2631
|
+
* the height calculation from --ion-safe-area-top (which is zeroed for
|
|
2632
|
+
* sheets to prevent header content from getting double-offset padding).
|
|
2567
2633
|
*/
|
|
2568
2634
|
setInitialSafeAreaOverrides() {
|
|
2569
2635
|
const context = this.getSafeAreaContext();
|
|
2570
2636
|
const safeAreaConfig = getInitialSafeAreaConfig(context);
|
|
2571
2637
|
applySafeAreaOverrides(this.el, safeAreaConfig);
|
|
2638
|
+
// Set the internal offset property with the resolved root safe-area-top value
|
|
2639
|
+
if (context.isSheetModal) {
|
|
2640
|
+
this.updateSheetOffsetTop();
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Resolves the current root --ion-safe-area-top value and sets the
|
|
2645
|
+
* internal --ion-modal-offset-top property on the host element.
|
|
2646
|
+
* Called on present and on resize (e.g., device rotation changes safe-area).
|
|
2647
|
+
*/
|
|
2648
|
+
updateSheetOffsetTop() {
|
|
2649
|
+
const safeAreaTop = getRootSafeAreaTop();
|
|
2650
|
+
this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
|
|
2572
2651
|
}
|
|
2573
2652
|
/**
|
|
2574
2653
|
* Updates safe-area overrides during dynamic state changes.
|
|
@@ -2577,19 +2656,10 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2577
2656
|
updateSafeAreaOverrides() {
|
|
2578
2657
|
const { wrapperEl, el } = this;
|
|
2579
2658
|
const context = this.getSafeAreaContext();
|
|
2580
|
-
// Sheet modals:
|
|
2581
|
-
//
|
|
2582
|
-
|
|
2583
|
-
if (context.isSheetModal) {
|
|
2584
|
-
const needsTopSafeArea = context.currentBreakpoint === 1;
|
|
2585
|
-
applySafeAreaOverrides(el, {
|
|
2586
|
-
top: needsTopSafeArea ? 'inherit' : '0px',
|
|
2587
|
-
bottom: 'inherit',
|
|
2588
|
-
left: '0px',
|
|
2589
|
-
right: '0px',
|
|
2590
|
-
});
|
|
2659
|
+
// Sheet modals: safe-area is fully determined at presentation time
|
|
2660
|
+
// (top is always 0px, height is frozen). Nothing to update.
|
|
2661
|
+
if (context.isSheetModal)
|
|
2591
2662
|
return;
|
|
2592
|
-
}
|
|
2593
2663
|
// Card modals have fixed safe-area requirements set by initial prediction.
|
|
2594
2664
|
if (context.isCardModal)
|
|
2595
2665
|
return;
|
|
@@ -2615,6 +2685,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2615
2685
|
// Check for standard Ionic layout children (ion-content, ion-footer),
|
|
2616
2686
|
// searching one level deep for wrapped components (e.g.,
|
|
2617
2687
|
// <app-footer><ion-footer>...</ion-footer></app-footer>).
|
|
2688
|
+
// Note: uses a manual loop instead of querySelector(':scope > ...') because
|
|
2689
|
+
// Stencil's mock-doc (used in spec tests) does not support :scope.
|
|
2618
2690
|
let hasContent = false;
|
|
2619
2691
|
let hasFooter = false;
|
|
2620
2692
|
for (const child of Array.from(el.children)) {
|
|
@@ -2647,6 +2719,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2647
2719
|
*/
|
|
2648
2720
|
cleanupSafeAreaOverrides() {
|
|
2649
2721
|
clearSafeAreaOverrides(this.el);
|
|
2722
|
+
// Remove internal sheet offset property
|
|
2723
|
+
this.el.style.removeProperty('--ion-modal-offset-top');
|
|
2650
2724
|
if (this.wrapperEl) {
|
|
2651
2725
|
this.wrapperEl.style.removeProperty('height');
|
|
2652
2726
|
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
@@ -2659,20 +2733,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2659
2733
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2660
2734
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2661
2735
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2662
|
-
return (h(Host, Object.assign({ key: '
|
|
2736
|
+
return (h(Host, Object.assign({ key: '038ae26c2cb040043c7d0c217a4de6a7ae909235', "no-router": true,
|
|
2663
2737
|
// Allow the modal to be navigable when the handle is focusable
|
|
2664
2738
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2665
2739
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2666
|
-
}, 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: '
|
|
2740
|
+
}, 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: '6bab796467b370e79b24ce17c5a3d51adb07ee8c', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'b9a19e86a2c7f079c88ef7a914c7c890aa601887', class: "modal-shadow" }), h("div", Object.assign({ key: 'f4315c34aa78be4e3540361755964be2ddf43265',
|
|
2667
2741
|
/*
|
|
2668
2742
|
role and aria-modal must be used on the
|
|
2669
2743
|
same element. They must also be set inside the
|
|
2670
2744
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2671
2745
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2672
2746
|
*/
|
|
2673
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2747
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '3f7792bd65e0f8634e62bf27f9edcb636a03030a', class: "modal-handle",
|
|
2674
2748
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2675
|
-
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: '
|
|
2749
|
+
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: '001e5b01831e240bd98f75ff62446b90b64d8394', onSlotchange: this.onSlotChange }))));
|
|
2676
2750
|
}
|
|
2677
2751
|
get el() { return this; }
|
|
2678
2752
|
static get watchers() { return {
|
|
@@ -1486,6 +1486,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1486
1486
|
const MODAL_INSET_MIN_WIDTH = 768;
|
|
1487
1487
|
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
1488
1488
|
const EDGE_THRESHOLD = 5;
|
|
1489
|
+
/**
|
|
1490
|
+
* Cache for resolved root safe-area-top value, invalidated once per frame.
|
|
1491
|
+
*/
|
|
1492
|
+
let cachedRootSafeAreaTop = null;
|
|
1493
|
+
let cacheInvalidationScheduled = false;
|
|
1489
1494
|
/**
|
|
1490
1495
|
* Determines if the current viewport meets the CSS media query conditions
|
|
1491
1496
|
* that cause regular modals to render as centered dialogs instead of fullscreen.
|
|
@@ -1497,6 +1502,38 @@ const isCenteredDialogViewport = () => {
|
|
|
1497
1502
|
return index.win.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
|
|
1498
1503
|
.matches;
|
|
1499
1504
|
};
|
|
1505
|
+
/**
|
|
1506
|
+
* Resolves the current root --ion-safe-area-top value to pixels.
|
|
1507
|
+
* Uses a temporary element because getComputedStyle on :root returns
|
|
1508
|
+
* the declared value of custom properties (e.g. "env(safe-area-inset-top)")
|
|
1509
|
+
* rather than a resolved number.
|
|
1510
|
+
*
|
|
1511
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
1512
|
+
*/
|
|
1513
|
+
const getRootSafeAreaTop = () => {
|
|
1514
|
+
if (cachedRootSafeAreaTop !== null) {
|
|
1515
|
+
return cachedRootSafeAreaTop;
|
|
1516
|
+
}
|
|
1517
|
+
const doc = index.win === null || index.win === void 0 ? void 0 : index.win.document;
|
|
1518
|
+
if (!(doc === null || doc === void 0 ? void 0 : doc.body)) {
|
|
1519
|
+
return 0;
|
|
1520
|
+
}
|
|
1521
|
+
const el = doc.createElement('div');
|
|
1522
|
+
el.style.cssText =
|
|
1523
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + 'padding-top:var(--ion-safe-area-top,0px);';
|
|
1524
|
+
doc.body.appendChild(el);
|
|
1525
|
+
const value = parseFloat(getComputedStyle(el).paddingTop) || 0;
|
|
1526
|
+
el.remove();
|
|
1527
|
+
cachedRootSafeAreaTop = value;
|
|
1528
|
+
if (!cacheInvalidationScheduled) {
|
|
1529
|
+
cacheInvalidationScheduled = true;
|
|
1530
|
+
helpers.raf(() => {
|
|
1531
|
+
cachedRootSafeAreaTop = null;
|
|
1532
|
+
cacheInvalidationScheduled = false;
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
return value;
|
|
1536
|
+
};
|
|
1500
1537
|
/**
|
|
1501
1538
|
* Returns the initial safe-area configuration based on modal type.
|
|
1502
1539
|
* This is called before animation starts and uses configuration-based prediction.
|
|
@@ -1506,10 +1543,14 @@ const isCenteredDialogViewport = () => {
|
|
|
1506
1543
|
*/
|
|
1507
1544
|
const getInitialSafeAreaConfig = (context) => {
|
|
1508
1545
|
const { isSheetModal, isCardModal } = context;
|
|
1509
|
-
// Sheet modals
|
|
1546
|
+
// Sheet modals always zero top safe-area. The sheet height offset from the
|
|
1547
|
+
// top edge is handled by --ion-modal-offset-top (set in modal.tsx) using
|
|
1548
|
+
// the resolved root value, so --ion-safe-area-top is never needed for
|
|
1549
|
+
// height calculation. Keeping it at 0px prevents header content from
|
|
1550
|
+
// getting double-offset padding.
|
|
1510
1551
|
if (isSheetModal) {
|
|
1511
1552
|
return {
|
|
1512
|
-
top:
|
|
1553
|
+
top: '0px',
|
|
1513
1554
|
bottom: 'inherit',
|
|
1514
1555
|
left: '0px',
|
|
1515
1556
|
right: '0px',
|
|
@@ -1594,9 +1635,9 @@ const clearSafeAreaOverrides = (hostEl) => {
|
|
|
1594
1635
|
hostEl.style.removeProperty('--ion-safe-area-right');
|
|
1595
1636
|
};
|
|
1596
1637
|
|
|
1597
|
-
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-
|
|
1638
|
+
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-modal-offset-top, 0px) + 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}";
|
|
1598
1639
|
|
|
1599
|
-
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-
|
|
1640
|
+
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-modal-offset-top, 0px) + 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}";
|
|
1600
1641
|
|
|
1601
1642
|
const Modal = class {
|
|
1602
1643
|
constructor(hostRef) {
|
|
@@ -1809,13 +1850,31 @@ const Modal = class {
|
|
|
1809
1850
|
}
|
|
1810
1851
|
}
|
|
1811
1852
|
onWindowResize() {
|
|
1812
|
-
|
|
1813
|
-
if (ionicGlobal.getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
1853
|
+
if (!this.presented)
|
|
1814
1854
|
return;
|
|
1815
|
-
}
|
|
1816
1855
|
clearTimeout(this.resizeTimeout);
|
|
1817
1856
|
this.resizeTimeout = setTimeout(() => {
|
|
1818
|
-
this.
|
|
1857
|
+
const context = this.getSafeAreaContext();
|
|
1858
|
+
// iOS card modals: handle portrait/landscape view transitions
|
|
1859
|
+
if (context.isCardModal && !this.enterAnimation && !this.leaveAnimation) {
|
|
1860
|
+
this.handleViewTransition();
|
|
1861
|
+
}
|
|
1862
|
+
// Sheet modals: re-compute the internal offset property since safe-area
|
|
1863
|
+
// values may change on device rotation (e.g., portrait notch vs landscape).
|
|
1864
|
+
if (context.isSheetModal) {
|
|
1865
|
+
this.updateSheetOffsetTop();
|
|
1866
|
+
}
|
|
1867
|
+
// Regular (non-sheet, non-card) modals: update safe-area overrides
|
|
1868
|
+
// since the viewport may have crossed the centered-dialog breakpoint.
|
|
1869
|
+
if (!context.isSheetModal && !context.isCardModal) {
|
|
1870
|
+
this.updateSafeAreaOverrides();
|
|
1871
|
+
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply
|
|
1872
|
+
if (this.wrapperEl) {
|
|
1873
|
+
this.wrapperEl.style.removeProperty('height');
|
|
1874
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
1875
|
+
}
|
|
1876
|
+
this.applyFullscreenSafeArea();
|
|
1877
|
+
}
|
|
1819
1878
|
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
1820
1879
|
}
|
|
1821
1880
|
breakpointsChanged(breakpoints) {
|
|
@@ -2129,8 +2188,6 @@ const Modal = class {
|
|
|
2129
2188
|
if (this.currentBreakpoint !== breakpoint) {
|
|
2130
2189
|
this.currentBreakpoint = breakpoint;
|
|
2131
2190
|
this.ionBreakpointDidChange.emit({ breakpoint });
|
|
2132
|
-
// Update safe-area overrides based on new position
|
|
2133
|
-
this.updateSafeAreaOverrides();
|
|
2134
2191
|
}
|
|
2135
2192
|
});
|
|
2136
2193
|
this.gesture = gesture;
|
|
@@ -2244,6 +2301,9 @@ const Modal = class {
|
|
|
2244
2301
|
if (this.gestureAnimationDismissing && role !== overlays.GESTURE) {
|
|
2245
2302
|
return false;
|
|
2246
2303
|
}
|
|
2304
|
+
// Cancel any pending resize timeout to prevent stale updates during dismiss
|
|
2305
|
+
clearTimeout(this.resizeTimeout);
|
|
2306
|
+
this.resizeTimeout = undefined;
|
|
2247
2307
|
/**
|
|
2248
2308
|
* Because the canDismiss check below is async,
|
|
2249
2309
|
* we need to claim a lock before the check happens,
|
|
@@ -2563,11 +2623,30 @@ const Modal = class {
|
|
|
2563
2623
|
/**
|
|
2564
2624
|
* Sets initial safe-area overrides before modal animation.
|
|
2565
2625
|
* Called in present() before animation starts.
|
|
2626
|
+
*
|
|
2627
|
+
* For sheet modals, the SCSS --height formula uses --ion-modal-offset-top
|
|
2628
|
+
* (an internal property) instead of --ion-safe-area-top. We resolve the
|
|
2629
|
+
* root safe-area-top to pixels and set --ion-modal-offset-top, decoupling
|
|
2630
|
+
* the height calculation from --ion-safe-area-top (which is zeroed for
|
|
2631
|
+
* sheets to prevent header content from getting double-offset padding).
|
|
2566
2632
|
*/
|
|
2567
2633
|
setInitialSafeAreaOverrides() {
|
|
2568
2634
|
const context = this.getSafeAreaContext();
|
|
2569
2635
|
const safeAreaConfig = getInitialSafeAreaConfig(context);
|
|
2570
2636
|
applySafeAreaOverrides(this.el, safeAreaConfig);
|
|
2637
|
+
// Set the internal offset property with the resolved root safe-area-top value
|
|
2638
|
+
if (context.isSheetModal) {
|
|
2639
|
+
this.updateSheetOffsetTop();
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Resolves the current root --ion-safe-area-top value and sets the
|
|
2644
|
+
* internal --ion-modal-offset-top property on the host element.
|
|
2645
|
+
* Called on present and on resize (e.g., device rotation changes safe-area).
|
|
2646
|
+
*/
|
|
2647
|
+
updateSheetOffsetTop() {
|
|
2648
|
+
const safeAreaTop = getRootSafeAreaTop();
|
|
2649
|
+
this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
|
|
2571
2650
|
}
|
|
2572
2651
|
/**
|
|
2573
2652
|
* Updates safe-area overrides during dynamic state changes.
|
|
@@ -2576,19 +2655,10 @@ const Modal = class {
|
|
|
2576
2655
|
updateSafeAreaOverrides() {
|
|
2577
2656
|
const { wrapperEl, el } = this;
|
|
2578
2657
|
const context = this.getSafeAreaContext();
|
|
2579
|
-
// Sheet modals:
|
|
2580
|
-
//
|
|
2581
|
-
|
|
2582
|
-
if (context.isSheetModal) {
|
|
2583
|
-
const needsTopSafeArea = context.currentBreakpoint === 1;
|
|
2584
|
-
applySafeAreaOverrides(el, {
|
|
2585
|
-
top: needsTopSafeArea ? 'inherit' : '0px',
|
|
2586
|
-
bottom: 'inherit',
|
|
2587
|
-
left: '0px',
|
|
2588
|
-
right: '0px',
|
|
2589
|
-
});
|
|
2658
|
+
// Sheet modals: safe-area is fully determined at presentation time
|
|
2659
|
+
// (top is always 0px, height is frozen). Nothing to update.
|
|
2660
|
+
if (context.isSheetModal)
|
|
2590
2661
|
return;
|
|
2591
|
-
}
|
|
2592
2662
|
// Card modals have fixed safe-area requirements set by initial prediction.
|
|
2593
2663
|
if (context.isCardModal)
|
|
2594
2664
|
return;
|
|
@@ -2614,6 +2684,8 @@ const Modal = class {
|
|
|
2614
2684
|
// Check for standard Ionic layout children (ion-content, ion-footer),
|
|
2615
2685
|
// searching one level deep for wrapped components (e.g.,
|
|
2616
2686
|
// <app-footer><ion-footer>...</ion-footer></app-footer>).
|
|
2687
|
+
// Note: uses a manual loop instead of querySelector(':scope > ...') because
|
|
2688
|
+
// Stencil's mock-doc (used in spec tests) does not support :scope.
|
|
2617
2689
|
let hasContent = false;
|
|
2618
2690
|
let hasFooter = false;
|
|
2619
2691
|
for (const child of Array.from(el.children)) {
|
|
@@ -2646,6 +2718,8 @@ const Modal = class {
|
|
|
2646
2718
|
*/
|
|
2647
2719
|
cleanupSafeAreaOverrides() {
|
|
2648
2720
|
clearSafeAreaOverrides(this.el);
|
|
2721
|
+
// Remove internal sheet offset property
|
|
2722
|
+
this.el.style.removeProperty('--ion-modal-offset-top');
|
|
2649
2723
|
if (this.wrapperEl) {
|
|
2650
2724
|
this.wrapperEl.style.removeProperty('height');
|
|
2651
2725
|
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
@@ -2658,20 +2732,20 @@ const Modal = class {
|
|
|
2658
2732
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2659
2733
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2660
2734
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2661
|
-
return (index$3.h(index$3.Host, Object.assign({ key: '
|
|
2735
|
+
return (index$3.h(index$3.Host, Object.assign({ key: '038ae26c2cb040043c7d0c217a4de6a7ae909235', "no-router": true,
|
|
2662
2736
|
// Allow the modal to be navigable when the handle is focusable
|
|
2663
2737
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2664
2738
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2665
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '
|
|
2739
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '6bab796467b370e79b24ce17c5a3d51adb07ee8c', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: 'b9a19e86a2c7f079c88ef7a914c7c890aa601887', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: 'f4315c34aa78be4e3540361755964be2ddf43265',
|
|
2666
2740
|
/*
|
|
2667
2741
|
role and aria-modal must be used on the
|
|
2668
2742
|
same element. They must also be set inside the
|
|
2669
2743
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2670
2744
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2671
2745
|
*/
|
|
2672
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: '
|
|
2746
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: '3f7792bd65e0f8634e62bf27f9edcb636a03030a', class: "modal-handle",
|
|
2673
2747
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2674
|
-
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) })), index$3.h("slot", { key: '
|
|
2748
|
+
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) })), index$3.h("slot", { key: '001e5b01831e240bd98f75ff62446b90b64d8394', onSlotchange: this.onSlotChange }))));
|
|
2675
2749
|
}
|
|
2676
2750
|
get el() { return index$3.getElement(this); }
|
|
2677
2751
|
static get watchers() { return {
|
|
@@ -186,9 +186,14 @@ ion-backdrop {
|
|
|
186
186
|
/**
|
|
187
187
|
* Ensure that the sheet modal does not
|
|
188
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).
|
|
189
194
|
*/
|
|
190
195
|
:host(.modal-sheet) {
|
|
191
|
-
--height: calc(100% - (var(--ion-
|
|
196
|
+
--height: calc(100% - (var(--ion-modal-offset-top, 0px) + 10px));
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
:host(.modal-sheet) .modal-wrapper,
|