@ionic/core 8.8.18-nightly.20260811 → 8.8.18

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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Build, Host, forceUpdate, h, readTask } from "@stencil/core";
4
+ import { Build, Host, forceUpdate, h, readTask, } from "@stencil/core";
5
5
  import { componentOnReady, hasLazyBuild, inheritAriaAttributes } from "../../utils/helpers";
6
6
  import { isPlatform } from "../../utils/platform";
7
7
  import { isRTL } from "../../utils/rtl/index";
@@ -75,6 +75,9 @@ export class Content {
75
75
  */
76
76
  this.scrollEvents = false;
77
77
  }
78
+ fullscreenChanged() {
79
+ this.setupFullscreenResizeObserver();
80
+ }
78
81
  componentWillLoad() {
79
82
  this.inheritedAttributes = inheritAriaAttributes(this.el);
80
83
  }
@@ -114,6 +117,12 @@ export class Content {
114
117
  closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
115
118
  }
116
119
  }
120
+ // Re-observe on reattach, since componentDidLoad only fires once.
121
+ this.setupFullscreenResizeObserver();
122
+ }
123
+ componentDidLoad() {
124
+ // The custom elements build assigns fullscreen after connectedCallback.
125
+ this.setupFullscreenResizeObserver();
117
126
  }
118
127
  disconnectedCallback() {
119
128
  this.onScrollEnd();
@@ -135,6 +144,42 @@ export class Content {
135
144
  clearTimeout(this.resizeTimeout);
136
145
  this.resizeTimeout = null;
137
146
  }
147
+ this.destroyFullscreenResizeObserver();
148
+ }
149
+ /**
150
+ * Header and footer sizes can change after load without a window resize
151
+ * firing, so the `resize` listener alone misses those changes.
152
+ *
153
+ * Popover content is excluded because `contain: none` lets its offsets drive
154
+ * the host's own height, which would feed back into the observer.
155
+ */
156
+ setupFullscreenResizeObserver() {
157
+ if (!Build.isBrowser || typeof ResizeObserver === 'undefined') {
158
+ return;
159
+ }
160
+ if (!this.fullscreen || hostContext('ion-popover', this.el)) {
161
+ this.destroyFullscreenResizeObserver();
162
+ return;
163
+ }
164
+ if (this.fullscreenResizeObserver !== undefined) {
165
+ return;
166
+ }
167
+ this.fullscreenResizeObserver = new ResizeObserver(() => {
168
+ // A hidden page reports a 0x0 box, which would zero the offsets. Same
169
+ // reasoning as the guard in onResize, minus its debounce so the
170
+ // correction lands in the next frame instead of 100ms later.
171
+ if (this.el.offsetParent === null) {
172
+ return;
173
+ }
174
+ this.resize();
175
+ });
176
+ this.fullscreenResizeObserver.observe(this.el);
177
+ }
178
+ destroyFullscreenResizeObserver() {
179
+ if (this.fullscreenResizeObserver !== undefined) {
180
+ this.fullscreenResizeObserver.disconnect();
181
+ this.fullscreenResizeObserver = undefined;
182
+ }
138
183
  }
