@ionic/core 8.7.17-dev.11768239180.18ee1069 → 8.7.17-dev.11769628168.11eca7cd

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 (39) hide show
  1. package/components/content.js +4 -3
  2. package/components/ion-range.js +12 -4
  3. package/components/modal.js +12 -213
  4. package/components/popover.js +11 -83
  5. package/dist/cjs/ion-app_8.cjs.entry.js +4 -3
  6. package/dist/cjs/ion-modal.cjs.entry.js +12 -213
  7. package/dist/cjs/ion-popover.cjs.entry.js +11 -83
  8. package/dist/cjs/ion-range.cjs.entry.js +12 -4
  9. package/dist/collection/components/content/content.js +4 -3
  10. package/dist/collection/components/modal/gestures/sheet.js +1 -3
  11. package/dist/collection/components/modal/gestures/swipe-to-close.js +1 -3
  12. package/dist/collection/components/modal/modal.ios.css +4 -0
  13. package/dist/collection/components/modal/modal.js +7 -205
  14. package/dist/collection/components/modal/modal.md.css +4 -0
  15. package/dist/collection/components/popover/animations/ios.enter.js +5 -21
  16. package/dist/collection/components/popover/animations/md.enter.js +5 -30
  17. package/dist/collection/components/popover/utils.js +1 -32
  18. package/dist/collection/components/range/range.js +12 -4
  19. package/dist/docs.json +1 -1
  20. package/dist/esm/ion-app_8.entry.js +4 -3
  21. package/dist/esm/ion-modal.entry.js +12 -213
  22. package/dist/esm/ion-popover.entry.js +11 -83
  23. package/dist/esm/ion-range.entry.js +12 -4
  24. package/dist/ionic/ionic.esm.js +1 -1
  25. package/dist/ionic/p-012212e4.entry.js +4 -0
  26. package/dist/ionic/p-91840a80.entry.js +4 -0
  27. package/dist/ionic/p-c73627c8.entry.js +4 -0
  28. package/dist/ionic/p-f9061316.entry.js +4 -0
  29. package/dist/types/components/modal/gestures/sheet.d.ts +1 -1
  30. package/dist/types/components/modal/gestures/swipe-to-close.d.ts +1 -1
  31. package/dist/types/components/modal/modal.d.ts +0 -45
  32. package/dist/types/components/popover/utils.d.ts +0 -2
  33. package/hydrate/index.js +38 -302
  34. package/hydrate/index.mjs +38 -302
  35. package/package.json +1 -1
  36. package/dist/ionic/p-1647c46c.entry.js +0 -4
  37. package/dist/ionic/p-732b2fd6.entry.js +0 -4
  38. package/dist/ionic/p-968a55d1.entry.js +0 -4
  39. package/dist/ionic/p-ec9ca3fe.entry.js +0 -4
@@ -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) {
@@ -2,7 +2,6 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Host, h, writeTask } from "@stencil/core";
5
- import { win } from "../../utils/browser/index";
6
5
  import { findIonContent, printIonContentErrorMsg } from "../../utils/content/index";
7
6
  import { CoreDelegate, attachComponent, detachComponent } from "../../utils/framework-delegate";
8
7
  import { raf, inheritAttributes, hasLazyBuild, getElementRoot } from "../../utils/helpers";
@@ -43,10 +42,6 @@ export class Modal {
43
42
  this.inline = false;
44
43
  // Whether or not modal is being dismissed via gesture
45
44
  this.gestureAnimationDismissing = false;
46
- // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
47
- this.skipSafeAreaCoordinateDetection = false;
48
- // Track previous safe-area state to avoid redundant DOM writes
49
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
50
45
  this.presented = false;
51
46
  /** @internal */
52
47
  this.hasController = false;
@@ -237,10 +232,7 @@ export class Modal {
237
232
  }
238
233
  }
