@ionic/core 8.7.17-dev.11767647939.17c197c2 → 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 +79 -6
- package/components/modal.js +81 -26
- package/dist/cjs/ion-app_8.cjs.entry.js +78 -5
- package/dist/cjs/ion-modal.cjs.entry.js +81 -26
- package/dist/collection/components/content/content.css +10 -0
- package/dist/collection/components/content/content.js +77 -4
- package/dist/collection/components/modal/modal.js +81 -26
- package/dist/docs.json +1 -1
- package/dist/esm/ion-app_8.entry.js +79 -6
- 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 +24 -0
- package/dist/types/components/modal/modal.d.ts +12 -2
- package/hydrate/index.js +159 -32
- package/hydrate/index.mjs +159 -32
- package/package.json +1 -1
- package/dist/ionic/p-732b2fd6.entry.js +0 -4
- package/dist/ionic/p-fedca459.entry.js +0 -4
package/components/content.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { proxyCustomElement, HTMLElement, createEvent, Build, readTask,
|
|
4
|
+
import { proxyCustomElement, HTMLElement, createEvent, forceUpdate, Build, readTask, h, Host } from '@stencil/core/internal/client';
|
|
5
5
|
import { i as inheritAriaAttributes, k as hasLazyBuild, c as componentOnReady } from './helpers.js';
|
|
6
6
|
import { b as getIonMode, a as isPlatform } from './ionic-global.js';
|
|
7
7
|
import { i as isRTL } from './dir.js';
|
|
8
8
|
import { c as createColorClasses, h as hostContext } from './theme.js';
|
|
9
9
|
|
|
10
|
-
const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
|
|
10
|
+
const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}:host(.safe-area-top) #background-content,:host(.safe-area-top) .inner-scroll{top:var(--ion-safe-area-top, 0px)}:host(.safe-area-bottom) #background-content,:host(.safe-area-bottom) .inner-scroll{bottom:var(--ion-safe-area-bottom, 0px)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
|
|
11
11
|
|
|
12
12
|
const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLElement {
|
|
13
13
|
constructor(registerHost) {
|
|
@@ -28,6 +28,12 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
|
|
|
28
28
|
this.isMainContent = true;
|
|
29
29
|
this.resizeTimeout = null;
|
|
30
30
|
this.inheritedAttributes = {};
|
|
31
|
+
/**
|
|
32
|
+
* Track whether this content has sibling header/footer elements.
|
|
33
|
+
* When absent, we need to apply safe-area padding directly.
|
|
34
|
+
*/
|
|
35
|
+
this.hasHeader = false;
|
|
36
|
+
this.hasFooter = false;
|
|
31
37
|
this.tabsElement = null;
|
|
32
38
|
// Detail is used in a hot loop in the scroll event, by allocating it here
|
|
33
39
|
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
|
@@ -83,6 +89,8 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
|
|
|
83
89
|
}
|
|
84
90
|
connectedCallback() {
|
|
85
91
|
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
|
|
92
|
+
// Detect sibling header/footer for safe-area handling
|
|
93
|
+
this.detectSiblingElements();
|
|
86
94
|
/**
|
|
87
95
|
* The fullscreen content offsets need to be
|
|
88
96
|
* computed after the tab bar has loaded. Since
|
|
@@ -118,8 +126,71 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
|
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Detects sibling ion-header and ion-footer elements and sets up
|
|
131
|
+
* a mutation observer to handle dynamic changes (e.g., conditional rendering).
|
|
132
|
+
*/
|
|
133
|
+
detectSiblingElements() {
|
|
134
|
+
this.updateSiblingDetection();
|
|
135
|
+
// Watch for dynamic header/footer changes (common in React conditional rendering)
|
|
136
|
+
const parent = this.el.parentElement;
|
|
137
|
+
if (parent && !this.parentMutationObserver) {
|
|
138
|
+
this.parentMutationObserver = new MutationObserver(() => {
|
|
139
|
+
this.updateSiblingDetection();
|
|
140
|
+
forceUpdate(this);
|
|
141
|
+
});
|
|
142
|
+
this.parentMutationObserver.observe(parent, { childList: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
147
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
148
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
149
|
+
*/
|
|
150
|
+
updateSiblingDetection() {
|
|
151
|
+
const parent = this.el.parentElement;
|
|
152
|
+
if (parent) {
|
|
153
|
+
// First check for direct ion-header/ion-footer siblings
|
|
154
|
+
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
155
|
+
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
156
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
157
|
+
if (!this.hasHeader) {
|
|
158
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
159
|
+
}
|
|
160
|
+
if (!this.hasFooter) {
|
|
161
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
165
|
+
if (!this.hasFooter) {
|
|
166
|
+
const tabs = this.el.closest('ion-tabs');
|
|
167
|
+
if (tabs) {
|
|
168
|
+
this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
174
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
175
|
+
*/
|
|
176
|
+
siblingContainsElement(parent, tagName) {
|
|
177
|
+
for (const sibling of parent.children) {
|
|
178
|
+
// Skip ion-content itself
|
|
179
|
+
if (sibling === this.el)
|
|
180
|
+
continue;
|
|
181
|
+
// Check if this sibling contains the target element as an immediate child
|
|
182
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
121
188
|
disconnectedCallback() {
|
|
189
|
+
var _a;
|
|
122
190
|
this.onScrollEnd();
|
|
191
|
+
// Clean up mutation observer to prevent memory leaks
|
|
192
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
193
|
+
this.parentMutationObserver = undefined;
|
|
123
194
|
if (hasLazyBuild(this.el)) {
|
|
124
195
|
/**
|
|
125
196
|
* The event listener and tabs caches need to
|
|
@@ -366,26 +437,28 @@ const Content = /*@__PURE__*/ proxyCustomElement(class Content extends HTMLEleme
|
|
|
366
437
|
}
|
|
367
438
|
}
|
|
368
439
|
render() {
|
|
369
|
-
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
440
|
+
const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
370
441
|
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
|
371
442
|
const mode = getIonMode(this);
|
|
372
443
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
373
444
|
const transitionShadow = mode === 'ios';
|
|
374
445
|
this.resize();
|
|
375
|
-
return (h(Host, Object.assign({ key: '
|
|
446
|
+
return (h(Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
|
|
376
447
|
[mode]: true,
|
|
377
448
|
'content-sizing': hostContext('ion-popover', this.el),
|
|
378
449
|
overscroll: forceOverscroll,
|
|
379
450
|
[`content-${rtl}`]: true,
|
|
451
|
+
'safe-area-top': isMainContent && !hasHeader,
|
|
452
|
+
'safe-area-bottom': isMainContent && !hasFooter,
|
|
380
453
|
}), style: {
|
|
381
454
|
'--offset-top': `${this.cTop}px`,
|
|
382
455
|
'--offset-bottom': `${this.cBottom}px`,
|
|
383
|
-
} }, inheritedAttributes), h("div", { key: '
|
|
456
|
+
} }, 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: {
|
|
384
457
|
'inner-scroll': true,
|
|
385
458
|
'scroll-x': scrollX,
|
|
386
459
|
'scroll-y': scrollY,
|
|
387
460
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
388
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '
|
|
461
|
+
}, 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));
|
|
389
462
|
}
|
|
390
463
|
get el() { return this; }
|
|
391
464
|
static get style() { return contentCss; }
|
package/components/modal.js
CHANGED
|
@@ -1506,6 +1506,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1506
1506
|
this.gestureAnimationDismissing = false;
|
|
1507
1507
|
// Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
|
|
1508
1508
|
this.skipSafeAreaCoordinateDetection = false;
|
|
1509
|
+
// Track previous safe-area state to avoid redundant DOM writes
|
|
1510
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
1509
1511
|
this.presented = false;
|
|
1510
1512
|
/** @internal */
|
|
1511
1513
|
this.hasController = false;
|
|
@@ -1696,7 +1698,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1696
1698
|
}
|
|
1697
1699
|
}
|
|
1698
1700
|
onWindowResize() {
|
|
1699
|
-
//
|
|
1701
|
+
// Invalidate safe-area cache on resize (device rotation may change values)
|
|
1702
|
+
this.cachedSafeAreas = undefined;
|
|
1700
1703
|
this.updateSafeAreaOverrides();
|
|
1701
1704
|
// Only handle view transition for iOS card modals when no custom animations are provided
|
|
1702
1705
|
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
|
@@ -2155,8 +2158,26 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2155
2158
|
*/
|
|
2156
2159
|
applyFullscreenSafeArea() {
|
|
2157
2160
|
this.skipSafeAreaCoordinateDetection = true;
|
|
2161
|
+
this.updateFooterPadding();
|
|
2162
|
+
// Watch for dynamic footer additions/removals (e.g., async data loading)
|
|
2163
|
+
if (!this.footerObserver) {
|
|
2164
|
+
this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
|
|
2165
|
+
this.footerObserver.observe(this.el, { childList: true, subtree: true });
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Updates wrapper padding based on footer presence.
|
|
2170
|
+
* Called initially and when footer is dynamically added/removed.
|
|
2171
|
+
*/
|
|
2172
|
+
updateFooterPadding() {
|
|
2173
|
+
if (!this.wrapperEl)
|
|
2174
|
+
return;
|
|
2158
2175
|
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
|
2159
|
-
if (
|
|
2176
|
+
if (hasFooter) {
|
|
2177
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
2178
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
2179
|
+
}
|
|
2180
|
+
else {
|
|
2160
2181
|
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2161
2182
|
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
|
2162
2183
|
}
|
|
@@ -2174,21 +2195,26 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2174
2195
|
}
|
|
2175
2196
|
/**
|
|
2176
2197
|
* Gets the root safe-area values from the document element.
|
|
2177
|
-
*
|
|
2198
|
+
* Uses cached values during gestures to avoid getComputedStyle calls.
|
|
2178
2199
|
*/
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2200
|
+
getSafeAreaValues() {
|
|
2201
|
+
if (!this.cachedSafeAreas) {
|
|
2202
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
2203
|
+
this.cachedSafeAreas = {
|
|
2204
|
+
top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
|
|
2205
|
+
bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
|
|
2206
|
+
left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
|
|
2207
|
+
right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
return this.cachedSafeAreas;
|
|
2187
2211
|
}
|
|
2188
2212
|
/**
|
|
2189
2213
|
* Updates safe-area CSS variable overrides based on whether the modal
|
|
2190
2214
|
* extends into each safe-area region. Called after animation
|
|
2191
2215
|
* and during gestures to handle dynamic position changes.
|
|
2216
|
+
*
|
|
2217
|
+
* Optimized to avoid redundant DOM writes by tracking previous state.
|
|
2192
2218
|
*/
|
|
2193
2219
|
updateSafeAreaOverrides() {
|
|
2194
2220
|
if (this.skipSafeAreaCoordinateDetection) {
|
|
@@ -2199,20 +2225,34 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2199
2225
|
return;
|
|
2200
2226
|
}
|
|
2201
2227
|
const rect = wrapper.getBoundingClientRect();
|
|
2202
|
-
const safeAreas = this.
|
|
2228
|
+
const safeAreas = this.getSafeAreaValues();
|
|
2203
2229
|
const extendsIntoTop = rect.top < safeAreas.top;
|
|
2204
2230
|
const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
|
|
2205
2231
|
const extendsIntoLeft = rect.left < safeAreas.left;
|
|
2206
2232
|
const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
|
|
2233
|
+
// Only update DOM when state actually changes
|
|
2234
|
+
const prev = this.prevSafeAreaState;
|
|
2207
2235
|
const style = this.el.style;
|
|
2208
|
-
extendsIntoTop
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2236
|
+
if (extendsIntoTop !== prev.top) {
|
|
2237
|
+
extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
|
|
2238
|
+
prev.top = extendsIntoTop;
|
|
2239
|
+
}
|
|
2240
|
+
if (extendsIntoBottom !== prev.bottom) {
|
|
2241
|
+
extendsIntoBottom
|
|
2242
|
+
? style.removeProperty('--ion-safe-area-bottom')
|
|
2243
|
+
: style.setProperty('--ion-safe-area-bottom', '0px');
|
|
2244
|
+
prev.bottom = extendsIntoBottom;
|
|
2245
|
+
}
|
|
2246
|
+
if (extendsIntoLeft !== prev.left) {
|
|
2247
|
+
extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
|
|
2248
|
+
prev.left = extendsIntoLeft;
|
|
2249
|
+
}
|
|
2250
|
+
if (extendsIntoRight !== prev.right) {
|
|
2251
|
+
extendsIntoRight
|
|
2252
|
+
? style.removeProperty('--ion-safe-area-right')
|
|
2253
|
+
: style.setProperty('--ion-safe-area-right', '0px');
|
|
2254
|
+
prev.right = extendsIntoRight;
|
|
2255
|
+
}
|
|
2216
2256
|
}
|
|
2217
2257
|
sheetOnDismiss() {
|
|
2218
2258
|
/**
|
|
@@ -2244,7 +2284,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2244
2284
|
* For example, `cancel` or `backdrop`.
|
|
2245
2285
|
*/
|
|
2246
2286
|
async dismiss(data, role) {
|
|
2247
|
-
var _a;
|
|
2287
|
+
var _a, _b;
|
|
2248
2288
|
if (this.gestureAnimationDismissing && role !== GESTURE) {
|
|
2249
2289
|
return false;
|
|
2250
2290
|
}
|
|
@@ -2306,8 +2346,23 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2306
2346
|
}
|
|
2307
2347
|
this.currentBreakpoint = undefined;
|
|
2308
2348
|
this.animation = undefined;
|
|
2309
|
-
// Reset safe-area
|
|
2349
|
+
// Reset safe-area state for potential re-presentation
|
|
2310
2350
|
this.skipSafeAreaCoordinateDetection = false;
|
|
2351
|
+
this.cachedSafeAreas = undefined;
|
|
2352
|
+
this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
|
|
2353
|
+
(_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
2354
|
+
this.footerObserver = undefined;
|
|
2355
|
+
// Clear styles that may have been set for safe-area handling
|
|
2356
|
+
if (this.wrapperEl) {
|
|
2357
|
+
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
2358
|
+
this.wrapperEl.style.removeProperty('box-sizing');
|
|
2359
|
+
}
|
|
2360
|
+
// Clear safe-area CSS variable overrides
|
|
2361
|
+
const style = this.el.style;
|
|
2362
|
+
style.removeProperty('--ion-safe-area-top');
|
|
2363
|
+
style.removeProperty('--ion-safe-area-bottom');
|
|
2364
|
+
style.removeProperty('--ion-safe-area-left');
|
|
2365
|
+
style.removeProperty('--ion-safe-area-right');
|
|
2311
2366
|
unlock();
|
|
2312
2367
|
return dismissed;
|
|
2313
2368
|
}
|
|
@@ -2557,20 +2612,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2557
2612
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2558
2613
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2559
2614
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2560
|
-
return (h(Host, Object.assign({ key: '
|
|
2615
|
+
return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
|
|
2561
2616
|
// Allow the modal to be navigable when the handle is focusable
|
|
2562
2617
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2563
2618
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2564
|
-
}, 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: '
|
|
2619
|
+
}, 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',
|
|
2565
2620
|
/*
|
|
2566
2621
|
role and aria-modal must be used on the
|
|
2567
2622
|
same element. They must also be set inside the
|
|
2568
2623
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2569
2624
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2570
2625
|
*/
|
|
2571
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2626
|
+
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",
|
|
2572
2627
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2573
|
-
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: '
|
|
2628
|
+
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 }))));
|
|
2574
2629
|
}
|
|
2575
2630
|
get el() { return this; }
|
|
2576
2631
|
static get watchers() { return {
|
|
@@ -154,7 +154,7 @@ Buttons.style = {
|
|
|
154
154
|
md: buttonsMdCss
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
-
const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
|
|
157
|
+
const contentCss = ":host{--background:var(--ion-background-color, #fff);--color:var(--ion-text-color, #000);--padding-top:0px;--padding-bottom:0px;--padding-start:0px;--padding-end:0px;--keyboard-offset:0px;--offset-top:0px;--offset-bottom:0px;--overflow:auto;display:block;position:relative;-ms-flex:1;flex:1;width:100%;height:100%;margin:0 !important;padding:0 !important;font-family:var(--ion-font-family, inherit);contain:size style}:host(.ion-color) .inner-scroll{background:var(--ion-color-base);color:var(--ion-color-contrast)}#background-content{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);position:absolute;background:var(--background)}.inner-scroll{left:0px;right:0px;top:calc(var(--offset-top) * -1);bottom:calc(var(--offset-bottom) * -1);-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end);padding-top:calc(var(--padding-top) + var(--offset-top));padding-bottom:calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));position:absolute;color:var(--color);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;-ms-touch-action:pan-x pan-y pinch-zoom;touch-action:pan-x pan-y pinch-zoom}.scroll-y,.scroll-x{-webkit-overflow-scrolling:touch;z-index:0;will-change:scroll-position}.scroll-y{overflow-y:var(--overflow);overscroll-behavior-y:contain}.scroll-x{overflow-x:var(--overflow);overscroll-behavior-x:contain}.overscroll::before,.overscroll::after{position:absolute;width:1px;height:1px;content:\"\"}.overscroll::before{bottom:-1px}.overscroll::after{top:-1px}:host(.content-sizing){display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:0;contain:none}:host(.content-sizing) .inner-scroll{position:relative;top:0;bottom:0;margin-top:calc(var(--offset-top) * -1);margin-bottom:calc(var(--offset-bottom) * -1)}.transition-effect{display:none;position:absolute;width:100%;height:100vh;opacity:0;pointer-events:none}:host(.content-ltr) .transition-effect{left:-100%;}:host(.content-rtl) .transition-effect{right:-100%;}.transition-cover{position:absolute;right:0;width:100%;height:100%;background:black;opacity:0.1}.transition-shadow{display:block;position:absolute;width:100%;height:100%;-webkit-box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03);box-shadow:inset -9px 0 9px 0 rgba(0, 0, 100, 0.03)}:host(.content-ltr) .transition-shadow{right:0;}:host(.content-rtl) .transition-shadow{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}:host(.safe-area-top) #background-content,:host(.safe-area-top) .inner-scroll{top:var(--ion-safe-area-top, 0px)}:host(.safe-area-bottom) #background-content,:host(.safe-area-bottom) .inner-scroll{bottom:var(--ion-safe-area-bottom, 0px)}::slotted([slot=fixed]){position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0)}";
|
|
158
158
|
|
|
159
159
|
const Content = class {
|
|
160
160
|
constructor(hostRef) {
|
|
@@ -171,6 +171,12 @@ const Content = class {
|
|
|
171
171
|
this.isMainContent = true;
|
|
172
172
|
this.resizeTimeout = null;
|
|
173
173
|
this.inheritedAttributes = {};
|
|
174
|
+
/**
|
|
175
|
+
* Track whether this content has sibling header/footer elements.
|
|
176
|
+
* When absent, we need to apply safe-area padding directly.
|
|
177
|
+
*/
|
|
178
|
+
this.hasHeader = false;
|
|
179
|
+
this.hasFooter = false;
|
|
174
180
|
this.tabsElement = null;
|
|
175
181
|
// Detail is used in a hot loop in the scroll event, by allocating it here
|
|
176
182
|
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
|
@@ -226,6 +232,8 @@ const Content = class {
|
|
|
226
232
|
}
|
|
227
233
|
connectedCallback() {
|
|
228
234
|
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
|
|
235
|
+
// Detect sibling header/footer for safe-area handling
|
|
236
|
+
this.detectSiblingElements();
|
|
229
237
|
/**
|
|
230
238
|
* The fullscreen content offsets need to be
|
|
231
239
|
* computed after the tab bar has loaded. Since
|
|
@@ -261,8 +269,71 @@ const Content = class {
|
|
|
261
269
|
}
|
|
262
270
|
}
|
|
263
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Detects sibling ion-header and ion-footer elements and sets up
|
|
274
|
+
* a mutation observer to handle dynamic changes (e.g., conditional rendering).
|
|
275
|
+
*/
|
|
276
|
+
detectSiblingElements() {
|
|
277
|
+
this.updateSiblingDetection();
|
|
278
|
+
// Watch for dynamic header/footer changes (common in React conditional rendering)
|
|
279
|
+
const parent = this.el.parentElement;
|
|
280
|
+
if (parent && !this.parentMutationObserver) {
|
|
281
|
+
this.parentMutationObserver = new MutationObserver(() => {
|
|
282
|
+
this.updateSiblingDetection();
|
|
283
|
+
index.forceUpdate(this);
|
|
284
|
+
});
|
|
285
|
+
this.parentMutationObserver.observe(parent, { childList: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Updates hasHeader/hasFooter based on current DOM state.
|
|
290
|
+
* Checks both direct siblings and elements wrapped in custom components
|
|
291
|
+
* (e.g., <my-header><ion-header>...</ion-header></my-header>).
|
|
292
|
+
*/
|
|
293
|
+
updateSiblingDetection() {
|
|
294
|
+
const parent = this.el.parentElement;
|
|
295
|
+
if (parent) {
|
|
296
|
+
// First check for direct ion-header/ion-footer siblings
|
|
297
|
+
this.hasHeader = parent.querySelector(':scope > ion-header') !== null;
|
|
298
|
+
this.hasFooter = parent.querySelector(':scope > ion-footer') !== null;
|
|
299
|
+
// If not found, check if any sibling contains them (wrapped components)
|
|
300
|
+
if (!this.hasHeader) {
|
|
301
|
+
this.hasHeader = this.siblingContainsElement(parent, 'ion-header');
|
|
302
|
+
}
|
|
303
|
+
if (!this.hasFooter) {
|
|
304
|
+
this.hasFooter = this.siblingContainsElement(parent, 'ion-footer');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// If no footer found, check if we're inside ion-tabs which has ion-tab-bar
|
|
308
|
+
if (!this.hasFooter) {
|
|
309
|
+
const tabs = this.el.closest('ion-tabs');
|
|
310
|
+
if (tabs) {
|
|
311
|
+
this.hasFooter = tabs.querySelector(':scope > ion-tab-bar') !== null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Checks if any sibling element of ion-content contains the specified element.
|
|
317
|
+
* Only searches one level deep to avoid finding elements in nested pages.
|
|
318
|
+
*/
|
|
319
|
+
siblingContainsElement(parent, tagName) {
|
|
320
|
+
for (const sibling of parent.children) {
|
|
321
|
+
// Skip ion-content itself
|
|
322
|
+
if (sibling === this.el)
|
|
323
|
+
continue;
|
|
324
|
+
// Check if this sibling contains the target element as an immediate child
|
|
325
|
+
if (sibling.querySelector(`:scope > ${tagName}`) !== null) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
264
331
|
disconnectedCallback() {
|
|
332
|
+
var _a;
|
|
265
333
|
this.onScrollEnd();
|
|
334
|
+
// Clean up mutation observer to prevent memory leaks
|
|
335
|
+
(_a = this.parentMutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
336
|
+
this.parentMutationObserver = undefined;
|
|
266
337
|
if (helpers.hasLazyBuild(this.el)) {
|
|
267
338
|
/**
|
|
268
339
|
* The event listener and tabs caches need to
|
|
@@ -509,26 +580,28 @@ const Content = class {
|
|
|
509
580
|
}
|
|
510
581
|
}
|
|
511
582
|
render() {
|
|
512
|
-
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
583
|
+
const { fixedSlotPlacement, hasFooter, hasHeader, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
|
513
584
|
const rtl = dir.isRTL(el) ? 'rtl' : 'ltr';
|
|
514
585
|
const mode = ionicGlobal.getIonMode(this);
|
|
515
586
|
const forceOverscroll = this.shouldForceOverscroll();
|
|
516
587
|
const transitionShadow = mode === 'ios';
|
|
517
588
|
this.resize();
|
|
518
|
-
return (index.h(index.Host, Object.assign({ key: '
|
|
589
|
+
return (index.h(index.Host, Object.assign({ key: 'c8e3a93e0b1ba6f7aa81a6a6065145ece9a6e2ef', role: isMainContent ? 'main' : undefined, class: theme.createColorClasses(this.color, {
|
|
519
590
|
[mode]: true,
|
|
520
591
|
'content-sizing': theme.hostContext('ion-popover', this.el),
|
|
521
592
|
overscroll: forceOverscroll,
|
|
522
593
|
[`content-${rtl}`]: true,
|
|
594
|
+
'safe-area-top': isMainContent && !hasHeader,
|
|
595
|
+
'safe-area-bottom': isMainContent && !hasFooter,
|
|
523
596
|
}), style: {
|
|
524
597
|
'--offset-top': `${this.cTop}px`,
|
|
525
598
|
'--offset-bottom': `${this.cBottom}px`,
|
|
526
|
-
} }, inheritedAttributes), index.h("div", { key: '
|
|
599
|
+
} }, inheritedAttributes), index.h("div", { key: '4c0482cda885348eea9eb66d7f076af6b38c52e5', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? index.h("slot", { name: "fixed" }) : null, index.h("div", { key: '6fbb39bf7ab7120009c56aea9340de45c934eeed', class: {
|
|
527
600
|
'inner-scroll': true,
|
|
528
601
|
'scroll-x': scrollX,
|
|
529
602
|
'scroll-y': scrollY,
|
|
530
603
|
overscroll: (scrollX || scrollY) && forceOverscroll,
|
|
531
|
-
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '
|
|
604
|
+
}, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, index.h("slot", { key: '6425bc84edbc0c5b1f2764a1d611df1b46628274' })), transitionShadow ? (index.h("div", { class: "transition-effect" }, index.h("div", { class: "transition-cover" }), index.h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? index.h("slot", { name: "fixed" }) : null));
|
|
532
605
|
}
|
|
533
606
|
get el() { return index.getElement(this); }
|
|
534
607
|
};
|