139
184
  /**
140
185
  * Rotating certain devices can update
@@ -373,7 +418,7 @@ export class Content {
373
418
  const forceOverscroll = this.shouldForceOverscroll();
374
419
  const transitionShadow = mode === 'ios';
375
420
  this.resize();
376
- return (h(Host, Object.assign({ key: '8c1bc4a4df8698d3884741eb38512dc059210c50', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
421
+ return (h(Host, Object.assign({ key: '9551c759df407b10d809171641fc6e8f14d92219', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
377
422
  [mode]: true,
378
423
  'content-fullscreen': this.fullscreen,
379
424
  'content-sizing': hostContext('ion-popover', this.el),
@@ -382,12 +427,12 @@ export class Content {
382
427
  }), style: {
383
428
  '--offset-top': `${this.cTop}px`,
384
429
  '--offset-bottom': `${this.cBottom}px`,
385
- } }, inheritedAttributes), h("div", { key: '5e7412549b51ef7543d9378ecab9aac6bc461588', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '33f6fbf7e1e027c7cbd2dda95b487f18dc648cef', class: {
430
+ } }, inheritedAttributes), h("div", { key: 'c6de8fd226f0d10a534eb107fe032ac155fbe68e', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '082fc2936061360060b48221ecb1c864a3711fa0', class: {
386
431
  'inner-scroll': true,
387
432
  'scroll-x': scrollX,
388
433
  'scroll-y': scrollY,
389
434
  overscroll: (scrollX || scrollY) && forceOverscroll,
390
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '00c8fe7b9e73533c702273c9457d274195d54e98' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
435
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '0d2286206facb22696a9ca7e74a3f6b7d2397aab' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
391
436
  }
392
437
  static get is() { return "ion-content"; }
393
438
  static get encapsulation() { return "shadow"; }
@@ -813,6 +858,12 @@ export class Content {
813
858
  };
814
859
  }
815
860
  static get elementRef() { return "el"; }
861
+ static get watchers() {
862
+ return [{
863
+ "propName": "fullscreen",
864
+ "methodName": "fullscreenChanged"
865
+ }];
866
+ }
816
867
  static get listeners() {
817
868
  return [{
818
869
  "name": "resize",
@@ -21,7 +21,7 @@ import { mdEnterAnimation } from "./animations/md.enter";
21
21
  import { mdLeaveAnimation } from "./animations/md.leave";
22
22
  import { createSheetGesture } from "./gestures/sheet";
23
23
  import { createSwipeToCloseGesture, SwipeToCloseDefaults } from "./gestures/swipe-to-close";
24
- import { getInitialSafeAreaConfig, getPositionBasedSafeAreaConfig, applySafeAreaOverrides, clearSafeAreaOverrides, getRootSafeAreaTop, hasCustomModalDimensions, } from "./safe-area-utils";
24
+ import { getInitialSafeAreaConfig, getPositionBasedSafeAreaConfig, applySafeAreaOverrides, clearSafeAreaOverrides, getRootSafeAreaTop, onRootSafeAreaTopChange, hasCustomModalDimensions, } from "./safe-area-utils";
25
25
  import { setCardStatusBarDark, setCardStatusBarDefault } from "./utils";
26
26
  // TODO(FW-2832): types
27
27
  /**
@@ -1055,16 +1055,18 @@ export class Modal {
1055
1055
  // Set the internal offset property with the resolved root safe-area-top value
1056
1056
  if (context.isSheetModal) {
1057
1057
  this.updateSheetOffsetTop();
1058
+ this.unsubscribeRootSafeAreaTop = onRootSafeAreaTopChange((safeAreaTop) => this.updateSheetOffsetTop(safeAreaTop));
1058
1059
  }
1059
1060
  }
1060
1061
  /**
1061
- * Resolves the current root --ion-safe-area-top value and sets the
1062
- * internal --ion-modal-offset-top property on the host element.
1063
- * Called on present and on resize (e.g., device rotation changes safe-area).
1062
+ * Sets the internal --ion-modal-offset-top property on the host element,
1063
+ * resolving the current root --ion-safe-area-top when no value is given.
1064
+ * Called on present, on resize (e.g., device rotation changes safe-area),
1065
+ * and whenever the root safe-area value itself changes.
1064
1066
  */