239
234
  onWindowResize() {
240
- // Invalidate safe-area cache on resize (device rotation may change values)
241
- this.cachedSafeAreas = undefined;
242
- this.updateSafeAreaOverrides();
243
- // 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
244
236
  if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
245
237
  return;
246
238
  }
@@ -263,8 +255,6 @@ export class Modal {
263
255
  this.triggerController.removeClickListener();
264
256
  this.cleanupViewTransitionListener();
265
257
  this.cleanupParentRemovalObserver();
266
- // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
267
- this.resetSafeAreaState();
268
258
  }
269
259
  componentWillLoad() {
270
260
  var _a;
@@ -424,8 +414,6 @@ export class Modal {
424
414
  else if (!this.keepContentsMounted) {
425
415
  await waitForMount();
426
416
  }
427
- // Predict safe-area needs based on modal configuration to avoid visual snap
428
- this.setInitialSafeAreaOverrides(presentingElement);
429
417
  writeTask(() => this.el.classList.add('show-modal'));
430
418
  const hasCardModal = presentingElement !== undefined;
431
419
  /**
@@ -487,8 +475,6 @@ export class Modal {
487
475
  else if (hasCardModal) {
488
476
  this.initSwipeToClose();
489
477
  }
490
- // Now that animation is complete, update safe-area based on actual position
491
- this.updateSafeAreaOverrides();
492
478
  // Initialize view transition listener for iOS card modals
493
479
  this.initViewTransitionListener();
494
480
  // Initialize parent removal observer
@@ -540,7 +526,7 @@ export class Modal {
540
526
  await this.dismiss(undefined, GESTURE);
541
527
  this.gestureAnimationDismissing = false;
542
528
  });
543
- }, () => this.updateSafeAreaOverrides());
529
+ });
544
530
  this.gesture.enable(true);
545
531
  }
546
532
  initSheetGesture() {
@@ -561,8 +547,7 @@ export class Modal {
561
547
  this.currentBreakpoint = breakpoint;
562
548
  this.ionBreakpointDidChange.emit({ breakpoint });
563
549
  }
564
- this.updateSafeAreaOverrides();
565
- }, () => this.updateSafeAreaOverrides());
550
+ });
566
551
  this.gesture = gesture;
567
552
  this.moveSheetToBreakpoint = moveSheetToBreakpoint;
568
553
  this.gesture.enable(true);
@@ -640,187 +625,6 @@ export class Modal {
640
625
  // Clear the cached reference
641
626
  this.cachedPageParent = undefined;
642
627
  }
643
- /**
644
- * Sets initial safe-area overrides based on modal configuration before
645
- * the modal becomes visible. This predicts whether the modal will touch
646
- * screen edges to avoid a visual snap after animation completes.
647
- */
648
- setInitialSafeAreaOverrides(presentingElement) {
649
- const style = this.el.style;
650
- const mode = getIonMode(this);
651
- const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
652
- // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
653
- const isCardModal = presentingElement !== undefined && mode === 'ios';
654
- const isTablet = window.innerWidth >= 768;
655
- // Sheet modals always touch bottom edge, never top/left/right
656
- if (isSheetModal) {
657
- style.setProperty('--ion-safe-area-top', '0px');
658
- style.setProperty('--ion-safe-area-left', '0px');
659
- style.setProperty('--ion-safe-area-right', '0px');
660
- return;
661
- }
662
- // Card modals have rounded top corners
663
- if (isCardModal) {
664
- style.setProperty('--ion-safe-area-top', '0px');
665
- if (isTablet) {
666
- // On tablets, card modals are inset from all edges
667
- this.zeroAllSafeAreas();
668
- }
669
- else {
670
- // On phones, card modals still extend to the bottom edge
671
- style.setProperty('--ion-safe-area-left', '0px');
672
- style.setProperty('--ion-safe-area-right', '0px');
673
- this.applyFullscreenSafeArea();
674
- }
675
- return;
676
- }
677
- // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
678
- if (!isTablet) {
679
- this.applyFullscreenSafeArea();
680
- return;
681
- }
682
- // Check if tablet modal is fullscreen via CSS custom properties
683
- const computedStyle = getComputedStyle(this.el);
684
- const width = computedStyle.getPropertyValue('--width').trim();
685
- const height = computedStyle.getPropertyValue('--height').trim();
686
- const isFullscreen = width === '100%' && height === '100%';
687
- if (isFullscreen) {
688
- this.applyFullscreenSafeArea();
689
- }
690
- else {
691
- // Centered dialog doesn't touch edges
692
- this.zeroAllSafeAreas();
693
- }
694
- }
695
- /**
696
- * Applies safe-area handling for fullscreen modals.
697
- * Adds wrapper padding when no footer is present to prevent
698
- * content from overlapping system navigation areas.
699
- */
700
- applyFullscreenSafeArea() {
701
- this.skipSafeAreaCoordinateDetection = true;
702
- this.updateFooterPadding();
703
- // Watch for dynamic footer additions/removals (e.g., async data loading)
704
- // Use subtree:true to support wrapped footers in framework components
705
- // (e.g., <my-footer><ion-footer>...</ion-footer></my-footer>)
706
- if (!this.footerObserver && win !== undefined && 'MutationObserver' in win) {
707
- this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
708
- this.footerObserver.observe(this.el, { childList: true, subtree: true });
709
- }
710
- }
711
- /**
712
- * Updates wrapper padding based on footer presence.
713
- * Called initially and when footer is dynamically added/removed.
714
- */
715
- updateFooterPadding() {
716
- if (!this.wrapperEl)
717
- return;
718
- const hasFooter = this.el.querySelector('ion-footer') !== null;
719
- if (hasFooter) {
720
- this.wrapperEl.style.removeProperty('padding-bottom');
721
- this.wrapperEl.style.removeProperty('box-sizing');
722
- }
723
- else {
724
- this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
725
- this.wrapperEl.style.setProperty('box-sizing', 'border-box');
726
- }
727
- }
728
- /**
729
- * Sets all safe-area CSS variables to 0px for modals that
730
- * don't touch screen edges.
731
- */
732
- zeroAllSafeAreas() {
733
- const style = this.el.style;
734
- style.setProperty('--ion-safe-area-top', '0px');
735
- style.setProperty('--ion-safe-area-bottom', '0px');
736
- style.setProperty('--ion-safe-area-left', '0px');
737
- style.setProperty('--ion-safe-area-right', '0px');
738
- }
739
- /**
740
- * Resets all safe-area related state and styles.
741
- * Called during dismiss and disconnectedCallback to ensure clean state
742
- * for re-presentation of inline modals.
743
- */
744
- resetSafeAreaState() {
745
- var _a;
746
- this.skipSafeAreaCoordinateDetection = false;
747
- this.cachedSafeAreas = undefined;
748
- this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
749
- (_a = this.footerObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
750
- this.footerObserver = undefined;
751
- // Clear wrapper styles that may have been set for safe-area handling
752
- if (this.wrapperEl) {
753
- this.wrapperEl.style.removeProperty('padding-bottom');
754
- this.wrapperEl.style.removeProperty('box-sizing');
755
- }
756
- // Clear safe-area CSS variable overrides
757
- const style = this.el.style;
758
- style.removeProperty('--ion-safe-area-top');
759
- style.removeProperty('--ion-safe-area-bottom');
760
- style.removeProperty('--ion-safe-area-left');
761
- style.removeProperty('--ion-safe-area-right');
762
- }
763
- /**
764
- * Gets the root safe-area values from the document element.
765
- * Uses cached values during gestures to avoid getComputedStyle calls.
766
- */
767
- getSafeAreaValues() {
768
- if (!this.cachedSafeAreas) {
769
- const rootStyle = getComputedStyle(document.documentElement);
770
- this.cachedSafeAreas = {
771
- top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
772
- bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
773
- left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
774
- right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
775
- };
776
- }
777
- return this.cachedSafeAreas;
778
- }
779
- /**
780
- * Updates safe-area CSS variable overrides based on whether the modal
781
- * extends into each safe-area region. Called after animation
782
- * and during gestures to handle dynamic position changes.
783
- *
784
- * Optimized to avoid redundant DOM writes by tracking previous state.
785
- */
786
- updateSafeAreaOverrides() {
787
- if (this.skipSafeAreaCoordinateDetection) {
788
- return;
789
- }
790
- const wrapper = this.wrapperEl;
791
- if (!wrapper) {
792
- return;
793
- }
794
- const rect = wrapper.getBoundingClientRect();
795
- const safeAreas = this.getSafeAreaValues();
796
- const extendsIntoTop = rect.top < safeAreas.top;
797
- const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
798
- const extendsIntoLeft = rect.left < safeAreas.left;
799
- const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
800
- // Only update DOM when state actually changes
801
- const prev = this.prevSafeAreaState;
802
- const style = this.el.style;
803
- if (extendsIntoTop !== prev.top) {
804
- extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
805
- prev.top = extendsIntoTop;
806
- }
807
- if (extendsIntoBottom !== prev.bottom) {
808
- extendsIntoBottom
809
- ? style.removeProperty('--ion-safe-area-bottom')
810
- : style.setProperty('--ion-safe-area-bottom', '0px');
811
- prev.bottom = extendsIntoBottom;
812
- }
813
- if (extendsIntoLeft !== prev.left) {
814
- extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
815
- prev.left = extendsIntoLeft;
816
- }
817
- if (extendsIntoRight !== prev.right) {
818
- extendsIntoRight
819
- ? style.removeProperty('--ion-safe-area-right')
820
- : style.setProperty('--ion-safe-area-right', '0px');
821
- prev.right = extendsIntoRight;
822
- }
823
- }
824
628
  sheetOnDismiss() {
825
629
  /**
826
630
  * While the gesture animation is finishing
@@ -913,8 +717,6 @@ export class Modal {
913
717
  }
914
718
  this.currentBreakpoint = undefined;
915
719
  this.animation = undefined;
916
- // Reset safe-area state for potential re-presentation
917
- this.resetSafeAreaState();
918
720
  unlock();
919
721
  return dismissed;
920
722
  }
@@ -1172,20 +974,20 @@ export class Modal {
1172
974
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1173
975
  const isHandleCycle = handleBehavior === 'cycle';
1174
976
  const isSheetModalWithHandle = isSheetModal && showHandle;
1175
- return (h(Host, Object.assign({ key: '44022099fcaf047b97d1c2cb45b9b51c930e707c', "no-router": true,
977
+ return (h(Host, Object.assign({ key: '87328006ea6c75ebc518ace300438492a567223e', "no-router": true,
1176
978
  // Allow the modal to be navigable when the handle is focusable
1177
979
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1178
980
  zIndex: `${20000 + this.overlayIndex}`,
1179
- }, 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: 'ddd7e4f6eef51ac1f62ac70e0af10fb01e707f07', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '58620980e3e4ec273c6787bde026e1c010b904b7', class: "modal-shadow" }), h("div", Object.assign({ key: '3fb7f6218644ba898fc504467775593eb89426a0',
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',
1180
982
  /*
1181
983
  role and aria-modal must be used on the
1182
984
  same element. They must also be set inside the
1183
985
  shadow DOM otherwise ion-button will not be highlighted
1184
986
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1185
987
  */
1186
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '9745cd590fdaa9d023a14b487ec2c87ddbafd7f7', 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",
1187
989
  // Prevents the handle from receiving keyboard focus when it does not cycle
1188
- 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: 'b9a8b5d2d3d3c9b06f99179f496c9f08907d0bad', 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 }))));
1189
991
  }
