@ionic/core 8.7.17-dev.11767796972.148e4bc4 → 8.7.17-dev.11767895575.16ea7cef

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 (42) hide show
  1. package/components/content.js +6 -79
  2. package/components/ion-tab-bar.js +23 -3
  3. package/components/modal.js +12 -200
  4. package/components/popover.js +10 -73
  5. package/dist/cjs/ion-app_8.cjs.entry.js +5 -78
  6. package/dist/cjs/ion-modal.cjs.entry.js +12 -200
  7. package/dist/cjs/ion-popover.cjs.entry.js +10 -73
  8. package/dist/cjs/ion-tab-bar_2.cjs.entry.js +23 -3
  9. package/dist/collection/components/content/content.css +0 -10
  10. package/dist/collection/components/content/content.js +4 -77
  11. package/dist/collection/components/modal/gestures/sheet.js +1 -3
  12. package/dist/collection/components/modal/gestures/swipe-to-close.js +1 -3
  13. package/dist/collection/components/modal/modal.ios.css +4 -0
  14. package/dist/collection/components/modal/modal.js +8 -192
  15. package/dist/collection/components/modal/modal.md.css +4 -0
  16. package/dist/collection/components/popover/animations/ios.enter.js +5 -21
  17. package/dist/collection/components/popover/animations/md.enter.js +4 -20
  18. package/dist/collection/components/popover/utils.js +1 -32
  19. package/dist/collection/components/tab-bar/tab-bar.js +23 -3
  20. package/dist/docs.json +1 -1
  21. package/dist/esm/ion-app_8.entry.js +6 -79
  22. package/dist/esm/ion-modal.entry.js +12 -200
  23. package/dist/esm/ion-popover.entry.js +10 -73
  24. package/dist/esm/ion-tab-bar_2.entry.js +23 -3
  25. package/dist/ionic/ionic.esm.js +1 -1
  26. package/dist/ionic/p-172a579f.entry.js +4 -0
  27. package/dist/ionic/p-732b2fd6.entry.js +4 -0
  28. package/dist/ionic/p-91840a80.entry.js +4 -0
  29. package/dist/ionic/p-f9061316.entry.js +4 -0
  30. package/dist/types/components/content/content.d.ts +0 -24
  31. package/dist/types/components/modal/gestures/sheet.d.ts +1 -1
  32. package/dist/types/components/modal/gestures/swipe-to-close.d.ts +1 -1
  33. package/dist/types/components/modal/modal.d.ts +0 -39
  34. package/dist/types/components/popover/utils.d.ts +0 -2
  35. package/dist/types/components/tab-bar/tab-bar.d.ts +1 -0
  36. package/hydrate/index.js +50 -353
  37. package/hydrate/index.mjs +50 -353
  38. package/package.json +1 -1
  39. package/dist/ionic/p-3830217a.entry.js +0 -4
  40. package/dist/ionic/p-7268efa5.entry.js +0 -4
  41. package/dist/ionic/p-a4827773.entry.js +0 -4
  42. package/dist/ionic/p-cecd141b.entry.js +0 -4
@@ -42,10 +42,6 @@ export class Modal {
42
42
  this.inline = false;
43
43
  // Whether or not modal is being dismissed via gesture
44
44
  this.gestureAnimationDismissing = false;
45
- // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
46
- this.skipSafeAreaCoordinateDetection = false;
47
- // Track previous safe-area state to avoid redundant DOM writes
48
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
49
45
  this.presented = false;
50
46
  /** @internal */
51
47
  this.hasController = false;
@@ -236,10 +232,7 @@ export class Modal {
236
232
  }
237
233
  }
238
234
  onWindowResize() {
239
- // Invalidate safe-area cache on resize (device rotation may change values)
240
- this.cachedSafeAreas = undefined;
241
- this.updateSafeAreaOverrides();
242
- // Only handle view transition for iOS card modals when no custom animations are provided
235
+ // Only handle resize for iOS card modals when no custom animations are provided
243
236
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
244
237
  return;
245
238
  }