1065
- updateSheetOffsetTop() {
1066
- const safeAreaTop = getRootSafeAreaTop();
1067
- this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
1067
+ updateSheetOffsetTop(safeAreaTop) {
1068
+ const value = safeAreaTop !== null && safeAreaTop !== void 0 ? safeAreaTop : getRootSafeAreaTop();
1069
+ this.el.style.setProperty('--ion-modal-offset-top', `${value}px`);
1068
1070
  }
1069
1071
  /**
1070
1072
  * Updates safe-area overrides during dynamic state changes.
@@ -1163,7 +1165,10 @@ export class Modal {
1163
1165
  * Clears all safe-area overrides and padding.
1164
1166
  */
1165
1167
  cleanupSafeAreaOverrides() {
1168
+ var _a;
1166
1169
  clearSafeAreaOverrides(this.el);
1170
+ (_a = this.unsubscribeRootSafeAreaTop) === null || _a === void 0 ? void 0 : _a.call(this);
1171
+ this.unsubscribeRootSafeAreaTop = undefined;
1167
1172
  // Remove internal sheet offset property
1168
1173
  this.el.style.removeProperty('--ion-modal-offset-top');
1169
1174
  const { contentEl } = this.findContentAndFooter();
@@ -1176,11 +1181,11 @@ export class Modal {
1176
1181
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1177
1182
  const isHandleCycle = handleBehavior === 'cycle';
1178
1183
  const isSheetModalWithHandle = isSheetModal && showHandle;
1179
- return (h(Host, Object.assign({ key: '09f09a10d858e976bc2a989f30db925348a7784c', "no-router": true,
1184
+ return (h(Host, Object.assign({ key: 'c4b2713ebc788ac8672e18fdd5069e4253c4da34', "no-router": true,
1180
1185
  // Allow the modal to be navigable when the handle is focusable
1181
1186
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1182
1187
  zIndex: `${20000 + this.overlayIndex}`,
1183
- }, 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: 'b197319bdddf0b979ccacdb63dbceccfd9bf2560', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'c848ff6a10e9092cdb10a6f21c9a299a72b17ca9', class: "modal-shadow" }), h("div", Object.assign({ key: 'e7c8e67807d49098aba63dce69a1bbf9647bbab6',
1188
+ }, 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: '2cc0afff8a815954492138afb0fca042243afddd', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '9844700b9436f9bfa1a69ba436e9ca46d70cf316', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab1cd56ca956c773c9242fb42ff320bac7627b4b',
1184
1189
  /*
1185
1190
  role and aria-modal must be used on the
1186
1191
  same element. They must also be set inside the
@@ -1193,9 +1198,9 @@ export class Modal {
1193
1198
  without the tabindex focus() would be a no-op and screen readers
1194
1199
  may not properly announce the dialog and its content when it opens.
1195
1200
  */
1196
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", tabIndex: -1, class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0851cc75ee7367e13d1993a9c4d8b1b4de1d3bf0', class: "modal-handle",
1201
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", tabIndex: -1, class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'c7706794e04a962178640e9a9a11c56288c45e08', class: "modal-handle",
1197
1202
  // Prevents the handle from receiving keyboard focus when it does not cycle
1198
- 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: 'b4d9bc13dc0867e858db8e1c7277bd09b7fce44c', onSlotchange: this.onSlotChange }))));
1203
+ 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: 'c44fd9810cdd3286f096af98e5a4072f8f44c193', onSlotchange: this.onSlotChange }))));
1199
1204
  }
1200
1205
  static get is() { return "ion-modal"; }
1201
1206
  static get encapsulation() { return "shadow"; }
@@ -69,6 +69,41 @@ export const getRootSafeAreaTop = () => {
69
69
  }
70
70
  return value;
71
71
  };
72
+ /**
73
+ * Calls back when the resolved root `--ion-safe-area-top` changes, which no
74
+ * event and no window resize covers. The probe's height tracks the variable, so
75
+ * a change to it becomes a size change the observer can see.
76
+ */
77
+ export const onRootSafeAreaTopChange = (callback) => {
78
+ const doc = win === null || win === void 0 ? void 0 : win.document;
79
+ if (!(doc === null || doc === void 0 ? void 0 : doc.body) || typeof ResizeObserver === 'undefined') {
80
+ return () => undefined;
81
+ }
82
+ const probe = doc.createElement('div');
83
+ probe.style.cssText =
84
+ 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;width:0;' +
85
+ 'height:var(--ion-safe-area-top,0px);';
86
+ doc.body.appendChild(probe);
87
+ /**
88
+ * Seeded with the value the caller has already applied, so a change that
89
+ * lands before the observer's first delivery still gets reported. Comparing
90
+ * against an unset value instead would consume that first delivery and treat
91
+ * the new inset as the baseline.
92
+ */
93
+ let lastHeight = getRootSafeAreaTop();
94
+ const observer = new ResizeObserver((entries) => {
95
+ const { height } = entries[0].contentRect;
96
+ if (height !== lastHeight) {
97
+ lastHeight = height;
98
+ callback(height);
99
+ }
100
+ });
101
+ observer.observe(probe);
102
+ return () => {
103
+ observer.disconnect();
104
+ probe.remove();
105
+ };
106
+ };
72
107
  /**
73
108
  * True when the modal host declares BOTH a non-fullscreen `--width` AND a
74
109
  * non-fullscreen `--height` (i.e. a centered-dialog-like modal that doesn't
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-08-11T06:21:34",
2
+ "timestamp": "2026-08-12T21:11:00",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.43.5",
@@ -6,7 +6,7 @@ import { shouldUseCloseWatcher } from './hardware-back-button-B93Gru0Y.js';
6
6
  import { a as isPlatform, b as getIonMode } from './ionic-global-Cp_eT4sZ.js';
7
7
  import { i as inheritAriaAttributes, h as hasLazyBuild, c as componentOnReady, e as clamp, s as shallowEqualStringMap } from './helpers-HEqiOzXb.js';
8
8
  import { i as isRTL } from './dir-C53feagD.js';
9
- import { c as createColorClasses, h as hostContext } from './theme-DiVJyqlX.js';
9
+ import { h as hostContext, c as createColorClasses } from './theme-DiVJyqlX.js';
10
10
  import { b as findIonContent, p as printIonContentErrorMsg, g as getScrollElement } from './index-Dw-wN0Ja.js';
11
11
  import { c as createKeyboardController } from './keyboard-controller-BaaVITYt.js';
12
12
  import { g as getTimeGivenProgression } from './cubic-bezier-hHmYLOfE.js';
@@ -224,6 +224,9 @@ const Content = class {
224
224
  */