1190
992
  static get is() { return "ion-modal"; }
1191
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,32 +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, checkSafeAreaLeft, checkSafeAreaRight, } = 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
- const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
41
- const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
42
- let topValue = `${top}px`;
43
- let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
44
- let leftValue = `${left}px`;
45
- if (checkSafeAreaTop) {
46
- topValue = `${top}px${safeAreaTop}`;
47
- }
48
- if (checkSafeAreaBottom && bottomValue !== undefined) {
49
- bottomValue = `${bottom}px${safeAreaBottom}`;
50
- }
51
- if (checkSafeAreaLeft) {
52
- leftValue = `${left}px${safeAreaLeft}`;
53
- }
54
- if (checkSafeAreaRight) {
55
- leftValue = `${left}px${safeAreaRight}`;
56
- }
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);
57
32
  const baseAnimation = createAnimation();
58
33
  const backdropAnimation = createAnimation();
59
34
  const wrapperAnimation = createAnimation();
@@ -70,13 +45,13 @@ export const mdEnterAnimation = (baseEl, opts) => {
70
45
  contentAnimation
71
46
  .addElement(contentEl)
72
47
  .beforeStyles({
73
- top: `calc(${topValue} + var(--offset-y, 0px))`,
74
- left: `calc(${leftValue} + var(--offset-x, 0px))`,
48
+ top: `calc(${top}px + var(--offset-y, 0px))`,
49
+ left: `calc(${left}px + var(--offset-x, 0px))`,
75
50
  'transform-origin': `${originY} ${originX}`,
76
51
  })
77
52
  .beforeAddWrite(() => {
78
- if (bottomValue !== undefined) {
79
- contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
53
+ if (bottom !== undefined) {
54
+ contentEl.style.setProperty('bottom', `${bottom}px`);
80
55
  }
81
56
  })
82
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,
@@ -665,7 +665,7 @@ export class Range {
665
665
  })));
