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