225
225
  this.scrollEvents = false;
226
226
  }
227
+ fullscreenChanged() {
228
+ this.setupFullscreenResizeObserver();
229
+ }
227
230
  componentWillLoad() {
228
231
  this.inheritedAttributes = inheritAriaAttributes(this.el);
229
232
  }
@@ -263,6 +266,12 @@ const Content = class {
263
266
  closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
264
267
  }
265
268
  }
269
+ // Re-observe on reattach, since componentDidLoad only fires once.
270
+ this.setupFullscreenResizeObserver();
271
+ }
272
+ componentDidLoad() {
273
+ // The custom elements build assigns fullscreen after connectedCallback.
274
+ this.setupFullscreenResizeObserver();
266
275
  }
267
276
  disconnectedCallback() {
268
277
  this.onScrollEnd();
@@ -284,6 +293,42 @@ const Content = class {
284
293
  clearTimeout(this.resizeTimeout);
285
294
  this.resizeTimeout = null;
286
295
  }
296
+ this.destroyFullscreenResizeObserver();
297
+ }
298
+ /**
299
+ * Header and footer sizes can change after load without a window resize
300
+ * firing, so the `resize` listener alone misses those changes.
301
+ *
302
+ * Popover content is excluded because `contain: none` lets its offsets drive
303
+ * the host's own height, which would feed back into the observer.
304
+ */
305
+ setupFullscreenResizeObserver() {
306
+ if (typeof ResizeObserver === 'undefined') {
307
+ return;
308
+ }
309
+ if (!this.fullscreen || hostContext('ion-popover', this.el)) {
310
+ this.destroyFullscreenResizeObserver();
311
+ return;
312
+ }
313
+ if (this.fullscreenResizeObserver !== undefined) {
314
+ return;
315
+ }
316
+ this.fullscreenResizeObserver = new ResizeObserver(() => {
317
+ // A hidden page reports a 0x0 box, which would zero the offsets. Same
318
+ // reasoning as the guard in onResize, minus its debounce so the
319
+ // correction lands in the next frame instead of 100ms later.
320
+ if (this.el.offsetParent === null) {
321
+ return;
322
+ }
323
+ this.resize();
324
+ });
325
+ this.fullscreenResizeObserver.observe(this.el);
326
+ }
327
+ destroyFullscreenResizeObserver() {
328
+ if (this.fullscreenResizeObserver !== undefined) {
329
+ this.fullscreenResizeObserver.disconnect();
330
+ this.fullscreenResizeObserver = undefined;
331
+ }
287
332
  }
