@ionic/core 8.7.12-nightly.20251210 → 8.7.12

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.
Files changed (68) hide show
  1. package/components/content.js +13 -3
  2. package/components/modal.js +97 -5
  3. package/components/overlays.js +11 -5
  4. package/components/popover.js +36 -2
  5. package/css/core.css +1 -1
  6. package/css/core.css.map +1 -1
  7. package/css/ionic.bundle.css +1 -1
  8. package/css/ionic.bundle.css.map +1 -1
  9. package/dist/cjs/index.cjs.js +1 -1
  10. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  11. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  12. package/dist/cjs/ion-app_8.cjs.entry.js +12 -3
  13. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  14. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  15. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  16. package/dist/cjs/ion-modal.cjs.entry.js +97 -6
  17. package/dist/cjs/ion-popover.cjs.entry.js +37 -3
  18. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  19. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  20. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  21. package/dist/cjs/ionic.cjs.js +1 -1
  22. package/dist/cjs/loader.cjs.js +1 -1
  23. package/dist/cjs/{overlays-DxIZwUXI.js → overlays--dagG4QM.js} +11 -5
  24. package/dist/collection/components/content/content.js +32 -3
  25. package/dist/collection/components/modal/modal.js +97 -5
  26. package/dist/collection/components/popover/popover.js +36 -2
  27. package/dist/collection/utils/overlays.js +11 -5
  28. package/dist/docs.json +1 -1
  29. package/dist/esm/index.js +1 -1
  30. package/dist/esm/ion-action-sheet.entry.js +1 -1
  31. package/dist/esm/ion-alert.entry.js +1 -1
  32. package/dist/esm/ion-app_8.entry.js +12 -3
  33. package/dist/esm/ion-datetime_3.entry.js +1 -1
  34. package/dist/esm/ion-loading.entry.js +1 -1
  35. package/dist/esm/ion-menu_3.entry.js +1 -1
  36. package/dist/esm/ion-modal.entry.js +97 -6
  37. package/dist/esm/ion-popover.entry.js +37 -3
  38. package/dist/esm/ion-select-modal.entry.js +1 -1
  39. package/dist/esm/ion-select_3.entry.js +1 -1
  40. package/dist/esm/ion-toast.entry.js +1 -1
  41. package/dist/esm/ionic.js +1 -1
  42. package/dist/esm/loader.js +1 -1
  43. package/dist/esm/{overlays-BymNv-BL.js → overlays-DCabi1dI.js} +11 -5
  44. package/dist/ionic/index.esm.js +1 -1
  45. package/dist/ionic/ionic.esm.js +1 -1
  46. package/dist/ionic/{p-7da39a4d.entry.js → p-07753df3.entry.js} +1 -1
  47. package/dist/ionic/{p-0b80d700.entry.js → p-0abeb0fc.entry.js} +1 -1
  48. package/dist/ionic/{p-5837f29f.entry.js → p-0bf76d0f.entry.js} +1 -1
  49. package/dist/ionic/{p-cb93126d.entry.js → p-4b658a7c.entry.js} +1 -1
  50. package/dist/ionic/{p-98fc09eb.entry.js → p-576e0965.entry.js} +1 -1
  51. package/dist/ionic/{p-8edc7565.entry.js → p-75ae4733.entry.js} +1 -1
  52. package/dist/ionic/p-C6F4hat2.js +4 -0
  53. package/dist/ionic/{p-83be404e.entry.js → p-a8ed848b.entry.js} +1 -1
  54. package/dist/ionic/p-c85a2127.entry.js +4 -0
  55. package/dist/ionic/p-cebb0328.entry.js +4 -0
  56. package/dist/ionic/p-ea509e3c.entry.js +4 -0
  57. package/dist/ionic/{p-15193d01.entry.js → p-ec654c42.entry.js} +1 -1
  58. package/dist/types/components/content/content.d.ts +7 -0
  59. package/dist/types/components/modal/modal.d.ts +14 -0
  60. package/dist/types/components/popover/popover.d.ts +7 -0
  61. package/dist/types/components.d.ts +4 -0
  62. package/hydrate/index.js +157 -15
  63. package/hydrate/index.mjs +157 -15
  64. package/package.json +1 -1
  65. package/dist/ionic/p-4c85d268.entry.js +0 -4
  66. package/dist/ionic/p-D87hU-Ly.js +0 -4
  67. package/dist/ionic/p-a80f1b04.entry.js +0 -4
  68. package/dist/ionic/p-e16b69e1.entry.js +0 -4