666
666
  }
667
667
  render() {
668
- const { disabled, el, hasLabel, rangeId, pin, pressedKnob, labelPlacement, label } = this;
668
+ const { disabled, el, hasLabel, rangeId, pin, pressedKnob, labelPlacement, label, dualKnobs, min, max } = this;
669
669
  const inItem = hostContext('ion-item', el);
670
670
  /**
671
671
  * If there is no start content then the knob at
@@ -680,8 +680,14 @@ export class Range {
680
680
  const hasEndContent = (hasLabel && labelPlacement === 'end') || this.hasEndSlotContent;
681
681
  const needsEndAdjustment = inItem && !hasEndContent;
682
682
  const mode = getIonMode(this);
683
+ /**
684
+ * Determine if any knob is at the min or max value to
685
+ * apply Host classes for styling.
686
+ */
687
+ const valueAtMin = dualKnobs ? this.valA === min || this.valB === min : this.valA === min;
688
+ const valueAtMax = dualKnobs ? this.valA === max || this.valB === max : this.valA === max;
683
689
  renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
684
- return (h(Host, { key: 'ef7b01f80515bcaeb2983934ad7f10a6bd5d13ec', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
690
+ return (h(Host, { key: 'ed646a42d51b8fe22012198c354cbcf5a389c108', onFocusin: this.onFocus, onFocusout: this.onBlur, id: rangeId, class: createColorClasses(this.color, {
685
691
  [mode]: true,
686
692
  'in-item': inItem,
687
693
  'range-disabled': disabled,
@@ -690,10 +696,12 @@ export class Range {
690
696
  [`range-label-placement-${labelPlacement}`]: true,
691
697
  'range-item-start-adjustment': needsStartAdjustment,
692
698
  'range-item-end-adjustment': needsEndAdjustment,
693
- }) }, h("label", { key: 'fd8aa90a9d52be9da024b907e68858dae424449d', class: "range-wrapper", id: "range-label" }, h("div", { key: '2172b4f329c22017dd23475c80aac25ba6e753eb', class: {
699
+ 'range-value-min': valueAtMin,
700
+ 'range-value-max': valueAtMax,
701
+ }) }, h("label", { key: '3083e4f2a624e3b268396acb4415f7c6ac44d851', class: "range-wrapper", id: "range-label" }, h("div", { key: '47b92f94d2a0381dd7c5cd3dda54ed2942096715', class: {
694
702
  'label-text-wrapper': true,
695
703
  'label-text-wrapper-hidden': !hasLabel,
696
- }, part: "label" }, label !== undefined ? h("div", { class: "label-text" }, label) : h("slot", { name: "label" })), h("div", { key: '3c318bf2ea0576646d4c010bf44573fd0f483186', class: "native-wrapper" }, h("slot", { key: '6586fd6fc96271e73f8a86c202d1913ad1a26f96', name: "start" }), this.renderRangeSlider(), h("slot", { key: '74ac0bc2d2cb66ef708bb729f88b6ecbc1b2155d', name: "end" })))));
704
+ }, part: "label" }, label !== undefined ? h("div", { class: "label-text" }, label) : h("slot", { name: "label" })), h("div", { key: '5341da8d19eb29091df680978a0e20cc8f2eec65', class: "native-wrapper" }, h("slot", { key: '09f1437078032676695442d8c827a16faa7dffe2', name: "start" }), this.renderRangeSlider(), h("slot", { key: '02b7781970ea4d44f10b5f4627a2ca36eca45f85', name: "end" })))));
697
705
  }