288
333
  /**
289
334
  * Rotating certain devices can update
@@ -522,7 +567,7 @@ const Content = class {
522
567
  const forceOverscroll = this.shouldForceOverscroll();
523
568
  const transitionShadow = mode === 'ios';
524
569
  this.resize();
525
- return (h(Host, Object.assign({ key: '8c1bc4a4df8698d3884741eb38512dc059210c50', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
570
+ return (h(Host, Object.assign({ key: '9551c759df407b10d809171641fc6e8f14d92219', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
526
571
  [mode]: true,
527
572
  'content-fullscreen': this.fullscreen,
528
573
  'content-sizing': hostContext('ion-popover', this.el),
@@ -531,14 +576,19 @@ const Content = class {
531
576
  }), style: {
532
577
  '--offset-top': `${this.cTop}px`,
533
578
  '--offset-bottom': `${this.cBottom}px`,
534
- } }, inheritedAttributes), h("div", { key: '5e7412549b51ef7543d9378ecab9aac6bc461588', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '33f6fbf7e1e027c7cbd2dda95b487f18dc648cef', class: {
579
+ } }, inheritedAttributes), h("div", { key: 'c6de8fd226f0d10a534eb107fe032ac155fbe68e', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '082fc2936061360060b48221ecb1c864a3711fa0', class: {
535
580
  'inner-scroll': true,
536
581
  'scroll-x': scrollX,
537
582
  'scroll-y': scrollY,
538
583
  overscroll: (scrollX || scrollY) && forceOverscroll,
539
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '00c8fe7b9e73533c702273c9457d274195d54e98' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
584
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '0d2286206facb22696a9ca7e74a3f6b7d2397aab' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
540
585
  }
541
586
  get el() { return getElement(this); }
587
+ static get watchers() { return {
588
+ "fullscreen": [{
589
+ "fullscreenChanged": 0
590
+ }]
591
+ }; }
542
592
  };
543
593
  const getParentElement = (el) => {
544
594
  var _a;
@@ -1695,6 +1695,41 @@ const getRootSafeAreaTop = () => {
1695
1695
  }
1696
1696
  return value;
1697
1697
  };
1698
+ /**
1699
+ * Calls back when the resolved root `--ion-safe-area-top` changes, which no
1700
+ * event and no window resize covers. The probe's height tracks the variable, so
1701
+ * a change to it becomes a size change the observer can see.
1702
+ */
1703
+ const onRootSafeAreaTopChange = (callback) => {
1704
+ const doc = win === null || win === void 0 ? void 0 : win.document;
1705
+ if (!(doc === null || doc === void 0 ? void 0 : doc.body) || typeof ResizeObserver === 'undefined') {
1706
+ return () => undefined;
1707
+ }
1708
+ const probe = doc.createElement('div');
1709
+ probe.style.cssText =
1710
+ 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;width:0;' +
1711
+ 'height:var(--ion-safe-area-top,0px);';
1712
+ doc.body.appendChild(probe);
1713
+ /**
1714
+ * Seeded with the value the caller has already applied, so a change that
1715
+ * lands before the observer's first delivery still gets reported. Comparing
1716
+ * against an unset value instead would consume that first delivery and treat
1717
+ * the new inset as the baseline.
1718
+ */
1719
+ let lastHeight = getRootSafeAreaTop();
1720
+ const observer = new ResizeObserver((entries) => {
1721
+ const { height } = entries[0].contentRect;
1722
+ if (height !== lastHeight) {
1723
+ lastHeight = height;
1724
+ callback(height);
1725
+ }
1726
+ });
1727
+ observer.observe(probe);
1728
+ return () => {
1729
+ observer.disconnect();
1730
+ probe.remove();
1731
+ };
1732
+ };
1698
1733
  /**
1699
1734
  * True when the modal host declares BOTH a non-fullscreen `--width` AND a
1700
1735
  * non-fullscreen `--height` (i.e. a centered-dialog-like modal that doesn't
@@ -2847,16 +2882,18 @@ const Modal = class {
2847
2882
  // Set the internal offset property with the resolved root safe-area-top value
2848
2883
  if (context.isSheetModal) {
2849
2884
  this.updateSheetOffsetTop();
2885
+ this.unsubscribeRootSafeAreaTop = onRootSafeAreaTopChange((safeAreaTop) => this.updateSheetOffsetTop(safeAreaTop));
2850
2886
  }
2851
2887
  }
2852
2888
  /**
2853
- * Resolves the current root --ion-safe-area-top value and sets the
2854
- * internal --ion-modal-offset-top property on the host element.
2855
- * Called on present and on resize (e.g., device rotation changes safe-area).
2889
+ * Sets the internal --ion-modal-offset-top property on the host element,
2890
+ * resolving the current root --ion-safe-area-top when no value is given.
2891
+ * Called on present, on resize (e.g., device rotation changes safe-area),
2892
+ * and whenever the root safe-area value itself changes.
2856
2893
  */