@@ -421,8 +414,6 @@ export class Modal {
421
414
  else if (!this.keepContentsMounted) {
422
415
  await waitForMount();
423
416
  }
424
- // Predict safe-area needs based on modal configuration to avoid visual snap
425
- this.setInitialSafeAreaOverrides(presentingElement);
426
417
  writeTask(() => this.el.classList.add('show-modal'));
427
418
  const hasCardModal = presentingElement !== undefined;
428
419
  /**
@@ -484,8 +475,6 @@ export class Modal {
484
475
  else if (hasCardModal) {
485
476
  this.initSwipeToClose();
486
477
  }
487
- // Now that animation is complete, update safe-area based on actual position
488
- this.updateSafeAreaOverrides();
489
478
  // Initialize view transition listener for iOS card modals
490
479
  this.initViewTransitionListener();
491
480
  // Initialize parent removal observer
@@ -537,7 +526,7 @@ export class Modal {
537
526
  await this.dismiss(undefined, GESTURE);
538
527
  this.gestureAnimationDismissing = false;
539
528
  });
540
- }, () => this.updateSafeAreaOverrides());
529
+ });
541
530
  this.gesture.enable(true);
542
531
  }
543
532
  initSheetGesture() {
@@ -558,8 +547,7 @@ export class Modal {
558
547
  this.currentBreakpoint = breakpoint;
559
548
  this.ionBreakpointDidChange.emit({ breakpoint });
560
549
  }
561
- this.updateSafeAreaOverrides();
562
- }, () => this.updateSafeAreaOverrides());
550
+ });
563
551
  this.gesture = gesture;
564
552
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
565
553
  this.gesture.enable(true);
@@ -637,161 +625,6 @@ export class Modal {
637
625
  // Clear the cached reference
638
626
  this.cachedPageParent = undefined;
639
627
  }
640
- /**
641
- * Sets initial safe-area overrides based on modal configuration before
642
- * the modal becomes visible. This predicts whether the modal will touch
643
- * screen edges to avoid a visual snap after animation completes.
644
- */
645
- setInitialSafeAreaOverrides(presentingElement) {
646
- const style = this.el.style;
647
- const mode = getIonMode(this);
648
- const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
649
- // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
650
- const isCardModal = presentingElement !== undefined && mode === 'ios';
651
- const isTablet = window.innerWidth >= 768;
652
- // Sheet modals always touch bottom edge, never top/left/right
653
- if (isSheetModal) {
654
- style.setProperty('--ion-safe-area-top', '0px');
655
- style.setProperty('--ion-safe-area-left', '0px');
656
- style.setProperty('--ion-safe-area-right', '0px');
657
- return;
658
- }
659
- // Card modals have rounded top corners
660
- if (isCardModal) {
661
- style.setProperty('--ion-safe-area-top', '0px');
662
- if (isTablet) {
663
- // On tablets, card modals are inset from all edges
664
- this.zeroAllSafeAreas();
665
- }
666
- else {
667
- // On phones, card modals still extend to the bottom edge
668
- style.setProperty('--ion-safe-area-left', '0px');
669
- style.setProperty('--ion-safe-area-right', '0px');
670
- this.applyFullscreenSafeArea();
671
- }
672
- return;
673
- }
674
- // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
675
- if (!isTablet) {
676
- this.applyFullscreenSafeArea();
677
- return;
678
- }
679
- // Check if tablet modal is fullscreen via CSS custom properties
680
- const computedStyle = getComputedStyle(this.el);
681
- const width = computedStyle.getPropertyValue('--width').trim();
682
- const height = computedStyle.getPropertyValue('--height').trim();
683
- const isFullscreen = width === '100%' && height === '100%';
684
- if (isFullscreen) {
685
- this.applyFullscreenSafeArea();
686
- }
687
- else {
688
- // Centered dialog doesn't touch edges
689
- this.zeroAllSafeAreas();
690
- }
691
- }
692
- /**
693
- * Applies safe-area handling for fullscreen modals.
694
- * Adds wrapper padding when no footer is present to prevent
695
- * content from overlapping system navigation areas.
696
- */
697
- applyFullscreenSafeArea() {
698
- this.skipSafeAreaCoordinateDetection = true;
699
- this.updateFooterPadding();
700
- // Watch for dynamic footer additions/removals (e.g., async data loading)
701
- if (!this.footerObserver) {
702
- this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
703
- this.footerObserver.observe(this.el, { childList: true, subtree: true });
704
- }
705
- }
706
- /**
707
- * Updates wrapper padding based on footer presence.
708
- * Called initially and when footer is dynamically added/removed.
709
- */
710
- updateFooterPadding() {
711
- if (!this.wrapperEl)
712
- return;
713
- const hasFooter = this.el.querySelector('ion-footer') !== null;
714
- if (hasFooter) {
715
- this.wrapperEl.style.removeProperty('padding-bottom');
716
- this.wrapperEl.style.removeProperty('box-sizing');
717
- }
718
- else {
719
- this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
720
- this.wrapperEl.style.setProperty('box-sizing', 'border-box');
721
- }
722
- }
723
- /**
724
- * Sets all safe-area CSS variables to 0px for modals that
725
- * don't touch screen edges.
726
- */
727
- zeroAllSafeAreas() {
728
- const style = this.el.style;
729
- style.setProperty('--ion-safe-area-top', '0px');
730
- style.setProperty('--ion-safe-area-bottom', '0px');
731
- style.setProperty('--ion-safe-area-left', '0px');
732
- style.setProperty('--ion-safe-area-right', '0px');
733
- }
734
- /**
735
- * Gets the root safe-area values from the document element.
736
- * Uses cached values during gestures to avoid getComputedStyle calls.
737
- */
738
- getSafeAreaValues() {
739
- if (!this.cachedSafeAreas) {
740
- const rootStyle = getComputedStyle(document.documentElement);
741
- this.cachedSafeAreas = {
742
- top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
743
- bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
744
- left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
745
- right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
746
- };
747
- }
748
- return this.cachedSafeAreas;
749
- }
750
- /**
751
- * Updates safe-area CSS variable overrides based on whether the modal
752
- * extends into each safe-area region. Called after animation
753
- * and during gestures to handle dynamic position changes.
754
- *
755
- * Optimized to avoid redundant DOM writes by tracking previous state.
756
- */
757
- updateSafeAreaOverrides() {
758
- if (this.skipSafeAreaCoordinateDetection) {
759
- return;
760
- }
761
- const wrapper = this.wrapperEl;
762
- if (!wrapper) {
763
- return;
764
- }
765
- const rect = wrapper.getBoundingClientRect();
766
- const safeAreas = this.getSafeAreaValues();
767
- const extendsIntoTop = rect.top < safeAreas.top;
768
- const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
769
- const extendsIntoLeft = rect.left < safeAreas.left;
770
- const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
771
- // Only update DOM when state actually changes
772
- const prev = this.prevSafeAreaState;
773
- const style = this.el.style;
774
- if (extendsIntoTop !== prev.top) {
775
- extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
776
- prev.top = extendsIntoTop;
777
- }
778
- if (extendsIntoBottom !== prev.bottom) {
779
- extendsIntoBottom
780
- ? style.removeProperty('--ion-safe-area-bottom')
781
- : style.setProperty('--ion-safe-area-bottom', '0px');
782
- prev.bottom = extendsIntoBottom;
783
- }
784
- if (extendsIntoLeft !== prev.left) {
785
- extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
786
- prev.left = extendsIntoLeft;
787
- }
788
- if (extendsIntoRight !== prev.right) {
789
- extendsIntoRight
790
- ? style.removeProperty('--ion-safe-area-right')
791
- : style.setProperty('--ion-safe-area-right', '0px');
792
- prev.right = extendsIntoRight;
793
- }
794
- }
795
628
  sheetOnDismiss() {
796
629
  /**
797
630
  * While the gesture animation is finishing
@@ -822,7 +655,7 @@ export class Modal {
822
655
  * For example, `cancel` or `backdrop`.
823
656
  */
824
657
  async dismiss(data, role) {
825
- var _a, _b;
658
+ var _a;
826
659
  if (this.gestureAnimationDismissing && role !== GESTURE) {
827
660
  return false;
828
661
  }
@@ -884,23 +717,6 @@ export class Modal {
884
717
  }
885
718
  this.currentBreakpoint = undefined;
886
719
  this.animation = undefined;
887
- // Reset safe-area state for potential re-presentation
888
- this.skipSafeAreaCoordinateDetection = false;
889
- this.cachedSafeAreas = undefined;
890
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
891
- (_b = this.footerObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
892
- this.footerObserver = undefined;
893
- // Clear styles that may have been set for safe-area handling
894
- if (this.wrapperEl) {
895
- this.wrapperEl.style.removeProperty('padding-bottom');
896
- this.wrapperEl.style.removeProperty('box-sizing');
897
- }
898
- // Clear safe-area CSS variable overrides
899
- const style = this.el.style;
900
- style.removeProperty('--ion-safe-area-top');
901
- style.removeProperty('--ion-safe-area-bottom');
902
- style.removeProperty('--ion-safe-area-left');
903
- style.removeProperty('--ion-safe-area-right');
904
720
  unlock();
905
721
  return dismissed;
906
722
  }
@@ -1158,20 +974,20 @@ export class Modal {
1158
974
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1159
975
  const isHandleCycle = handleBehavior === 'cycle';
1160
976
  const isSheetModalWithHandle = isSheetModal && showHandle;
1161
- return (h(Host, Object.assign({ key: '11cd16cc481093a38a327abdd94467be3f71718d', "no-router": true,
977
+ return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
1162
978
  // Allow the modal to be navigable when the handle is focusable
1163
979
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1164
980
  zIndex: `${20000 + this.overlayIndex}`,
1165
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '125658fbb071960da3905854668078e15bce56da', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '4a63815ef165e5806fef85ef48bf509813ff55c9', class: "modal-shadow" }), h("div", Object.assign({ key: 'cfc4b20354cbf2c0f873f6aee91c9b8f553de61d',
981
+ }, 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: 'ee94ff8e09b691dd4ad4e4db1720f06bc3c5a469', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'bffd69b4635c22d9f249725bd952c1e93d5615c7', class: "modal-shadow" }), h("div", Object.assign({ key: '1d394d3c68916e464ff1fbf5242419f4a3d3cca1',
1166
982
  /*
1167
983
  role and aria-modal must be used on the
1168
984
  same element. They must also be set inside the
1169
985
  shadow DOM otherwise ion-button will not be highlighted
1170
986
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1171
987
  */
1172
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '33a4ee89bb8b6512883cb8756641c1e27fdb0ebc', class: "modal-handle",
988
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2dcf58792018e557e0c323baad2d672bc99c0bb1', class: "modal-handle",
1173
989
  // Prevents the handle from receiving keyboard focus when it does not cycle
1174
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '986fafb71234591c96e927f92b7330bb5c76fc2e', onSlotchange: this.onSlotChange }))));
990
+ 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: '44164b1e8710c3895400ad9f44ecd99873874ad5', onSlotchange: this.onSlotChange }))));
1175
991
  }
