@ionic/core 8.7.18-dev.11770316772.1e7b64e5 → 8.7.18-dev.11770674094.18396f54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ion-refresher.js +3 -32
- package/components/modal.js +248 -13
- package/components/popover.js +152 -30
- package/dist/cjs/ion-modal.cjs.entry.js +248 -13
- 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 +0 -4
- package/dist/collection/components/modal/modal.js +127 -11
- package/dist/collection/components/modal/modal.md.css +0 -4
- package/dist/collection/components/modal/safe-area-utils.js +123 -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 +248 -13
- 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-36d78f36.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 +23 -0
- package/dist/types/components/modal/safe-area-utils.d.ts +55 -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 +403 -75
- package/hydrate/index.mjs +403 -75
- 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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { win } from "../../utils/browser/index";
|
|
5
|
+
/**
|
|
6
|
+
* These thresholds match the SCSS media query breakpoints in modal.vars.scss
|
|
7
|
+
* that trigger the centered dialog layout (non-fullscreen modal).
|
|
8
|
+
*
|
|
9
|
+
* SCSS defines two height breakpoints: $modal-inset-min-height-small (600px)
|
|
10
|
+
* and $modal-inset-min-height-large (768px). We use the smaller one because
|
|
11
|
+
* that's the threshold where the modal transitions from fullscreen to centered
|
|
12
|
+
* dialog — the larger breakpoint only increases the dialog's height.
|
|
13
|
+
*/
|
|
14
|
+
const MODAL_INSET_MIN_WIDTH = 768;
|
|
15
|
+
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
16
|
+
const EDGE_THRESHOLD = 5;
|
|
17
|
+
/**
|
|
18
|
+
* Determines if the current viewport meets the CSS media query conditions
|
|
19
|
+
* that cause regular modals to render as centered dialogs instead of fullscreen.
|
|
20
|
+
* Matches: @media (min-width: 768px) and (min-height: 600px)
|
|
21
|
+
*/
|
|
22
|
+
const isCenteredDialogViewport = () => {
|
|
23
|
+
if (!win)
|
|
24
|
+
return false;
|
|
25
|
+
return win.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
|
|
26
|
+
.matches;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Returns the initial safe-area configuration based on modal type.
|
|
30
|
+
* This is called before animation starts and uses configuration-based prediction.
|
|
31
|
+
*
|
|
32
|
+
* @param context - Modal context information
|
|
33
|
+
* @returns SafeAreaConfig with initial safe-area values
|
|
34
|
+
*/
|
|
35
|
+
export const getInitialSafeAreaConfig = (context) => {
|
|
36
|
+
const { isSheetModal, isCardModal } = context;
|
|
37
|
+
// Sheet modals use bottom safe-area, and top safe-area only when fully expanded
|
|
38
|
+
if (isSheetModal) {
|
|
39
|
+
return {
|
|
40
|
+
top: context.currentBreakpoint === 1 ? 'inherit' : '0px',
|
|
41
|
+
bottom: 'inherit',
|
|
42
|
+
left: '0px',
|
|
43
|
+
right: '0px',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Card modals need safe-area for height calculation.
|
|
47
|
+
// Note: isCardModal is already gated on mode === 'ios' by the caller.
|
|
48
|
+
if (isCardModal) {
|
|
49
|
+
return {
|
|
50
|
+
top: 'inherit',
|
|
51
|
+
bottom: 'inherit',
|
|
52
|
+
left: '0px',
|
|
53
|
+
right: '0px',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// On viewports that meet the centered dialog media query breakpoints,
|
|
57
|
+
// regular modals render as centered dialogs (not fullscreen), so they
|
|
58
|
+
// don't touch any screen edges and don't need safe-area insets.
|
|
59
|
+
if (isCenteredDialogViewport()) {
|
|
60
|
+
return {
|
|
61
|
+
top: '0px',
|
|
62
|
+
bottom: '0px',
|
|
63
|
+
left: '0px',
|
|
64
|
+
right: '0px',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Fullscreen modals on phone - inherit all safe areas
|
|
68
|
+
return {
|
|
69
|
+
top: 'inherit',
|
|
70
|
+
bottom: 'inherit',
|
|
71
|
+
left: 'inherit',
|
|
72
|
+
right: 'inherit',
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Returns safe-area configuration based on actual modal position.
|
|
77
|
+
* Detects which edges the modal overlaps with and only applies safe-area to those edges.
|
|
78
|
+
*
|
|
79
|
+
* Note: On Android edge-to-edge (API 36+), getBoundingClientRect() may report
|
|
80
|
+
* inconsistent values. Sheet and card modals avoid this by using configuration-based
|
|
81
|
+
* prediction instead. Regular modals use coordinate detection which works reliably
|
|
82
|
+
* on web and iOS; Android edge-to-edge may need a configuration-based fallback
|
|
83
|
+
* once a reliable detection mechanism is available.
|
|
84
|
+
*
|
|
85
|
+
* @param wrapperEl - The modal wrapper element to measure
|
|
86
|
+
* @returns SafeAreaConfig based on position
|
|
87
|
+
*/
|
|
88
|
+
export const getPositionBasedSafeAreaConfig = (wrapperEl) => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const rect = wrapperEl.getBoundingClientRect();
|
|
91
|
+
const vh = (_a = win === null || win === void 0 ? void 0 : win.innerHeight) !== null && _a !== void 0 ? _a : 0;
|
|
92
|
+
const vw = (_b = win === null || win === void 0 ? void 0 : win.innerWidth) !== null && _b !== void 0 ? _b : 0;
|
|
93
|
+
// Only apply safe-area to sides where modal overlaps with screen edge
|
|
94
|
+
return {
|
|
95
|
+
top: rect.top <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
96
|
+
bottom: rect.bottom >= vh - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
97
|
+
left: rect.left <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
98
|
+
right: rect.right >= vw - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Applies safe-area CSS custom property overrides to the modal host element.
|
|
103
|
+
*
|
|
104
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
105
|
+
* @param config - Safe-area configuration to apply
|
|
106
|
+
*/
|
|
107
|
+
export const applySafeAreaOverrides = (hostEl, config) => {
|
|
108
|
+
hostEl.style.setProperty('--ion-safe-area-top', config.top);
|
|
109
|
+
hostEl.style.setProperty('--ion-safe-area-bottom', config.bottom);
|
|
110
|
+
hostEl.style.setProperty('--ion-safe-area-left', config.left);
|
|
111
|
+
hostEl.style.setProperty('--ion-safe-area-right', config.right);
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Clears safe-area CSS custom property overrides from the modal host element.
|
|
115
|
+
*
|
|
116
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
117
|
+
*/
|
|
118
|
+
export const clearSafeAreaOverrides = (hostEl) => {
|
|
119
|
+
hostEl.style.removeProperty('--ion-safe-area-top');
|
|
120
|
+
hostEl.style.removeProperty('--ion-safe-area-bottom');
|
|
121
|
+
hostEl.style.removeProperty('--ion-safe-area-left');
|
|
122
|
+
hostEl.style.removeProperty('--ion-safe-area-right');
|
|
123
|
+
};
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { createAnimation } from "../../../utils/animation/animation";
|
|
5
5
|
import { getElementRoot } from "../../../utils/helpers";
|
|
6
|
-
import { calculateWindowAdjustment, getArrowDimensions, getPopoverDimensions, getPopoverPosition, shouldShowArrow, } from "../utils";
|
|
6
|
+
import { calculateWindowAdjustment, getArrowDimensions, getPopoverDimensions, getPopoverPosition, getSafeAreaInsets, shouldShowArrow, } from "../utils";
|
|
7
7
|
const POPOVER_IOS_BODY_PADDING = 5;
|
|
8
|
+
/**
|
|
9
|
+
* Minimum edge margin for iOS popovers ensures visual spacing from screen
|
|
10
|
+
* edges on devices without safe areas (e.g., older iPhones without notches).
|
|
11
|
+
* Previously this was a hardcoded `safeAreaMargin = 25` that served dual
|
|
12
|
+
* purpose: safe-area avoidance AND visual spacing. Now that actual safe-area
|
|
13
|
+
* insets are read dynamically, this floor preserves the visual spacing when
|
|
14
|
+
* safe-area values are 0.
|
|
15
|
+
*/
|
|
16
|
+
const POPOVER_IOS_MIN_EDGE_MARGIN = 25;
|
|
8
17
|
/**
|
|
9
18
|
* iOS Popover Enter Animation
|
|
10
19
|
*/
|
|
@@ -30,8 +39,16 @@ export const iosEnterAnimation = (baseEl, opts) => {
|
|
|
30
39
|
};
|
|
31
40
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
32
41
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
42
|
+
const rawSafeArea = getSafeAreaInsets(doc);
|
|
43
|
+
const safeArea = size === 'cover'
|
|
44
|
+
? { top: 0, bottom: 0, left: 0, right: 0 }
|
|
45
|
+
: {
|
|
46
|
+
top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
47
|
+
bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
48
|
+
left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
49
|
+
right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
50
|
+
};
|
|
51
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, checkSafeAreaTop, checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass, hideArrow, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
|
|
35
52
|
const baseAnimation = createAnimation();
|
|
36
53
|
const backdropAnimation = createAnimation();
|
|
37
54
|
const contentAnimation = createAnimation();
|
|
@@ -62,10 +79,14 @@ export const iosEnterAnimation = (baseEl, opts) => {
|
|
|
62
79
|
baseEl.classList.add('popover-bottom');
|
|
63
80
|
}
|
|
64
81
|
if (bottom !== undefined) {
|
|
65
|
-
|
|
82
|
+
let bottomValue = `${bottom}px`;
|
|
83
|
+
if (checkSafeAreaBottom) {
|
|
84
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
85
|
+
}
|
|
86
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
66
87
|
}
|
|
67
|
-
const safeAreaLeft = ' + var(--ion-safe-area-left,
|
|
68
|
-
const safeAreaRight = ' - var(--ion-safe-area-right,
|
|
88
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)';
|
|
89
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0px)';
|
|
69
90
|
let leftValue = `${left}px`;
|
|
70
91
|
if (checkSafeAreaLeft) {
|
|
71
92
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
@@ -73,12 +94,16 @@ export const iosEnterAnimation = (baseEl, opts) => {
|
|
|
73
94
|
if (checkSafeAreaRight) {
|
|
74
95
|
leftValue = `${left}px${safeAreaRight}`;
|
|
75
96
|
}
|
|
76
|
-
|
|
97
|
+
let topValue = `${top}px`;
|
|
98
|
+
if (checkSafeAreaTop) {
|
|
99
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
100
|
+
}
|
|
101
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
77
102
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
78
103
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
79
104
|
if (arrowEl !== null) {
|
|
80
105
|
const didAdjustBounds = results.top !== top || results.left !== left;
|
|
81
|
-
const showArrow = shouldShowArrow(side, didAdjustBounds, ev, trigger);
|
|
106
|
+
const showArrow = !hideArrow && shouldShowArrow(side, didAdjustBounds, ev, trigger);
|
|
82
107
|
if (showArrow) {
|
|
83
108
|
arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
|
|
84
109
|
arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { createAnimation } from "../../../utils/animation/animation";
|
|
5
5
|
import { getElementRoot } from "../../../utils/helpers";
|
|
6
|
-
import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition } from "../utils";
|
|
6
|
+
import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition, getSafeAreaInsets } from "../utils";
|
|
7
7
|
const POPOVER_MD_BODY_PADDING = 12;
|
|
8
8
|
/**
|
|
9
9
|
* Md Popover Enter Animation
|
|
@@ -28,7 +28,23 @@ export const mdEnterAnimation = (baseEl, opts) => {
|
|
|
28
28
|
};
|
|
29
29
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
30
30
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
31
|
-
|
|
31
|
+
// MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas).
|
|
32
|
+
// This is needed for Android edge-to-edge (API 36+) where system bars overlap content.
|
|
33
|
+
const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc);
|
|
34
|
+
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);
|
|
35
|
+
const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)';
|
|
36
|
+
const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)';
|
|
37
|
+
let leftValue = `${left}px`;
|
|
38
|
+
if (checkSafeAreaLeft) {
|
|
39
|
+
leftValue = `${left}px${safeAreaLeftCalc}`;
|
|
40
|
+
}
|
|
41
|
+
if (checkSafeAreaRight) {
|
|
42
|
+
leftValue = `${left}px${safeAreaRightCalc}`;
|
|
43
|
+
}
|
|
44
|
+
let topValue = `${top}px`;
|
|
45
|
+
if (checkSafeAreaTop) {
|
|
46
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
47
|
+
}
|
|
32
48
|
const baseAnimation = createAnimation();
|
|
33
49
|
const backdropAnimation = createAnimation();
|
|
34
50
|
const wrapperAnimation = createAnimation();
|
|
@@ -45,13 +61,17 @@ export const mdEnterAnimation = (baseEl, opts) => {
|
|
|
45
61
|
contentAnimation
|
|
46
62
|
.addElement(contentEl)
|
|
47
63
|
.beforeStyles({
|
|
48
|
-
top: `calc(${
|
|
49
|
-
left: `calc(${
|
|
64
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
65
|
+
left: `calc(${leftValue} + var(--offset-x, 0px))`,
|
|
50
66
|
'transform-origin': `${originY} ${originX}`,
|
|
51
67
|
})
|
|
52
68
|
.beforeAddWrite(() => {
|
|
53
69
|
if (bottom !== undefined) {
|
|
54
|
-
|
|
70
|
+
let bottomValue = `${bottom}px`;
|
|
71
|
+
if (checkSafeAreaBottom) {
|
|
72
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
73
|
+
}
|
|
74
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
55
75
|
}
|
|
56
76
|
})
|
|
57
77
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -63,7 +83,7 @@ export const mdEnterAnimation = (baseEl, opts) => {
|
|
|
63
83
|
if (size === 'cover') {
|
|
64
84
|
baseEl.style.setProperty('--width', `${contentWidth}px`);
|
|
65
85
|
}
|
|
66
|
-
if (
|
|
86
|
+
if (addPopoverBottomClass) {
|
|
67
87
|
baseEl.classList.add('popover-bottom');
|
|
68
88
|
}
|
|
69
89
|
})
|
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { getElementRoot, raf } from "../../utils/helpers";
|
|
5
|
+
/**
|
|
6
|
+
* Shared per-frame cache for safe-area insets. Avoids creating a temporary
|
|
7
|
+
* DOM element and forcing a synchronous reflow on every call within the same
|
|
8
|
+
* frame (e.g., multiple popovers presenting simultaneously). Invalidated
|
|
9
|
+
* after the next animation frame so values stay fresh across orientation
|
|
10
|
+
* changes and viewport resizes.
|
|
11
|
+
*/
|
|
12
|
+
let cachedInsets = null;
|
|
13
|
+
let cacheInvalidationScheduled = false;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the current --ion-safe-area-* CSS custom property values
|
|
16
|
+
* to actual pixel numbers. A temporary element is needed because
|
|
17
|
+
* getComputedStyle on :root returns the declared value of custom
|
|
18
|
+
* properties (e.g. "env(safe-area-inset-top)") rather than a
|
|
19
|
+
* resolved number. By assigning them to standard padding properties,
|
|
20
|
+
* the browser resolves the value.
|
|
21
|
+
*
|
|
22
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
23
|
+
*/
|
|
24
|
+
export const getSafeAreaInsets = (doc) => {
|
|
25
|
+
if (cachedInsets !== null) {
|
|
26
|
+
return cachedInsets;
|
|
27
|
+
}
|
|
28
|
+
if (doc.body === null) {
|
|
29
|
+
return { top: 0, bottom: 0, left: 0, right: 0 };
|
|
30
|
+
}
|
|
31
|
+
const el = doc.createElement('div');
|
|
32
|
+
el.style.cssText =
|
|
33
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
|
|
34
|
+
'padding-top:var(--ion-safe-area-top,0px);' +
|
|
35
|
+
'padding-bottom:var(--ion-safe-area-bottom,0px);' +
|
|
36
|
+
'padding-left:var(--ion-safe-area-left,0px);' +
|
|
37
|
+
'padding-right:var(--ion-safe-area-right,0px);';
|
|
38
|
+
doc.body.appendChild(el);
|
|
39
|
+
const style = getComputedStyle(el);
|
|
40
|
+
const insets = {
|
|
41
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
42
|
+
bottom: parseFloat(style.paddingBottom) || 0,
|
|
43
|
+
left: parseFloat(style.paddingLeft) || 0,
|
|
44
|
+
right: parseFloat(style.paddingRight) || 0,
|
|
45
|
+
};
|
|
46
|
+
el.remove();
|
|
47
|
+
cachedInsets = insets;
|
|
48
|
+
if (!cacheInvalidationScheduled) {
|
|
49
|
+
cacheInvalidationScheduled = true;
|
|
50
|
+
raf(() => {
|
|
51
|
+
cachedInsets = null;
|
|
52
|
+
cacheInvalidationScheduled = false;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return insets;
|
|
56
|
+
};
|
|
5
57
|
/**
|
|
6
58
|
* Returns the dimensions of the popover
|
|
7
59
|
* arrow on `ios` mode. If arrow is disabled
|
|
@@ -639,8 +691,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
|
|
|
639
691
|
* Adjusts popover positioning coordinates
|
|
640
692
|
* such that popover does not appear offscreen
|
|
641
693
|
* or overlapping safe area bounds.
|
|
694
|
+
*
|
|
695
|
+
* @internal - This is not part of the public API.
|
|
642
696
|
*/
|
|
643
|
-
export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight,
|
|
697
|
+
export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
|
|
644
698
|
let arrowTop = coordArrowTop;
|
|
645
699
|
const arrowLeft = coordArrowLeft;
|
|
646
700
|
let left = coordLeft;
|
|
@@ -650,16 +704,19 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
650
704
|
let originY = contentOriginY;
|
|
651
705
|
let checkSafeAreaLeft = false;
|
|
652
706
|
let checkSafeAreaRight = false;
|
|
707
|
+
let checkSafeAreaTop = false;
|
|
708
|
+
let checkSafeAreaBottom = false;
|
|
653
709
|
const triggerTop = triggerCoordinates
|
|
654
710
|
? triggerCoordinates.top + triggerCoordinates.height
|
|
655
711
|
: bodyHeight / 2 - contentHeight / 2;
|
|
656
712
|
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
|
|
657
713
|
let addPopoverBottomClass = false;
|
|
714
|
+
const hideArrow = false;
|
|
658
715
|
/**
|
|
659
716
|
* Adjust popover so it does not
|
|
660
717
|
* go off the left of the screen.
|
|
661
718
|
*/
|
|
662
|
-
if (left < bodyPadding +
|
|
719
|
+
if (left < bodyPadding + safeArea.left) {
|
|
663
720
|
left = bodyPadding;
|
|
664
721
|
checkSafeAreaLeft = true;
|
|
665
722
|
originX = 'left';
|
|
@@ -668,41 +725,58 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
668
725
|
* go off the right of the screen.
|
|
669
726
|
*/
|
|
670
727
|
}
|
|
671
|
-
else if (contentWidth + bodyPadding + left +
|
|
728
|
+
else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
|
|
672
729
|
checkSafeAreaRight = true;
|
|
673
730
|
left = bodyWidth - contentWidth - bodyPadding;
|
|
674
731
|
originX = 'right';
|
|
675
732
|
}
|
|
676
733
|
/**
|
|
677
734
|
* Adjust popover so it does not
|
|
678
|
-
* go off the
|
|
735
|
+
* go off the bottom of the screen
|
|
736
|
+
* or overlap the bottom safe area.
|
|
679
737
|
* If popover is on the left or the right of
|
|
680
738
|
* the trigger, then we should not adjust top
|
|
681
739
|
* margins.
|
|
682
740
|
*/
|
|
683
|
-
if (triggerTop + triggerHeight + contentHeight > bodyHeight
|
|
684
|
-
|
|
741
|
+
if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
|
|
742
|
+
(side === 'top' || side === 'bottom')) {
|
|
743
|
+
/**
|
|
744
|
+
* Calculate where the popover top would be if flipped
|
|
745
|
+
* above the trigger. Check whether that position clears
|
|
746
|
+
* the top safe area with room for bodyPadding.
|
|
747
|
+
*/
|
|
748
|
+
const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
|
|
749
|
+
if (idealFlipTop >= safeArea.top + bodyPadding) {
|
|
685
750
|
/**
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
* off the bottom of the screen. However, we do not want to move
|
|
690
|
-
* the popover up so much that it goes off the top of the screen.
|
|
691
|
-
*
|
|
692
|
-
* We chose 12 here so that the popover position looks a bit nicer as
|
|
693
|
-
* it is not right up against the edge of the screen.
|
|
751
|
+
* Popover fits above the trigger without overlapping
|
|
752
|
+
* the top safe area. Use the ideal position directly —
|
|
753
|
+
* no safe-area CSS vars needed since it clears both edges.
|
|
694
754
|
*/
|
|
695
|
-
top =
|
|
755
|
+
top = idealFlipTop;
|
|
696
756
|
arrowTop = top + contentHeight;
|
|
697
757
|
originY = 'bottom';
|
|
698
758
|
addPopoverBottomClass = true;
|
|
699
|
-
/**
|
|
700
|
-
* If not enough room for popover to appear
|
|
701
|
-
* above trigger, then cut it off.
|
|
702
|
-
*/
|
|
703
759
|
}
|
|
704
760
|
else {
|
|
761
|
+
/**
|
|
762
|
+
* Can't flip above the trigger. Constrain the bottom
|
|
763
|
+
* edge while keeping the top near the trigger. This
|
|
764
|
+
* creates a scrollable area anchored to the trigger.
|
|
765
|
+
*/
|
|
705
766
|
bottom = bodyPadding;
|
|
767
|
+
checkSafeAreaBottom = true;
|
|
768
|
+
/**
|
|
769
|
+
* When the trigger is near the bottom of the screen,
|
|
770
|
+
* the calculated top may be at or past the bottom
|
|
771
|
+
* constraint, leaving zero visible height. In that
|
|
772
|
+
* case, pull top up to the top safe area so the
|
|
773
|
+
* popover fills the available space between safe areas.
|
|
774
|
+
*/
|
|
775
|
+
const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
|
|
776
|
+
if (top >= bottomEdge) {
|
|
777
|
+
top = safeArea.top + bodyPadding;
|
|
778
|
+
checkSafeAreaTop = true;
|
|
779
|
+
}
|
|
706
780
|
}
|
|
707
781
|
}
|
|
708
782
|
return {
|
|
@@ -713,9 +787,12 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
|
|
|
713
787
|
originY,
|
|
714
788
|
checkSafeAreaLeft,
|
|
715
789
|
checkSafeAreaRight,
|
|
790
|
+
checkSafeAreaTop,
|
|
791
|
+
checkSafeAreaBottom,
|
|
716
792
|
arrowTop,
|
|
717
793
|
arrowLeft,
|
|
718
794
|
addPopoverBottomClass,
|
|
795
|
+
hideArrow,
|
|
719
796
|
};
|
|
720
797
|
};
|
|
721
798
|
export const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
@@ -68,8 +68,8 @@ export class Refresher {
|
|
|
68
68
|
* than `1`. The default value is `1` which is equal to the speed of the cursor.
|
|
69
69
|
* If a negative value is passed in, the factor will be `1` instead.
|
|
70
70
|
*
|
|
71
|
-
* For example
|
|
72
|
-
* `10` pixels, instead of `10` pixels
|
|
71
|
+
* For example: If the value passed is `1.2` and the content is dragged by
|
|
72
|
+
* `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels
|
|
73
73
|
* (an increase of 20 percent). If the value passed is `0.8`, the dragged amount
|
|
74
74
|
* will be `8` pixels, less than the amount the cursor has moved.
|
|
75
75
|
*
|
|
@@ -119,9 +119,6 @@ export class Refresher {
|
|
|
119
119
|
this.animations = [];
|
|
120
120
|
this.progress = 0;
|
|
121
121
|
this.state = 1 /* RefresherState.Inactive */;
|
|
122
|
-
this.ionPullEnd.emit({
|
|
123
|
-
reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
|
|
124
|
-
});
|
|
125
122
|
}
|
|
126
123
|
async setupiOSNativeRefresher(pullingSpinner, refreshingSpinner) {
|
|
127
124
|
this.elementToTransform = this.scrollEl;
|
|
@@ -154,7 +151,6 @@ export class Refresher {
|
|
|
154
151
|
if (!this.didStart) {
|
|
155
152
|
this.didStart = true;
|
|
156
153
|
this.ionStart.emit();
|
|
157
|
-
this.ionPullStart.emit();
|
|
158
154
|
}
|
|
159
155
|
// emit "pulling" on every move
|
|
160
156
|
if (this.pointerDown) {
|
|
@@ -230,7 +226,6 @@ export class Refresher {
|
|
|
230
226
|
this.lastVelocityY = ev.velocityY;
|
|
231
227
|
},
|
|
232
228
|
onEnd: () => {
|
|
233
|
-
const hadStarted = this.didStart;
|
|
234
229
|
this.pointerDown = false;
|
|
235
230
|
this.didStart = false;
|
|
236
231
|
if (this.needsCompletion) {
|
|
@@ -240,13 +235,6 @@ export class Refresher {
|
|
|
240
235
|
else if (this.didRefresh) {
|
|
241
236
|
readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
|
|
242
237
|
}
|
|
243
|
-
else if (hadStarted) {
|
|
244
|
-
/**
|
|
245
|
-
* User started pulling but released before reaching the refresh threshold.
|
|
246
|
-
* Emit ionPullEnd to complete the event pair.
|
|
247
|
-
*/
|
|
248
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
249
|
-
}
|
|
250
238
|
},
|
|
251
239
|
});
|
|
252
240
|
this.disabledChanged();
|
|
@@ -293,7 +281,6 @@ export class Refresher {
|
|
|
293
281
|
ev.data.animation = animation;
|
|
294
282
|
animation.progressStart(false, 0);
|
|
295
283
|
this.ionStart.emit();
|
|
296
|
-
this.ionPullStart.emit();
|
|
297
284
|
this.animations.push(animation);
|
|
298
285
|
return;
|
|
299
286
|
}
|
|
@@ -316,7 +303,6 @@ export class Refresher {
|
|
|
316
303
|
this.animations = [];
|
|
317
304
|
this.gesture.enable(true);
|
|
318
305
|
this.state = 1 /* RefresherState.Inactive */;
|
|
319
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
320
306
|
});
|
|
321
307
|
return;
|
|
322
308
|
}
|
|
@@ -561,7 +547,6 @@ export class Refresher {
|
|
|
561
547
|
if (!this.didStart) {
|
|
562
548
|
this.didStart = true;
|
|
563
549
|
this.ionStart.emit();
|
|
564
|
-
this.ionPullStart.emit();
|
|
565
550
|
}
|
|
566
551
|
// emit "pulling" on every move
|
|
567
552
|
this.ionPull.emit();
|
|
@@ -605,15 +590,6 @@ export class Refresher {
|
|
|
605
590
|
* available right away.
|
|
606
591
|
*/
|
|
607
592
|
this.restoreOverflowStyle();
|
|
608
|
-
/**
|
|
609
|
-
* If ionPullStart was emitted, we need to emit ionPullEnd
|
|
610
|
-
* even though the gesture was aborted before reaching the
|
|
611
|
-
* pulling threshold.
|
|
612
|
-
*/
|
|
613
|
-
if (this.didStart) {
|
|
614
|
-
this.didStart = false;
|
|
615
|
-
this.ionPullEnd.emit({ reason: 'cancel' });
|
|
616
|
-
}
|
|
617
593
|
}
|
|
618
594
|
}
|
|
619
595
|
beginRefresh() {
|
|
@@ -660,9 +636,6 @@ export class Refresher {
|
|
|
660
636
|
if (this.contentFullscreen && this.backgroundContentEl) {
|
|
661
637
|
(_a = this.backgroundContentEl) === null || _a === void 0 ? void 0 : _a.style.removeProperty('--offset-top');
|
|
662
638
|
}
|
|
663
|
-
this.ionPullEnd.emit({
|
|
664
|
-
reason: state === 32 /* RefresherState.Completing */ ? 'complete' : 'cancel',
|
|
665
|
-
});
|
|
666
639
|
}, 600);
|
|
667
640
|
// reset the styles on the scroll element
|
|
668
641
|
// set that the refresh is actively cancelling/completing
|
|
@@ -716,7 +689,7 @@ export class Refresher {
|
|
|
716
689
|
}
|
|
717
690
|
render() {
|
|
718
691
|
const mode = getIonMode(this);
|
|
719
|
-
return (h(Host, { key: '
|
|
692
|
+
return (h(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
|
|
720
693
|
[mode]: true,
|
|
721
694
|
// Used internally for styling
|
|
722
695
|
[`refresher-${mode}`]: true,
|
|
@@ -836,7 +809,7 @@ export class Refresher {
|
|
|
836
809
|
"optional": false,
|
|
837
810
|
"docs": {
|
|
838
811
|
"tags": [],
|
|
839
|
-
"text": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example
|
|
812
|
+
"text": "How much to multiply the pull speed by. To slow the pull animation down,\npass a number less than `1`. To speed up the pull, pass a number greater\nthan `1`. The default value is `1` which is equal to the speed of the cursor.\nIf a negative value is passed in, the factor will be `1` instead.\n\nFor example: If the value passed is `1.2` and the content is dragged by\n`10` pixels, instead of `10` pixels the content will be pulled by `12` pixels\n(an increase of 20 percent). If the value passed is `0.8`, the dragged amount\nwill be `8` pixels, less than the amount the cursor has moved.\n\nDoes not apply when the refresher content uses a spinner,\nenabling the native refresher."
|
|
840
813
|
},
|
|
841
814
|
"getter": false,
|
|
842
815
|
"setter": false,
|
|
@@ -915,24 +888,6 @@ export class Refresher {
|
|
|
915
888
|
"bubbles": true,
|
|
916
889
|
"cancelable": true,
|
|
917
890
|
"composed": true,
|
|
918
|
-
"docs": {
|
|
919
|
-
"tags": [{
|
|
920
|
-
"name": "deprecated",
|
|
921
|
-
"text": "Use `ionPullStart` instead."
|
|
922
|
-
}],
|
|
923
|
-
"text": "Emitted when the user begins to start pulling down.\nTODO(FW-7044): Remove this in a major release"
|
|
924
|
-
},
|
|
925
|
-
"complexType": {
|
|
926
|
-
"original": "void",
|
|
927
|
-
"resolved": "void",
|
|
928
|
-
"references": {}
|
|
929
|
-
}
|
|
930
|
-
}, {
|
|
931
|
-
"method": "ionPullStart",
|
|
932
|
-
"name": "ionPullStart",
|
|
933
|
-
"bubbles": true,
|
|
934
|
-
"cancelable": true,
|
|
935
|
-
"composed": true,
|
|
936
891
|
"docs": {
|
|
937
892
|
"tags": [],
|
|
938
893
|
"text": "Emitted when the user begins to start pulling down."
|
|
@@ -942,27 +897,6 @@ export class Refresher {
|
|
|
942
897
|
"resolved": "void",
|
|
943
898
|
"references": {}
|
|
944
899
|
}
|
|
945
|
-
}, {
|
|
946
|
-
"method": "ionPullEnd",
|
|
947
|
-
"name": "ionPullEnd",
|
|
948
|
-
"bubbles": true,
|
|
949
|
-
"cancelable": true,
|
|
950
|
-
"composed": true,
|
|
951
|
-
"docs": {
|
|
952
|
-
"tags": [],
|
|
953
|
-
"text": "Emitted when the refresher has returned to the inactive state\nafter a pull gesture. This fires whether the refresh completed\nsuccessfully or was canceled."
|
|
954
|
-
},
|
|
955
|
-
"complexType": {
|
|
956
|
-
"original": "RefresherPullEndEventDetail",
|
|
957
|
-
"resolved": "RefresherPullEndEventDetail",
|
|
958
|
-
"references": {
|
|
959
|
-
"RefresherPullEndEventDetail": {
|
|
960
|
-
"location": "import",
|
|
961
|
-
"path": "./refresher-interface",
|
|
962
|
-
"id": "src/components/refresher/refresher-interface.ts::RefresherPullEndEventDetail"
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
900
|
}];
|
|
967
901
|
}
|
|
968
902
|
static get methods() {
|