2857
- updateSheetOffsetTop() {
2858
- const safeAreaTop = getRootSafeAreaTop();
2859
- this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`);
2894
+ updateSheetOffsetTop(safeAreaTop) {
2895
+ const value = safeAreaTop !== null && safeAreaTop !== void 0 ? safeAreaTop : getRootSafeAreaTop();
2896
+ this.el.style.setProperty('--ion-modal-offset-top', `${value}px`);
2860
2897
  }
2861
2898
  /**
2862
2899
  * Updates safe-area overrides during dynamic state changes.
@@ -2955,7 +2992,10 @@ const Modal = class {
2955
2992
  * Clears all safe-area overrides and padding.
2956
2993
  */
2957
2994
  cleanupSafeAreaOverrides() {
2995
+ var _a;
2958
2996
  clearSafeAreaOverrides(this.el);
2997
+ (_a = this.unsubscribeRootSafeAreaTop) === null || _a === void 0 ? void 0 : _a.call(this);
2998
+ this.unsubscribeRootSafeAreaTop = undefined;
2959
2999
  // Remove internal sheet offset property
2960
3000
  this.el.style.removeProperty('--ion-modal-offset-top');
2961
3001
  const { contentEl } = this.findContentAndFooter();
@@ -2968,11 +3008,11 @@ const Modal = class {
2968
3008
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2969
3009
  const isHandleCycle = handleBehavior === 'cycle';
2970
3010
  const isSheetModalWithHandle = isSheetModal && showHandle;
2971
- return (h(Host, Object.assign({ key: '09f09a10d858e976bc2a989f30db925348a7784c', "no-router": true,
3011
+ return (h(Host, Object.assign({ key: 'c4b2713ebc788ac8672e18fdd5069e4253c4da34', "no-router": true,
2972
3012
  // Allow the modal to be navigable when the handle is focusable
2973
3013
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2974
3014
  zIndex: `${20000 + this.overlayIndex}`,
2975
- }, 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: 'b197319bdddf0b979ccacdb63dbceccfd9bf2560', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'c848ff6a10e9092cdb10a6f21c9a299a72b17ca9', class: "modal-shadow" }), h("div", Object.assign({ key: 'e7c8e67807d49098aba63dce69a1bbf9647bbab6',
3015
+ }, 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: '2cc0afff8a815954492138afb0fca042243afddd', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '9844700b9436f9bfa1a69ba436e9ca46d70cf316', class: "modal-shadow" }), h("div", Object.assign({ key: 'ab1cd56ca956c773c9242fb42ff320bac7627b4b',
2976
3016
  /*
2977
3017
  role and aria-modal must be used on the
2978
3018
  same element. They must also be set inside the
@@ -2985,9 +3025,9 @@ const Modal = class {
2985
3025
  without the tabindex focus() would be a no-op and screen readers
2986
3026
  may not properly announce the dialog and its content when it opens.
2987
3027
  */
2988
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", tabIndex: -1, class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0851cc75ee7367e13d1993a9c4d8b1b4de1d3bf0', class: "modal-handle",
3028
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", tabIndex: -1, class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'c7706794e04a962178640e9a9a11c56288c45e08', class: "modal-handle",
2989
3029
  // Prevents the handle from receiving keyboard focus when it does not cycle
2990
- 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: 'b4d9bc13dc0867e858db8e1c7277bd09b7fce44c', onSlotchange: this.onSlotChange }))));
3030
+ 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: 'c44fd9810cdd3286f096af98e5a4072f8f44c193', onSlotchange: this.onSlotChange }))));
2991
3031
  }
2992
3032
  get el() { return getElement(this); }
2993
3033
  static get watchers() { return {