@ionic/core 8.7.18-dev.11771365900.125bf49c → 8.7.18-dev.11772217280.122eed89
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-app.js +17 -12
- package/components/ion-checkbox.js +1 -1
- package/components/ion-content.js +1 -1
- package/components/ion-datetime.js +7 -4
- package/components/ion-fab-list.js +10 -3
- package/components/ion-img.js +11 -2
- package/components/ion-label.js +1 -1
- package/components/ion-modal.js +1 -1
- package/components/ion-popover.js +1 -1
- package/components/ion-radio-group.js +1 -1
- package/components/ion-searchbar.js +17 -6
- package/components/ion-select-modal.js +1 -1
- package/components/ion-select-popover.js +1 -1
- package/components/ion-select.js +8 -8
- package/components/{p-CJrGvZ1Z.js → p-51c2oSfC.js} +3 -3
- package/components/{p-BnpDIASH.js → p-BANeLH_G.js} +2 -2
- package/components/{p-D3KEyQ8e.js → p-BRLve2EP.js} +3 -3
- package/components/{p-DRHwNkVo.js → p-C6dkQWq2.js} +152 -30
- package/components/{p-CUfY9DTD.js → p-Cdm3dKWf.js} +8 -3
- package/components/{p-DyFBFrV7.js → p-D--b4514.js} +4 -4
- package/components/{p-DPm73mCi.js → p-DGOrLIyS.js} +7 -3
- package/components/{p-IHgAGzvW.js → p-iyo0Toaz.js} +326 -17
- package/dist/cjs/ion-app_8.cjs.entry.js +22 -13
- package/dist/cjs/ion-checkbox.cjs.entry.js +3 -3
- package/dist/cjs/ion-datetime_3.cjs.entry.js +6 -3
- package/dist/cjs/ion-fab_3.cjs.entry.js +10 -3
- package/dist/cjs/ion-img.cjs.entry.js +11 -2
- package/dist/cjs/ion-item_8.cjs.entry.js +8 -3
- package/dist/cjs/ion-modal.cjs.entry.js +326 -17
- package/dist/cjs/ion-popover.cjs.entry.js +152 -30
- package/dist/cjs/ion-radio_2.cjs.entry.js +2 -2
- package/dist/cjs/ion-searchbar.cjs.entry.js +17 -6
- package/dist/collection/components/app/app.js +15 -10
- package/dist/collection/components/checkbox/checkbox.js +4 -4
- package/dist/collection/components/content/content.js +7 -3
- package/dist/collection/components/datetime/datetime.js +6 -3
- package/dist/collection/components/fab-list/fab-list.js +10 -3
- package/dist/collection/components/img/img.js +11 -2
- package/dist/collection/components/label/label.js +8 -3
- 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/radio-group/radio-group.ios.css +0 -4
- package/dist/collection/components/radio-group/radio-group.md.css +0 -4
- package/dist/collection/components/searchbar/searchbar.js +17 -6
- package/dist/docs.json +1 -1
- package/dist/esm/ion-app_8.entry.js +24 -15
- package/dist/esm/ion-checkbox.entry.js +4 -4
- package/dist/esm/ion-datetime_3.entry.js +6 -3
- package/dist/esm/ion-fab_3.entry.js +10 -3
- package/dist/esm/ion-img.entry.js +11 -2
- package/dist/esm/ion-item_8.entry.js +8 -3
- package/dist/esm/ion-modal.entry.js +326 -17
- package/dist/esm/ion-popover.entry.js +152 -30
- package/dist/esm/ion-radio_2.entry.js +2 -2
- package/dist/esm/ion-searchbar.entry.js +17 -6
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/{p-72af946b.entry.js → p-2ac254dc.entry.js} +1 -1
- package/dist/ionic/p-7cad388a.entry.js +4 -0
- package/dist/ionic/p-7d218555.entry.js +4 -0
- package/dist/ionic/p-8303e3fc.entry.js +4 -0
- package/dist/ionic/p-915b3e3d.entry.js +4 -0
- package/dist/ionic/{p-eaf3f5ff.entry.js → p-b0ebf9f8.entry.js} +1 -1
- package/dist/ionic/p-e4c57008.entry.js +4 -0
- package/dist/ionic/p-e5d6d0f0.entry.js +4 -0
- package/dist/ionic/p-f1dfee25.entry.js +4 -0
- package/dist/ionic/{p-6444c606.entry.js → p-f9f7b9db.entry.js} +1 -1
- package/dist/types/components/app/app.d.ts +3 -0
- package/dist/types/components/datetime/datetime.d.ts +1 -0
- package/dist/types/components/fab-list/fab-list.d.ts +2 -0
- package/dist/types/components/img/img.d.ts +2 -0
- package/dist/types/components/label/label.d.ts +2 -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/searchbar/searchbar.d.ts +3 -0
- package/hydrate/index.js +556 -73
- package/hydrate/index.mjs +556 -73
- package/package.json +1 -1
- package/dist/ionic/p-01e27965.entry.js +0 -4
- package/dist/ionic/p-3ec563c1.entry.js +0 -4
- package/dist/ionic/p-42db6404.entry.js +0 -4
- package/dist/ionic/p-46d74291.entry.js +0 -4
- package/dist/ionic/p-bc293244.entry.js +0 -4
- package/dist/ionic/p-d1f54e28.entry.js +0 -4
- package/dist/ionic/p-e338f669.entry.js +0 -4
package/hydrate/index.mjs
CHANGED
|
@@ -8785,6 +8785,11 @@ class App {
|
|
|
8785
8785
|
}
|
|
8786
8786
|
componentDidLoad() {
|
|
8787
8787
|
}
|
|
8788
|
+
disconnectedCallback() {
|
|
8789
|
+
if (this.loadTimeout) {
|
|
8790
|
+
clearTimeout(this.loadTimeout);
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8788
8793
|
/**
|
|
8789
8794
|
* Used to set focus on an element that uses `ion-focusable`.
|
|
8790
8795
|
* Do not use this if focusing the element as a result of a keyboard
|
|
@@ -8801,9 +8806,17 @@ class App {
|
|
|
8801
8806
|
this.focusVisible.setFocus(elements);
|
|
8802
8807
|
}
|
|
8803
8808
|
}
|
|
8809
|
+
rIC(callback) {
|
|
8810
|
+
if ('requestIdleCallback' in window) {
|
|
8811
|
+
window.requestIdleCallback(callback);
|
|
8812
|
+
}
|
|
8813
|
+
else {
|
|
8814
|
+
this.loadTimeout = setTimeout(callback, 32);
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8804
8817
|
render() {
|
|
8805
8818
|
const mode = getIonMode$1(this);
|
|
8806
|
-
return (hAsync(Host, { key: '
|
|
8819
|
+
return (hAsync(Host, { key: '77a7459d3e42b3715a5a7e1cf468e2ebc99e54bb', class: {
|
|
8807
8820
|
[mode]: true,
|
|
8808
8821
|
'ion-page': true,
|
|
8809
8822
|
'force-statusbar-padding': config.getBoolean('_forceStatusbarPadding'),
|
|
@@ -10237,7 +10250,7 @@ class Checkbox {
|
|
|
10237
10250
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
10238
10251
|
// The host element must have a checkbox role to ensure proper VoiceOver
|
|
10239
10252
|
// support in Safari for accessibility.
|
|
10240
|
-
return (hAsync(Host, { key: '
|
|
10253
|
+
return (hAsync(Host, { key: 'fca2e80987d5e2e7f9138eeac3314d883ea1252b', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": hasLabelContent ? this.inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses$1(color, {
|
|
10241
10254
|
[mode]: true,
|
|
10242
10255
|
'in-item': hostContext('ion-item', el),
|
|
10243
10256
|
'checkbox-checked': checked,
|
|
@@ -10247,10 +10260,10 @@ class Checkbox {
|
|
|
10247
10260
|
[`checkbox-justify-${justify}`]: justify !== undefined,
|
|
10248
10261
|
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
|
|
10249
10262
|
[`checkbox-label-placement-${labelPlacement}`]: true,
|
|
10250
|
-
}) }, hAsync("label", { key: '
|
|
10263
|
+
}) }, hAsync("label", { key: '5cf50b6abdb9a3ae1f846f18bd8d5345de0ea2e9', class: "checkbox-wrapper", htmlFor: inputId }, hAsync("input", Object.assign({ key: 'b8c3c6b01f65ff1b723c0de4b5d6f1b62940f481', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), hAsync("div", { key: '7b29b18b97b8151f454760d73aca1543c8325e41', class: {
|
|
10251
10264
|
'label-text-wrapper': true,
|
|
10252
10265
|
'label-text-wrapper-hidden': !hasLabelContent,
|
|
10253
|
-
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '
|
|
10266
|
+
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: 'ffbce8010274a68b8e67f9c0f19ec012944df484', onSlotchange: () => forceUpdate() }), this.renderHintText()), hAsync("div", { key: '8e21fb89c72fbd52df70fa1916367ab5a543f281', class: "native-wrapper" }, hAsync("svg", { key: 'c2519ff1a5c7f69e03ab26197ccfa8718b0951c6', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
|
|
10254
10267
|
}
|
|
10255
10268
|
getSVGPath(mode, indeterminate) {
|
|
10256
10269
|
let path = indeterminate ? (hAsync("path", { d: "M6 12L18 12", part: "mark" })) : (hAsync("path", { d: "M5.9,12.5l3.8,3.8l8.8-8.8", part: "mark" }));
|
|
@@ -10627,6 +10640,10 @@ class Content {
|
|
|
10627
10640
|
this.tabsElement = null;
|
|
10628
10641
|
this.tabsLoadCallback = undefined;
|
|
10629
10642
|
}
|
|
10643
|
+
if (this.resizeTimeout) {
|
|
10644
|
+
clearTimeout(this.resizeTimeout);
|
|
10645
|
+
this.resizeTimeout = null;
|
|
10646
|
+
}
|
|
10630
10647
|
}
|
|
10631
10648
|
/**
|
|
10632
10649
|
* Rotating certain devices can update
|
|
@@ -10847,7 +10864,7 @@ class Content {
|
|
|
10847
10864
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
10848
10865
|
const transitionShadow = mode === 'ios';
|
|
10849
10866
|
this.resize();
|
|
10850
|
-
return (hAsync(Host, Object.assign({ key: '
|
|
10867
|
+
return (hAsync(Host, Object.assign({ key: '287f5bb78888b0f66394a35ba2a3ad636d8f3412', role: isMainContent ? 'main' : undefined, class: createColorClasses$1(this.color, {
|
|
10851
10868
|
[mode]: true,
|
|
10852
10869
|
'content-sizing': hostContext('ion-popover', this.el),
|
|
10853
10870
|
overscroll: forceOverscroll,
|
|
@@ -10855,12 +10872,12 @@ class Content {
|
|
|
10855
10872
|
}), style: {
|
|
10856
10873
|
'--offset-top': `${this.cTop}px`,
|
|
10857
10874
|
'--offset-bottom': `${this.cBottom}px`,
|
|
10858
|
-
} }, inheritedAttributes), hAsync("div", { key: '
|
|
10875
|
+
} }, inheritedAttributes), hAsync("div", { key: '2e70e5e0ab4209478ea711a630058fa0e83af7f8', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? hAsync("slot", { name: "fixed" }) : null, hAsync("div", { key: '018a83406173ec7f7725f09b4bd1c0d964874ee4', class: {
|
|
10859
10876
|
'inner-scroll': true,
|
|
10860
10877
|
'scroll-x': scrollX,
|
|
10861
10878
|
'scroll-y': scrollY,
|
|
10862
10879
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
10863
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '
|
|
10880
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '6f028eeb7c9dc7e1b82a8e549e1dd8aaf1d15fca' })), transitionShadow ? (hAsync("div", { class: "transition-effect" }, hAsync("div", { class: "transition-cover" }), hAsync("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? hAsync("slot", { name: "fixed" }) : null));
|
|
10864
10881
|
}
|
|
10865
10882
|
get el() { return getElement(this); }
|
|
10866
10883
|
static get style() { return contentCss; }
|
|
@@ -13735,6 +13752,9 @@ class Datetime {
|
|
|
13735
13752
|
this.clearFocusVisible();
|
|
13736
13753
|
this.clearFocusVisible = undefined;
|
|
13737
13754
|
}
|
|
13755
|
+
if (this.loadTimeout) {
|
|
13756
|
+
clearTimeout(this.loadTimeout);
|
|
13757
|
+
}
|
|
13738
13758
|
}
|
|
13739
13759
|
initializeListeners() {
|
|
13740
13760
|
this.initializeCalendarListener();
|
|
@@ -13783,7 +13803,7 @@ class Datetime {
|
|
|
13783
13803
|
*
|
|
13784
13804
|
* We schedule this after everything has had a chance to run.
|
|
13785
13805
|
*/
|
|
13786
|
-
setTimeout(() => {
|
|
13806
|
+
this.loadTimeout = setTimeout(() => {
|
|
13787
13807
|
this.ensureReadyIfVisible();
|
|
13788
13808
|
}, 100);
|
|
13789
13809
|
/**
|
|
@@ -14541,7 +14561,7 @@ class Datetime {
|
|
|
14541
14561
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
14542
14562
|
const hasWheelVariant = hasDatePresentation && preferWheel;
|
|
14543
14563
|
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
|
14544
|
-
return (hAsync(Host, { key: '
|
|
14564
|
+
return (hAsync(Host, { key: 'b81c3531f4654be3f6948df5f790c0f6ab488000', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses$1(color, {
|
|
14545
14565
|
[mode]: true,
|
|
14546
14566
|
['datetime-readonly']: readonly,
|
|
14547
14567
|
['datetime-disabled']: disabled,
|
|
@@ -14551,7 +14571,7 @@ class Datetime {
|
|
|
14551
14571
|
[`datetime-size-${size}`]: true,
|
|
14552
14572
|
[`datetime-prefer-wheel`]: hasWheelVariant,
|
|
14553
14573
|
[`datetime-grid`]: isGridStyle,
|
|
14554
|
-
})) }, hAsync("div", { key: '
|
|
14574
|
+
})) }, hAsync("div", { key: 'f7f39f8e56146186210d0ab70ae95e829573e549', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
|
|
14555
14575
|
}
|
|
14556
14576
|
get el() { return getElement(this); }
|
|
14557
14577
|
static get watchers() { return {
|
|
@@ -15206,6 +15226,7 @@ const fabListCss = ":host{margin-left:0;margin-right:0;margin-top:calc(100% + 10
|
|
|
15206
15226
|
class FabList {
|
|
15207
15227
|
constructor(hostRef) {
|
|
15208
15228
|
registerInstance(this, hostRef);
|
|
15229
|
+
this.activateTimeouts = [];
|
|
15209
15230
|
/**
|
|
15210
15231
|
* If `true`, the fab list will show all fab buttons in the list.
|
|
15211
15232
|
*/
|
|
@@ -15216,20 +15237,26 @@ class FabList {
|
|
|
15216
15237
|
this.side = 'bottom';
|
|
15217
15238
|
}
|
|
15218
15239
|
activatedChanged(activated) {
|
|
15240
|
+
this.activateTimeouts.forEach(clearTimeout);
|
|
15241
|
+
this.activateTimeouts = [];
|
|
15219
15242
|
const fabs = Array.from(this.el.querySelectorAll('ion-fab-button'));
|
|
15220
15243
|
// if showing the fabs add a timeout, else show immediately
|
|
15221
15244
|
const timeout = activated ? 30 : 0;
|
|
15222
15245
|
fabs.forEach((fab, i) => {
|
|
15223
|
-
setTimeout(() => (fab.show = activated), i * timeout);
|
|
15246
|
+
this.activateTimeouts.push(setTimeout(() => (fab.show = activated), i * timeout));
|
|
15224
15247
|
});
|
|
15225
15248
|
}
|
|
15249
|
+
disconnectedCallback() {
|
|
15250
|
+
this.activateTimeouts.forEach(clearTimeout);
|
|
15251
|
+
this.activateTimeouts = [];
|
|
15252
|
+
}
|
|
15226
15253
|
render() {
|
|
15227
15254
|
const mode = getIonMode$1(this);
|
|
15228
|
-
return (hAsync(Host, { key: '
|
|
15255
|
+
return (hAsync(Host, { key: '03a8920c29a93c6df4bab14753a78a8a30722ce8', class: {
|
|
15229
15256
|
[mode]: true,
|
|
15230
15257
|
'fab-list-active': this.activated,
|
|
15231
15258
|
[`fab-list-side-${this.side}`]: true,
|
|
15232
|
-
} }, hAsync("slot", { key: '
|
|
15259
|
+
} }, hAsync("slot", { key: 'd9e1541cb044e209bc2dff14080f3b938a2b84ae' })));
|
|
15233
15260
|
}
|
|
15234
15261
|
get el() { return getElement(this); }
|
|
15235
15262
|
static get watchers() { return {
|
|
@@ -16435,7 +16462,16 @@ class Img {
|
|
|
16435
16462
|
componentDidLoad() {
|
|
16436
16463
|
this.addIO();
|
|
16437
16464
|
}
|
|
16465
|
+
disconnectedCallback() {
|
|
16466
|
+
if (this.loadTimeout) {
|
|
16467
|
+
clearTimeout(this.loadTimeout);
|
|
16468
|
+
}
|
|
16469
|
+
}
|
|
16438
16470
|
addIO() {
|
|
16471
|
+
if (this.loadTimeout) {
|
|
16472
|
+
clearTimeout(this.loadTimeout);
|
|
16473
|
+
this.loadTimeout = undefined;
|
|
16474
|
+
}
|
|
16439
16475
|
if (this.src === undefined) {
|
|
16440
16476
|
return;
|
|
16441
16477
|
}
|
|
@@ -16459,7 +16495,7 @@ class Img {
|
|
|
16459
16495
|
}
|
|
16460
16496
|
else {
|
|
16461
16497
|
// fall back to setTimeout for Safari and IE
|
|
16462
|
-
setTimeout(() => this.load(), 200);
|
|
16498
|
+
this.loadTimeout = setTimeout(() => this.load(), 200);
|
|
16463
16499
|
}
|
|
16464
16500
|
}
|
|
16465
16501
|
load() {
|
|
@@ -16476,7 +16512,7 @@ class Img {
|
|
|
16476
16512
|
render() {
|
|
16477
16513
|
const { loadSrc, alt, onLoad, loadError, inheritedAttributes } = this;
|
|
16478
16514
|
const { draggable } = inheritedAttributes;
|
|
16479
|
-
return (hAsync(Host, { key: '
|
|
16515
|
+
return (hAsync(Host, { key: '9bfaa659b0680189fd29f3acb2fdce7f70be6060', class: getIonMode$1(this) }, hAsync("img", { key: 'd484beae0d5cbe0406958bfad40cdd6c05625340', decoding: "async", src: loadSrc, alt: alt, onLoad: onLoad, onError: loadError, part: "image", draggable: isDraggable(draggable) })));
|
|
16480
16516
|
}
|
|
16481
16517
|
get el() { return getElement(this); }
|
|
16482
16518
|
static get watchers() { return {
|
|
@@ -19266,11 +19302,16 @@ class Label {
|
|
|
19266
19302
|
}
|
|
19267
19303
|
componentDidLoad() {
|
|
19268
19304
|
if (this.noAnimate) {
|
|
19269
|
-
setTimeout(() => {
|
|
19305
|
+
this.loadTimeout = setTimeout(() => {
|
|
19270
19306
|
this.noAnimate = false;
|
|
19271
19307
|
}, 1000);
|
|
19272
19308
|
}
|
|
19273
19309
|
}
|
|
19310
|
+
disconnectedCallback() {
|
|
19311
|
+
if (this.loadTimeout) {
|
|
19312
|
+
clearTimeout(this.loadTimeout);
|
|
19313
|
+
}
|
|
19314
|
+
}
|
|
19274
19315
|
colorChanged() {
|
|
19275
19316
|
this.emitColor();
|
|
19276
19317
|
}
|
|
@@ -19299,13 +19340,13 @@ class Label {
|
|
|
19299
19340
|
render() {
|
|
19300
19341
|
const position = this.position;
|
|
19301
19342
|
const mode = getIonMode$1(this);
|
|
19302
|
-
return (hAsync(Host, { key: '
|
|
19343
|
+
return (hAsync(Host, { key: 'd603670c442213c28737b0c052c094705b8c84ef', class: createColorClasses$1(this.color, {
|
|
19303
19344
|
[mode]: true,
|
|
19304
19345
|
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
|
19305
19346
|
[`label-${position}`]: position !== undefined,
|
|
19306
19347
|
[`label-no-animate`]: this.noAnimate,
|
|
19307
19348
|
'label-rtl': document.dir === 'rtl',
|
|
19308
|
-
}) }, hAsync("slot", { key: '
|
|
19349
|
+
}) }, hAsync("slot", { key: 'a8c3aed240a20ee5856c423662878481a1e6d4cf' })));
|
|
19309
19350
|
}
|
|
19310
19351
|
get el() { return getElement(this); }
|
|
19311
19352
|
static get watchers() { return {
|
|
@@ -22804,9 +22845,170 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
22804
22845
|
};
|
|
22805
22846
|
};
|
|
22806
22847
|
|
|
22807
|
-
|
|
22848
|
+
/**
|
|
22849
|
+
* These thresholds match the SCSS media query breakpoints in modal.vars.scss
|
|
22850
|
+
* that trigger the centered dialog layout (non-fullscreen modal).
|
|
22851
|
+
*
|
|
22852
|
+
* SCSS defines two height breakpoints: $modal-inset-min-height-small (600px)
|
|
22853
|
+
* and $modal-inset-min-height-large (768px). We use the smaller one because
|
|
22854
|
+
* that's the threshold where the modal transitions from fullscreen to centered
|
|
22855
|
+
* dialog — the larger breakpoint only increases the dialog's height.
|
|
22856
|
+
*/
|
|
22857
|
+
const MODAL_INSET_MIN_WIDTH = 768;
|
|
22858
|
+
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
22859
|
+
const EDGE_THRESHOLD = 5;
|
|
22860
|
+
/**
|
|
22861
|
+
* Cache for resolved root safe-area-top value, invalidated once per frame.
|
|
22862
|
+
*/
|
|
22863
|
+
let cachedRootSafeAreaTop = null;
|
|
22864
|
+
let cacheInvalidationScheduled$1 = false;
|
|
22865
|
+
/**
|
|
22866
|
+
* Determines if the current viewport meets the CSS media query conditions
|
|
22867
|
+
* that cause regular modals to render as centered dialogs instead of fullscreen.
|
|
22868
|
+
* Matches: @media (min-width: 768px) and (min-height: 600px)
|
|
22869
|
+
*/
|
|
22870
|
+
const isCenteredDialogViewport = () => {
|
|
22871
|
+
if (!win$1)
|
|
22872
|
+
return false;
|
|
22873
|
+
return win$1.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`)
|
|
22874
|
+
.matches;
|
|
22875
|
+
};
|
|
22876
|
+
/**
|
|
22877
|
+
* Resolves the current root --ion-safe-area-top value to pixels.
|
|
22878
|
+
* Uses a temporary element because getComputedStyle on :root returns
|
|
22879
|
+
* the declared value of custom properties (e.g. "env(safe-area-inset-top)")
|
|
22880
|
+
* rather than a resolved number.
|
|
22881
|
+
*
|
|
22882
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
22883
|
+
*/
|
|
22884
|
+
const getRootSafeAreaTop = () => {
|
|
22885
|
+
if (cachedRootSafeAreaTop !== null) {
|
|
22886
|
+
return cachedRootSafeAreaTop;
|
|
22887
|
+
}
|
|
22888
|
+
const doc = win$1 === null || win$1 === void 0 ? void 0 : win$1.document;
|
|
22889
|
+
if (!(doc === null || doc === void 0 ? void 0 : doc.body)) {
|
|
22890
|
+
return 0;
|
|
22891
|
+
}
|
|
22892
|
+
const el = doc.createElement('div');
|
|
22893
|
+
el.style.cssText =
|
|
22894
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + 'padding-top:var(--ion-safe-area-top,0px);';
|
|
22895
|
+
doc.body.appendChild(el);
|
|
22896
|
+
const value = parseFloat(getComputedStyle(el).paddingTop) || 0;
|
|
22897
|
+
el.remove();
|
|
22898
|
+
cachedRootSafeAreaTop = value;
|
|
22899
|
+
if (!cacheInvalidationScheduled$1) {
|
|
22900
|
+
cacheInvalidationScheduled$1 = true;
|
|
22901
|
+
raf(() => {
|
|
22902
|
+
cachedRootSafeAreaTop = null;
|
|
22903
|
+
cacheInvalidationScheduled$1 = false;
|
|
22904
|
+
});
|
|
22905
|
+
}
|
|
22906
|
+
return value;
|
|
22907
|
+
};
|
|
22908
|
+
/**
|
|
22909
|
+
* Returns the initial safe-area configuration based on modal type.
|
|
22910
|
+
* This is called before animation starts and uses configuration-based prediction.
|
|
22911
|
+
*
|
|
22912
|
+
* @param context - Modal context information
|
|
22913
|
+
* @returns SafeAreaConfig with initial safe-area values
|
|
22914
|
+
*/
|
|
22915
|
+
const getInitialSafeAreaConfig = (context) => {
|
|
22916
|
+
const { isSheetModal, isCardModal } = context;
|
|
22917
|
+
// Sheet modals always zero top safe-area. The sheet height offset from the
|
|
22918
|
+
// top edge is handled by --ion-modal-offset-top (set in modal.tsx) using
|
|
22919
|
+
// the resolved root value, so --ion-safe-area-top is never needed for
|
|
22920
|
+
// height calculation. Keeping it at 0px prevents header content from
|
|
22921
|
+
// getting double-offset padding.
|
|
22922
|
+
if (isSheetModal) {
|
|
22923
|
+
return {
|
|
22924
|
+
top: '0px',
|
|
22925
|
+
bottom: 'inherit',
|
|
22926
|
+
left: '0px',
|
|
22927
|
+
right: '0px',
|
|
22928
|
+
};
|
|
22929
|
+
}
|
|
22930
|
+
// Card modals need safe-area for height calculation.
|
|
22931
|
+
// Note: isCardModal is already gated on mode === 'ios' by the caller.
|
|
22932
|
+
if (isCardModal) {
|
|
22933
|
+
return {
|
|
22934
|
+
top: 'inherit',
|
|
22935
|
+
bottom: 'inherit',
|
|
22936
|
+
left: '0px',
|
|
22937
|
+
right: '0px',
|
|
22938
|
+
};
|
|
22939
|
+
}
|
|
22940
|
+
// On viewports that meet the centered dialog media query breakpoints,
|
|
22941
|
+
// regular modals render as centered dialogs (not fullscreen), so they
|
|
22942
|
+
// don't touch any screen edges and don't need safe-area insets.
|
|
22943
|
+
if (isCenteredDialogViewport()) {
|
|
22944
|
+
return {
|
|
22945
|
+
top: '0px',
|
|
22946
|
+
bottom: '0px',
|
|
22947
|
+
left: '0px',
|
|
22948
|
+
right: '0px',
|
|
22949
|
+
};
|
|
22950
|
+
}
|
|
22951
|
+
// Fullscreen modals on phone - inherit all safe areas
|
|
22952
|
+
return {
|
|
22953
|
+
top: 'inherit',
|
|
22954
|
+
bottom: 'inherit',
|
|
22955
|
+
left: 'inherit',
|
|
22956
|
+
right: 'inherit',
|
|
22957
|
+
};
|
|
22958
|
+
};
|
|
22959
|
+
/**
|
|
22960
|
+
* Returns safe-area configuration based on actual modal position.
|
|
22961
|
+
* Detects which edges the modal overlaps with and only applies safe-area to those edges.
|
|
22962
|
+
*
|
|
22963
|
+
* Note: On Android edge-to-edge (API 36+), getBoundingClientRect() may report
|
|
22964
|
+
* inconsistent values. Sheet and card modals avoid this by using configuration-based
|
|
22965
|
+
* prediction instead. Regular modals use coordinate detection which works reliably
|
|
22966
|
+
* on web and iOS; Android edge-to-edge may need a configuration-based fallback
|
|
22967
|
+
* once a reliable detection mechanism is available.
|
|
22968
|
+
*
|
|
22969
|
+
* @param wrapperEl - The modal wrapper element to measure
|
|
22970
|
+
* @returns SafeAreaConfig based on position
|
|
22971
|
+
*/
|
|
22972
|
+
const getPositionBasedSafeAreaConfig = (wrapperEl) => {
|
|
22973
|
+
var _a, _b;
|
|
22974
|
+
const rect = wrapperEl.getBoundingClientRect();
|
|
22975
|
+
const vh = (_a = win$1 === null || win$1 === void 0 ? void 0 : win$1.innerHeight) !== null && _a !== void 0 ? _a : 0;
|
|
22976
|
+
const vw = (_b = win$1 === null || win$1 === void 0 ? void 0 : win$1.innerWidth) !== null && _b !== void 0 ? _b : 0;
|
|
22977
|
+
// Only apply safe-area to sides where modal overlaps with screen edge
|
|
22978
|
+
return {
|
|
22979
|
+
top: rect.top <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22980
|
+
bottom: rect.bottom >= vh - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22981
|
+
left: rect.left <= EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22982
|
+
right: rect.right >= vw - EDGE_THRESHOLD ? 'inherit' : '0px',
|
|
22983
|
+
};
|
|
22984
|
+
};
|
|
22985
|
+
/**
|
|
22986
|
+
* Applies safe-area CSS custom property overrides to the modal host element.
|
|
22987
|
+
*
|
|
22988
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
22989
|
+
* @param config - Safe-area configuration to apply
|
|
22990
|
+
*/
|
|
22991
|
+
const applySafeAreaOverrides = (hostEl, config) => {
|
|
22992
|
+
hostEl.style.setProperty('--ion-safe-area-top', config.top);
|
|
22993
|
+
hostEl.style.setProperty('--ion-safe-area-bottom', config.bottom);
|
|
22994
|
+
hostEl.style.setProperty('--ion-safe-area-left', config.left);
|
|
22995
|
+
hostEl.style.setProperty('--ion-safe-area-right', config.right);
|
|
22996
|
+
};
|
|
22997
|
+
/**
|
|
22998
|
+
* Clears safe-area CSS custom property overrides from the modal host element.
|
|
22999
|
+
*
|
|
23000
|
+
* @param hostEl - The modal host element (ion-modal)
|
|
23001
|
+
*/
|
|
23002
|
+
const clearSafeAreaOverrides = (hostEl) => {
|
|
23003
|
+
hostEl.style.removeProperty('--ion-safe-area-top');
|
|
23004
|
+
hostEl.style.removeProperty('--ion-safe-area-bottom');
|
|
23005
|
+
hostEl.style.removeProperty('--ion-safe-area-left');
|
|
23006
|
+
hostEl.style.removeProperty('--ion-safe-area-right');
|
|
23007
|
+
};
|
|
22808
23008
|
|
|
22809
|
-
const
|
|
23009
|
+
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}";
|
|
23010
|
+
|
|
23011
|
+
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}";
|
|
22810
23012
|
|
|
22811
23013
|
// TODO(FW-2832): types
|
|
22812
23014
|
/**
|
|
@@ -23029,13 +23231,31 @@ class Modal {
|
|
|
23029
23231
|
}
|
|
23030
23232
|
}
|
|
23031
23233
|
onWindowResize() {
|
|
23032
|
-
|
|
23033
|
-
if (getIonMode$1(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
23234
|
+
if (!this.presented)
|
|
23034
23235
|
return;
|
|
23035
|
-
}
|
|
23036
23236
|
clearTimeout(this.resizeTimeout);
|
|
23037
23237
|
this.resizeTimeout = setTimeout(() => {
|
|
23038
|
-
this.
|
|
23238
|
+
const context = this.getSafeAreaContext();
|
|
23239
|
+
// iOS card modals: handle portrait/landscape view transitions
|
|
23240
|
+
if (context.isCardModal && !this.enterAnimation && !this.leaveAnimation) {
|
|
23241
|
+
this.handleViewTransition();
|
|
23242
|
+
}
|
|
23243
|
+
// Sheet modals: re-compute the internal offset property since safe-area
|
|
23244
|
+
// values may change on device rotation (e.g., portrait notch vs landscape).
|
|
23245
|
+
if (context.isSheetModal) {
|
|
23246
|
+
this.updateSheetOffsetTop();
|
|
23247
|
+
}
|
|
23248
|
+
// Regular (non-sheet, non-card) modals: update safe-area overrides
|
|
23249
|
+
// since the viewport may have crossed the centered-dialog breakpoint.
|
|
23250
|
+
if (!context.isSheetModal && !context.isCardModal) {
|
|
23251
|
+
this.updateSafeAreaOverrides();
|
|
23252
|
+
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply
|
|
23253
|
+
if (this.wrapperEl) {
|
|
23254
|
+
this.wrapperEl.style.removeProperty('height');
|
|
23255
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
23256
|
+
}
|
|
23257
|
+
this.applyFullscreenSafeArea();
|
|
23258
|
+
}
|
|
23039
23259
|
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
23040
23260
|
}
|
|
23041
23261
|
breakpointsChanged(breakpoints) {
|
|
@@ -23052,6 +23272,9 @@ class Modal {
|
|
|
23052
23272
|
this.triggerController.removeClickListener();
|
|
23053
23273
|
this.cleanupViewTransitionListener();
|
|
23054
23274
|
this.cleanupParentRemovalObserver();
|
|
23275
|
+
// Also called in dismiss() — intentional dual cleanup covers both
|
|
23276
|
+
// dismiss-then-remove and direct DOM removal without dismiss.
|
|
23277
|
+
this.cleanupSafeAreaOverrides();
|
|
23055
23278
|
}
|
|
23056
23279
|
componentWillLoad() {
|
|
23057
23280
|
var _a;
|
|
@@ -23212,6 +23435,11 @@ class Modal {
|
|
|
23212
23435
|
await waitForMount();
|
|
23213
23436
|
}
|
|
23214
23437
|
writeTask(() => this.el.classList.add('show-modal'));
|
|
23438
|
+
// Recalculate isSheetModal before safe-area setup because framework
|
|
23439
|
+
// bindings (e.g., Angular) may not have been applied when componentWillLoad ran.
|
|
23440
|
+
this.isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
23441
|
+
// Set initial safe-area overrides before animation
|
|
23442
|
+
this.setInitialSafeAreaOverrides();
|
|
23215
23443
|
const hasCardModal = presentingElement !== undefined;
|
|
23216
23444
|
/**
|
|
23217
23445
|
* We need to change the status bar at the
|
|
@@ -23229,6 +23457,10 @@ class Modal {
|
|
|
23229
23457
|
backdropBreakpoint: this.backdropBreakpoint,
|
|
23230
23458
|
expandToScroll: this.expandToScroll,
|
|
23231
23459
|
});
|
|
23460
|
+
// Update safe-area based on actual position after animation
|
|
23461
|
+
this.updateSafeAreaOverrides();
|
|
23462
|
+
// Apply fullscreen safe-area padding if needed
|
|
23463
|
+
this.applyFullscreenSafeArea();
|
|
23232
23464
|
/* tslint:disable-next-line */
|
|
23233
23465
|
if (typeof window !== 'undefined') {
|
|
23234
23466
|
/**
|
|
@@ -23260,13 +23492,7 @@ class Modal {
|
|
|
23260
23492
|
};
|
|
23261
23493
|
window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
|
|
23262
23494
|
}
|
|
23263
|
-
|
|
23264
|
-
* Recalculate isSheetModal because framework bindings (e.g., Angular)
|
|
23265
|
-
* may not have been applied when componentWillLoad ran.
|
|
23266
|
-
*/
|
|
23267
|
-
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
|
23268
|
-
this.isSheetModal = isSheetModal;
|
|
23269
|
-
if (isSheetModal) {
|
|
23495
|
+
if (this.isSheetModal) {
|
|
23270
23496
|
this.initSheetGesture();
|
|
23271
23497
|
}
|
|
23272
23498
|
else if (hasCardModal) {
|
|
@@ -23456,6 +23682,9 @@ class Modal {
|
|
|
23456
23682
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
23457
23683
|
return false;
|
|
23458
23684
|
}
|
|
23685
|
+
// Cancel any pending resize timeout to prevent stale updates during dismiss
|
|
23686
|
+
clearTimeout(this.resizeTimeout);
|
|
23687
|
+
this.resizeTimeout = undefined;
|
|
23459
23688
|
/**
|
|
23460
23689
|
* Because the canDismiss check below is async,
|
|
23461
23690
|
* we need to claim a lock before the check happens,
|
|
@@ -23510,6 +23739,7 @@ class Modal {
|
|
|
23510
23739
|
}
|
|
23511
23740
|
this.cleanupViewTransitionListener();
|
|
23512
23741
|
this.cleanupParentRemovalObserver();
|
|
23742
|
+
this.cleanupSafeAreaOverrides();
|
|
23513
23743
|
this.cleanupChildRoutePassthrough();
|
|
23514
23744
|
}
|
|
23515
23745
|
this.currentBreakpoint = undefined;
|
|
@@ -23630,6 +23860,9 @@ class Modal {
|
|
|
23630
23860
|
this.viewTransitionAnimation = transitionAnimation;
|
|
23631
23861
|
transitionAnimation.play().then(() => {
|
|
23632
23862
|
this.viewTransitionAnimation = undefined;
|
|
23863
|
+
// Wait for a layout pass after the transition so getBoundingClientRect()
|
|
23864
|
+
// in getPositionBasedSafeAreaConfig() reflects the new dimensions.
|
|
23865
|
+
raf(() => this.updateSafeAreaOverrides());
|
|
23633
23866
|
// After orientation transition, recreate the swipe-to-close gesture
|
|
23634
23867
|
// with updated animation that reflects the new presenting element state
|
|
23635
23868
|
this.reinitSwipeToClose();
|
|
@@ -23756,6 +23989,123 @@ class Modal {
|
|
|
23756
23989
|
(_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
23757
23990
|
this.parentRemovalObserver = undefined;
|
|
23758
23991
|
}
|
|
23992
|
+
/**
|
|
23993
|
+
* Creates the context object for safe-area utilities.
|
|
23994
|
+
*/
|
|
23995
|
+
getSafeAreaContext() {
|
|
23996
|
+
return {
|
|
23997
|
+
isSheetModal: this.isSheetModal,
|
|
23998
|
+
isCardModal: this.presentingElement !== undefined && getIonMode$1(this) === 'ios',
|
|
23999
|
+
presentingElement: this.presentingElement,
|
|
24000
|
+
breakpoints: this.breakpoints,
|
|
24001
|
+
currentBreakpoint: this.currentBreakpoint,
|
|
24002
|
+
};
|
|
24003
|
+
}
|
|
24004
|
+
/**
|
|
24005
|
+
* Sets initial safe-area overrides before modal animation.
|
|
24006
|
+
* Called in present() before animation starts.
|
|
24007
|
+
*
|
|
24008
|
+
* For sheet modals, the SCSS --height formula uses --ion-modal-offset-top
|
|
24009
|
+
* (an internal property) instead of --ion-safe-area-top. We resolve the
|
|
24010
|
+
* root safe-area-top to pixels and set --ion-modal-offset-top, decoupling
|
|
24011
|
+
* the height calculation from --ion-safe-area-top (which is zeroed for
|
|
24012
|
+
* sheets to prevent header content from getting double-offset padding).
|
|
24013
|
+
*/
|
|
24014
|
+
setInitialSafeAreaOverrides() {
|
|
24015
|
+
const context = this.getSafeAreaContext();
|
|
24016
|
+
const safeAreaConfig = getInitialSafeAreaConfig(context);
|
|
24017
|
+
applySafeAreaOverrides(this.el, safeAreaConfig);
|
|
24018
|
+
// Set the internal offset property with the resolved root safe-area-top value
|
|
24019
|
+
if (context.isSheetModal) {
|
|
24020
|
+
this.updateSheetOffsetTop();
|
|
24021
|
+
}
|
|
24022
|
+
}
|
|
24023
|
+
/**
|
|
24024
|
+
* Resolves the current root --ion-safe-area-top value and sets the
|
|
24025
|
+
* internal --ion-modal-offset-top property on the host element.
|
|
24026
|
+
* Called on present and on resize (e.g., device rotation changes safe-area).
|
|
24027
|
+
*/
|
|
24028
|
+
updateSheetOffsetTop() {
|
|
24029
|
+
const safeAreaTop = getRootSafeAreaTop();
|
|
24030
|
+
this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
|
|
24031
|
+
}
|
|
24032
|
+
/**
|
|
24033
|
+
* Updates safe-area overrides during dynamic state changes.
|
|
24034
|
+
* Called after animations, during gestures, and on orientation changes.
|
|
24035
|
+
*/
|
|
24036
|
+
updateSafeAreaOverrides() {
|
|
24037
|
+
const { wrapperEl, el } = this;
|
|
24038
|
+
const context = this.getSafeAreaContext();
|
|
24039
|
+
// Sheet modals: safe-area is fully determined at presentation time
|
|
24040
|
+
// (top is always 0px, height is frozen). Nothing to update.
|
|
24041
|
+
if (context.isSheetModal)
|
|
24042
|
+
return;
|
|
24043
|
+
// Card modals have fixed safe-area requirements set by initial prediction.
|
|
24044
|
+
if (context.isCardModal)
|
|
24045
|
+
return;
|
|
24046
|
+
// wrapperEl is required for position-based detection below
|
|
24047
|
+
if (!wrapperEl)
|
|
24048
|
+
return;
|
|
24049
|
+
// Regular modals: use position-based detection to correctly handle both
|
|
24050
|
+
// fullscreen modals and centered dialogs with custom dimensions.
|
|
24051
|
+
const safeAreaConfig = getPositionBasedSafeAreaConfig(wrapperEl);
|
|
24052
|
+
applySafeAreaOverrides(el, safeAreaConfig);
|
|
24053
|
+
}
|
|
24054
|
+
/**
|
|
24055
|
+
* Applies padding-bottom to fullscreen modal wrapper to prevent
|
|
24056
|
+
* content from overlapping system navigation bar.
|
|
24057
|
+
*/
|
|
24058
|
+
applyFullscreenSafeArea() {
|
|
24059
|
+
const { wrapperEl, el } = this;
|
|
24060
|
+
if (!wrapperEl)
|
|
24061
|
+
return;
|
|
24062
|
+
const context = this.getSafeAreaContext();
|
|
24063
|
+
if (context.isSheetModal || context.isCardModal)
|
|
24064
|
+
return;
|
|
24065
|
+
// Check for standard Ionic layout children (ion-content, ion-footer),
|
|
24066
|
+
// searching one level deep for wrapped components (e.g.,
|
|
24067
|
+
// <app-footer><ion-footer>...</ion-footer></app-footer>).
|
|
24068
|
+
// Note: uses a manual loop instead of querySelector(':scope > ...') because
|
|
24069
|
+
// Stencil's mock-doc (used in spec tests) does not support :scope.
|
|
24070
|
+
let hasContent = false;
|
|
24071
|
+
let hasFooter = false;
|
|
24072
|
+
for (const child of Array.from(el.children)) {
|
|
24073
|
+
if (child.tagName === 'ION-CONTENT')
|
|
24074
|
+
hasContent = true;
|
|
24075
|
+
if (child.tagName === 'ION-FOOTER')
|
|
24076
|
+
hasFooter = true;
|
|
24077
|
+
for (const grandchild of Array.from(child.children)) {
|
|
24078
|
+
if (grandchild.tagName === 'ION-CONTENT')
|
|
24079
|
+
hasContent = true;
|
|
24080
|
+
if (grandchild.tagName === 'ION-FOOTER')
|
|
24081
|
+
hasFooter = true;
|
|
24082
|
+
}
|
|
24083
|
+
}
|
|
24084
|
+
// Only apply wrapper padding for standard Ionic layouts (has ion-content
|
|
24085
|
+
// but no ion-footer). Custom modals with raw HTML are fully
|
|
24086
|
+
// developer-controlled and should not be modified.
|
|
24087
|
+
if (!hasContent || hasFooter)
|
|
24088
|
+
return;
|
|
24089
|
+
// Reduce wrapper height by safe-area and add equivalent padding so the
|
|
24090
|
+
// total visual size stays the same but the flex content area shrinks.
|
|
24091
|
+
// Using height + padding instead of box-sizing: border-box avoids
|
|
24092
|
+
// breaking custom modals that set --border-width (border-box would
|
|
24093
|
+
// include the border inside the height, changing the layout).
|
|
24094
|
+
wrapperEl.style.setProperty('height', 'calc(var(--height) - var(--ion-safe-area-bottom, 0px))');
|
|
24095
|
+
wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
24096
|
+
}
|
|
24097
|
+
/**
|
|
24098
|
+
* Clears all safe-area overrides and padding from wrapper.
|
|
24099
|
+
*/
|
|
24100
|
+
cleanupSafeAreaOverrides() {
|
|
24101
|
+
clearSafeAreaOverrides(this.el);
|
|
24102
|
+
// Remove internal sheet offset property
|
|
24103
|
+
this.el.style.removeProperty('--ion-modal-offset-top');
|
|
24104
|
+
if (this.wrapperEl) {
|
|
24105
|
+
this.wrapperEl.style.removeProperty('height');
|
|
24106
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
24107
|
+
}
|
|
24108
|
+
}
|
|
23759
24109
|
render() {
|
|
23760
24110
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
23761
24111
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -23763,20 +24113,20 @@ class Modal {
|
|
|
23763
24113
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
23764
24114
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
23765
24115
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
23766
|
-
return (hAsync(Host, Object.assign({ key: '
|
|
24116
|
+
return (hAsync(Host, Object.assign({ key: '952cbd52d1fc737332a23d523b02d5fa60abf30a', "no-router": true,
|
|
23767
24117
|
// Allow the modal to be navigable when the handle is focusable
|
|
23768
24118
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
23769
24119
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
23770
|
-
}, 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: '
|
|
24120
|
+
}, 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: '66e3658cfcc2961d99ecc1174f26b1794ceb65e9', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: '3d1b9b26daf87374e2029d0e0a7fc09b9257fd77', class: "modal-shadow" }), hAsync("div", Object.assign({ key: 'e652c2b3eb66afa8367d94c3b14ba0e825877a82',
|
|
23771
24121
|
/*
|
|
23772
24122
|
role and aria-modal must be used on the
|
|
23773
24123
|
same element. They must also be set inside the
|
|
23774
24124
|
shadow DOM otherwise ion-button will not be highlighted
|
|
23775
24125
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
23776
24126
|
*/
|
|
23777
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '
|
|
24127
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '41662183450e8ef2f7d96dac2bea5952bad8b7a5', class: "modal-handle",
|
|
23778
24128
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
23779
|
-
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: '
|
|
24129
|
+
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: '4aac4e2928929b9e5e59b33c83395182105da529', onSlotchange: this.onSlotChange }))));
|
|
23780
24130
|
}
|
|
23781
24131
|
get el() { return getElement(this); }
|
|
23782
24132
|
static get watchers() { return {
|
|
@@ -26710,6 +27060,58 @@ class PickerColumnOption {
|
|
|
26710
27060
|
}; }
|
|
26711
27061
|
}
|
|
26712
27062
|
|
|
27063
|
+
/**
|
|
27064
|
+
* Shared per-frame cache for safe-area insets. Avoids creating a temporary
|
|
27065
|
+
* DOM element and forcing a synchronous reflow on every call within the same
|
|
27066
|
+
* frame (e.g., multiple popovers presenting simultaneously). Invalidated
|
|
27067
|
+
* after the next animation frame so values stay fresh across orientation
|
|
27068
|
+
* changes and viewport resizes.
|
|
27069
|
+
*/
|
|
27070
|
+
let cachedInsets = null;
|
|
27071
|
+
let cacheInvalidationScheduled = false;
|
|
27072
|
+
/**
|
|
27073
|
+
* Resolves the current --ion-safe-area-* CSS custom property values
|
|
27074
|
+
* to actual pixel numbers. A temporary element is needed because
|
|
27075
|
+
* getComputedStyle on :root returns the declared value of custom
|
|
27076
|
+
* properties (e.g. "env(safe-area-inset-top)") rather than a
|
|
27077
|
+
* resolved number. By assigning them to standard padding properties,
|
|
27078
|
+
* the browser resolves the value.
|
|
27079
|
+
*
|
|
27080
|
+
* Results are cached for the current frame to avoid repeated reflows.
|
|
27081
|
+
*/
|
|
27082
|
+
const getSafeAreaInsets = (doc) => {
|
|
27083
|
+
if (cachedInsets !== null) {
|
|
27084
|
+
return cachedInsets;
|
|
27085
|
+
}
|
|
27086
|
+
if (doc.body === null) {
|
|
27087
|
+
return { top: 0, bottom: 0, left: 0, right: 0 };
|
|
27088
|
+
}
|
|
27089
|
+
const el = doc.createElement('div');
|
|
27090
|
+
el.style.cssText =
|
|
27091
|
+
'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' +
|
|
27092
|
+
'padding-top:var(--ion-safe-area-top,0px);' +
|
|
27093
|
+
'padding-bottom:var(--ion-safe-area-bottom,0px);' +
|
|
27094
|
+
'padding-left:var(--ion-safe-area-left,0px);' +
|
|
27095
|
+
'padding-right:var(--ion-safe-area-right,0px);';
|
|
27096
|
+
doc.body.appendChild(el);
|
|
27097
|
+
const style = getComputedStyle(el);
|
|
27098
|
+
const insets = {
|
|
27099
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
27100
|
+
bottom: parseFloat(style.paddingBottom) || 0,
|
|
27101
|
+
left: parseFloat(style.paddingLeft) || 0,
|
|
27102
|
+
right: parseFloat(style.paddingRight) || 0,
|
|
27103
|
+
};
|
|
27104
|
+
el.remove();
|
|
27105
|
+
cachedInsets = insets;
|
|
27106
|
+
if (!cacheInvalidationScheduled) {
|
|
27107
|
+
cacheInvalidationScheduled = true;
|
|
27108
|
+
raf(() => {
|
|
27109
|
+
cachedInsets = null;
|
|
27110
|
+
cacheInvalidationScheduled = false;
|
|
27111
|
+
});
|
|
27112
|
+
}
|
|
27113
|
+
return insets;
|
|
27114
|
+
};
|
|
26713
27115
|
/**
|
|
26714
27116
|
* Returns the dimensions of the popover
|
|
26715
27117
|
* arrow on `ios` mode. If arrow is disabled
|
|
@@ -27345,8 +27747,10 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
|
|
|
27345
27747
|
* Adjusts popover positioning coordinates
|
|
27346
27748
|
* such that popover does not appear offscreen
|
|
27347
27749
|
* or overlapping safe area bounds.
|
|
27750
|
+
*
|
|
27751
|
+
* @internal - This is not part of the public API.
|
|
27348
27752
|
*/
|
|
27349
|
-
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight,
|
|
27753
|
+
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeArea, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
|
|
27350
27754
|
let arrowTop = coordArrowTop;
|
|
27351
27755
|
const arrowLeft = coordArrowLeft;
|
|
27352
27756
|
let left = coordLeft;
|
|
@@ -27356,16 +27760,19 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27356
27760
|
let originY = contentOriginY;
|
|
27357
27761
|
let checkSafeAreaLeft = false;
|
|
27358
27762
|
let checkSafeAreaRight = false;
|
|
27763
|
+
let checkSafeAreaTop = false;
|
|
27764
|
+
let checkSafeAreaBottom = false;
|
|
27359
27765
|
const triggerTop = triggerCoordinates
|
|
27360
27766
|
? triggerCoordinates.top + triggerCoordinates.height
|
|
27361
27767
|
: bodyHeight / 2 - contentHeight / 2;
|
|
27362
27768
|
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
|
|
27363
27769
|
let addPopoverBottomClass = false;
|
|
27770
|
+
const hideArrow = false;
|
|
27364
27771
|
/**
|
|
27365
27772
|
* Adjust popover so it does not
|
|
27366
27773
|
* go off the left of the screen.
|
|
27367
27774
|
*/
|
|
27368
|
-
if (left < bodyPadding +
|
|
27775
|
+
if (left < bodyPadding + safeArea.left) {
|
|
27369
27776
|
left = bodyPadding;
|
|
27370
27777
|
checkSafeAreaLeft = true;
|
|
27371
27778
|
originX = 'left';
|
|
@@ -27374,41 +27781,58 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27374
27781
|
* go off the right of the screen.
|
|
27375
27782
|
*/
|
|
27376
27783
|
}
|
|
27377
|
-
else if (contentWidth + bodyPadding + left +
|
|
27784
|
+
else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) {
|
|
27378
27785
|
checkSafeAreaRight = true;
|
|
27379
27786
|
left = bodyWidth - contentWidth - bodyPadding;
|
|
27380
27787
|
originX = 'right';
|
|
27381
27788
|
}
|
|
27382
27789
|
/**
|
|
27383
27790
|
* Adjust popover so it does not
|
|
27384
|
-
* go off the
|
|
27791
|
+
* go off the bottom of the screen
|
|
27792
|
+
* or overlap the bottom safe area.
|
|
27385
27793
|
* If popover is on the left or the right of
|
|
27386
27794
|
* the trigger, then we should not adjust top
|
|
27387
27795
|
* margins.
|
|
27388
27796
|
*/
|
|
27389
|
-
if (triggerTop + triggerHeight + contentHeight > bodyHeight
|
|
27390
|
-
|
|
27797
|
+
if (triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom &&
|
|
27798
|
+
(side === 'top' || side === 'bottom')) {
|
|
27799
|
+
/**
|
|
27800
|
+
* Calculate where the popover top would be if flipped
|
|
27801
|
+
* above the trigger. Check whether that position clears
|
|
27802
|
+
* the top safe area with room for bodyPadding.
|
|
27803
|
+
*/
|
|
27804
|
+
const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1);
|
|
27805
|
+
if (idealFlipTop >= safeArea.top + bodyPadding) {
|
|
27391
27806
|
/**
|
|
27392
|
-
*
|
|
27393
|
-
*
|
|
27394
|
-
*
|
|
27395
|
-
* off the bottom of the screen. However, we do not want to move
|
|
27396
|
-
* the popover up so much that it goes off the top of the screen.
|
|
27397
|
-
*
|
|
27398
|
-
* We chose 12 here so that the popover position looks a bit nicer as
|
|
27399
|
-
* it is not right up against the edge of the screen.
|
|
27807
|
+
* Popover fits above the trigger without overlapping
|
|
27808
|
+
* the top safe area. Use the ideal position directly —
|
|
27809
|
+
* no safe-area CSS vars needed since it clears both edges.
|
|
27400
27810
|
*/
|
|
27401
|
-
top =
|
|
27811
|
+
top = idealFlipTop;
|
|
27402
27812
|
arrowTop = top + contentHeight;
|
|
27403
27813
|
originY = 'bottom';
|
|
27404
27814
|
addPopoverBottomClass = true;
|
|
27405
|
-
/**
|
|
27406
|
-
* If not enough room for popover to appear
|
|
27407
|
-
* above trigger, then cut it off.
|
|
27408
|
-
*/
|
|
27409
27815
|
}
|
|
27410
27816
|
else {
|
|
27817
|
+
/**
|
|
27818
|
+
* Can't flip above the trigger. Constrain the bottom
|
|
27819
|
+
* edge while keeping the top near the trigger. This
|
|
27820
|
+
* creates a scrollable area anchored to the trigger.
|
|
27821
|
+
*/
|
|
27411
27822
|
bottom = bodyPadding;
|
|
27823
|
+
checkSafeAreaBottom = true;
|
|
27824
|
+
/**
|
|
27825
|
+
* When the trigger is near the bottom of the screen,
|
|
27826
|
+
* the calculated top may be at or past the bottom
|
|
27827
|
+
* constraint, leaving zero visible height. In that
|
|
27828
|
+
* case, pull top up to the top safe area so the
|
|
27829
|
+
* popover fills the available space between safe areas.
|
|
27830
|
+
*/
|
|
27831
|
+
const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding;
|
|
27832
|
+
if (top >= bottomEdge) {
|
|
27833
|
+
top = safeArea.top + bodyPadding;
|
|
27834
|
+
checkSafeAreaTop = true;
|
|
27835
|
+
}
|
|
27412
27836
|
}
|
|
27413
27837
|
}
|
|
27414
27838
|
return {
|
|
@@ -27419,9 +27843,12 @@ const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyW
|
|
|
27419
27843
|
originY,
|
|
27420
27844
|
checkSafeAreaLeft,
|
|
27421
27845
|
checkSafeAreaRight,
|
|
27846
|
+
checkSafeAreaTop,
|
|
27847
|
+
checkSafeAreaBottom,
|
|
27422
27848
|
arrowTop,
|
|
27423
27849
|
arrowLeft,
|
|
27424
27850
|
addPopoverBottomClass,
|
|
27851
|
+
hideArrow,
|
|
27425
27852
|
};
|
|
27426
27853
|
};
|
|
27427
27854
|
const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
@@ -27451,6 +27878,15 @@ const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
|
|
|
27451
27878
|
};
|
|
27452
27879
|
|
|
27453
27880
|
const POPOVER_IOS_BODY_PADDING = 5;
|
|
27881
|
+
/**
|
|
27882
|
+
* Minimum edge margin for iOS popovers ensures visual spacing from screen
|
|
27883
|
+
* edges on devices without safe areas (e.g., older iPhones without notches).
|
|
27884
|
+
* Previously this was a hardcoded `safeAreaMargin = 25` that served dual
|
|
27885
|
+
* purpose: safe-area avoidance AND visual spacing. Now that actual safe-area
|
|
27886
|
+
* insets are read dynamically, this floor preserves the visual spacing when
|
|
27887
|
+
* safe-area values are 0.
|
|
27888
|
+
*/
|
|
27889
|
+
const POPOVER_IOS_MIN_EDGE_MARGIN = 25;
|
|
27454
27890
|
/**
|
|
27455
27891
|
* iOS Popover Enter Animation
|
|
27456
27892
|
*/
|
|
@@ -27476,8 +27912,16 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27476
27912
|
};
|
|
27477
27913
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
27478
27914
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
27479
|
-
const
|
|
27480
|
-
const
|
|
27915
|
+
const rawSafeArea = getSafeAreaInsets(doc);
|
|
27916
|
+
const safeArea = size === 'cover'
|
|
27917
|
+
? { top: 0, bottom: 0, left: 0, right: 0 }
|
|
27918
|
+
: {
|
|
27919
|
+
top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27920
|
+
bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27921
|
+
left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27922
|
+
right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN),
|
|
27923
|
+
};
|
|
27924
|
+
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);
|
|
27481
27925
|
const baseAnimation = createAnimation();
|
|
27482
27926
|
const backdropAnimation = createAnimation();
|
|
27483
27927
|
const contentAnimation = createAnimation();
|
|
@@ -27508,10 +27952,14 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27508
27952
|
baseEl.classList.add('popover-bottom');
|
|
27509
27953
|
}
|
|
27510
27954
|
if (bottom !== undefined) {
|
|
27511
|
-
|
|
27955
|
+
let bottomValue = `${bottom}px`;
|
|
27956
|
+
if (checkSafeAreaBottom) {
|
|
27957
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
27958
|
+
}
|
|
27959
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
27512
27960
|
}
|
|
27513
|
-
const safeAreaLeft = ' + var(--ion-safe-area-left,
|
|
27514
|
-
const safeAreaRight = ' - var(--ion-safe-area-right,
|
|
27961
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)';
|
|
27962
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0px)';
|
|
27515
27963
|
let leftValue = `${left}px`;
|
|
27516
27964
|
if (checkSafeAreaLeft) {
|
|
27517
27965
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
@@ -27519,7 +27967,11 @@ const iosEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27519
27967
|
if (checkSafeAreaRight) {
|
|
27520
27968
|
leftValue = `${left}px${safeAreaRight}`;
|
|
27521
27969
|
}
|
|
27522
|
-
|
|
27970
|
+
let topValue = `${top}px`;
|
|
27971
|
+
if (checkSafeAreaTop) {
|
|
27972
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
27973
|
+
}
|
|
27974
|
+
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
|
27523
27975
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
27524
27976
|
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
27525
27977
|
if (arrowEl !== null) {
|
|
@@ -27595,7 +28047,23 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27595
28047
|
};
|
|
27596
28048
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
|
|
27597
28049
|
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
|
|
27598
|
-
|
|
28050
|
+
// MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas).
|
|
28051
|
+
// This is needed for Android edge-to-edge (API 36+) where system bars overlap content.
|
|
28052
|
+
const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc);
|
|
28053
|
+
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);
|
|
28054
|
+
const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)';
|
|
28055
|
+
const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)';
|
|
28056
|
+
let leftValue = `${left}px`;
|
|
28057
|
+
if (checkSafeAreaLeft) {
|
|
28058
|
+
leftValue = `${left}px${safeAreaLeftCalc}`;
|
|
28059
|
+
}
|
|
28060
|
+
if (checkSafeAreaRight) {
|
|
28061
|
+
leftValue = `${left}px${safeAreaRightCalc}`;
|
|
28062
|
+
}
|
|
28063
|
+
let topValue = `${top}px`;
|
|
28064
|
+
if (checkSafeAreaTop) {
|
|
28065
|
+
topValue = `${top}px + var(--ion-safe-area-top, 0px)`;
|
|
28066
|
+
}
|
|
27599
28067
|
const baseAnimation = createAnimation();
|
|
27600
28068
|
const backdropAnimation = createAnimation();
|
|
27601
28069
|
const wrapperAnimation = createAnimation();
|
|
@@ -27612,13 +28080,17 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27612
28080
|
contentAnimation
|
|
27613
28081
|
.addElement(contentEl)
|
|
27614
28082
|
.beforeStyles({
|
|
27615
|
-
top: `calc(${
|
|
27616
|
-
left: `calc(${
|
|
28083
|
+
top: `calc(${topValue} + var(--offset-y, 0px))`,
|
|
28084
|
+
left: `calc(${leftValue} + var(--offset-x, 0px))`,
|
|
27617
28085
|
'transform-origin': `${originY} ${originX}`,
|
|
27618
28086
|
})
|
|
27619
28087
|
.beforeAddWrite(() => {
|
|
27620
28088
|
if (bottom !== undefined) {
|
|
27621
|
-
|
|
28089
|
+
let bottomValue = `${bottom}px`;
|
|
28090
|
+
if (checkSafeAreaBottom) {
|
|
28091
|
+
bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`;
|
|
28092
|
+
}
|
|
28093
|
+
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
|
27622
28094
|
}
|
|
27623
28095
|
})
|
|
27624
28096
|
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
|
@@ -27630,7 +28102,7 @@ const mdEnterAnimation$1 = (baseEl, opts) => {
|
|
|
27630
28102
|
if (size === 'cover') {
|
|
27631
28103
|
baseEl.style.setProperty('--width', `${contentWidth}px`);
|
|
27632
28104
|
}
|
|
27633
|
-
if (
|
|
28105
|
+
if (addPopoverBottomClass) {
|
|
27634
28106
|
baseEl.classList.add('popover-bottom');
|
|
27635
28107
|
}
|
|
27636
28108
|
})
|
|
@@ -28465,9 +28937,9 @@ class Radio {
|
|
|
28465
28937
|
}
|
|
28466
28938
|
let radioButtonIds = 0;
|
|
28467
28939
|
|
|
28468
|
-
const radioGroupIosCss = "ion-radio-group{vertical-align:top}.radio-group-
|
|
28940
|
+
const radioGroupIosCss = "ion-radio-group{vertical-align:top}.radio-group-top{line-height:1.5}.radio-group-top .error-text{display:none;color:var(--ion-color-danger, #c5000f)}.radio-group-top .helper-text{display:block;color:var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d))}.ion-touched.ion-invalid .radio-group-top .error-text{display:block}.ion-touched.ion-invalid .radio-group-top .helper-text{display:none}ion-list .radio-group-top{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px}";
|
|
28469
28941
|
|
|
28470
|
-
const radioGroupMdCss = "ion-radio-group{vertical-align:top}.radio-group-
|
|
28942
|
+
const radioGroupMdCss = "ion-radio-group{vertical-align:top}.radio-group-top{line-height:1.5}.radio-group-top .error-text{display:none;color:var(--ion-color-danger, #c5000f)}.radio-group-top .helper-text{display:block;color:var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d))}.ion-touched.ion-invalid .radio-group-top .error-text{display:block}.ion-touched.ion-invalid .radio-group-top .helper-text{display:none}ion-list .radio-group-top{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px}";
|
|
28471
28943
|
|
|
28472
28944
|
class RadioGroup {
|
|
28473
28945
|
constructor(hostRef) {
|
|
@@ -32367,11 +32839,14 @@ class Searchbar {
|
|
|
32367
32839
|
* Clears the input field and triggers the control change.
|
|
32368
32840
|
*/
|
|
32369
32841
|
this.onClearInput = async (shouldFocus) => {
|
|
32842
|
+
if (this.clearTimeout) {
|
|
32843
|
+
clearTimeout(this.clearTimeout);
|
|
32844
|
+
}
|
|
32370
32845
|
this.ionClear.emit();
|
|
32371
32846
|
return new Promise((resolve) => {
|
|
32372
32847
|
// setTimeout() fixes https://github.com/ionic-team/ionic-framework/issues/7527
|
|
32373
32848
|
// wait for 4 frames
|
|
32374
|
-
setTimeout(() => {
|
|
32849
|
+
this.clearTimeout = setTimeout(() => {
|
|
32375
32850
|
const value = this.getValue();
|
|
32376
32851
|
if (value !== '') {
|
|
32377
32852
|
this.value = '';
|
|
@@ -32503,10 +32978,18 @@ class Searchbar {
|
|
|
32503
32978
|
this.originalIonInput = this.ionInput;
|
|
32504
32979
|
this.positionElements();
|
|
32505
32980
|
this.debounceChanged();
|
|
32506
|
-
setTimeout(() => {
|
|
32981
|
+
this.loadTimeout = setTimeout(() => {
|
|
32507
32982
|
this.noAnimate = false;
|
|
32508
32983
|
}, 300);
|
|
32509
32984
|
}
|
|
32985
|
+
disconnectedCallback() {
|
|
32986
|
+
if (this.loadTimeout) {
|
|
32987
|
+
clearTimeout(this.loadTimeout);
|
|
32988
|
+
}
|
|
32989
|
+
if (this.clearTimeout) {
|
|
32990
|
+
clearTimeout(this.clearTimeout);
|
|
32991
|
+
}
|
|
32992
|
+
}
|
|
32510
32993
|
emitStyle() {
|
|
32511
32994
|
this.ionStyle.emit({
|
|
32512
32995
|
searchbar: true,
|
|
@@ -32695,8 +33178,8 @@ class Searchbar {
|
|
|
32695
33178
|
const clearIcon = this.clearIcon || (mode === 'ios' ? closeCircle : closeSharp);
|
|
32696
33179
|
const searchIcon = this.searchIcon || (mode === 'ios' ? searchOutline : searchSharp);
|
|
32697
33180
|
const shouldShowCancelButton = this.shouldShowCancelButton();
|
|
32698
|
-
const cancelButton = this.showCancelButton !== 'never' && (hAsync("button", { key: '
|
|
32699
|
-
return (hAsync(Host, { key: '
|
|
33181
|
+
const cancelButton = this.showCancelButton !== 'never' && (hAsync("button", { key: '9e9f3a7f6ee66140181f8c3cfa42bbeee75adafc', "aria-label": cancelButtonText, "aria-hidden": shouldShowCancelButton ? undefined : 'true', type: "button", tabIndex: mode === 'ios' && !shouldShowCancelButton ? -1 : undefined, onMouseDown: this.onCancelSearchbar, onTouchStart: this.onCancelSearchbar, class: "searchbar-cancel-button" }, hAsync("div", { key: '5f83fbcc0708830637815e8c0b69356cad381870', "aria-hidden": "true" }, mode === 'md' ? (hAsync("ion-icon", { "aria-hidden": "true", mode: mode, icon: this.cancelButtonIcon, lazy: false })) : (cancelButtonText))));
|
|
33182
|
+
return (hAsync(Host, { key: '219c263c1f3cc63e18bc2d2ba5d672b7806eddd0', role: "search", "aria-disabled": this.disabled ? 'true' : null, class: createColorClasses$1(this.color, {
|
|
32700
33183
|
[mode]: true,
|
|
32701
33184
|
'searchbar-animated': animated,
|
|
32702
33185
|
'searchbar-disabled': this.disabled,
|
|
@@ -32706,14 +33189,14 @@ class Searchbar {
|
|
|
32706
33189
|
'searchbar-has-focus': this.focused,
|
|
32707
33190
|
'searchbar-should-show-clear': this.shouldShowClearButton(),
|
|
32708
33191
|
'searchbar-should-show-cancel': this.shouldShowCancelButton(),
|
|
32709
|
-
}) }, hAsync("div", { key: '
|
|
33192
|
+
}) }, hAsync("div", { key: '3f1c25442d6d87c0df67f79482a925b80dcc403d', class: "searchbar-input-container" }, hAsync("input", Object.assign({ key: 'a386fecafda0fab449ea1488906ba6cfc55e2b5c', "aria-label": "search text", disabled: this.disabled, ref: (el) => (this.nativeInput = el), class: "searchbar-input", inputMode: this.inputmode, enterKeyHint: this.enterkeyhint, name: this.name, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, minLength: this.minlength, maxLength: this.maxlength, placeholder: this.placeholder, type: this.type, value: this.getValue(), autoCapitalize: autocapitalize === 'default' ? undefined : autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, spellcheck: this.spellcheck }, this.inheritedAttributes)), mode === 'md' && cancelButton, hAsync("ion-icon", { key: '5f81642628335def39a14f0726e0fafdc7203776', "aria-hidden": "true", mode: mode, icon: searchIcon, lazy: false, class: "searchbar-search-icon" }), hAsync("button", { key: 'da3073646f2d7f639261b556517fbadabc3c10e9', "aria-label": "reset", type: "button", "no-blur": true, class: "searchbar-clear-button", onPointerDown: (ev) => {
|
|
32710
33193
|
/**
|
|
32711
33194
|
* This prevents mobile browsers from
|
|
32712
33195
|
* blurring the input when the clear
|
|
32713
33196
|
* button is activated.
|
|
32714
33197
|
*/
|
|
32715
33198
|
ev.preventDefault();
|
|
32716
|
-
}, onClick: () => this.onClearInput(true) }, hAsync("ion-icon", { key: '
|
|
33199
|
+
}, onClick: () => this.onClearInput(true) }, hAsync("ion-icon", { key: '065cd6c707ae462c0767df1bab04ce8045f340a4', "aria-hidden": "true", mode: mode, icon: clearIcon, lazy: false, class: "searchbar-clear-icon" }))), mode === 'ios' && cancelButton));
|
|
32717
33200
|
}
|
|
32718
33201
|
get el() { return getElement(this); }
|
|
32719
33202
|
static get watchers() { return {
|