@ionic/core 8.7.17-dev.11767717752.14fe98a4 → 8.7.17-dev.11767796972.148e4bc4
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/content.js +51 -7
- package/components/modal.js +81 -26
- package/dist/cjs/ion-app_8.cjs.entry.js +50 -6
- package/dist/cjs/ion-modal.cjs.entry.js +81 -26
- package/dist/collection/components/content/content.js +50 -6
- package/dist/collection/components/modal/modal.js +81 -26
- package/dist/docs.json +1 -1
- package/dist/esm/ion-app_8.entry.js +51 -7
- package/dist/esm/ion-modal.entry.js +81 -26
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-3830217a.entry.js +4 -0
- package/dist/ionic/p-cecd141b.entry.js +4 -0
- package/dist/types/components/content/content.d.ts +15 -2
- package/dist/types/components/modal/modal.d.ts +12 -2
- package/hydrate/index.js +131 -33
- package/hydrate/index.mjs +131 -33
- package/package.json +1 -1
- package/dist/ionic/p-34cdcd15.entry.js +0 -4
- package/dist/ionic/p-fedca459.entry.js +0 -4
|
@@ -124,15 +124,39 @@ export class Content {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Detects sibling ion-header and ion-footer elements
|
|
128
|
-
*
|
|
127
|
+
* Detects sibling ion-header and ion-footer elements and sets up
|
|
128
|
+
* a mutation observer to handle dynamic changes (e.g., conditional rendering).
|
|
129
129
|
*/
|
|
130
130
|
detectSiblingElements() {
|
|
131
|
-
|
|
131
|
+
this.updateSiblingDetection();
|
|
132
|
+
// Watch for dynamic header/footer changes (common in React conditional rendering)
|
|
133
|
+
const parent = this.el.parentElement;
|
|
134
|
+
if (parent && !this.parentMutationObserver) {
|
|
135
|
+
this.parentMutationObserver = new MutationObserver(() => {
|
|
136
|
+
this.updateSiblingDetection();
|
|
137
|
+
forceUpdate(this);
|
|
138
|
+
});
|
|
139
|
+
this.parentMutationObserver.observe(parent, { childList: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
144
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
145
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
146
|
+
*/
|
|
147
|
+
updateSiblingDetection() {
|
|
132
148
|
const parent = this.el.parentElement;
|
|
133
149
|
if (parent) {
|
|
150
|
+
// First check for direct ion-header/ion-footer siblings
|
|
134
151
|
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
135
152
|
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
153
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
154
|
+
if (!this.hasHeader) {
|
|
155
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
156
|
+
}
|
|
157
|
+
if (!this.hasFooter) {
|
|
158
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
159
|
+
}
|
|
136
160
|
}
|
|
137
161
|
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
138
162
|
if (!this.hasFooter) {
|
|
@@ -142,8 +166,28 @@ export class Content {
|
|
|
142
166
|
}
|
|
143
167
|
}
|
|
144
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
171
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
172
|
+
*/
|
|
173
|
+
siblingContainsElement(parent, tagName) {
|
|
174
|
+
for (const sibling of parent.children) {
|
|
175
|
+
// Skip ion-content itself
|
|
176
|
+
if (sibling === this.el)
|
|
177
|
+
continue;
|
|
178
|
+
// Check if this sibling contains the target element as an immediate child
|
|
179
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
145
185
|
disconnectedCallback() {
|
|
186
|
+
var _a;
|
|
146
187
|
this.onScrollEnd();
|
|
188
|
+
// Clean up mutation observer to prevent memory leaks
|
|
189
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
190
|
+
this.parentMutationObserver = undefined;
|
|
147
191
|
if (hasLazyBuild(this.el)) {
|
|
148
192
|
/**
|
|
149
193
|
* The event listener and tabs caches need to
|
|
@@ -396,7 +440,7 @@ export class Content {
|
|
|
396
440
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
397
441
|
const transitionShadow = mode === 'ios';
|
|
398
442
|
this.resize();
|
|
399
|
-
return (h(Host, Object.assign({ key: '
|
|
443
|
+
return (h(Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
|
|
400
444
|
[mode]: true,
|
|
401
445
|
'content-sizing': hostContext('ion-popover', this.el),
|
|
402
446
|
overscroll: forceOverscroll,
|
|
@@ -406,12 +450,12 @@ export class Content {
|
|
|
406
450
|
}), style: {
|
|
407
451
|
'--offset-top': `${this.cTop}px`,
|
|
408
452
|
'--offset-bottom': `${this.cBottom}px`,
|
|
409
|
-
} }, inheritedAttributes), h("div", { key: '
|
|
453
|
+
} }, inheritedAttributes), h("div", { key: '4c0482cda885348eea9eb66d7f076af6b38c52e5', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '6fbb39bf7ab7120009c56aea9340de45c934eeed', class: {
|
|
410
454
|
'inner-scroll': true,
|
|
411
455
|
'scroll-x': scrollX,
|
|
412
456
|
'scroll-y': scrollY,
|
|
413
457
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
414
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '
|
|
458
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6425bc84edbc0c5b1f2764a1d611df1b46628274' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
|
|
415
459
|
}
|
|
416
460
|
static get is() { return "ion-content"; }
|
|
417
461
|
static get encapsulation() { return "shadow"; }
|
|
@@ -44,6 +44,8 @@ export class Modal {
|
|
|
44
44
|
this.gestureAnimationDismissing = false;
|
|
45
45
|
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
46
46
|
this.skipSafeAreaCoordinateDetection = false;
|
|
47
|
+
// Track previous safe-area state to avoid redundant DOM writes
|
|
48
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
47
49
|
this.presented = false;
|
|
48
50
|
/** @internal */
|
|
49
51
|
this.hasController = false;
|
|
@@ -234,7 +236,8 @@ export class Modal {
|
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
onWindowResize() {
|
|
237
|
-
//
|
|
239
|
+
// Invalidate safe-area cache on resize (device rotation may change values)
|
|
240
|
+
this.cachedSafeAreas = undefined;
|
|
238
241
|
this.updateSafeAreaOverrides();
|
|
239
242
|
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
240
243
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
@@ -693,8 +696,26 @@ export class Modal {
|
|
|
693
696
|
*/
|
|
694
697
|
applyFullscreenSafeArea() {
|
|
695
698
|
this.skipSafeAreaCoordinateDetection = true;
|
|
699
|
+
this.updateFooterPadding();
|
|
700
|
+
// Watch for dynamic footer additions/removals (e.g., async data loading)
|
|
701
|
+
if (!this.footerObserver) {
|
|
702
|
+
this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
|
|
703
|
+
this.footerObserver.observe(this.el, { childList: true, subtree: true });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Updates wrapper padding based on footer presence.
|
|
708
|
+
* Called initially and when footer is dynamically added/removed.
|
|
709
|
+
*/
|
|
710
|
+
updateFooterPadding() {
|
|
711
|
+
if (!this.wrapperEl)
|
|
712
|
+
return;
|
|
696
713
|
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
697
|
-
if (
|
|
714
|
+
if (hasFooter) {
|
|
715
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
716
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
698
719
|
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
699
720
|
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
700
721
|
}
|
|
@@ -712,21 +733,26 @@ export class Modal {
|
|
|
712
733
|
}
|
|
713
734
|
/**
|
|
714
735
|
* Gets the root safe-area values from the document element.
|
|
715
|
-
*
|
|
736
|
+
* Uses cached values during gestures to avoid getComputedStyle calls.
|
|
716
737
|
*/
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
738
|
+
getSafeAreaValues() {
|
|
739
|
+
if (!this.cachedSafeAreas) {
|
|
740
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
741
|
+
this.cachedSafeAreas = {
|
|
742
|
+
top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
|
|
743
|
+
bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
|
|
744
|
+
left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
|
|
745
|
+
right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return this.cachedSafeAreas;
|
|
725
749
|
}
|
|
726
750
|
/**
|
|
727
751
|
* Updates safe-area CSS variable overrides based on whether the modal
|
|
728
752
|
* extends into each safe-area region. Called after animation
|
|
729
753
|
* and during gestures to handle dynamic position changes.
|
|
754
|
+
*
|
|
755
|
+
* Optimized to avoid redundant DOM writes by tracking previous state.
|
|
730
756
|
*/
|
|
731
757
|
updateSafeAreaOverrides() {
|
|
732
758
|
if (this.skipSafeAreaCoordinateDetection) {
|
|
@@ -737,20 +763,34 @@ export class Modal {
|
|
|
737
763
|
return;
|
|
738
764
|
}
|
|
739
765
|
const rect = wrapper.getBoundingClientRect();
|
|
740
|
-
const safeAreas = this.
|
|
766
|
+
const safeAreas = this.getSafeAreaValues();
|
|
741
767
|
const extendsIntoTop = rect.top < safeAreas.top;
|
|
742
768
|
const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
|
|
743
769
|
const extendsIntoLeft = rect.left < safeAreas.left;
|
|
744
770
|
const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
|
|
771
|
+
// Only update DOM when state actually changes
|
|
772
|
+
const prev = this.prevSafeAreaState;
|
|
745
773
|
const style = this.el.style;
|
|
746
|
-
extendsIntoTop
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
774
|
+
if (extendsIntoTop !== prev.top) {
|
|
775
|
+
extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
776
|
+
prev.top = extendsIntoTop;
|
|
777
|
+
}
|
|
778
|
+
if (extendsIntoBottom !== prev.bottom) {
|
|
779
|
+
extendsIntoBottom
|
|
780
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
781
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
782
|
+
prev.bottom = extendsIntoBottom;
|
|
783
|
+
}
|
|
784
|
+
if (extendsIntoLeft !== prev.left) {
|
|
785
|
+
extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
786
|
+
prev.left = extendsIntoLeft;
|
|
787
|
+
}
|
|
788
|
+
if (extendsIntoRight !== prev.right) {
|
|
789
|
+
extendsIntoRight
|
|
790
|
+
? style.removeProperty('--ion-safe-area-right')
|
|
791
|
+
: style.setProperty('--ion-safe-area-right', '0px');
|
|
792
|
+
prev.right = extendsIntoRight;
|
|
793
|
+
}
|
|
754
794
|
}
|
|
755
795
|
sheetOnDismiss() {
|
|
756
796
|
/**
|
|
@@ -782,7 +822,7 @@ export class Modal {
|
|
|
782
822
|
* For example, `cancel` or `backdrop`.
|
|
783
823
|
*/
|
|
784
824
|
async dismiss(data, role) {
|
|
785
|
-
var _a;
|
|
825
|
+
var _a, _b;
|
|
786
826
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
787
827
|
return false;
|
|
788
828
|
}
|
|
@@ -844,8 +884,23 @@ export class Modal {
|
|
|
844
884
|
}
|
|
845
885
|
this.currentBreakpoint = undefined;
|
|
846
886
|
this.animation = undefined;
|
|
847
|
-
// Reset safe-area
|
|
887
|
+
// Reset safe-area state for potential re-presentation
|
|
848
888
|
this.skipSafeAreaCoordinateDetection = false;
|
|
889
|
+
this.cachedSafeAreas = undefined;
|
|
890
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
891
|
+
(_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
892
|
+
this.footerObserver = undefined;
|
|
893
|
+
// Clear styles that may have been set for safe-area handling
|
|
894
|
+
if (this.wrapperEl) {
|
|
895
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
896
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
897
|
+
}
|
|
898
|
+
// Clear safe-area CSS variable overrides
|
|
899
|
+
const style = this.el.style;
|
|
900
|
+
style.removeProperty('--ion-safe-area-top');
|
|
901
|
+
style.removeProperty('--ion-safe-area-bottom');
|
|
902
|
+
style.removeProperty('--ion-safe-area-left');
|
|
903
|
+
style.removeProperty('--ion-safe-area-right');
|
|
849
904
|
unlock();
|
|
850
905
|
return dismissed;
|
|
851
906
|
}
|
|
@@ -1103,20 +1158,20 @@ export class Modal {
|
|
|
1103
1158
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
1104
1159
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
1105
1160
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
1106
|
-
return (h(Host, Object.assign({ key: '
|
|
1161
|
+
return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
|
|
1107
1162
|
// Allow the modal to be navigable when the handle is focusable
|
|
1108
1163
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
1109
1164
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
1110
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '
|
|
1165
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '125658fbb071960da3905854668078e15bce56da', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '4a63815ef165e5806fef85ef48bf509813ff55c9', class: "modal-shadow" }), h("div", Object.assign({ key: 'cfc4b20354cbf2c0f873f6aee91c9b8f553de61d',
|
|
1111
1166
|
/*
|
|
1112
1167
|
role and aria-modal must be used on the
|
|
1113
1168
|
same element. They must also be set inside the
|
|
1114
1169
|
shadow DOM otherwise ion-button will not be highlighted
|
|
1115
1170
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
1116
1171
|
*/
|
|
1117
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
1172
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '33a4ee89bb8b6512883cb8756641c1e27fdb0ebc', class: "modal-handle",
|
|
1118
1173
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
1119
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '
|
|
1174
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '986fafb71234591c96e927f92b7330bb5c76fc2e', onSlotchange: this.onSlotChange }))));
|
|
1120
1175
|
}
|
|
1121
1176
|
static get is() { return "ion-modal"; }
|
|
1122
1177
|
static get encapsulation() { return "shadow"; }
|
package/dist/docs.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { r as registerInstance, h, e as config, d as Host, g as getElement, f as printIonWarning, c as createEvent,
|
|
4
|
+
import { r as registerInstance, h, e as config, d as Host, g as getElement, f as printIonWarning, c as createEvent, i as forceUpdate, a as readTask, w as writeTask, j as printIonError } from './index-C8IsBmNU.js';
|
|
5
5
|
import { shouldUseCloseWatcher } from './hardware-back-button-CPLxO-Ev.js';
|
|
6
6
|
import { b as getIonMode, a as isPlatform } from './ionic-global-CDrldh-5.js';
|
|
7
7
|
import { i as inheritAriaAttributes, h as hasLazyBuild, c as componentOnReady, e as clamp, s as shallowEqualStringMap } from './helpers-DEn3pfjm.js';
|
|
@@ -268,15 +268,39 @@ const Content = class {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
/**
|
|
271
|
-
* Detects sibling ion-header and ion-footer elements
|
|
272
|
-
*
|
|
271
|
+
* Detects sibling ion-header and ion-footer elements and sets up
|
|
272
|
+
* a mutation observer to handle dynamic changes (e.g., conditional rendering).
|
|
273
273
|
*/
|
|
274
274
|
detectSiblingElements() {
|
|
275
|
-
|
|
275
|
+
this.updateSiblingDetection();
|
|
276
|
+
// Watch for dynamic header/footer changes (common in React conditional rendering)
|
|
277
|
+
const parent = this.el.parentElement;
|
|
278
|
+
if (parent && !this.parentMutationObserver) {
|
|
279
|
+
this.parentMutationObserver = new MutationObserver(() => {
|
|
280
|
+
this.updateSiblingDetection();
|
|
281
|
+
forceUpdate(this);
|
|
282
|
+
});
|
|
283
|
+
this.parentMutationObserver.observe(parent, { childList: true });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
288
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
289
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
290
|
+
*/
|
|
291
|
+
updateSiblingDetection() {
|
|
276
292
|
const parent = this.el.parentElement;
|
|
277
293
|
if (parent) {
|
|
294
|
+
// First check for direct ion-header/ion-footer siblings
|
|
278
295
|
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
279
296
|
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
297
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
298
|
+
if (!this.hasHeader) {
|
|
299
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
300
|
+
}
|
|
301
|
+
if (!this.hasFooter) {
|
|
302
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
303
|
+
}
|
|
280
304
|
}
|
|
281
305
|
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
282
306
|
if (!this.hasFooter) {
|
|
@@ -286,8 +310,28 @@ const Content = class {
|
|
|
286
310
|
}
|
|
287
311
|
}
|
|
288
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
315
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
316
|
+
*/
|
|
317
|
+
siblingContainsElement(parent, tagName) {
|
|
318
|
+
for (const sibling of parent.children) {
|
|
319
|
+
// Skip ion-content itself
|
|
320
|
+
if (sibling === this.el)
|
|
321
|
+
continue;
|
|
322
|
+
// Check if this sibling contains the target element as an immediate child
|
|
323
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
289
329
|
disconnectedCallback() {
|
|
330
|
+
var _a;
|
|
290
331
|
this.onScrollEnd();
|
|
332
|
+
// Clean up mutation observer to prevent memory leaks
|
|
333
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
334
|
+
this.parentMutationObserver = undefined;
|
|
291
335
|
if (hasLazyBuild(this.el)) {
|
|
292
336
|
/**
|
|
293
337
|
* The event listener and tabs caches need to
|
|
@@ -540,7 +584,7 @@ const Content = class {
|
|
|
540
584
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
541
585
|
const transitionShadow = mode === 'ios';
|
|
542
586
|
this.resize();
|
|
543
|
-
return (h(Host, Object.assign({ key: '
|
|
587
|
+
return (h(Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
|
|
544
588
|
[mode]: true,
|
|
545
589
|
'content-sizing': hostContext('ion-popover', this.el),
|
|
546
590
|
overscroll: forceOverscroll,
|
|
@@ -550,12 +594,12 @@ const Content = class {
|
|
|
550
594
|
}), style: {
|
|
551
595
|
'--offset-top': `${this.cTop}px`,
|
|
552
596
|
'--offset-bottom': `${this.cBottom}px`,
|
|
553
|
-
} }, inheritedAttributes), h("div", { key: '
|
|
597
|
+
} }, inheritedAttributes), h("div", { key: '4c0482cda885348eea9eb66d7f076af6b38c52e5', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '6fbb39bf7ab7120009c56aea9340de45c934eeed', class: {
|
|
554
598
|
'inner-scroll': true,
|
|
555
599
|
'scroll-x': scrollX,
|
|
556
600
|
'scroll-y': scrollY,
|
|
557
601
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
558
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '
|
|
602
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6425bc84edbc0c5b1f2764a1d611df1b46628274' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
|
|
559
603
|
}
|
|
560
604
|
get el() { return getElement(this); }
|
|
561
605
|
};
|
|
@@ -1503,6 +1503,8 @@ const Modal = class {
|
|
|
1503
1503
|
this.gestureAnimationDismissing = false;
|
|
1504
1504
|
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
1505
1505
|
this.skipSafeAreaCoordinateDetection = false;
|
|
1506
|
+
// Track previous safe-area state to avoid redundant DOM writes
|
|
1507
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
1506
1508
|
this.presented = false;
|
|
1507
1509
|
/** @internal */
|
|
1508
1510
|
this.hasController = false;
|
|
@@ -1693,7 +1695,8 @@ const Modal = class {
|
|
|
1693
1695
|
}
|
|
1694
1696
|
}
|
|
1695
1697
|
onWindowResize() {
|
|
1696
|
-
//
|
|
1698
|
+
// Invalidate safe-area cache on resize (device rotation may change values)
|
|
1699
|
+
this.cachedSafeAreas = undefined;
|
|
1697
1700
|
this.updateSafeAreaOverrides();
|
|
1698
1701
|
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
1699
1702
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
@@ -2152,8 +2155,26 @@ const Modal = class {
|
|
|
2152
2155
|
*/
|
|
2153
2156
|
applyFullscreenSafeArea() {
|
|
2154
2157
|
this.skipSafeAreaCoordinateDetection = true;
|
|
2158
|
+
this.updateFooterPadding();
|
|
2159
|
+
// Watch for dynamic footer additions/removals (e.g., async data loading)
|
|
2160
|
+
if (!this.footerObserver) {
|
|
2161
|
+
this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
|
|
2162
|
+
this.footerObserver.observe(this.el, { childList: true, subtree: true });
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Updates wrapper padding based on footer presence.
|
|
2167
|
+
* Called initially and when footer is dynamically added/removed.
|
|
2168
|
+
*/
|
|
2169
|
+
updateFooterPadding() {
|
|
2170
|
+
if (!this.wrapperEl)
|
|
2171
|
+
return;
|
|
2155
2172
|
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
2156
|
-
if (
|
|
2173
|
+
if (hasFooter) {
|
|
2174
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
2175
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
2176
|
+
}
|
|
2177
|
+
else {
|
|
2157
2178
|
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2158
2179
|
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
2159
2180
|
}
|
|
@@ -2171,21 +2192,26 @@ const Modal = class {
|
|
|
2171
2192
|
}
|
|
2172
2193
|
/**
|
|
2173
2194
|
* Gets the root safe-area values from the document element.
|
|
2174
|
-
*
|
|
2195
|
+
* Uses cached values during gestures to avoid getComputedStyle calls.
|
|
2175
2196
|
*/
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2197
|
+
getSafeAreaValues() {
|
|
2198
|
+
if (!this.cachedSafeAreas) {
|
|
2199
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
2200
|
+
this.cachedSafeAreas = {
|
|
2201
|
+
top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
|
|
2202
|
+
bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
|
|
2203
|
+
left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
|
|
2204
|
+
right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
return this.cachedSafeAreas;
|
|
2184
2208
|
}
|
|
2185
2209
|
/**
|
|
2186
2210
|
* Updates safe-area CSS variable overrides based on whether the modal
|
|
2187
2211
|
* extends into each safe-area region. Called after animation
|
|
2188
2212
|
* and during gestures to handle dynamic position changes.
|
|
2213
|
+
*
|
|
2214
|
+
* Optimized to avoid redundant DOM writes by tracking previous state.
|
|
2189
2215
|
*/
|
|
2190
2216
|
updateSafeAreaOverrides() {
|
|
2191
2217
|
if (this.skipSafeAreaCoordinateDetection) {
|
|
@@ -2196,20 +2222,34 @@ const Modal = class {
|
|
|
2196
2222
|
return;
|
|
2197
2223
|
}
|
|
2198
2224
|
const rect = wrapper.getBoundingClientRect();
|
|
2199
|
-
const safeAreas = this.
|
|
2225
|
+
const safeAreas = this.getSafeAreaValues();
|
|
2200
2226
|
const extendsIntoTop = rect.top < safeAreas.top;
|
|
2201
2227
|
const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
|
|
2202
2228
|
const extendsIntoLeft = rect.left < safeAreas.left;
|
|
2203
2229
|
const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
|
|
2230
|
+
// Only update DOM when state actually changes
|
|
2231
|
+
const prev = this.prevSafeAreaState;
|
|
2204
2232
|
const style = this.el.style;
|
|
2205
|
-
extendsIntoTop
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2233
|
+
if (extendsIntoTop !== prev.top) {
|
|
2234
|
+
extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
2235
|
+
prev.top = extendsIntoTop;
|
|
2236
|
+
}
|
|
2237
|
+
if (extendsIntoBottom !== prev.bottom) {
|
|
2238
|
+
extendsIntoBottom
|
|
2239
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
2240
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2241
|
+
prev.bottom = extendsIntoBottom;
|
|
2242
|
+
}
|
|
2243
|
+
if (extendsIntoLeft !== prev.left) {
|
|
2244
|
+
extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
2245
|
+
prev.left = extendsIntoLeft;
|
|
2246
|
+
}
|
|
2247
|
+
if (extendsIntoRight !== prev.right) {
|
|
2248
|
+
extendsIntoRight
|
|
2249
|
+
? style.removeProperty('--ion-safe-area-right')
|
|
2250
|
+
: style.setProperty('--ion-safe-area-right', '0px');
|
|
2251
|
+
prev.right = extendsIntoRight;
|
|
2252
|
+
}
|
|
2213
2253
|
}
|
|
2214
2254
|
sheetOnDismiss() {
|
|
2215
2255
|
/**
|
|
@@ -2241,7 +2281,7 @@ const Modal = class {
|
|
|
2241
2281
|
* For example, `cancel` or `backdrop`.
|
|
2242
2282
|
*/
|
|
2243
2283
|
async dismiss(data, role) {
|
|
2244
|
-
var _a;
|
|
2284
|
+
var _a, _b;
|
|
2245
2285
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
2246
2286
|
return false;
|
|
2247
2287
|
}
|
|
@@ -2303,8 +2343,23 @@ const Modal = class {
|
|
|
2303
2343
|
}
|
|
2304
2344
|
this.currentBreakpoint = undefined;
|
|
2305
2345
|
this.animation = undefined;
|
|
2306
|
-
// Reset safe-area
|
|
2346
|
+
// Reset safe-area state for potential re-presentation
|
|
2307
2347
|
this.skipSafeAreaCoordinateDetection = false;
|
|
2348
|
+
this.cachedSafeAreas = undefined;
|
|
2349
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
2350
|
+
(_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
2351
|
+
this.footerObserver = undefined;
|
|
2352
|
+
// Clear styles that may have been set for safe-area handling
|
|
2353
|
+
if (this.wrapperEl) {
|
|
2354
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
2355
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
2356
|
+
}
|
|
2357
|
+
// Clear safe-area CSS variable overrides
|
|
2358
|
+
const style = this.el.style;
|
|
2359
|
+
style.removeProperty('--ion-safe-area-top');
|
|
2360
|
+
style.removeProperty('--ion-safe-area-bottom');
|
|
2361
|
+
style.removeProperty('--ion-safe-area-left');
|
|
2362
|
+
style.removeProperty('--ion-safe-area-right');
|
|
2308
2363
|
unlock();
|
|
2309
2364
|
return dismissed;
|
|
2310
2365
|
}
|
|
@@ -2554,20 +2609,20 @@ const Modal = class {
|
|
|
2554
2609
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2555
2610
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2556
2611
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2557
|
-
return (h(Host, Object.assign({ key: '
|
|
2612
|
+
return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
|
|
2558
2613
|
// Allow the modal to be navigable when the handle is focusable
|
|
2559
2614
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2560
2615
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2561
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '
|
|
2616
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '125658fbb071960da3905854668078e15bce56da', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '4a63815ef165e5806fef85ef48bf509813ff55c9', class: "modal-shadow" }), h("div", Object.assign({ key: 'cfc4b20354cbf2c0f873f6aee91c9b8f553de61d',
|
|
2562
2617
|
/*
|
|
2563
2618
|
role and aria-modal must be used on the
|
|
2564
2619
|
same element. They must also be set inside the
|
|
2565
2620
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2566
2621
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2567
2622
|
*/
|
|
2568
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2623
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '33a4ee89bb8b6512883cb8756641c1e27fdb0ebc', class: "modal-handle",
|
|
2569
2624
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2570
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '
|
|
2625
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '986fafb71234591c96e927f92b7330bb5c76fc2e', onSlotchange: this.onSlotChange }))));
|
|
2571
2626
|
}
|
|
2572
2627
|
get el() { return getElement(this); }
|
|
2573
2628
|
static get watchers() { return {
|