1176
992
  static get is() { return "ion-modal"; }
1177
993
  static get encapsulation() { return "shadow"; }
@@ -135,6 +135,10 @@ ion-backdrop {
135
135
  :host {
136
136
  --width: 600px;
137
137
  --height: 500px;
138
+ --ion-safe-area-top: 0px;
139
+ --ion-safe-area-bottom: 0px;
140
+ --ion-safe-area-right: 0px;
141
+ --ion-safe-area-left: 0px;
138
142
  }
139
143
  }
140
144
  @media only screen and (min-width: 768px) and (min-height: 768px) {
@@ -31,7 +31,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
31
31
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
32
32
  const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
33
33
  const margin = size === 'cover' ? 0 : 25;
34
- const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
34
+ const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
35
35
  const baseAnimation = createAnimation();
36
36
  const backdropAnimation = createAnimation();
37
37
  const contentAnimation = createAnimation();
@@ -61,35 +61,19 @@ export const iosEnterAnimation = (baseEl, opts) => {
61
61
  if (addPopoverBottomClass) {
62
62
  baseEl.classList.add('popover-bottom');
63
63
  }
64
- /**
65
- * Safe area CSS variable adjustments.
66
- * When the popover is positioned near an edge, we add the corresponding
67
- * safe-area inset to ensure the popover doesn't overlap with system UI
68
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
69
- */
70
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
71
- const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
64
+ if (bottom !== undefined) {
65
+ contentEl.style.setProperty('bottom', `${bottom}px`);
66
+ }
72
67
  const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
73
68
  const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
74
- let topValue = `${top}px`;
75
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
76
69
  let leftValue = `${left}px`;
77
- if (checkSafeAreaTop) {
78
- topValue = `${top}px${safeAreaTop}`;
79
- }
80
- if (checkSafeAreaBottom && bottomValue !== undefined) {
81
- bottomValue = `${bottom}px${safeAreaBottom}`;
82
- }
83
70
  if (checkSafeAreaLeft) {
84
71
  leftValue = `${left}px${safeAreaLeft}`;
85
72
  }
86
73
  if (checkSafeAreaRight) {
87
74
  leftValue = `${left}px${safeAreaRight}`;
88
75
  }
89
- if (bottomValue !== undefined) {
90
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
91
- }
92
- contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
76
+ contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
93
77
  contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
94
78
  contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
95
79
  if (arrowEl !== null) {
@@ -28,23 +28,7 @@ export const mdEnterAnimation = (baseEl, opts) => {
28
28
  };
29
29
  const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
30
30
  const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
31
- const { originX, originY, top, left, bottom, checkSafeAreaTop, checkSafeAreaBottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
32
- /**
33
- * Safe area CSS variable adjustments.
34
- * When the popover is positioned near an edge, we add the corresponding
35
- * safe-area inset to ensure the popover doesn't overlap with system UI
36
- * (status bars, home indicators, navigation bars on Android API 36+, etc.)
37
- */
38
- const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
39
- const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
40
- let topValue = `${top}px`;
41
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
42
- if (checkSafeAreaTop) {
43
- topValue = `${top}px${safeAreaTop}`;
44
- }
45
- if (checkSafeAreaBottom && bottomValue !== undefined) {
46
- bottomValue = `${bottom}px${safeAreaBottom}`;
47
- }
31
+ const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
48
32
  const baseAnimation = createAnimation();
49
33
  const backdropAnimation = createAnimation();
50
34
  const wrapperAnimation = createAnimation();
@@ -61,13 +45,13 @@ export const mdEnterAnimation = (baseEl, opts) => {
61
45
  contentAnimation
62
46
  .addElement(contentEl)
63
47
  .beforeStyles({
64
- top: `calc(${topValue} + var(--offset-y, 0px))`,
48
+ top: `calc(${top}px + var(--offset-y, 0px))`,
65
49
  left: `calc(${left}px + var(--offset-x, 0px))`,
66
50
  'transform-origin': `${originY} ${originX}`,
67
51
  })
68
52
  .beforeAddWrite(() => {
69
- if (bottomValue !== undefined) {
70
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
53
+ if (bottom !== undefined) {
54
+ contentEl.style.setProperty('bottom', `${bottom}px`);
71
55
  }
72
56
  })
73
57
  .fromTo('transform', 'scale(0.8)', 'scale(1)');
@@ -648,8 +648,6 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
648
648
  let bottom;
649
649
  let originX = contentOriginX;
650
650
  let originY = contentOriginY;
651
- let checkSafeAreaTop = false;
652
- let checkSafeAreaBottom = false;
653
651
  let checkSafeAreaLeft = false;
654
652
  let checkSafeAreaRight = false;
655
653
  const triggerTop = triggerCoordinates
@@ -694,18 +692,10 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
694
692
  * We chose 12 here so that the popover position looks a bit nicer as
695
693
  * it is not right up against the edge of the screen.
696
694
  */
697
- top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
695
+ top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
698
696
  arrowTop = top + contentHeight;
699
697
  originY = 'bottom';
700
698
  addPopoverBottomClass = true;
701
- /**
702
- * If the popover is positioned near the top edge, account for safe area.
703
- * This ensures the popover doesn't overlap with status bars or notches.
704
- */
705
- if (top <= bodyPadding + safeAreaMargin) {
706
- checkSafeAreaTop = true;
707
- top = bodyPadding;
708
- }
709
699
  /**
710
700
  * If not enough room for popover to appear
711
701
  * above trigger, then cut it off.
@@ -713,35 +703,14 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
713
703
  }
714
704
  else {
715
705
  bottom = bodyPadding;
716
- /**
717
- * When the popover is pinned to the bottom, account for safe area.
718
- * This ensures the popover doesn't overlap with home indicators
719
- * or navigation bars (e.g., Android API 36+ edge-to-edge).
720
- */
721
- checkSafeAreaBottom = true;
722
706
  }
723
707
  }
724
- /**
725
- * Final check: If the popover extends into any safe-area region,
726
- * ensure the corresponding flag is set regardless of side.
727
- * This handles cases where a side-positioned popover (left/right)
728
- * still needs bottom safe-area padding because it extends into that region.
729
- */
730
- const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
731
- if (popoverBottom + safeAreaMargin > bodyHeight) {
732
- checkSafeAreaBottom = true;
733
- }
734
- if (top < safeAreaMargin) {
735
- checkSafeAreaTop = true;
736
- }
737
708
  return {
738
709
  top,
739
710
  left,
740
711
  bottom,
741
712
  originX,
742
713
  originY,
743
- checkSafeAreaTop,
744
- checkSafeAreaBottom,
745
714
  checkSafeAreaLeft,
746
715
  checkSafeAreaRight,
747
716
  arrowTop,
@@ -11,6 +11,7 @@ import { getIonMode } from "../../global/ionic-global";
11
11
  export class TabBar {
12
12
  constructor() {
13
13
  this.keyboardCtrl = null;
14
+ this.keyboardCtrlPromise = null;
14
15
  this.didLoad = false;
15
16
  this.keyboardVisible = false;
16
17
  /**
@@ -46,7 +47,7 @@ export class TabBar {
46
47
  }
47
48
  }
48
49
  async connectedCallback() {
49
- this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
50
+ const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
50
51
  /**
51
52
  * If the keyboard is hiding, then we need to wait
52
53
  * for the webview to resize. Otherwise, the tab bar
@@ -57,21 +58,40 @@ export class TabBar {
57
58
  }
58
59
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
59
60
  });
61
+ this.keyboardCtrlPromise = promise;
62
+ const keyboardCtrl = await promise;
63
+ /**
64
+ * Only assign if this is still the current promise.
65
+ * Otherwise, a new connectedCallback has started or
66
+ * disconnectedCallback was called, so destroy this instance.
67
+ */
68
+ if (this.keyboardCtrlPromise === promise) {
69
+ this.keyboardCtrl = keyboardCtrl;
70
+ this.keyboardCtrlPromise = null;
71
+ }
72
+ else {
73
+ keyboardCtrl.destroy();
74
+ }
60
75
  }
61
76
  disconnectedCallback() {
77
+ if (this.keyboardCtrlPromise) {
78
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
79
+ this.keyboardCtrlPromise = null;
80
+ }
62
81
  if (this.keyboardCtrl) {
63
82
  this.keyboardCtrl.destroy();
83
+ this.keyboardCtrl = null;
64
84
  }
65
85
  }
66
86
  render() {
67
87
  const { color, translucent, keyboardVisible } = this;
68
88
  const mode = getIonMode(this);
69
89
  const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
70
- return (h(Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
90
+ return (h(Host, { key: '9daf4e2acaff6e3ce3878cf9dd5109fb1afbbebe', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
71
91
  [mode]: true,
72
92
  'tab-bar-translucent': translucent,
73
93
  'tab-bar-hidden': shouldHide,
74
- }) }, h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
94
+ }) }, h("slot", { key: '1d15aa2da8501e8e7eff11ad4a491478be845c43' })));
75
95
  }
76
96
  static get is() { return "ion-tab-bar"; }
77
97
  static get encapsulation() { return "shadow"; }
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-07T14:44:35",
2
+ "timestamp": "2026-01-08T18:08:20",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",