698
706
  static get is() { return "ion-range"; }
699
707
  static get encapsulation() { return "shadow"; }
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-01-12T17:35:00",
2
+ "timestamp": "2026-01-28T19:24:49",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -513,20 +513,21 @@ const Content = class {
513
513
  const forceOverscroll = this.shouldForceOverscroll();
514
514
  const transitionShadow = mode === 'ios';
515
515
  this.resize();
516
- return (h(Host, Object.assign({ key: 'cd8781f848d8dc926fe66f43d43c49564425a507', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
516
+ return (h(Host, Object.assign({ key: '212b1438f044061887984e02e1c8943ee1d33c20', role: isMainContent ? 'main' : undefined, class: createColorClasses(this.color, {
517
517
  [mode]: true,
518
+ 'content-fullscreen': this.fullscreen,
518
519
  'content-sizing': hostContext('ion-popover', this.el),
519
520
  overscroll: forceOverscroll,
520
521
  [`content-${rtl}`]: true,
521
522
  }), style: {
522
523
  '--offset-top': `${this.cTop}px`,
523
524
  '--offset-bottom': `${this.cBottom}px`,
524
- } }, inheritedAttributes), h("div", { key: '95b112d7cae30f22ef778ceffb88edb4d941c170', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: '2fdfcbc39fb66f11b6191911f2941c660f4c12e5', class: {
525
+ } }, inheritedAttributes), h("div", { key: 'ea46641492eef8cc7b08fc398d0285115b5a7100', ref: (el) => (this.backgroundContentEl = el), id: "background-content", part: "background" }), fixedSlotPlacement === 'before' ? h("slot", { name: "fixed" }) : null, h("div", { key: 'dc9096f0b97ab6145fb46cf065cd244f4af1cab5', class: {
525
526
  'inner-scroll': true,
526
527
  'scroll-x': scrollX,
527
528
  'scroll-y': scrollY,
528
529
  overscroll: (scrollX || scrollY) && forceOverscroll,
529
- }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '6bc77e0054ec8e21635a7f2abfe0ca46e0962e03' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
530
+ }, ref: (scrollEl) => (this.scrollEl = scrollEl), onScroll: this.scrollEvents ? (ev) => this.onScroll(ev) : undefined, part: "scroll" }, h("slot", { key: '4b13fd5b7e124353d43b47a30e975400ae2a0341' })), transitionShadow ? (h("div", { class: "transition-effect" }, h("div", { class: "transition-cover" }), h("div", { class: "transition-shadow" }))) : null, fixedSlotPlacement === 'after' ? h("slot", { name: "fixed" }) : null));
530
531
  }
531
532
  get el() { return getElement(this); }
532
533
  };