@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.
- package/components/ion-refresher.js +3 -32
- package/components/modal.js +326 -17
- package/components/popover.js +152 -30
- package/dist/cjs/ion-modal.cjs.entry.js +326 -17
- package/dist/cjs/ion-popover.cjs.entry.js +152 -30
- package/dist/cjs/ion-refresher_2.cjs.entry.js +3 -32
- package/dist/collection/components/modal/modal.ios.css +6 -5
- package/dist/collection/components/modal/modal.js +164 -15
- package/dist/collection/components/modal/modal.md.css +6 -5
- package/dist/collection/components/modal/safe-area-utils.js +165 -0
- package/dist/collection/components/popover/animations/ios.enter.js +33 -8
- package/dist/collection/components/popover/animations/md.enter.js +26 -6
- package/dist/collection/components/popover/utils.js +96 -19
- package/dist/collection/components/refresher/refresher.js +4 -70
- package/dist/docs.json +4 -49
- package/dist/esm/ion-modal.entry.js +326 -17
- package/dist/esm/ion-popover.entry.js +152 -30
- package/dist/esm/ion-refresher_2.entry.js +3 -32
- package/dist/html.html-data.json +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-3e69999d.entry.js +4 -0
- package/dist/ionic/p-639dd543.entry.js +4 -0
- package/dist/ionic/p-cc3446d5.entry.js +4 -0
- package/dist/types/components/modal/modal.d.ts +35 -0
- package/dist/types/components/modal/safe-area-utils.d.ts +66 -0
- package/dist/types/components/popover/utils.d.ts +23 -1
- package/dist/types/components/refresher/refresher-interface.d.ts +0 -7
- package/dist/types/components/refresher/refresher.d.ts +3 -16
- package/dist/types/components.d.ts +5 -16
- package/dist/types/interface.d.ts +1 -1
- package/hydrate/index.js +481 -79
- package/hydrate/index.mjs +481 -79
- package/package.json +1 -1
- package/dist/ionic/p-90f2d3ff.entry.js +0 -4
- package/dist/ionic/p-91840a80.entry.js +0 -4
- package/dist/ionic/p-f9061316.entry.js +0 -4
package/hydrate/index.js
CHANGED
|
@@ -22806,9 +22806,170 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
22806
22806
|
};
|
|
22807
22807
|
};
|
|
22808
22808
|
|
|
22809
|
-
|
|
22809
|
+
/**
|
|
22810
|
+
* These thresholds match the SCSS media query breakpoints in modal.vars.scss
|
|
22811
|
+
* that trigger the centered dialog layout (non-fullscreen modal).
|
|
22812
|
+
*
|
|
22813
|
+
* SCSS defines two height breakpoints: $modal-inset-min-height-small (600px)
|
|
22814
|
+
* and $modal-inset-min-height-large (768px). We use the smaller one because
|
|
22815
|
+
* that's the threshold where the modal transitions from fullscreen to centered
|
|
22816
|
+
* dialog — the larger breakpoint only increases the dialog's height.
|
|
22817
|
+
*/
|
|
22818
|
+
const MODAL_INSET_MIN_WIDTH = 768;
|
|
22819
|
+
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
22820
|
+
const EDGE_THRESHOLD = 5;
|
|
22821
|
+
/**
|
|
22822
|
+
* Cache for resolved root safe-area-top value, invalidated once per frame.
|
|
22823
|
+
*/
|
|
22824
|
+
let cachedRootSafeAreaTop = null;
|
|
22825
|
+
let cacheInvalidationScheduled$1 = false;
|
|
22826
|
+
/**
|
|
22827
|
+
* Determines if the current viewport meets the CSS media query conditions
|
|
22828
|
+
* that cause regular modals to render as centered dialogs instead of fullscreen.
|
|
22829
|
+
* Matches: @media (min-width: 768px) and (min-height: 600px)
|
|
22830
|
+
*/
|
|
22831
|
+
const isCenteredDialogViewport = () => {
|
|
22832
|
+
if (!win$1)
|
|
22833
|
+
return false;
|
|
22834
|
+
return win$1.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
|
|
22835
|
+
.matches;
|
|
22836
|
+
};
|
|
22837
|
+
/**
|
|
22838
|
+
* Resolves the current root --ion-safe-area-top value to pixels.
|
|
22839
|
+
* Uses a temporary element because getComputedStyle on :root returns
|
|
22840
|
+
* the declared value of custom properties (e.g. "env(safe-area-inset-top)")
|
|
22841
|
+
* rather than a resolved number.
|
|
22842
|
+
*
|
|
22843
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
22844
|
+
*/
|
|
22845
|
+
const getRootSafeAreaTop = () => {
|
|
22846
|
+
if (cachedRootSafeAreaTop !== null) {
|
|
22847
|
+
return cachedRootSafeAreaTop;
|
|
22848
|
+
}
|
|
22849
|
+
const doc = win$1 === null || win$1 === void 0 ? void 0 : win$1.document;
|
|
22850
|
+
if (!(doc === null || doc === void 0 ? void 0 : doc.body)) {
|
|
22851
|
+
return 0;
|
|
22852
|
+
}
|
|
22853
|
+
const el = doc.createElement('div');
|
|
22854
|
+
el.style.cssText =
|
|
22855
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + 'padding-top:var(--ion-safe-area-top,0px);';
|
|
22856
|
+
doc.body.appendChild(el);
|
|
22857
|
+
const value = parseFloat(getComputedStyle(el).paddingTop) || 0;
|
|
22858
|
+
el.remove();
|
|
22859
|
+
cachedRootSafeAreaTop = value;
|
|
22860
|
+
if (!cacheInvalidationScheduled$1) {
|
|
22861
|
+
cacheInvalidationScheduled$1 = true;
|
|
22862
|
+
raf(() => {
|
|
22863
|
+
cachedRootSafeAreaTop = null;
|
|
22864
|
+
cacheInvalidationScheduled$1 = false;
|
|
22865
|
+
});
|
|
22866
|
+
}
|
|
22867
|
+
return value;
|
|
22868
|
+
};
|
|
22869
|
+
/**
|
|
22870
|
+
* Returns the initial safe-area configuration based on modal type.
|
|
22871
|
+
* This is called before animation starts and uses configuration-based prediction.
|
|
22872
|
+
*
|
|
22873
|
+
* @param context - Modal context information
|
|
22874
|
+
* @returns SafeAreaConfig with initial safe-area values
|
|
22875
|
+
*/
|
|
22876
|
+
const getInitialSafeAreaConfig = (context) => {
|
|
22877
|
+
const { isSheetModal, isCardModal } = context;
|
|
22878
|
+
// Sheet modals always zero top safe-area. The sheet height offset from the
|
|
22879
|
+
// top edge is handled by --ion-modal-offset-top (set in modal.tsx) using
|
|
22880
|
+
// the resolved root value, so --ion-safe-area-top is never needed for
|
|
22881
|
+
// height calculation. Keeping it at 0px prevents header content from
|
|
22882
|
+
// getting double-offset padding.
|
|
22883
|
+
if (isSheetModal) {
|
|
22884
|
+
return {
|
|
22885
|
+
top: '0px',
|
|
22886
|
+
bottom: 'inherit',
|
|
22887
|
+
left: '0px',
|
|
22888
|
+
right: '0px',
|
|
22889
|
+
};
|
|
22890
|
+
}
|
|
22891
|
+
// Card modals need safe-area for height calculation.
|
|
22892
|
+
// Note: isCardModal is already gated on mode === 'ios' by the caller.
|
|
22893
|
+
if (isCardModal) {
|
|
22894
|
+
return {
|
|
22895
|
+
top: 'inherit',
|
|
22896
|
+
bottom: 'inherit',
|
|
22897
|
+
left: '0px',
|
|
22898
|
+
right: '0px',
|
|
22899
|
+
};
|
|
22900
|
+
}
|
|
22901
|
+
// On viewports that meet the centered dialog media query breakpoints,
|
|
22902
|
+
// regular modals render as centered dialogs (not fullscreen), so they
|
|
22903
|
+
// don't touch any screen edges and don't need safe-area insets.
|
|
22904
|
+
if (isCenteredDialogViewport()) {
|
|
22905
|
+
return {
|
|
22906
|
+
top: '0px',
|
|
22907
|
+
bottom: '0px',
|
|
22908
|
+
left: '0px',
|
|
22909
|
+
right: '0px',
|
|
22910
|
+
};
|
|
22911
|
+
}
|
|
22912
|
+
// Fullscreen modals on phone - inherit all safe areas
|
|
22913
|
+
return {
|
|
22914
|
+
top: 'inherit',
|
|
22915
|
+
bottom: 'inherit',
|
|
22916
|
+
left: 'inherit',
|
|
22917
|
+
right: 'inherit',
|
|
22918
|
+
};
|
|
22919
|
+
};
|
|
22920
|
+
/**
|
|
22921
|
+
* Returns safe-area configuration based on actual modal position.
|
|
22922
|
+
* Detects which edges the modal overlaps with and only applies safe-area to those edges.
|
|
22923
|
+
*
|
|
22924
|
+
* Note: On Android edge-to-edge (API 36+), getBoundingClientRect() may report
|
|
22925
|
+
* inconsistent values. Sheet and card modals avoid this by using configuration-based
|
|
22926
|
+
* prediction instead. Regular modals use coordinate detection which works reliably
|
|
22927
|
+
* on web and iOS; Android edge-to-edge may need a configuration-based fallback
|
|
22928
|
+
* once a reliable detection mechanism is available.
|
|
22929
|
+
*
|
|
22930
|
+
* @param wrapperEl - The modal wrapper element to measure
|
|
22931
|
+
* @returns SafeAreaConfig based on position
|
|
22932
|
+
*/
|
|
22933
|
+
const getPositionBasedSafeAreaConfig = (wrapperEl) => {
|
|
22934
|
+
var _a, _b;
|
|
22935
|
+
const rect = wrapperEl.getBoundingClientRect();
|
|
22936
|
+
const vh = (_a = win$1 === null || win$1 === void 0 ? void 0 : win$1.innerHeight) !== null && _a !== void 0 ? _a : 0;
|
|
22937
|
+
const vw = (_b = win$1 === null || win$1 === void 0 ? void 0 : win$1.innerWidth) !== null && _b !== void 0 ? _b : 0;
|
|
22938
|
+
// Only apply safe-area to sides where modal overlaps with screen edge
|
|
22939
|
+
return {
|
|
22940
|
+
top: rect.top <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22941
|
+
bottom: rect.bottom >= vh - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22942
|
+
left: rect.left <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22943
|
+
right: rect.right >= vw - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22944
|
+
};
|
|
22945
|
+
};
|
|
22946
|
+
/**
|
|
22947
|
+
* Applies safe-area CSS custom property overrides to the modal host element.
|
|
22948
|
+
*
|
|
22949
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
22950
|
+
* @param config - Safe-area configuration to apply
|
|
22951
|
+
*/
|
|
22952
|
+
const applySafeAreaOverrides = (hostEl, config) => {
|
|
22953
|
+
hostEl.style.setProperty('--ion-safe-area-top', config.top);
|
|
22954
|
+
hostEl.style.setProperty('--ion-safe-area-bottom', config.bottom);
|
|
22955
|
+
hostEl.style.setProperty('--ion-safe-area-left', config.left);
|
|
22956
|
+
hostEl.style.setProperty('--ion-safe-area-right', config.right);
|
|
22957
|
+
};
|
|
22958
|
+
/**
|
|
22959
|
+
* Clears safe-area CSS custom property overrides from the modal host element.
|
|
22960
|
+
*
|
|
22961
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
22962
|
+
*/
|
|
22963
|
+
const clearSafeAreaOverrides = (hostEl) => {
|
|
22964
|
+
hostEl.style.removeProperty('--ion-safe-area-top');
|
|
22965
|
+
hostEl.style.removeProperty('--ion-safe-area-bottom');
|
|
22966
|
+
hostEl.style.removeProperty('--ion-safe-area-left');
|
|
22967
|
+
hostEl.style.removeProperty('--ion-safe-area-right');
|
|
22968
|
+
};
|
|
22969
|
+
|
|
22970
|
+
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}";
|
|
22810
22971
|
|
|
22811
|
-
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
|
|
22972
|
+
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}";
|
|
22812
22973
|
|
|
22813
22974
|
// TODO(FW-2832): types
|
|
22814
22975
|
/**
|
|
@@ -23031,13 +23192,31 @@ class Modal {
|
|
|
23031
23192
|
}
|
|
23032
23193
|
}
|
|
23033
23194
|
onWindowResize() {
|
|
23034
|
-
|
|
23035
|
-
if (getIonMode$1(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
23195
|
+
if (!this.presented)
|
|
23036
23196
|
return;
|
|
23037
|
-
}
|
|
23038
23197
|
clearTimeout(this.resizeTimeout);
|
|
23039
23198
|
this.resizeTimeout = setTimeout(() => {
|
|
23040
|
-
this.
|
|
23199
|
+
const context = this.getSafeAreaContext();
|
|
23200
|
+
// iOS card modals: handle portrait/landscape view transitions
|
|
23201
|
+
if (context.isCardModal && !this.enterAnimation && !this.leaveAnimation) {
|
|
23202
|
+
this.handleViewTransition();
|
|
23203
|
+
}
|
|
23204
|
+
// Sheet modals: re-compute the internal offset property since safe-area
|
|
23205
|
+
// values may change on device rotation (e.g., portrait notch vs landscape).
|
|
23206
|
+
if (context.isSheetModal) {
|
|
23207
|
+
this.updateSheetOffsetTop();
|
|
23208
|
+
}
|
|
23209
|
+
// Regular (non-sheet, non-card) modals: update safe-area overrides
|
|
23210
|
+
// since the viewport may have crossed the centered-dialog breakpoint.
|
|
23211
|
+
if (!context.isSheetModal && !context.isCardModal) {
|
|
23212
|
+
this.updateSafeAreaOverrides();
|
|
23213
|
+
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply
|
|
23214
|
+
if (this.wrapperEl) {
|
|
23215
|
+
this.wrapperEl.style.removeProperty('height');
|
|
23216
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
23217
|
+
}
|
|
23218
|
+
this.applyFullscreenSafeArea();
|
|
23219
|
+
}
|
|
23041
23220
|
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
23042
23221
|
}
|
|
23043
23222
|
breakpointsChanged(breakpoints) {
|
|
@@ -23054,6 +23233,9 @@ class Modal {
|
|
|
23054
23233
|
this.triggerController.removeClickListener();
|
|
23055
23234
|
this.cleanupViewTransitionListener();
|
|
23056
23235
|
this.cleanupParentRemovalObserver();
|
|
23236
|
+
// Also called in dismiss() — intentional dual cleanup covers both
|
|
23237
|
+
// dismiss-then-remove and direct DOM removal without dismiss.
|
|
23238
|
+
this.cleanupSafeAreaOverrides();
|
|
23057
23239
|
}
|
|
23058
23240
|
componentWillLoad() {
|
|
23059
23241
|
var _a;
|
|
@@ -23214,6 +23396,11 @@ class Modal {
|
|
|
23214
23396
|
await waitForMount();
|
|
23215
23397
|
}
|
|
23216
23398
|
writeTask(() => this.el.classList.add('show-modal'));
|
|
23399
|
+
// Recalculate isSheetModal before safe-area setup because framework
|
|
23400
|
+
// bindings (e.g., Angular) may not have been applied when componentWillLoad ran.
|
|
23401
|
+
this.isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
23402
|
+
// Set initial safe-area overrides before animation
|
|
23403
|
+
this.setInitialSafeAreaOverrides();
|
|
23217
23404
|
const hasCardModal = presentingElement !== undefined;
|
|
23218
23405
|
/**
|
|
23219
23406
|
* We need to change the status bar at the
|
|
@@ -23231,6 +23418,10 @@ class Modal {
|
|
|
23231
23418
|
backdropBreakpoint: this.backdropBreakpoint,
|
|
23232
23419
|
expandToScroll: this.expandToScroll,
|
|
23233
23420
|
});
|
|
23421
|
+
// Update safe-area based on actual position after animation
|
|
23422
|
+
this.updateSafeAreaOverrides();
|
|
23423
|
+
// Apply fullscreen safe-area padding if needed
|
|
23424
|
+
this.applyFullscreenSafeArea();
|
|
23234
23425
|
/* tslint:disable-next-line */
|
|
23235
23426
|
if (typeof window !== 'undefined') {
|
|
23236
23427
|
/**
|
|
@@ -23262,13 +23453,7 @@ class Modal {
|
|
|
23262
23453
|
};
|
|
23263
23454
|
window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
|
|
23264
23455
|
}
|
|
23265
|
-
|
|
23266
|
-
* Recalculate isSheetModal because framework bindings (e.g., Angular)
|
|
23267
|
-
* may not have been applied when componentWillLoad ran.
|
|
23268
|
-
*/
|
|
23269
|
-
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
23270
|
-
this.isSheetModal = isSheetModal;
|
|
23271
|
-
if (isSheetModal) {
|
|
23456
|
+
if (this.isSheetModal) {
|
|
23272
23457
|
this.initSheetGesture();
|
|
23273
23458
|
}
|
|
23274
23459
|
else if (hasCardModal) {
|
|
@@ -23458,6 +23643,9 @@ class Modal {
|
|
|
23458
23643
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
23459
23644
|
return false;
|
|
23460
23645
|
}
|
|
23646
|
+
// Cancel any pending resize timeout to prevent stale updates during dismiss
|
|
23647
|
+
clearTimeout(this.resizeTimeout);
|
|
23648
|
+
this.resizeTimeout = undefined;
|
|
23461
23649
|
/**
|
|
23462
23650
|
* Because the canDismiss check below is async,
|
|
23463
23651
|
* we need to claim a lock before the check happens,
|
|
@@ -23512,6 +23700,7 @@ class Modal {
|
|
|
23512
23700
|
}
|
|
23513
23701
|
this.cleanupViewTransitionListener();
|
|
23514
23702
|
this.cleanupParentRemovalObserver();
|
|
23703
|
+
this.cleanupSafeAreaOverrides();
|
|
23515
23704
|
this.cleanupChildRoutePassthrough();
|
|
23516
23705
|
}
|
|
23517
23706
|
this.currentBreakpoint = undefined;
|
|
@@ -23632,6 +23821,9 @@ class Modal {
|
|
|
23632
23821
|
this.viewTransitionAnimation = transitionAnimation;
|
|
23633
23822
|
transitionAnimation.play().then(() => {
|
|
23634
23823
|
this.viewTransitionAnimation = undefined;
|
|
23824
|
+
// Wait for a layout pass after the transition so getBoundingClientRect()
|
|
23825
|
+
// in getPositionBasedSafeAreaConfig() reflects the new dimensions.
|
|
23826
|
+
raf(() => this.updateSafeAreaOverrides());
|
|
23635
23827
|
// After orientation transition, recreate the swipe-to-close gesture
|
|
23636
23828
|
// with updated animation that reflects the new presenting element state
|
|
23637
23829
|
this.reinitSwipeToClose();
|
|
@@ -23758,6 +23950,123 @@ class Modal {
|
|
|
23758
23950
|
(_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
23759
23951
|
this.parentRemovalObserver = undefined;
|
|
23760
23952
|
}
|
|
23953
|
+
/**
|
|
23954
|
+
* Creates the context object for safe-area utilities.
|
|
23955
|
+
*/
|
|
23956
|
+
getSafeAreaContext() {
|
|
23957
|
+
return {
|
|
23958
|
+
isSheetModal: this.isSheetModal,
|
|
23959
|
+
isCardModal: this.presentingElement !== undefined && getIonMode$1(this) === 'ios',
|
|
23960
|
+
presentingElement: this.presentingElement,
|
|
23961
|
+
breakpoints: this.breakpoints,
|
|
23962
|
+
currentBreakpoint: this.currentBreakpoint,
|
|
23963
|
+
};
|
|
23964
|
+
}
|
|
23965
|
+
/**
|
|
23966
|
+
* Sets initial safe-area overrides before modal animation.
|
|
23967
|
+
* Called in present() before animation starts.
|
|
23968
|
+
*
|
|
23969
|
+
* For sheet modals, the SCSS --height formula uses --ion-modal-offset-top
|
|
23970
|
+
* (an internal property) instead of --ion-safe-area-top. We resolve the
|
|
23971
|
+
* root safe-area-top to pixels and set --ion-modal-offset-top, decoupling
|
|
23972
|
+
* the height calculation from --ion-safe-area-top (which is zeroed for
|
|
23973
|
+
* sheets to prevent header content from getting double-offset padding).
|
|
23974
|
+
*/
|
|
23975
|
+
setInitialSafeAreaOverrides() {
|
|
23976
|
+
const context = this.getSafeAreaContext();
|
|
23977
|
+
const safeAreaConfig = getInitialSafeAreaConfig(context);
|
|
23978
|
+
applySafeAreaOverrides(this.el, safeAreaConfig);
|
|
23979
|
+
// Set the internal offset property with the resolved root safe-area-top value
|
|
23980
|
+
if (context.isSheetModal) {
|
|
23981
|
+
this.updateSheetOffsetTop();
|
|
23982
|
+
}
|
|
23983
|
+
}
|
|
23984
|
+
/**
|
|
23985
|
+
* Resolves the current root --ion-safe-area-top value and sets the
|
|
23986
|
+
* internal --ion-modal-offset-top property on the host element.
|
|
23987
|
+
* Called on present and on resize (e.g., device rotation changes safe-area).
|
|
23988
|
+
*/
|
|
23989
|
+
updateSheetOffsetTop() {
|
|
23990
|
+
const safeAreaTop = getRootSafeAreaTop();
|
|
23991
|
+
this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
|
|
23992
|
+
}
|
|
23993
|
+
/**
|
|
23994
|
+
* Updates safe-area overrides during dynamic state changes.
|
|
23995
|
+
* Called after animations, during gestures, and on orientation changes.
|
|
23996
|
+
*/
|
|
23997
|
+
updateSafeAreaOverrides() {
|
|
23998
|
+
const { wrapperEl, el } = this;
|
|
23999
|
+
const context = this.getSafeAreaContext();
|
|
24000
|
+
// Sheet modals: safe-area is fully determined at presentation time
|
|
24001
|
+
// (top is always 0px, height is frozen). Nothing to update.
|
|
24002
|
+
if (context.isSheetModal)
|
|
24003
|
+
return;
|
|
24004
|
+
// Card modals have fixed safe-area requirements set by initial prediction.
|
|
24005
|
+
if (context.isCardModal)
|
|
24006
|
+
return;
|
|
24007
|
+
// wrapperEl is required for position-based detection below
|
|
24008
|
+
if (!wrapperEl)
|
|
24009
|
+
return;
|
|
24010
|
+
// Regular modals: use position-based detection to correctly handle both
|
|
24011
|
+
// fullscreen modals and centered dialogs with custom dimensions.
|
|
24012
|
+
const safeAreaConfig = getPositionBasedSafeAreaConfig(wrapperEl);
|
|
24013
|
+
applySafeAreaOverrides(el, safeAreaConfig);
|
|
24014
|
+
}
|
|
24015
|
+
/**
|
|
24016
|
+
* Applies padding-bottom to fullscreen modal wrapper to prevent
|
|
24017
|
+
* content from overlapping system navigation bar.
|
|
24018
|
+
*/
|
|
24019
|
+
applyFullscreenSafeArea() {
|
|
24020
|
+
const { wrapperEl, el } = this;
|
|
24021
|
+
if (!wrapperEl)
|
|
24022
|
+
return;
|
|
24023
|
+
const context = this.getSafeAreaContext();
|
|
24024
|
+
if (context.isSheetModal || context.isCardModal)
|
|
24025
|
+
return;
|
|
24026
|
+
// Check for standard Ionic layout children (ion-content, ion-footer),
|
|
24027
|
+
// searching one level deep for wrapped components (e.g.,
|
|
24028
|
+
// <app-footer><ion-footer>...</ion-footer></app-footer>).
|
|
24029
|
+
// Note: uses a manual loop instead of querySelector(':scope > ...') because
|
|
24030
|
+
// Stencil's mock-doc (used in spec tests) does not support :scope.
|
|
24031
|
+
let hasContent = false;
|
|
24032
|
+
let hasFooter = false;
|
|
24033
|
+
for (const child of Array.from(el.children)) {
|
|
24034
|
+
if (child.tagName === 'ION-CONTENT')
|
|
24035
|
+
hasContent = true;
|
|
24036
|
+
if (child.tagName === 'ION-FOOTER')
|
|
24037
|
+
hasFooter = true;
|
|
24038
|
+
for (const grandchild of Array.from(child.children)) {
|
|
24039
|
+
if (grandchild.tagName === 'ION-CONTENT')
|
|
24040
|
+
hasContent = true;
|
|
24041
|
+
if (grandchild.tagName === 'ION-FOOTER')
|
|
24042
|
+
hasFooter = true;
|
|
24043
|
+
}
|
|
24044
|
+
}
|
|
24045
|
+
// Only apply wrapper padding for standard Ionic layouts (has ion-content
|
|
24046
|
+
// but no ion-footer). Custom modals with raw HTML are fully
|
|
24047
|
+
// developer-controlled and should not be modified.
|
|
24048
|
+
if (!hasContent || hasFooter)
|
|
24049
|
+
return;
|
|
24050
|
+
// Reduce wrapper height by safe-area and add equivalent padding so the
|
|
24051
|
+
// total visual size stays the same but the flex content area shrinks.
|
|
24052
|
+
// Using height + padding instead of box-sizing: border-box avoids
|
|
24053
|
+
// breaking custom modals that set --border-width (border-box would
|
|
24054
|
+
// include the border inside the height, changing the layout).
|
|
24055
|
+
wrapperEl.style.setProperty('height', 'calc(var(--height) - var(--ion-safe-area-bottom, 0px))');
|
|
24056
|
+
wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
24057
|
+
}
|
|
24058
|
+
/**
|
|
24059
|
+
* Clears all safe-area overrides and padding from wrapper.
|
|
24060
|
+
*/
|
|
24061
|
+
cleanupSafeAreaOverrides() {
|
|
24062
|
+
clearSafeAreaOverrides(this.el);
|
|
24063
|
+
// Remove internal sheet offset property
|
|
24064
|
+
this.el.style.removeProperty('--ion-modal-offset-top');
|
|
24065
|
+
if (this.wrapperEl) {
|
|
24066
|
+
this.wrapperEl.style.removeProperty('height');
|
|
24067
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
24068
|
+
}
|
|
24069
|
+
}
|
|
23761
24070
|
render() {
|
|
23762
24071
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
23763
24072
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -23765,20 +24074,20 @@ class Modal {
|
|
|
23765
24074
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
23766
24075
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
23767
24076
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
23768
|
-
return (hAsync(Host, Object.assign({ key: '
|
|
24077
|
+
return (hAsync(Host, Object.assign({ key: '038ae26c2cb040043c7d0c217a4de6a7ae909235', "no-router": true,
|
|
23769
24078
|
// Allow the modal to be navigable when the handle is focusable
|
|
23770
24079
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
23771
24080
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
23772
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: '
|
|
24081
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: '6bab796467b370e79b24ce17c5a3d51adb07ee8c', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: 'b9a19e86a2c7f079c88ef7a914c7c890aa601887', class: "modal-shadow" }), hAsync("div", Object.assign({ key: 'f4315c34aa78be4e3540361755964be2ddf43265',
|
|
23773
24082
|
/*
|
|
23774
24083
|
role and aria-modal must be used on the
|
|
23775
24084
|
same element. They must also be set inside the
|
|
23776
24085
|
shadow DOM otherwise ion-button will not be highlighted
|
|
23777
24086
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
23778
24087
|
*/
|
|
23779
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '
|
|
24088
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '3f7792bd65e0f8634e62bf27f9edcb636a03030a', class: "modal-handle",
|
|
23780
24089
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
23781
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: '
|
|
24090
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: '001e5b01831e240bd98f75ff62446b90b64d8394', onSlotchange: this.onSlotChange }))));
|
|
23782
24091
|
}
|
|
23783
24092
|
get el() { return getElement(this); }
|
|
23784
24093
|
static get watchers() { return {
|
|
@@ -26712,6 +27021,58 @@ class PickerColumnOption {
|
|
|
26712
27021
|
}; }
|
|
26713
27022
|
}
|
|
26714
27023
|
|
|
27024
|
+
/**
|
|
27025
|
+
* Shared per-frame cache for safe-area insets. Avoids creating a temporary
|
|
27026
|
+
* DOM element and forcing a synchronous reflow on every call within the same
|
|
27027
|
+
* frame (e.g., multiple popovers presenting simultaneously). Invalidated
|
|
27028
|
+
* after the next animation frame so values stay fresh across orientation
|
|
27029
|
+
* changes and viewport resizes.
|
|
27030
|
+
*/
|
|
27031
|
+
let cachedInsets = null;
|
|
27032
|
+
let cacheInvalidationScheduled = false;
|
|
27033
|
+
/**
|
|
27034
|
+
* Resolves the current --ion-safe-area-* CSS custom property values
|
|
27035
|
+
* to actual pixel numbers. A temporary element is needed because
|
|
27036
|
+
* getComputedStyle on :root returns the declared value of custom
|
|
27037
|
+
* properties (e.g. "env(safe-area-inset-top)") rather than a
|
|
27038
|
+
* resolved number. By assigning them to standard padding properties,
|
|
27039
|
+
* the browser resolves the value.
|
|
27040
|
+
*
|
|
27041
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
27042
|
+
*/
|
|
27043
|
+
const getSafeAreaInsets = (doc) => {
|
|
27044
|
+
if (cachedInsets !== null) {
|
|
27045
|
+
return cachedInsets;
|
|
27046
|
+
}
|
|
27047
|
+
if (doc.body === null) {
|
|
27048
|
+
return { top: 0, bottom: 0, left: 0, right: 0 };
|
|
27049
|
+
}
|
|
27050
|
+
const el = doc.createElement('div');
|
|
27051
|
+
el.style.cssText =
|
|
27052
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
|
|
27053
|
+
'padding-top:var(--ion-safe-area-top,0px);' +
|
|
27054
|
+
'padding-bottom:var(--ion-safe-area-bottom,0px);' +
|
|
27055
|
+
'padding-left:var(--ion-safe-area-left,0px);' +
|
|
27056
|
+
'padding-right:var(--ion-safe-area-right,0px);';
|
|
27057
|
+
doc.body.appendChild(el);
|
|
27058
|
+
const style = getComputedStyle(el);
|
|
27059
|
+
const insets = {
|
|
27060
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
27061
|
+
bottom: parseFloat(style.paddingBottom) || 0,
|
|
27062
|
+
left: parseFloat(style.paddingLeft) || 0,
|
|
27063
|
+
right: parseFloat(style.paddingRight) || 0,
|
|
27064
|
+
};
|
|
27065
|
+
el.remove();
|
|
27066
|
+
cachedInsets = insets;
|
|
27067
|
+
if (!cacheInvalidationScheduled) {
|
|
27068
|
+
cacheInvalidationScheduled = true;
|
|
27069
|
+
raf(() => {
|
|
27070
|
+
cachedInsets = null;
|
|
27071
|
+
cacheInvalidationScheduled = false;
|
|
27072
|
+
});
|
|
27073
|
+
}
|
|
27074
|
+
return insets;
|
|
27075
|
+
};
|
|
26715
27076
|
/**
|
|
26716
27077
|
* Returns the dimensions of the popover
|
|
26717
27078
|
* arrow on `ios` mode. If arrow is disabled
|
|
@@ -27347,8 +27708,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
|
|
|
27347
27708
|
* Adjusts popover positioning coordinates
|
|
27348
27709
|
* such that popover does not appear offscreen
|
|
27349
27710
|
* or overlapping safe area bounds.
|
|
27711
|
+
*
|
|
27712
|
+
* @internal - This is not part of the public API.
|
|
27350
27713
|
*/
|
|
27351
|
-
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight,
|
|
27714
|
+
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
|
|
27352
27715
|
let arrowTop = coordArrowTop;
|
|
27353
27716
|
const arrowLeft = coordArrowLeft;
|
|
27354
27717
|
let left = coordLeft;
|
|
@@ -27358,16 +27721,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27358
27721
|
let originY = contentOriginY;
|
|
27359
27722
|
let checkSafeAreaLeft = false;
|
|
27360
27723
|
let checkSafeAreaRight = false;
|
|
27724
|
+
let checkSafeAreaTop = false;
|
|
27725
|
+
let checkSafeAreaBottom = false;
|
|
27361
27726
|
const triggerTop = triggerCoordinates
|
|
27362
27727
|
? triggerCoordinates.top + triggerCoordinates.height
|
|
27363
27728
|
: bodyHeight / 2 - contentHeight / 2;
|
|
27364
27729
|
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
|
|
27365
27730
|
let addPopoverBottomClass = false;
|
|
27731
|
+
const hideArrow = false;
|
|
27366
27732
|
/**
|
|
27367
27733
|
* Adjust popover so it does not
|
|
27368
27734
|
* go off the left of the screen.
|
|
27369
27735
|
*/
|
|
27370
|
-
if (left < bodyPadding +
|
|
27736
|
+
if (left < bodyPadding + safeArea.left) {
|
|
27371
27737
|
left = bodyPadding;
|
|
27372
27738
|
checkSafeAreaLeft = true;
|
|
27373
27739
|
originX = 'left';
|
|
@@ -27376,41 +27742,58 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27376
27742
|
* go off the right of the screen.
|
|
27377
27743
|
*/
|
|
27378
27744
|
}
|
|
27379
|
-
else if (contentWidth + bodyPadding + left +
|
|
27745
|
+
else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
|
|
27380
27746
|
checkSafeAreaRight = true;
|
|
27381
27747
|
left = bodyWidth - contentWidth - bodyPadding;
|
|
27382
27748
|
originX = 'right';
|
|
27383
27749
|
}
|
|
27384
27750
|
/**
|
|
27385
27751
|
* Adjust popover so it does not
|
|
27386
|
-
* go off the
|
|
27752
|
+
* go off the bottom of the screen
|
|
27753
|
+
* or overlap the bottom safe area.
|
|
27387
27754
|
* If popover is on the left or the right of
|
|
27388
27755
|
* the trigger, then we should not adjust top
|
|
27389
27756
|
* margins.
|
|
27390
27757
|
*/
|
|
27391
|
-
if (triggerTop + triggerHeight + contentHeight > bodyHeight
|
|
27392
|
-
|
|
27758
|
+
if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
|
|
27759
|
+
(side === 'top' || side === 'bottom')) {
|
|
27760
|
+
/**
|
|
27761
|
+
* Calculate where the popover top would be if flipped
|
|
27762
|
+
* above the trigger. Check whether that position clears
|
|
27763
|
+
* the top safe area with room for bodyPadding.
|
|
27764
|
+
*/
|
|
27765
|
+
const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
|
|
27766
|
+
if (idealFlipTop >= safeArea.top + bodyPadding) {
|
|
27393
27767
|
/**
|
|
27394
|
-
*
|
|
27395
|
-
*
|
|
27396
|
-
*
|
|
27397
|
-
* off the bottom of the screen. However, we do not want to move
|
|
27398
|
-
* the popover up so much that it goes off the top of the screen.
|
|
27399
|
-
*
|
|
27400
|
-
* We chose 12 here so that the popover position looks a bit nicer as
|
|
27401
|
-
* it is not right up against the edge of the screen.
|
|
27768
|
+
* Popover fits above the trigger without overlapping
|
|
27769
|
+
* the top safe area. Use the ideal position directly —
|
|
27770
|
+
* no safe-area CSS vars needed since it clears both edges.
|
|
27402
27771
|
*/
|
|
27403
|
-
top =
|
|
27772
|
+
top = idealFlipTop;
|
|
27404
27773
|
arrowTop = top + contentHeight;
|
|
27405
27774
|
originY = 'bottom';
|
|
27406
27775
|
addPopoverBottomClass = true;
|
|
27407
|
-
/**
|
|
27408
|
-
* If not enough room for popover to appear
|
|
27409
|
-
* above trigger, then cut it off.
|
|
27410
|
-
*/
|
|
27411
27776
|
}
|
|
27412
27777
|
else {
|
|
27778
|
+
/**
|
|
27779
|
+
* Can't flip above the trigger. Constrain the bottom
|
|
27780
|
+
* edge while keeping the top near the trigger. This
|
|
27781
|
+
* creates a scrollable area anchored to the trigger.
|
|
27782
|
+
*/
|
|
27413
27783
|
bottom = bodyPadding;
|
|
27784
|
+
checkSafeAreaBottom = true;
|
|
27785
|
+
/**
|
|
27786
|
+
* When the trigger is near the bottom of the screen,
|
|
27787
|
+
* the calculated top may be at or past the bottom
|
|
27788
|
+
* constraint, leaving zero visible height. In that
|
|
27789
|
+
* case, pull top up to the top safe area so the
|
|
27790
|
+
* popover fills the available space between safe areas.
|
|
27791
|
+
*/
|
|
27792
|
+
const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
|
|
27793
|
+
if (top >= bottomEdge) {
|
|
27794
|
+
top = safeArea.top + bodyPadding;
|
|
27795
|
+
checkSafeAreaTop = true;
|
|
27796
|
+
}
|
|
27414
27797
|
}
|
|
27415
27798
|
}
|
|
27416
27799
|
return {
|
|
@@ -27421,9 +27804,12 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27421
27804
|
originY,
|
|
27422
27805
|
checkSafeAreaLeft,
|
|
27423
27806
|
checkSafeAreaRight,
|
|
27807
|
+
checkSafeAreaTop,
|
|
27808
|
+
checkSafeAreaBottom,
|
|
27424
27809
|
arrowTop,
|
|
27425
27810
|
arrowLeft,
|
|
27426
27811
|
addPopoverBottomClass,
|
|
27812
|
+
hideArrow,
|
|
27427
27813
|
};
|
|
27428
27814
|
};
|
|
27429
27815
|
const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
@@ -27453,6 +27839,15 @@ const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
|
27453
27839
|
};
|
|
27454
27840
|
|
|
27455
27841
|
const POPOVER_IOS_BODY_PADDING = 5;
|
|
27842
|
+
/**
|
|
27843
|
+
* Minimum edge margin for iOS popovers ensures visual spacing from screen
|
|
27844
|
+
* edges on devices without safe areas (e.g., older iPhones without notches).
|
|
27845
|
+
* Previously this was a hardcoded `safeAreaMargin = 25` that served dual
|
|
27846
|
+
* purpose: safe-area avoidance AND visual spacing. Now that actual safe-area
|
|
27847
|
+
* insets are read dynamically, this floor preserves the visual spacing when
|
|
27848
|
+
* safe-area values are 0.
|
|
27849
|
+
*/
|
|
27850
|
+
const POPOVER_IOS_MIN_EDGE_MARGIN = 25;
|
|
27456
27851
|
/**
|
|
27457
27852
|
* iOS Popover Enter Animation
|
|
27458
27853
|
*/
|
|
@@ -27478,8 +27873,16 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27478
27873
|
};
|
|
27479
27874
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
27480
27875
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
27481
|
-
const
|
|
27482
|
-
const
|
|
27876
|
+
const rawSafeArea = getSafeAreaInsets(doc);
|
|
27877
|
+
const safeArea = size === 'cover'
|
|
27878
|
+
? { top: 0, bottom: 0, left: 0, right: 0 }
|
|
27879
|
+
: {
|
|
27880
|
+
top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27881
|
+
bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27882
|
+
left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27883
|
+
right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27884
|
+
};
|
|
27885
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass} = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
|
|
27483
27886
|
const baseAnimation = createAnimation();
|
|
27484
27887
|
const backdropAnimation = createAnimation();
|
|
27485
27888
|
const contentAnimation = createAnimation();
|
|
@@ -27510,10 +27913,14 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27510
27913
|
baseEl.classList.add('popover-bottom');
|
|
27511
27914
|
}
|
|
27512
27915
|
if (bottom !== undefined) {
|
|
27513
|
-
|
|
27916
|
+
let bottomValue = `${bottom}px`;
|
|
27917
|
+
if (checkSafeAreaBottom) {
|
|
27918
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
27919
|
+
}
|
|
27920
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
27514
27921
|
}
|
|
27515
|
-
const safeAreaLeft = ' + var(--ion-safe-area-left,
|
|
27516
|
-
const safeAreaRight = ' - var(--ion-safe-area-right,
|
|
27922
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)';
|
|
27923
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0px)';
|
|
27517
27924
|
let leftValue = `${left}px`;
|
|
27518
27925
|
if (checkSafeAreaLeft) {
|
|
27519
27926
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
@@ -27521,7 +27928,11 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27521
27928
|
if (checkSafeAreaRight) {
|
|
27522
27929
|
leftValue = `${left}px${safeAreaRight}`;
|
|
27523
27930
|
}
|
|
27524
|
-
|
|
27931
|
+
let topValue = `${top}px`;
|
|
27932
|
+
if (checkSafeAreaTop) {
|
|
27933
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
27934
|
+
}
|
|
27935
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
27525
27936
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
27526
27937
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
27527
27938
|
if (arrowEl !== null) {
|
|
@@ -27597,7 +28008,23 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27597
28008
|
};
|
|
27598
28009
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
27599
28010
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
27600
|
-
|
|
28011
|
+
// MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas).
|
|
28012
|
+
// This is needed for Android edge-to-edge (API 36+) where system bars overlap content.
|
|
28013
|
+
const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc);
|
|
28014
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates);
|
|
28015
|
+
const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)';
|
|
28016
|
+
const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)';
|
|
28017
|
+
let leftValue = `${left}px`;
|
|
28018
|
+
if (checkSafeAreaLeft) {
|
|
28019
|
+
leftValue = `${left}px${safeAreaLeftCalc}`;
|
|
28020
|
+
}
|
|
28021
|
+
if (checkSafeAreaRight) {
|
|
28022
|
+
leftValue = `${left}px${safeAreaRightCalc}`;
|
|
28023
|
+
}
|
|
28024
|
+
let topValue = `${top}px`;
|
|
28025
|
+
if (checkSafeAreaTop) {
|
|
28026
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
28027
|
+
}
|
|
27601
28028
|
const baseAnimation = createAnimation();
|
|
27602
28029
|
const backdropAnimation = createAnimation();
|
|
27603
28030
|
const wrapperAnimation = createAnimation();
|
|
@@ -27614,13 +28041,17 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27614
28041
|
contentAnimation
|
|
27615
28042
|
.addElement(contentEl)
|
|
27616
28043
|
.beforeStyles({
|
|
27617
|
-
top: `calc(${
|
|
27618
|
-
left: `calc(${
|
|
28044
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
28045
|
+
left: `calc(${leftValue} + var(--offset-x, 0px))`,
|
|
27619
28046
|
'transform-origin': `${originY} ${originX}`,
|
|
27620
28047
|
})
|
|
27621
28048
|
.beforeAddWrite(() => {
|
|
27622
28049
|
if (bottom !== undefined) {
|
|
27623
|
-
|
|
28050
|
+
let bottomValue = `${bottom}px`;
|
|
28051
|
+
if (checkSafeAreaBottom) {
|
|
28052
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
28053
|
+
}
|
|
28054
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
27624
28055
|
}
|
|
27625
28056
|
})
|
|
27626
28057
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -27632,7 +28063,7 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27632
28063
|
if (size === 'cover') {
|
|
27633
28064
|
baseEl.style.setProperty('--width', `${contentWidth}px`);
|
|
27634
28065
|
}
|
|
27635
|
-
if (
|
|
28066
|
+
if (addPopoverBottomClass) {
|
|
27636
28067
|
baseEl.classList.add('popover-bottom');
|
|
27637
28068
|
}
|
|
27638
28069
|
})
|
|
@@ -29737,8 +30168,6 @@ class Refresher {
|
|
|
29737
30168
|
this.ionRefresh = createEvent(this, "ionRefresh", 7);
|
|
29738
30169
|
this.ionPull = createEvent(this, "ionPull", 7);
|
|
29739
30170
|
this.ionStart = createEvent(this, "ionStart", 7);
|
|
29740
|
-
this.ionPullStart = createEvent(this, "ionPullStart", 7);
|
|
29741
|
-
this.ionPullEnd = createEvent(this, "ionPullEnd", 7);
|
|
29742
30171
|
this.appliedStyles = false;
|
|
29743
30172
|
this.didStart = false;
|
|
29744
30173
|
this.progress = 0;
|
|
@@ -29793,8 +30222,8 @@ class Refresher {
|
|
|
29793
30222
|
* than `1`. The default value is `1` which is equal to the speed of the cursor.
|
|
29794
30223
|
* If a negative value is passed in, the factor will be `1` instead.
|
|
29795
30224
|
*
|
|
29796
|
-
* For example
|
|
29797
|
-
* `10` pixels, instead of `10` pixels
|
|
30225
|
+
* For example: If the value passed is `1.2` and the content is dragged by
|
|
30226
|
+
* `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
|
|
29798
30227
|
* (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
|
|
29799
30228
|
* will be `8` pixels, less than the amount the cursor has moved.
|
|
29800
30229
|
*
|
|
@@ -29844,9 +30273,6 @@ class Refresher {
|
|
|
29844
30273
|
this.animations = [];
|
|
29845
30274
|
this.progress = 0;
|
|
29846
30275
|
this.state = 1 /* RefresherState.Inactive */;
|
|
29847
|
-
this.ionPullEnd.emit({
|
|
29848
|
-
reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
|
|
29849
|
-
});
|
|
29850
30276
|
}
|
|
29851
30277
|
async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
|
|
29852
30278
|
this.elementToTransform = this.scrollEl;
|
|
@@ -29879,7 +30305,6 @@ class Refresher {
|
|
|
29879
30305
|
if (!this.didStart) {
|
|
29880
30306
|
this.didStart = true;
|
|
29881
30307
|
this.ionStart.emit();
|
|
29882
|
-
this.ionPullStart.emit();
|
|
29883
30308
|
}
|
|
29884
30309
|
// emit "pulling" on every move
|
|
29885
30310
|
if (this.pointerDown) {
|
|
@@ -29955,7 +30380,6 @@ class Refresher {
|
|
|
29955
30380
|
this.lastVelocityY = ev.velocityY;
|
|
29956
30381
|
},
|
|
29957
30382
|
onEnd: () => {
|
|
29958
|
-
const hadStarted = this.didStart;
|
|
29959
30383
|
this.pointerDown = false;
|
|
29960
30384
|
this.didStart = false;
|
|
29961
30385
|
if (this.needsCompletion) {
|
|
@@ -29965,13 +30389,6 @@ class Refresher {
|
|
|
29965
30389
|
else if (this.didRefresh) {
|
|
29966
30390
|
readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
|
|
29967
30391
|
}
|
|
29968
|
-
else if (hadStarted) {
|
|
29969
|
-
/**
|
|
29970
|
-
* User started pulling but released before reaching the refresh threshold.
|
|
29971
|
-
* Emit ionPullEnd to complete the event pair.
|
|
29972
|
-
*/
|
|
29973
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
29974
|
-
}
|
|
29975
30392
|
},
|
|
29976
30393
|
});
|
|
29977
30394
|
this.disabledChanged();
|
|
@@ -30018,7 +30435,6 @@ class Refresher {
|
|
|
30018
30435
|
ev.data.animation = animation;
|
|
30019
30436
|
animation.progressStart(false, 0);
|
|
30020
30437
|
this.ionStart.emit();
|
|
30021
|
-
this.ionPullStart.emit();
|
|
30022
30438
|
this.animations.push(animation);
|
|
30023
30439
|
return;
|
|
30024
30440
|
}
|
|
@@ -30041,7 +30457,6 @@ class Refresher {
|
|
|
30041
30457
|
this.animations = [];
|
|
30042
30458
|
this.gesture.enable(true);
|
|
30043
30459
|
this.state = 1 /* RefresherState.Inactive */;
|
|
30044
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
30045
30460
|
});
|
|
30046
30461
|
return;
|
|
30047
30462
|
}
|
|
@@ -30286,7 +30701,6 @@ class Refresher {
|
|
|
30286
30701
|
if (!this.didStart) {
|
|
30287
30702
|
this.didStart = true;
|
|
30288
30703
|
this.ionStart.emit();
|
|
30289
|
-
this.ionPullStart.emit();
|
|
30290
30704
|
}
|
|
30291
30705
|
// emit "pulling" on every move
|
|
30292
30706
|
this.ionPull.emit();
|
|
@@ -30330,15 +30744,6 @@ class Refresher {
|
|
|
30330
30744
|
* available right away.
|
|
30331
30745
|
*/
|
|
30332
30746
|
this.restoreOverflowStyle();
|
|
30333
|
-
/**
|
|
30334
|
-
* If ionPullStart was emitted, we need to emit ionPullEnd
|
|
30335
|
-
* even though the gesture was aborted before reaching the
|
|
30336
|
-
* pulling threshold.
|
|
30337
|
-
*/
|
|
30338
|
-
if (this.didStart) {
|
|
30339
|
-
this.didStart = false;
|
|
30340
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
30341
|
-
}
|
|
30342
30747
|
}
|
|
30343
30748
|
}
|
|
30344
30749
|
beginRefresh() {
|
|
@@ -30385,9 +30790,6 @@ class Refresher {
|
|
|
30385
30790
|
if (this.contentFullscreen && this.backgroundContentEl) {
|
|
30386
30791
|
(_a = this.backgroundContentEl) === null || _a === void 0 ? void 0 : _a.style.removeProperty('--offset-top');
|
|
30387
30792
|
}
|
|
30388
|
-
this.ionPullEnd.emit({
|
|
30389
|
-
reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
|
|
30390
|
-
});
|
|
30391
30793
|
}, 600);
|
|
30392
30794
|
// reset the styles on the scroll element
|
|
30393
30795
|
// set that the refresh is actively cancelling/completing
|
|
@@ -30441,7 +30843,7 @@ class Refresher {
|
|
|
30441
30843
|
}
|
|
30442
30844
|
render() {
|
|
30443
30845
|
const mode = getIonMode$1(this);
|
|
30444
|
-
return (hAsync(Host, { key: '
|
|
30846
|
+
return (hAsync(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
|
|
30445
30847
|
[mode]: true,
|
|
30446
30848
|
// Used internally for styling
|
|
30447
30849
|
[`refresher-${mode}`]: true,
|