package/hydrate/index.js CHANGED
@@ -5851,6 +5851,14 @@ const CoreDelegate = () => {
5851
5851
  let lastOverlayIndex = 0;
5852
5852
  let lastId = 0;
5853
5853
  const activeAnimations = new WeakMap();
5854
+ /**
5855
+ * Determines if the overlay's backdrop is always blocking (no background interaction).
5856
+ * Returns false if showBackdrop=false or backdropBreakpoint > 0.
5857
+ */
5858
+ const isBackdropAlwaysBlocking = (el) => {
5859
+ var _a;
5860
+ return el.showBackdrop !== false && !(((_a = el.backdropBreakpoint) !== null && _a !== void 0 ? _a : 0) > 0);
5861
+ };
5854
5862
  const createController = (tagName) => {
5855
5863
  return {
5856
5864
  create(options) {
@@ -6288,9 +6296,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
6288
6296
  */
6289
6297
  const overlayEl = overlay.el;
6290
6298
  const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false;
6291
- // Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
6292
- // expect background interaction to remain enabled.
6293
- const shouldLockRoot = shouldTrapFocus && overlayEl.showBackdrop !== false;
6299
+ const shouldLockRoot = shouldTrapFocus && isBackdropAlwaysBlocking(overlayEl);
6294
6300
  overlay.presented = true;
6295
6301
  overlay.willPresent.emit();
6296
6302
  if (shouldLockRoot) {
@@ -6406,10 +6412,10 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
6406
6412
  */
6407
6413
  const overlaysLockingRoot = presentedOverlays.filter((o) => {
6408
6414
  const el = o;
6409
- return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && el.showBackdrop !== false;
6415
+ return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && isBackdropAlwaysBlocking(el);
6410
6416
  });
6411
6417
  const overlayEl = overlay.el;
6412
- const locksRoot = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && overlayEl.showBackdrop !== false;
6418
+ const locksRoot = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && isBackdropAlwaysBlocking(overlayEl);
6413
6419
  /**
6414
6420
  * If this is the last visible overlay that is trapping focus
6415
6421
  * then we want to re-add the root to the accessibility tree.
@@ -10663,6 +10669,15 @@ class Content {
10663
10669
  }
10664
10670
  resize() {
10665
10671
  }
10672
+ /**
10673
+ * Recalculate content dimensions. Called by overlays (e.g., popover) when
10674
+ * sibling elements like headers or footers have finished rendering and their
10675
+ * heights are available, ensuring accurate offset-top calculations.
10676
+ * @internal
10677
+ */
10678
+ async recalculateDimensions() {
10679
+ readTask(() => this.readDimensions());
10680
+ }
10666
10681
  readDimensions() {
10667
10682
  const page = getPageElement(this.el);
10668
10683
  const top = Math.max(this.el.offsetTop, 0);
@@ -10831,7 +10846,7 @@ class Content {
10831
10846
  const forceOverscroll = this.shouldForceOverscroll();
10832
10847
  const transitionShadow = mode === 'ios';
10833
10848
  this.resize();
10834
- return (hAsync(Host, Object.assign({ key: 'f2a24aa66dbf5c76f9d4b06f708eb73cadc239df', role: isMainContent ? 'main' : undefined, class: createColorClasses$1(this.color, {
10849
+ return (hAsync(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses$1(this.color, {
10835
10850
  [mode]: true,
10836
10851
  'content-sizing': hostContext('ion-popover', this.el),
10837
10852
  overscroll: forceOverscroll,
@@ -10839,12 +10854,12 @@ class Content {
10839
10854
  }), style: {
10840
10855
  '--offset-top': `${this.cTop}px`,
10841
10856
  '--offset-bottom': `${this.cBottom}px`,
10842
- } }, inheritedAttributes), hAsync("div", { key: '6480ca7648b278abb36477b3838bccbcd4995e2a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? hAsync("slot", { name: "fixed" }) : null, hAsync("div", { key: '29a23b663f5f0215bb000820c01e1814c0d55985', class: {
10857
+ } }, inheritedAttributes), hAsync("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? hAsync("slot", { name: "fixed" }) : null, hAsync("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
10843
10858
  'inner-scroll': true,
10844
10859
  'scroll-x': scrollX,
10845
10860
  'scroll-y': scrollY,
10846
10861
  overscroll: (scrollX || scrollY) && forceOverscroll,
10847
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '0fe1bd05609a4b88ae2ce9addf5d5dc5dc1806f0' })), transitionShadow ? (hAsync("div", { class: "transition-effect" }, hAsync("div", { class: "transition-cover" }), hAsync("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? hAsync("slot", { name: "fixed" }) : null));
10862
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (hAsync("div", { class: "transition-effect" }, hAsync("div", { class: "transition-cover" }), hAsync("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? hAsync("slot", { name: "fixed" }) : null));
10848
10863
  }
10849
10864
  get el() { return getElement(this); }
10850
10865
  static get style() { return contentCss; }
@@ -10859,6 +10874,7 @@ class Content {
10859
10874
  "scrollX": [4, "scroll-x"],
10860
10875
  "scrollY": [4, "scroll-y"],
10861
10876
  "scrollEvents": [4, "scroll-events"],
10877
+ "recalculateDimensions": [64],
10862
10878
  "getScrollElement": [64],
10863
10879
  "getBackgroundElement": [64],
10864
10880
  "scrollToTop": [64],
@@ -23195,7 +23211,13 @@ class Modal {
23195
23211
  };
23196
23212
  window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
23197
23213
  }
23198
- if (this.isSheetModal) {
23214
+ /**
23215
+ * Recalculate isSheetModal because framework bindings (e.g., Angular)
23216
+ * may not have been applied when componentWillLoad ran.
23217
+ */
23218
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
23219
+ this.isSheetModal = isSheetModal;
23220
+ if (isSheetModal) {
23199
23221
  this.initSheetGesture();
23200
23222
  }
23201
23223
  else if (hasCardModal) {
@@ -23277,6 +23299,79 @@ class Modal {
23277
23299
  this.gesture = gesture;
23278
23300
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
23279
23301
  this.gesture.enable(true);
23302
+ /**
23303
+ * When backdrop interaction is allowed, nested router outlets from child routes
23304
+ * may block pointer events to parent content. Apply passthrough styles only when
23305
+ * the modal was the sole content of a child route page.
23306
+ * See https://github.com/ionic-team/ionic-framework/issues/30700
23307
+ */
23308
+ const backdropNotBlocking = this.showBackdrop === false || this.focusTrap === false || backdropBreakpoint > 0;
23309
+ if (backdropNotBlocking) {
23310
+ this.setupChildRoutePassthrough();
23311
+ }
23312
+ }
23313
+ /**
23314
+ * For sheet modals that allow background interaction, sets up pointer-events
23315
+ * passthrough on child route page wrappers and nested router outlets.
23316
+ */
23317
+ setupChildRoutePassthrough() {
23318
+ var _a;
23319
+ // Cache the page parent for cleanup
23320
+ this.cachedPageParent = this.getOriginalPageParent();
23321
+ const pageParent = this.cachedPageParent;
23322
+ // Skip ion-app (controller modals) and pages with visible sibling content next to the modal
23323
+ if (!pageParent || pageParent.tagName === 'ION-APP') {
23324
+ return;
23325
+ }
23326
+ const hasVisibleContent = Array.from(pageParent.children).some((child) => {
23327
+ var _a;
23328
+ return child !== this.el &&
23329
+ !(child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') &&
23330
+ child.tagName !== 'TEMPLATE' &&
23331
+ child.tagName !== 'SLOT' &&
23332
+ !(child.nodeType === Node.TEXT_NODE && !((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
23333
+ });
23334
+ if (hasVisibleContent) {
23335
+ return;
23336
+ }
23337
+ // Child route case: page only contained the modal
23338
+ pageParent.classList.add('ion-page-overlay-passthrough');
23339
+ // Also make nested router outlets passthrough
23340
+ const routerOutlet = pageParent.parentElement;
23341
+ if ((routerOutlet === null || routerOutlet === void 0 ? void 0 : routerOutlet.tagName) === 'ION-ROUTER-OUTLET' && ((_a = routerOutlet.parentElement) === null || _a === void 0 ? void 0 : _a.tagName) !== 'ION-APP') {
23342
+ routerOutlet.style.setProperty('pointer-events', 'none');
23343
+ routerOutlet.setAttribute('data-overlay-passthrough', 'true');
23344
+ }
23345
+ }
23346
+ /**
23347
+ * Finds the ion-page ancestor of the modal's original parent location.
23348
+ */
23349
+ getOriginalPageParent() {
23350
+ if (!this.cachedOriginalParent) {
23351
+ return null;
23352
+ }
23353
+ let pageParent = this.cachedOriginalParent;
23354
+ while (pageParent && !pageParent.classList.contains('ion-page')) {
23355
+ pageParent = pageParent.parentElement;
23356
+ }
23357
+ return pageParent;
23358
+ }
23359
+ /**
23360
+ * Removes passthrough styles added by setupChildRoutePassthrough.
23361
+ */
23362
+ cleanupChildRoutePassthrough() {
23363
+ const pageParent = this.cachedPageParent;
23364
+ if (!pageParent) {
23365
+ return;
23366
+ }
23367
+ pageParent.classList.remove('ion-page-overlay-passthrough');
23368
+ const routerOutlet = pageParent.parentElement;
23369
+ if (routerOutlet === null || routerOutlet === void 0 ? void 0 : routerOutlet.hasAttribute('data-overlay-passthrough')) {
23370
+ routerOutlet.style.removeProperty('pointer-events');
23371
+ routerOutlet.removeAttribute('data-overlay-passthrough');
23372
+ }
23373
+ // Clear the cached reference
23374
+ this.cachedPageParent = undefined;
23280
23375
  }
23281
23376
  sheetOnDismiss() {
23282
23377
  /**
@@ -23366,6 +23461,7 @@ class Modal {
23366
23461
  }
23367
23462
  this.cleanupViewTransitionListener();
23368
23463
  this.cleanupParentRemovalObserver();
23464
+ this.cleanupChildRoutePassthrough();
23369
23465
  }
23370
23466
  this.currentBreakpoint = undefined;
23371
23467
  this.animation = undefined;
@@ -23562,6 +23658,17 @@ class Modal {
23562
23658
  this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
23563
23659
  return;
23564
23660
  }
23661
+ /**
23662
+ * Don't observe for controller-based modals or when the parent is the
23663
+ * app root (document.body or ion-app). These parents won't be removed,
23664
+ * and observing document.body with subtree: true causes performance
23665
+ * issues with frameworks like Angular during change detection.
23666
+ */
23667
+ if (this.hasController ||
23668
+ this.cachedOriginalParent === document.body ||
23669
+ this.cachedOriginalParent.tagName === 'ION-APP') {
23670
+ return;
23671
+ }
23565
23672
  this.parentRemovalObserver = new MutationObserver((mutations) => {
23566
23673
  mutations.forEach((mutation) => {
23567
23674
  if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
@@ -23603,20 +23710,20 @@ class Modal {
23603
23710
  const isCardModal = presentingElement !== undefined && mode === 'ios';
23604
23711
  const isHandleCycle = handleBehavior === 'cycle';
23605
23712
  const isSheetModalWithHandle = isSheetModal && showHandle;
23606
- return (hAsync(Host, Object.assign({ key: '9e9a7bd591eb17a225a00b4fa2e379e94601d17f', "no-router": true,
23713
+ return (hAsync(Host, Object.assign({ key: '9a75095a13de0cfc96f1fa69fd92777d25da8daa', "no-router": true,
23607
23714
  // Allow the modal to be navigable when the handle is focusable
23608
23715
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
23609
23716
  zIndex: `${20000 + this.overlayIndex}`,
23610
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: 'e5eae2c14f830f75e308fcd7f4c10c86fac5b962', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: 'e268f9cd310c3cf4e051b5b92524ce4fb70d005e', class: "modal-shadow" }), hAsync("div", Object.assign({ key: '9c380f36c18144c153077b15744d1c3346bce63e',
23717
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: 'd02612d8063ef20f59f173ff47795f71cdaaf63e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: '708761b70a93e34c08faae079569f444c7416a4c', class: "modal-shadow" }), hAsync("div", Object.assign({ key: 'a72226ff1a98229f9bfd9207b98fc57e02baa430',
23611
23718
  /*
23612
23719
  role and aria-modal must be used on the
23613
23720
  same element. They must also be set inside the
23614
23721
  shadow DOM otherwise ion-button will not be highlighted
23615
23722
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
23616
23723
  */
23617
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '2d5ee6d5959d97309c306e8ce72eb0f2c19be144', class: "modal-handle",
23724
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '0547f32323882660221385d84d492929caa77c6b', class: "modal-handle",
23618
23725
  // Prevents the handle from receiving keyboard focus when it does not cycle
23619
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: '5590434c35ea04c42fc006498bc189038e15a298', onSlotchange: this.onSlotChange }))));
23726
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: 'fccbd64518b6fa22f9e874deb6b4ba55d8d89c3b', onSlotchange: this.onSlotChange }))));
23620
23727
  }
23621
23728
  get el() { return getElement(this); }
23622
23729
  static get watchers() { return {
@@ -23656,6 +23763,7 @@ class Modal {
23656
23763
  "keepContentsMounted": [4, "keep-contents-mounted"],
23657
23764
  "focusTrap": [4, "focus-trap"],
23658
23765
  "canDismiss": [4, "can-dismiss"],
23766
+ "isSheetModal": [32],
23659
23767
  "presented": [32],
23660
23768
  "present": [64],
23661
23769
  "dismiss": [64],
@@ -27723,6 +27831,10 @@ class Popover {
27723
27831
  if (destroyTriggerInteraction) {
27724
27832
  destroyTriggerInteraction();
27725
27833
  }
27834
+ if (this.headerResizeObserver) {
27835
+ this.headerResizeObserver.disconnect();
27836
+ this.headerResizeObserver = undefined;
27837
+ }
27726
27838
  }
27727
27839
  componentWillLoad() {
27728
27840
  var _a, _b;
@@ -27825,6 +27937,7 @@ class Popover {
27825
27937
  */
27826
27938
  this.ionMount.emit();
27827
27939
  this.usersElement = await attachComponent(delegate, el, this.component, ['popover-viewport'], this.componentProps, inline);
27940
+ this.recalculateContentOnHeaderReady();
27828
27941
  if (!this.keyboardEvents) {
27829
27942
  this.configureKeyboardInteraction();
27830
27943
  }
@@ -27870,6 +27983,35 @@ class Popover {
27870
27983
  }
27871
27984
  unlock();
27872
27985
  }
27986
+ /**
27987
+ * Watch the header for height changes and trigger content dimension
27988
+ * recalculation when the header has a height > 0. This sets the offset-top
27989
+ * of the content to the height of the header correctly.
27990
+ */
27991
+ recalculateContentOnHeaderReady() {
27992
+ var _a;
27993
+ const popoverContent = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.popover-content');
27994
+ if (!popoverContent) {
27995
+ return;
27996
+ }
27997
+ const contentContainer = this.usersElement || popoverContent;
27998
+ const header = contentContainer.querySelector('ion-header');
27999
+ const contentElements = contentContainer.querySelectorAll('ion-content');
28000
+ if (!header || contentElements.length === 0) {
28001
+ return;
28002
+ }
28003
+ this.headerResizeObserver = new ResizeObserver(async () => {
28004
+ var _a;
28005
+ if (header.offsetHeight > 0) {
28006
+ (_a = this.headerResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
28007
+ this.headerResizeObserver = undefined;
28008
+ for (const contentEl of contentElements) {
28009
+ await contentEl.recalculateDimensions();
28010
+ }
28011
+ }
28012
+ });
28013
+ this.headerResizeObserver.observe(header);
28014
+ }
27873
28015
  /**
27874
28016
  * Dismiss the popover overlay after it has been presented.
27875
28017
  * This is a no-op if the overlay has not been presented yet. If you want
@@ -27931,9 +28073,9 @@ class Popover {
27931
28073
  const { onLifecycle, parentPopover, dismissOnSelect, side, arrow, htmlAttributes, focusTrap } = this;
27932
28074
  const desktop = isPlatform('desktop');
27933
28075
  const enableArrow = arrow && !parentPopover;
27934
- return (hAsync(Host, Object.assign({ key: '16866c02534968c982cf4730d2936d03a5107c8b', "aria-modal": "true", "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
28076
+ return (hAsync(Host, Object.assign({ key: '42863f748c93f709d433931d969230137b37d42d', "aria-modal": "true", "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
27935
28077
  zIndex: `${20000 + this.overlayIndex}`,
27936
- }, class: Object.assign(Object.assign({}, getClassMap(this.cssClass)), { [mode]: true, 'popover-translucent': this.translucent, 'overlay-hidden': true, 'popover-desktop': desktop, [`popover-side-${side}`]: true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false, 'popover-nested': !!parentPopover }), onIonPopoverDidPresent: onLifecycle, onIonPopoverWillPresent: onLifecycle, onIonPopoverWillDismiss: onLifecycle, onIonPopoverDidDismiss: onLifecycle, onIonBackdropTap: this.onBackdropTap }), !parentPopover && hAsync("ion-backdrop", { key: '0df258601a4d30df3c27aa8234a7d5e056c3ecbb', tappable: this.backdropDismiss, visible: this.showBackdrop, part: "backdrop" }), hAsync("div", { key: 'f94e80ed996b957b5cd09b826472b4f60e8fcc78', class: "popover-wrapper ion-overlay-wrapper", onClick: dismissOnSelect ? () => this.dismiss() : undefined }, enableArrow && hAsync("div", { key: '185ce22f6386e8444a9cc7b8818dbfc16c463c99', class: "popover-arrow", part: "arrow" }), hAsync("div", { key: '206202b299404e110de5397b229678cca18568d3', class: "popover-content", part: "content" }, hAsync("slot", { key: 'ee543a0b92d6e35a837c0a0e4617c7b0fc4ad0b0' })))));
28078
+ }, class: Object.assign(Object.assign({}, getClassMap(this.cssClass)), { [mode]: true, 'popover-translucent': this.translucent, 'overlay-hidden': true, 'popover-desktop': desktop, [`popover-side-${side}`]: true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false, 'popover-nested': !!parentPopover }), onIonPopoverDidPresent: onLifecycle, onIonPopoverWillPresent: onLifecycle, onIonPopoverWillDismiss: onLifecycle, onIonPopoverDidDismiss: onLifecycle, onIonBackdropTap: this.onBackdropTap }), !parentPopover && hAsync("ion-backdrop", { key: '22b6d82178b52158b76ab3fd9a7dd738fd6e4bbf', tappable: this.backdropDismiss, visible: this.showBackdrop, part: "backdrop" }), hAsync("div", { key: 'b76335c64e992a964ed3fb91d17a992c3474b4cd', class: "popover-wrapper ion-overlay-wrapper", onClick: dismissOnSelect ? () => this.dismiss() : undefined }, enableArrow && hAsync("div", { key: '018c846c32e7ff7fa010528e6b37a17e5f03c84c', class: "popover-arrow", part: "arrow" }), hAsync("div", { key: '350c468c80052da3a07768bceab98fe159c35a43', class: "popover-content", part: "content" }, hAsync("slot", { key: '686443c17ac9873d33905c1cdb67e6d6da675282' })))));
27937
28079
  }
27938
28080
  get el() { return getElement(this); }
27939
28081
  static get watchers() { return {
package/hydrate/index.mjs CHANGED
@@ -5849,6 +5849,14 @@ const CoreDelegate = () => {
5849
5849
  let lastOverlayIndex = 0;
5850
5850
  let lastId = 0;
5851
5851
  const activeAnimations = new WeakMap();
5852
+ /**
5853
+ * Determines if the overlay's backdrop is always blocking (no background interaction).
5854
+ * Returns false if showBackdrop=false or backdropBreakpoint > 0.
5855
+ */
5856
+ const isBackdropAlwaysBlocking = (el) => {
5857
+ var _a;
5858
+ return el.showBackdrop !== false && !(((_a = el.backdropBreakpoint) !== null && _a !== void 0 ? _a : 0) > 0);
5859
+ };
5852
5860
  const createController = (tagName) => {
5853
5861
  return {
5854
5862
  create(options) {
@@ -6286,9 +6294,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
6286
6294
  */
6287
6295
  const overlayEl = overlay.el;
6288
6296
  const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false;
6289
- // Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
6290
- // expect background interaction to remain enabled.
6291
- const shouldLockRoot = shouldTrapFocus && overlayEl.showBackdrop !== false;
6297
+ const shouldLockRoot = shouldTrapFocus && isBackdropAlwaysBlocking(overlayEl);
6292
6298
  overlay.presented = true;
6293
6299
  overlay.willPresent.emit();
6294
6300
  if (shouldLockRoot) {
@@ -6404,10 +6410,10 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
6404
6410
  */
6405
6411
  const overlaysLockingRoot = presentedOverlays.filter((o) => {
6406
6412
  const el = o;
6407
- return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && el.showBackdrop !== false;
6413
+ return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && isBackdropAlwaysBlocking(el);
6408
6414
  });
6409
6415
  const overlayEl = overlay.el;
6410
- const locksRoot = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && overlayEl.showBackdrop !== false;
6416
+ const locksRoot = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && isBackdropAlwaysBlocking(overlayEl);
6411
6417
  /**
6412
6418
  * If this is the last visible overlay that is trapping focus
6413
6419
  * then we want to re-add the root to the accessibility tree.
@@ -10661,6 +10667,15 @@ class Content {
10661
10667
  }
10662
10668
  resize() {
10663
10669
  }
10670
+ /**
10671
+ * Recalculate content dimensions. Called by overlays (e.g., popover) when
10672
+ * sibling elements like headers or footers have finished rendering and their
10673
+ * heights are available, ensuring accurate offset-top calculations.
10674
+ * @internal
10675
+ */
10676
+ async recalculateDimensions() {
10677
+ readTask(() => this.readDimensions());
10678
+ }
10664
10679
  readDimensions() {
10665
10680
  const page = getPageElement(this.el);
10666
10681
  const top = Math.max(this.el.offsetTop, 0);
@@ -10829,7 +10844,7 @@ class Content {
10829
10844
  const forceOverscroll = this.shouldForceOverscroll();
10830
10845
  const transitionShadow = mode === 'ios';
10831
10846
  this.resize();
10832
- return (hAsync(Host, Object.assign({ key: 'f2a24aa66dbf5c76f9d4b06f708eb73cadc239df', role: isMainContent ? 'main' : undefined, class: createColorClasses$1(this.color, {
10847
+ return (hAsync(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses$1(this.color, {
10833
10848
  [mode]: true,
10834
10849
  'content-sizing': hostContext('ion-popover', this.el),
10835
10850
  overscroll: forceOverscroll,
@@ -10837,12 +10852,12 @@ class Content {
10837
10852
  }), style: {
10838
10853
  '--offset-top': `${this.cTop}px`,
10839
10854
  '--offset-bottom': `${this.cBottom}px`,
10840
- } }, inheritedAttributes), hAsync("div", { key: '6480ca7648b278abb36477b3838bccbcd4995e2a', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? hAsync("slot", { name: "fixed" }) : null, hAsync("div", { key: '29a23b663f5f0215bb000820c01e1814c0d55985', class: {
10855
+ } }, inheritedAttributes), hAsync("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? hAsync("slot", { name: "fixed" }) : null, hAsync("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
10841
10856
  'inner-scroll': true,
10842
10857
  'scroll-x': scrollX,
10843
10858
  'scroll-y': scrollY,
10844
10859
  overscroll: (scrollX || scrollY) && forceOverscroll,
10845
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '0fe1bd05609a4b88ae2ce9addf5d5dc5dc1806f0' })), transitionShadow ? (hAsync("div", { class: "transition-effect" }, hAsync("div", { class: "transition-cover" }), hAsync("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? hAsync("slot", { name: "fixed" }) : null));
10860
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, hAsync("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (hAsync("div", { class: "transition-effect" }, hAsync("div", { class: "transition-cover" }), hAsync("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? hAsync("slot", { name: "fixed" }) : null));
10846
10861
  }
10847
10862
  get el() { return getElement(this); }
10848
10863
  static get style() { return contentCss; }
@@ -10857,6 +10872,7 @@ class Content {
10857
10872
  "scrollX": [4, "scroll-x"],
10858
10873
  "scrollY": [4, "scroll-y"],
10859
10874
  "scrollEvents": [4, "scroll-events"],
10875
+ "recalculateDimensions": [64],
10860
10876
  "getScrollElement": [64],
10861
10877
  "getBackgroundElement": [64],
10862
10878
  "scrollToTop": [64],
@@ -23193,7 +23209,13 @@ class Modal {
23193
23209
  };
23194
23210
  window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
23195
23211
  }
23196
- if (this.isSheetModal) {
23212
+ /**
23213
+ * Recalculate isSheetModal because framework bindings (e.g., Angular)
23214
+ * may not have been applied when componentWillLoad ran.
23215
+ */
23216
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
23217
+ this.isSheetModal = isSheetModal;
23218
+ if (isSheetModal) {
23197
23219
  this.initSheetGesture();
23198
23220
  }
23199
23221
  else if (hasCardModal) {
@@ -23275,6 +23297,79 @@ class Modal {
23275
23297
  this.gesture = gesture;
23276
23298
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
23277
23299
  this.gesture.enable(true);
23300
+ /**
23301
+ * When backdrop interaction is allowed, nested router outlets from child routes
23302
+ * may block pointer events to parent content. Apply passthrough styles only when
23303
+ * the modal was the sole content of a child route page.
23304
+ * See https://github.com/ionic-team/ionic-framework/issues/30700
23305
+ */
23306
+ const backdropNotBlocking = this.showBackdrop === false || this.focusTrap === false || backdropBreakpoint > 0;
23307
+ if (backdropNotBlocking) {
23308
+ this.setupChildRoutePassthrough();
23309
+ }
23310
+ }
23311
+ /**
23312
+ * For sheet modals that allow background interaction, sets up pointer-events
23313
+ * passthrough on child route page wrappers and nested router outlets.
23314
+ */
23315
+ setupChildRoutePassthrough() {
23316
+ var _a;
23317
+ // Cache the page parent for cleanup
23318
+ this.cachedPageParent = this.getOriginalPageParent();
23319
+ const pageParent = this.cachedPageParent;
23320
+ // Skip ion-app (controller modals) and pages with visible sibling content next to the modal
23321
+ if (!pageParent || pageParent.tagName === 'ION-APP') {
23322
+ return;
23323
+ }
23324
+ const hasVisibleContent = Array.from(pageParent.children).some((child) => {
23325
+ var _a;
23326
+ return child !== this.el &&
23327
+ !(child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') &&
23328
+ child.tagName !== 'TEMPLATE' &&
23329
+ child.tagName !== 'SLOT' &&
23330
+ !(child.nodeType === Node.TEXT_NODE && !((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
23331
+ });
23332
+ if (hasVisibleContent) {
23333
+ return;
23334
+ }
23335
+ // Child route case: page only contained the modal
23336
+ pageParent.classList.add('ion-page-overlay-passthrough');
23337
+ // Also make nested router outlets passthrough
23338
+ const routerOutlet = pageParent.parentElement;
23339
+ if ((routerOutlet === null || routerOutlet === void 0 ? void 0 : routerOutlet.tagName) === 'ION-ROUTER-OUTLET' && ((_a = routerOutlet.parentElement) === null || _a === void 0 ? void 0 : _a.tagName) !== 'ION-APP') {
23340
+ routerOutlet.style.setProperty('pointer-events', 'none');
23341
+ routerOutlet.setAttribute('data-overlay-passthrough', 'true');
23342
+ }
23343
+ }
23344
+ /**
23345
+ * Finds the ion-page ancestor of the modal's original parent location.
23346
+ */
23347
+ getOriginalPageParent() {
23348
+ if (!this.cachedOriginalParent) {
23349
+ return null;
23350
+ }
23351
+ let pageParent = this.cachedOriginalParent;
23352
+ while (pageParent && !pageParent.classList.contains('ion-page')) {
23353
+ pageParent = pageParent.parentElement;
23354
+ }
23355
+ return pageParent;
23356
+ }
23357
+ /**
23358
+ * Removes passthrough styles added by setupChildRoutePassthrough.
23359
+ */
23360
+ cleanupChildRoutePassthrough() {
23361
+ const pageParent = this.cachedPageParent;
23362
+ if (!pageParent) {
23363
+ return;
23364
+ }
23365
+ pageParent.classList.remove('ion-page-overlay-passthrough');
23366
+ const routerOutlet = pageParent.parentElement;
23367
+ if (routerOutlet === null || routerOutlet === void 0 ? void 0 : routerOutlet.hasAttribute('data-overlay-passthrough')) {
23368
+ routerOutlet.style.removeProperty('pointer-events');
23369
+ routerOutlet.removeAttribute('data-overlay-passthrough');
23370
+ }
23371
+ // Clear the cached reference
23372
+ this.cachedPageParent = undefined;
23278
23373
  }
23279
23374
  sheetOnDismiss() {
23280
23375
  /**
@@ -23364,6 +23459,7 @@ class Modal {
23364
23459
  }
23365
23460
  this.cleanupViewTransitionListener();
23366
23461
  this.cleanupParentRemovalObserver();
23462
+ this.cleanupChildRoutePassthrough();
23367
23463
  }
23368
23464
  this.currentBreakpoint = undefined;
23369
23465
  this.animation = undefined;
@@ -23560,6 +23656,17 @@ class Modal {
23560
23656
  this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
23561
23657
  return;
23562
23658
  }
23659
+ /**
23660
+ * Don't observe for controller-based modals or when the parent is the
23661
+ * app root (document.body or ion-app). These parents won't be removed,
23662
+ * and observing document.body with subtree: true causes performance
23663
+ * issues with frameworks like Angular during change detection.
23664
+ */
23665
+ if (this.hasController ||
23666
+ this.cachedOriginalParent === document.body ||
23667
+ this.cachedOriginalParent.tagName === 'ION-APP') {
23668
+ return;
23669
+ }
23563
23670
  this.parentRemovalObserver = new MutationObserver((mutations) => {
23564
23671
  mutations.forEach((mutation) => {
23565
23672
  if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
@@ -23601,20 +23708,20 @@ class Modal {
23601
23708
  const isCardModal = presentingElement !== undefined && mode === 'ios';
23602
23709
  const isHandleCycle = handleBehavior === 'cycle';
23603
23710
  const isSheetModalWithHandle = isSheetModal && showHandle;
23604
- return (hAsync(Host, Object.assign({ key: '9e9a7bd591eb17a225a00b4fa2e379e94601d17f', "no-router": true,
23711
+ return (hAsync(Host, Object.assign({ key: '9a75095a13de0cfc96f1fa69fd92777d25da8daa', "no-router": true,
23605
23712
  // Allow the modal to be navigable when the handle is focusable
23606
23713
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
23607
23714
  zIndex: `${20000 + this.overlayIndex}`,
23608
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: 'e5eae2c14f830f75e308fcd7f4c10c86fac5b962', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: 'e268f9cd310c3cf4e051b5b92524ce4fb70d005e', class: "modal-shadow" }), hAsync("div", Object.assign({ key: '9c380f36c18144c153077b15744d1c3346bce63e',
23715
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), hAsync("ion-backdrop", { key: 'd02612d8063ef20f59f173ff47795f71cdaaf63e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && hAsync("div", { key: '708761b70a93e34c08faae079569f444c7416a4c', class: "modal-shadow" }), hAsync("div", Object.assign({ key: 'a72226ff1a98229f9bfd9207b98fc57e02baa430',
23609
23716
  /*
23610
23717
  role and aria-modal must be used on the
23611
23718
  same element. They must also be set inside the
23612
23719
  shadow DOM otherwise ion-button will not be highlighted
23613
23720
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
23614
23721
  */
23615
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '2d5ee6d5959d97309c306e8ce72eb0f2c19be144', class: "modal-handle",
23722
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (hAsync("button", { key: '0547f32323882660221385d84d492929caa77c6b', class: "modal-handle",
23616
23723
  // Prevents the handle from receiving keyboard focus when it does not cycle
23617
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: '5590434c35ea04c42fc006498bc189038e15a298', onSlotchange: this.onSlotChange }))));
23724
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), hAsync("slot", { key: 'fccbd64518b6fa22f9e874deb6b4ba55d8d89c3b', onSlotchange: this.onSlotChange }))));
23618
23725
  }
23619
23726
  get el() { return getElement(this); }
23620
23727
  static get watchers() { return {
@@ -23654,6 +23761,7 @@ class Modal {
23654
23761
  "keepContentsMounted": [4, "keep-contents-mounted"],
23655
23762
  "focusTrap": [4, "focus-trap"],
23656
23763
  "canDismiss": [4, "can-dismiss"],
23764
+ "isSheetModal": [32],
23657
23765
  "presented": [32],
23658
23766
  "present": [64],
23659
23767
  "dismiss": [64],
@@ -27721,6 +27829,10 @@ class Popover {
27721
27829
  if (destroyTriggerInteraction) {
27722
27830
  destroyTriggerInteraction();
27723
27831
  }
27832
+ if (this.headerResizeObserver) {
27833
+ this.headerResizeObserver.disconnect();
27834
+ this.headerResizeObserver = undefined;
27835
+ }
27724
27836
  }
27725
27837
  componentWillLoad() {
27726
27838
  var _a, _b;
@@ -27823,6 +27935,7 @@ class Popover {
27823
27935
  */
27824
27936
  this.ionMount.emit();
27825
27937
  this.usersElement = await attachComponent(delegate, el, this.component, ['popover-viewport'], this.componentProps, inline);
27938
+ this.recalculateContentOnHeaderReady();
27826
27939
  if (!this.keyboardEvents) {
27827
27940
  this.configureKeyboardInteraction();
27828
27941
  }
@@ -27868,6 +27981,35 @@ class Popover {
27868
27981
  }
27869
27982
  unlock();
27870
27983
  }
27984
+ /**
27985
+ * Watch the header for height changes and trigger content dimension
27986
+ * recalculation when the header has a height > 0. This sets the offset-top
27987
+ * of the content to the height of the header correctly.
27988
+ */
27989
+ recalculateContentOnHeaderReady() {
27990
+ var _a;
27991
+ const popoverContent = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.popover-content');
27992
+ if (!popoverContent) {
27993
+ return;
27994
+ }
27995
+ const contentContainer = this.usersElement || popoverContent;
27996
+ const header = contentContainer.querySelector('ion-header');
27997
+ const contentElements = contentContainer.querySelectorAll('ion-content');
27998
+ if (!header || contentElements.length === 0) {
27999
+ return;
28000
+ }
28001
+ this.headerResizeObserver = new ResizeObserver(async () => {
28002
+ var _a;
28003
+ if (header.offsetHeight > 0) {
28004
+ (_a = this.headerResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
28005
+ this.headerResizeObserver = undefined;
28006
+ for (const contentEl of contentElements) {
28007
+ await contentEl.recalculateDimensions();
28008
+ }
28009
+ }
28010
+ });
28011
+ this.headerResizeObserver.observe(header);
28012
+ }
27871
28013
  /**
27872
28014
  * Dismiss the popover overlay after it has been presented.
27873
28015
  * This is a no-op if the overlay has not been presented yet. If you want
@@ -27929,9 +28071,9 @@ class Popover {
27929
28071
  const { onLifecycle, parentPopover, dismissOnSelect, side, arrow, htmlAttributes, focusTrap } = this;
27930
28072
  const desktop = isPlatform('desktop');
27931
28073
  const enableArrow = arrow && !parentPopover;
27932
- return (hAsync(Host, Object.assign({ key: '16866c02534968c982cf4730d2936d03a5107c8b', "aria-modal": "true", "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
28074
+ return (hAsync(Host, Object.assign({ key: '42863f748c93f709d433931d969230137b37d42d', "aria-modal": "true", "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
27933
28075
  zIndex: `${20000 + this.overlayIndex}`,
27934
- }, class: Object.assign(Object.assign({}, getClassMap(this.cssClass)), { [mode]: true, 'popover-translucent': this.translucent, 'overlay-hidden': true, 'popover-desktop': desktop, [`popover-side-${side}`]: true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false, 'popover-nested': !!parentPopover }), onIonPopoverDidPresent: onLifecycle, onIonPopoverWillPresent: onLifecycle, onIonPopoverWillDismiss: onLifecycle, onIonPopoverDidDismiss: onLifecycle, onIonBackdropTap: this.onBackdropTap }), !parentPopover && hAsync("ion-backdrop", { key: '0df258601a4d30df3c27aa8234a7d5e056c3ecbb', tappable: this.backdropDismiss, visible: this.showBackdrop, part: "backdrop" }), hAsync("div", { key: 'f94e80ed996b957b5cd09b826472b4f60e8fcc78', class: "popover-wrapper ion-overlay-wrapper", onClick: dismissOnSelect ? () => this.dismiss() : undefined }, enableArrow && hAsync("div", { key: '185ce22f6386e8444a9cc7b8818dbfc16c463c99', class: "popover-arrow", part: "arrow" }), hAsync("div", { key: '206202b299404e110de5397b229678cca18568d3', class: "popover-content", part: "content" }, hAsync("slot", { key: 'ee543a0b92d6e35a837c0a0e4617c7b0fc4ad0b0' })))));
28076
+ }, class: Object.assign(Object.assign({}, getClassMap(this.cssClass)), { [mode]: true, 'popover-translucent': this.translucent, 'overlay-hidden': true, 'popover-desktop': desktop, [`popover-side-${side}`]: true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false, 'popover-nested': !!parentPopover }), onIonPopoverDidPresent: onLifecycle, onIonPopoverWillPresent: onLifecycle, onIonPopoverWillDismiss: onLifecycle, onIonPopoverDidDismiss: onLifecycle, onIonBackdropTap: this.onBackdropTap }), !parentPopover && hAsync("ion-backdrop", { key: '22b6d82178b52158b76ab3fd9a7dd738fd6e4bbf', tappable: this.backdropDismiss, visible: this.showBackdrop, part: "backdrop" }), hAsync("div", { key: 'b76335c64e992a964ed3fb91d17a992c3474b4cd', class: "popover-wrapper ion-overlay-wrapper", onClick: dismissOnSelect ? () => this.dismiss() : undefined }, enableArrow && hAsync("div", { key: '018c846c32e7ff7fa010528e6b37a17e5f03c84c', class: "popover-arrow", part: "arrow" }), hAsync("div", { key: '350c468c80052da3a07768bceab98fe159c35a43', class: "popover-content", part: "content" }, hAsync("slot", { key: '686443c17ac9873d33905c1cdb67e6d6da675282' })))));
27935
28077
  }
27936
28078
  get el() { return getElement(this); }
27937
28079
  static get watchers() { return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ionic/core",
3
- "version": "8.7.12-nightly.20251210",
3
+ "version": "8.7.12",
4
4
  "description": "Base components for Ionic",
5
5
  "engines": {
6
6
  "node": "24.x"