@ionic/core 8.6.3-dev.11751378808.12cc4a5c → 8.6.3-dev.11751478001.1cb7436f

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 (34) hide show
  1. package/components/focus-visible.js +27 -18
  2. package/components/ion-select.js +3 -3
  3. package/components/modal.js +8 -220
  4. package/dist/cjs/{focus-visible-CCvKiLh3.js → focus-visible-ZV7jHMPt.js} +27 -18
  5. package/dist/cjs/ion-app_8.cjs.entry.js +1 -1
  6. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  7. package/dist/cjs/ion-modal.cjs.entry.js +8 -220
  8. package/dist/cjs/ion-select_3.cjs.entry.js +3 -3
  9. package/dist/collection/components/modal/animations/ios.enter.js +2 -2
  10. package/dist/collection/components/modal/animations/ios.leave.js +2 -2
  11. package/dist/collection/components/modal/modal.js +4 -87
  12. package/dist/collection/components/select/select.js +3 -3
  13. package/dist/collection/utils/focus-visible.js +27 -18
  14. package/dist/docs.json +1 -1
  15. package/dist/esm/{focus-visible-BmVRXR1y.js → focus-visible-DZ2gZBzK.js} +27 -18
  16. package/dist/esm/ion-app_8.entry.js +1 -1
  17. package/dist/esm/ion-datetime_3.entry.js +1 -1
  18. package/dist/esm/ion-modal.entry.js +8 -220
  19. package/dist/esm/ion-select_3.entry.js +3 -3
  20. package/dist/ionic/ionic.esm.js +1 -1
  21. package/dist/ionic/{p-01123ecf.entry.js → p-2d46fbe7.entry.js} +1 -1
  22. package/dist/ionic/{p-5f671887.entry.js → p-78a61b3f.entry.js} +1 -1
  23. package/dist/ionic/p-7d5cf8c1.entry.js +4 -0
  24. package/dist/ionic/p-9e32212d.entry.js +4 -0
  25. package/dist/ionic/p-DZ2gZBzK.js +4 -0
  26. package/dist/types/components/modal/modal.d.ts +0 -6
  27. package/hydrate/index.js +38 -241
  28. package/hydrate/index.mjs +38 -241
  29. package/package.json +1 -1
  30. package/dist/collection/components/modal/animations/ios.transition.js +0 -143
  31. package/dist/ionic/p-4ddc10ef.entry.js +0 -4
  32. package/dist/ionic/p-6f74a187.entry.js +0 -4
  33. package/dist/ionic/p-BmVRXR1y.js +0 -4
  34. package/dist/types/components/modal/animations/ios.transition.d.ts +0 -14
@@ -589,7 +589,7 @@ const iosEnterAnimation = (baseEl, opts) => {
589
589
  baseAnimation.addAnimation(contentAnimation);
590
590
  }
591
591
  if (presentingEl) {
592
- const isPortrait = window.innerWidth < 768;
592
+ const isMobile = window.innerWidth < 768;
593
593
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
594
594
  const presentingElRoot = helpers.getElementRoot(presentingEl);
595
595
  const presentingAnimation = animation.createAnimation().beforeStyles({
@@ -598,7 +598,7 @@ const iosEnterAnimation = (baseEl, opts) => {
598
598
  overflow: 'hidden',
599
599
  });
600
600
  const bodyEl = document.body;
601
- if (isPortrait) {
601
+ if (isMobile) {
602
602
  /**
603
603
  * Fallback for browsers that does not support `max()` (ex: Firefox)
604
604
  * No need to worry about statusbar padding since engines like Gecko
@@ -676,7 +676,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
676
676
  .duration(duration)
677
677
  .addAnimation(wrapperAnimation);
678
678
  if (presentingEl) {
679
- const isPortrait = window.innerWidth < 768;
679
+ const isMobile = window.innerWidth < 768;
680
680
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
681
681
  const presentingElRoot = helpers.getElementRoot(presentingEl);
682
682
  const presentingAnimation = animation.createAnimation()
@@ -694,7 +694,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
694
694
  }
695
695
  });
696
696
  const bodyEl = document.body;
697
- if (isPortrait) {
697
+ if (isMobile) {
698
698
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
699
699
  const modalTransform = hasCardModal ? '-10px' : transformOffset;
700
700
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
@@ -741,144 +741,6 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
741
741
  return baseAnimation;
742
742
  };
743
743
 
744
- /**
745
- * Transition animation from portrait view to landscape view
746
- * This handles the case where a card modal is open in portrait view
747
- * and the user switches to landscape view
748
- */
749
- const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
750
- const { presentingEl } = opts;
751
- if (!presentingEl) {
752
- // No transition needed for non-card modals
753
- return animation.createAnimation('portrait-to-landscape-transition');
754
- }
755
- const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
756
- const presentingElRoot = helpers.getElementRoot(presentingEl);
757
- const bodyEl = document.body;
758
- const baseAnimation = animation.createAnimation('portrait-to-landscape-transition')
759
- .addElement(baseEl)
760
- .easing('cubic-bezier(0.32,0.72,0,1)')
761
- .duration(duration);
762
- const presentingAnimation = animation.createAnimation().beforeStyles({
763
- transform: 'translateY(0)',
764
- 'transform-origin': 'top center',
765
- overflow: 'hidden',
766
- });
767
- if (!hasCardModal) {
768
- // Non-card modal: transition from portrait state to landscape state
769
- // Portrait: presentingEl has transform and body has black background
770
- // Landscape: no transform, no body background, modal wrapper opacity changes
771
- const root = helpers.getElementRoot(baseEl);
772
- const wrapperAnimation = animation.createAnimation()
773
- .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
774
- .fromTo('opacity', '1', '1'); // Keep wrapper visible in landscape
775
- const backdropAnimation = animation.createAnimation()
776
- .addElement(root.querySelector('ion-backdrop'))
777
- .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
778
- // Animate presentingEl from portrait state back to normal
779
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
780
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
781
- const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
782
- presentingAnimation
783
- .addElement(presentingEl)
784
- .afterStyles({
785
- transform: 'translateY(0px) scale(1)',
786
- 'border-radius': '0px',
787
- })
788
- .beforeAddWrite(() => bodyEl.style.setProperty('background-color', ''))
789
- .fromTo('transform', fromTransform, 'translateY(0px) scale(1)')
790
- .fromTo('filter', 'contrast(0.85)', 'contrast(1)')
791
- .fromTo('border-radius', '10px 10px 0 0', '0px');
792
- baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
793
- }
794
- else {
795
- // Card modal: transition from portrait card state to landscape card state
796
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
797
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
798
- const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
799
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
800
- presentingAnimation
801
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
802
- .fromTo('transform', fromTransform, toTransform)
803
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
804
- const shadowAnimation = animation.createAnimation()
805
- .addElement(presentingElRoot.querySelector('.modal-shadow'))
806
- .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
807
- .fromTo('transform', fromTransform, toTransform);
808
- baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
809
- }
810
- return baseAnimation;
811
- };
812
- /**
813
- * Transition animation from landscape view to portrait view
814
- * This handles the case where a card modal is open in landscape view
815
- * and the user switches to portrait view
816
- */
817
- const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
818
- const { presentingEl } = opts;
819
- if (!presentingEl) {
820
- // No transition needed for non-card modals
821
- return animation.createAnimation('landscape-to-portrait-transition');
822
- }
823
- const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
824
- const presentingElRoot = helpers.getElementRoot(presentingEl);
825
- const bodyEl = document.body;
826
- const baseAnimation = animation.createAnimation('landscape-to-portrait-transition')
827
- .addElement(baseEl)
828
- .easing('cubic-bezier(0.32,0.72,0,1)')
829
- .duration(duration);
830
- const presentingAnimation = animation.createAnimation().beforeStyles({
831
- transform: 'translateY(0)',
832
- 'transform-origin': 'top center',
833
- overflow: 'hidden',
834
- });
835
- if (!hasCardModal) {
836
- // Non-card modal: transition from landscape state to portrait state
837
- const root = helpers.getElementRoot(baseEl);
838
- const wrapperAnimation = animation.createAnimation()
839
- .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
840
- .fromTo('opacity', '1', '1'); // Keep wrapper visible
841
- const backdropAnimation = animation.createAnimation()
842
- .addElement(root.querySelector('ion-backdrop'))
843
- .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
844
- // Animate presentingEl from normal state to portrait state
845
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
846
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
847
- const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
848
- presentingAnimation
849
- .addElement(presentingEl)
850
- .afterStyles({
851
- transform: toTransform,
852
- 'border-radius': '10px 10px 0 0',
853
- filter: 'contrast(0.85)',
854
- overflow: 'hidden',
855
- 'transform-origin': 'top center',
856
- })
857
- .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
858
- .fromTo('transform', 'translateY(0px) scale(1)', toTransform)
859
- .fromTo('filter', 'contrast(1)', 'contrast(0.85)')
860
- .fromTo('border-radius', '0px', '10px 10px 0 0');
861
- baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
862
- }
863
- else {
864
- // Card modal: transition from landscape card state to portrait card state
865
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
866
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
867
- const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
868
- const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
869
- presentingAnimation
870
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
871
- .fromTo('transform', fromTransform, toTransform)
872
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
873
- const shadowAnimation = animation.createAnimation()
874
- .addElement(presentingElRoot.querySelector('.modal-shadow'))
875
- .fromTo('opacity', '0', '0') // Shadow stays hidden
876
- .fromTo('transform', fromTransform, toTransform);
877
- baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
878
- }
879
- return baseAnimation;
880
- };
881
-
882
744
  const createEnterAnimation = () => {
883
745
  const backdropAnimation = animation.createAnimation()
884
746
  .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
@@ -1660,7 +1522,6 @@ const Modal = class {
1660
1522
  }
1661
1523
  disconnectedCallback() {
1662
1524
  this.triggerController.removeClickListener();
1663
- this.cleanupViewTransitionListener();
1664
1525
  }
1665
1526
  componentWillLoad() {
1666
1527
  var _a;
@@ -1871,8 +1732,6 @@ const Modal = class {
1871
1732
  else if (hasCardModal) {
1872
1733
  this.initSwipeToClose();
1873
1734
  }
1874
- // Initialize view transition listener for iOS card modals
1875
- this.initViewTransitionListener();
1876
1735
  unlock();
1877
1736
  }
1878
1737
  initSwipeToClose() {
@@ -2026,7 +1885,6 @@ const Modal = class {
2026
1885
  if (this.gesture) {
2027
1886
  this.gesture.destroy();
2028
1887
  }
2029
- this.cleanupViewTransitionListener();
2030
1888
  }
2031
1889
  this.currentBreakpoint = undefined;
2032
1890
  this.animation = undefined;
@@ -2102,76 +1960,6 @@ const Modal = class {
2102
1960
  await this.setCurrentBreakpoint(nextBreakpoint);
2103
1961
  return true;
2104
1962
  }
2105
- initViewTransitionListener() {
2106
- // Only enable for iOS card modals when no custom animations are provided
2107
- if (index$3.getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
2108
- return;
2109
- }
2110
- // Set initial view state
2111
- this.currentViewIsPortrait = window.innerWidth < 768;
2112
- // Create debounced resize handler
2113
- let resizeTimeout;
2114
- this.resizeListener = () => {
2115
- clearTimeout(resizeTimeout);
2116
- resizeTimeout = setTimeout(() => {
2117
- this.handleViewTransition();
2118
- }, 100); // Debounce for 100ms to avoid excessive calls
2119
- };
2120
- window.addEventListener('resize', this.resizeListener);
2121
- }
2122
- handleViewTransition() {
2123
- const isPortrait = window.innerWidth < 768;
2124
- // Only transition if view state actually changed
2125
- if (this.currentViewIsPortrait === isPortrait) {
2126
- return;
2127
- }
2128
- // Cancel any ongoing transition animation
2129
- if (this.viewTransitionAnimation) {
2130
- this.viewTransitionAnimation.destroy();
2131
- this.viewTransitionAnimation = undefined;
2132
- }
2133
- const { presentingElement } = this;
2134
- if (!presentingElement) {
2135
- return;
2136
- }
2137
- // Create transition animation
2138
- let transitionAnimation;
2139
- if (this.currentViewIsPortrait && !isPortrait) {
2140
- // Portrait to landscape transition
2141
- transitionAnimation = portraitToLandscapeTransition(this.el, {
2142
- presentingEl: presentingElement});
2143
- }
2144
- else {
2145
- // Landscape to portrait transition
2146
- transitionAnimation = landscapeToPortraitTransition(this.el, {
2147
- presentingEl: presentingElement});
2148
- }
2149
- // Update state and play animation
2150
- this.currentViewIsPortrait = isPortrait;
2151
- this.viewTransitionAnimation = transitionAnimation;
2152
- transitionAnimation.play().then(() => {
2153
- this.viewTransitionAnimation = undefined;
2154
- });
2155
- }
2156
- cleanupViewTransitionListener() {
2157
- const hasCardView = !!this.resizeListener;
2158
- if (this.resizeListener) {
2159
- window.removeEventListener('resize', this.resizeListener);
2160
- this.resizeListener = undefined;
2161
- }
2162
- if (this.viewTransitionAnimation) {
2163
- this.viewTransitionAnimation.destroy();
2164
- this.viewTransitionAnimation = undefined;
2165
- }
2166
- if (hasCardView) {
2167
- // If we had a card view, let's trigger the view transition
2168
- // one last time to make sure we're in the right state.
2169
- // This will prevent tricky things like resizing the modal causing
2170
- // it to dismiss programatically too quickly and preventing the view transition
2171
- // from being applied.
2172
- this.handleViewTransition();
2173
- }
2174
- }
2175
1963
  render() {
2176
1964
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
2177
1965
  const showHandle = handle !== false && isSheetModal;
@@ -2179,20 +1967,20 @@ const Modal = class {
2179
1967
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2180
1968
  const isHandleCycle = handleBehavior === 'cycle';
2181
1969
  const isSheetModalWithHandle = isSheetModal && showHandle;
2182
- return (index$3.h(index$3.Host, Object.assign({ key: '857fbf50b5d0b43c1f1a335d740649cedf1d6e2b', "no-router": true,
1970
+ return (index$3.h(index$3.Host, Object.assign({ key: '8add05bb43a2cdb5e3cf180147d31eb85a018fe0', "no-router": true,
2183
1971
  // Allow the modal to be navigable when the handle is focusable
2184
1972
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2185
1973
  zIndex: `${20000 + this.overlayIndex}`,
2186
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '07b6143d368748194e4b209b48c7d9efbeed12e0', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '14eaf25ad683ca5c7369e69908ea93401dd5536b', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '9b996da27066c8dfdce8fcf3abdff65948010b5e',
1974
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '90a6605a9564a699d6f66cf71cf6b506796a2963', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: 'a97d071395333bf803c0a9347bda000cf7500d8d', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: 'e7b7985c7414a13e3ba8dcecf497b76e92edf53e',
2187
1975
  /*
2188
1976
  role and aria-modal must be used on the
2189
1977
  same element. They must also be set inside the
2190
1978
  shadow DOM otherwise ion-button will not be highlighted
2191
1979
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2192
1980
  */
2193
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: 'd3b429aa40e094548a5eeaa295f4cb1b1f921339', class: "modal-handle",
1981
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: '8258b65570b11a8ee9c9df2537d6419cd2e34536', class: "modal-handle",
2194
1982
  // Prevents the handle from receiving keyboard focus when it does not cycle
2195
- 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) })), index$3.h("slot", { key: '86160fcdf92d6ca2a9afe5c801efb89c32ce2cc1' }))));
1983
+ 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) })), index$3.h("slot", { key: '394370d0ed03ee03152f8f8abae7ff7664ca5c13' }))));
2196
1984
  }
2197
1985
  get el() { return index$3.getElement(this); }
2198
1986
  static get watchers() { return {
@@ -216,7 +216,7 @@ const Select = class {
216
216
  const scrollSelectedIntoView = () => {
217
217
  const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
218
218
  if (indexOfSelected > -1) {
219
- const selectedItem = overlay.querySelector(`.select-interface-option:nth-child(${indexOfSelected + 1})`);
219
+ const selectedItem = overlay.querySelector(`.select-interface-option:nth-of-type(${indexOfSelected + 1})`);
220
220
  if (selectedItem) {
221
221
  /**
222
222
  * Browsers such as Firefox do not
@@ -736,7 +736,7 @@ const Select = class {
736
736
  * TODO(FW-5592): Remove hasStartEndSlots condition
737
737
  */
738
738
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
739
- return (index.h(index.Host, { key: 'e33253af9b9daa14460c7c259fb138b4d2b875ae', onClick: this.onClick, class: theme.createColorClasses(this.color, {
739
+ return (index.h(index.Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: theme.createColorClasses(this.color, {
740
740
  [mode]: true,
741
741
  'in-item': inItem,
742
742
  'in-item-color': theme.hostContext('ion-item.ion-color', el),
@@ -754,7 +754,7 @@ const Select = class {
754
754
  [`select-justify-${justify}`]: justifyEnabled,
755
755
  [`select-shape-${shape}`]: shape !== undefined,
756
756
  [`select-label-placement-${labelPlacement}`]: true,
757
- }) }, index.h("label", { key: '03303b7131c77a78a933e8e496e7251b6b5c485b', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '9037674db6a0adc189e887c9048f817d5afdf635', class: "select-wrapper-inner" }, index.h("slot", { key: '7c9226c838ba4bcf5bc0b234fa1613ce335cf73d', name: "start" }), index.h("div", { key: 'a7432c3efb5c65f88a5e4b0d35c5a433fa4e949d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), index.h("slot", { key: '028e683df26a8f573d7670da76e0ad26d21c83e0', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && index.h("div", { key: '20341bcae3753048ce905d2aceffa5351a0db125', class: "select-highlight" })), this.renderBottomContent()));
757
+ }) }, index.h("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, index.h("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), index.h("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), index.h("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && index.h("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
758
758
  }
759
759
  get el() { return index.getElement(this); }
760
760
  static get watchers() { return {
@@ -36,7 +36,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
36
36
  baseAnimation.addAnimation(contentAnimation);
37
37
  }
38
38
  if (presentingEl) {
39
- const isPortrait = window.innerWidth < 768;
39
+ const isMobile = window.innerWidth < 768;
40
40
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
41
41
  const presentingElRoot = getElementRoot(presentingEl);
42
42
  const presentingAnimation = createAnimation().beforeStyles({
@@ -45,7 +45,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
45
45
  overflow: 'hidden',
46
46
  });
47
47
  const bodyEl = document.body;
48
- if (isPortrait) {
48
+ if (isMobile) {
49
49
  /**
50
50
  * Fallback for browsers that does not support `max()` (ex: Firefox)
51
51
  * No need to worry about statusbar padding since engines like Gecko
@@ -25,7 +25,7 @@ export const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
25
25
  .duration(duration)
26
26
  .addAnimation(wrapperAnimation);
27
27
  if (presentingEl) {
28
- const isPortrait = window.innerWidth < 768;
28
+ const isMobile = window.innerWidth < 768;
29
29
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
30
30
  const presentingElRoot = getElementRoot(presentingEl);
31
31
  const presentingAnimation = createAnimation()
@@ -43,7 +43,7 @@ export const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
43
43
  }
44
44
  });
45
45
  const bodyEl = document.body;
46
- if (isPortrait) {
46
+ if (isMobile) {
47
47
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
48
48
  const modalTransform = hasCardModal ? '-10px' : transformOffset;
49
49
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
@@ -16,7 +16,6 @@ import { getIonMode } from "../../global/ionic-global";
16
16
  import { KEYBOARD_DID_OPEN } from "../../utils/keyboard/keyboard";
17
17
  import { iosEnterAnimation } from "./animations/ios.enter";
18
18
  import { iosLeaveAnimation } from "./animations/ios.leave";
19
- import { portraitToLandscapeTransition, landscapeToPortraitTransition } from "./animations/ios.transition";
20
19
  import { mdEnterAnimation } from "./animations/md.enter";
21
20
  import { mdLeaveAnimation } from "./animations/md.leave";
22
21
  import { createSheetGesture } from "./gestures/sheet";
@@ -223,7 +222,6 @@ export class Modal {
223
222
  }
224
223
  disconnectedCallback() {
225
224
  this.triggerController.removeClickListener();
226
- this.cleanupViewTransitionListener();
227
225
  }
228
226
  componentWillLoad() {
229
227
  var _a;
@@ -434,8 +432,6 @@ export class Modal {
434
432
  else if (hasCardModal) {
435
433
  this.initSwipeToClose();
436
434
  }
437
- // Initialize view transition listener for iOS card modals
438
- this.initViewTransitionListener();
439
435
  unlock();
440
436
  }
441
437
  initSwipeToClose() {
@@ -589,7 +585,6 @@ export class Modal {
589
585
  if (this.gesture) {
590
586
  this.gesture.destroy();
591
587
  }
592
- this.cleanupViewTransitionListener();
593
588
  }
594
589
  this.currentBreakpoint = undefined;
595
590
  this.animation = undefined;
@@ -665,84 +660,6 @@ export class Modal {
665
660
  await this.setCurrentBreakpoint(nextBreakpoint);
666
661
  return true;
667
662
  }
668
- initViewTransitionListener() {
669
- // Only enable for iOS card modals when no custom animations are provided
670
- if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
671
- return;
672
- }
673
- // Set initial view state
674
- this.currentViewIsPortrait = window.innerWidth < 768;
675
- // Create debounced resize handler
676
- let resizeTimeout;
677
- this.resizeListener = () => {
678
- clearTimeout(resizeTimeout);
679
- resizeTimeout = setTimeout(() => {
680
- this.handleViewTransition();
681
- }, 100); // Debounce for 100ms to avoid excessive calls
682
- };
683
- window.addEventListener('resize', this.resizeListener);
684
- }
685
- handleViewTransition() {
686
- const isPortrait = window.innerWidth < 768;
687
- // Only transition if view state actually changed
688
- if (this.currentViewIsPortrait === isPortrait) {
689
- return;
690
- }
691
- // Cancel any ongoing transition animation
692
- if (this.viewTransitionAnimation) {
693
- this.viewTransitionAnimation.destroy();
694
- this.viewTransitionAnimation = undefined;
695
- }
696
- const { presentingElement } = this;
697
- if (!presentingElement) {
698
- return;
699
- }
700
- // Create transition animation
701
- let transitionAnimation;
702
- if (this.currentViewIsPortrait && !isPortrait) {
703
- // Portrait to landscape transition
704
- transitionAnimation = portraitToLandscapeTransition(this.el, {
705
- presentingEl: presentingElement,
706
- currentBreakpoint: this.currentBreakpoint,
707
- backdropBreakpoint: this.backdropBreakpoint,
708
- expandToScroll: this.expandToScroll,
709
- });
710
- }
711
- else {
712
- // Landscape to portrait transition
713
- transitionAnimation = landscapeToPortraitTransition(this.el, {
714
- presentingEl: presentingElement,
715
- currentBreakpoint: this.currentBreakpoint,
716
- backdropBreakpoint: this.backdropBreakpoint,
717
- expandToScroll: this.expandToScroll,
718
- });
719
- }
720
- // Update state and play animation
721
- this.currentViewIsPortrait = isPortrait;
722
- this.viewTransitionAnimation = transitionAnimation;
723
- transitionAnimation.play().then(() => {
724
- this.viewTransitionAnimation = undefined;
725
- });
726
- }
727
- cleanupViewTransitionListener() {
728
- const hasCardView = !!this.resizeListener;
729
- if (this.resizeListener) {
730
- window.removeEventListener('resize', this.resizeListener);
731
- this.resizeListener = undefined;
732
- }
733
- if (this.viewTransitionAnimation) {
734
- this.viewTransitionAnimation.destroy();
735
- this.viewTransitionAnimation = undefined;
736
- }
737
- if (hasCardView) {
738
- // If we had a card view, let's trigger the view transition
739
- // one last time to make sure we're in the right state.
740
- // This will prevent tricky things like resizing the modal causing
741
- // it to dismiss programatically too quickly and preventing the view transition
742
- // from being applied.
743
- this.handleViewTransition();
744
- }
745
- }
746
663
  render() {
747
664
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
748
665
  const showHandle = handle !== false && isSheetModal;
@@ -750,20 +667,20 @@ export class Modal {
750
667
  const isCardModal = presentingElement !== undefined && mode === 'ios';
751
668
  const isHandleCycle = handleBehavior === 'cycle';
752
669
  const isSheetModalWithHandle = isSheetModal && showHandle;
753
- return (h(Host, Object.assign({ key: '857fbf50b5d0b43c1f1a335d740649cedf1d6e2b', "no-router": true,
670
+ return (h(Host, Object.assign({ key: '8add05bb43a2cdb5e3cf180147d31eb85a018fe0', "no-router": true,
754
671
  // Allow the modal to be navigable when the handle is focusable
755
672
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
756
673
  zIndex: `${20000 + this.overlayIndex}`,
757
- }, 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: '07b6143d368748194e4b209b48c7d9efbeed12e0', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '14eaf25ad683ca5c7369e69908ea93401dd5536b', class: "modal-shadow" }), h("div", Object.assign({ key: '9b996da27066c8dfdce8fcf3abdff65948010b5e',
674
+ }, 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: '90a6605a9564a699d6f66cf71cf6b506796a2963', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'a97d071395333bf803c0a9347bda000cf7500d8d', class: "modal-shadow" }), h("div", Object.assign({ key: 'e7b7985c7414a13e3ba8dcecf497b76e92edf53e',
758
675
  /*
759
676
  role and aria-modal must be used on the
760
677
  same element. They must also be set inside the
761
678
  shadow DOM otherwise ion-button will not be highlighted
762
679
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
763
680
  */
764
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'd3b429aa40e094548a5eeaa295f4cb1b1f921339', class: "modal-handle",
681
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '8258b65570b11a8ee9c9df2537d6419cd2e34536', class: "modal-handle",
765
682
  // Prevents the handle from receiving keyboard focus when it does not cycle
766
- 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: '86160fcdf92d6ca2a9afe5c801efb89c32ce2cc1' }))));
683
+ 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: '394370d0ed03ee03152f8f8abae7ff7664ca5c13' }))));
767
684
  }
768
685
  static get is() { return "ion-modal"; }
769
686
  static get encapsulation() { return "shadow"; }
@@ -216,7 +216,7 @@ export class Select {
216
216
  const scrollSelectedIntoView = () => {
217
217
  const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
218
218
  if (indexOfSelected > -1) {
219
- const selectedItem = overlay.querySelector(`.select-interface-option:nth-child(${indexOfSelected + 1})`);
219
+ const selectedItem = overlay.querySelector(`.select-interface-option:nth-of-type(${indexOfSelected + 1})`);
220
220
  if (selectedItem) {
221
221
  /**
222
222
  * Browsers such as Firefox do not
@@ -782,7 +782,7 @@ export class Select {
782
782
  * TODO(FW-5592): Remove hasStartEndSlots condition
783
783
  */
784
784
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
785
- return (h(Host, { key: 'e33253af9b9daa14460c7c259fb138b4d2b875ae', onClick: this.onClick, class: createColorClasses(this.color, {
785
+ return (h(Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: createColorClasses(this.color, {
786
786
  [mode]: true,
787
787
  'in-item': inItem,
788
788
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -800,7 +800,7 @@ export class Select {
800
800
  [`select-justify-${justify}`]: justifyEnabled,
801
801
  [`select-shape-${shape}`]: shape !== undefined,
802
802
  [`select-label-placement-${labelPlacement}`]: true,
803
- }) }, h("label", { key: '03303b7131c77a78a933e8e496e7251b6b5c485b', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '9037674db6a0adc189e887c9048f817d5afdf635', class: "select-wrapper-inner" }, h("slot", { key: '7c9226c838ba4bcf5bc0b234fa1613ce335cf73d', name: "start" }), h("div", { key: 'a7432c3efb5c65f88a5e4b0d35c5a433fa4e949d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: '028e683df26a8f573d7670da76e0ad26d21c83e0', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '20341bcae3753048ce905d2aceffa5351a0db125', class: "select-highlight" })), this.renderBottomContent()));
803
+ }) }, h("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, h("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), h("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
804
804
  }
805
805
  static get is() { return "ion-select"; }
806
806
  static get encapsulation() { return "shadow"; }
@@ -19,33 +19,44 @@ const FOCUS_KEYS = [
19
19
  ];
20
20
  export const startFocusVisible = (rootEl) => {
21
21
  let currentFocus = [];
22
- let keyboardMode = true;
22
+ // Tracks if the last interaction was a pointer event (mouse, touch, pen)
23
+ // Used to distinguish between pointer and keyboard navigation for focus styling
24
+ let hadPointerEvent = false;
23
25
  const ref = rootEl ? rootEl.shadowRoot : document;
24
26
  const root = rootEl ? rootEl : document.body;
27
+ // Adds or removes the focused class for styling
25
28
  const setFocus = (elements) => {
26
29
  currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED));
27
30
  elements.forEach((el) => el.classList.add(ION_FOCUSED));
28
31
  currentFocus = elements;
29
32
  };
30
- const pointerDown = () => {
31
- keyboardMode = false;
32
- setFocus([]);
33
+ // Do not set focus on pointer interactions
34
+ const pointerDown = (ev) => {
35
+ if (ev instanceof PointerEvent && ev.pointerType !== '') {
36
+ hadPointerEvent = true;
37
+ // Reset after the event loop so only the immediate focusin is suppressed
38
+ setTimeout(() => {
39
+ hadPointerEvent = false;
40
+ }, 0);
41
+ }
33
42
  };
43
+ // Clear hadPointerEvent so keyboard navigation shows focus
44
+ // Also, clear focus if the key is not a navigation key
34
45
  const onKeydown = (ev) => {
35
- keyboardMode = FOCUS_KEYS.includes(ev.key);
36
- if (!keyboardMode) {
46
+ hadPointerEvent = false;
47
+ const keyboardEvent = ev;
48
+ if (!FOCUS_KEYS.includes(keyboardEvent.key)) {
37
49
  setFocus([]);
38
50
  }
39
51
  };
52
+ // Set focus if the last interaction was NOT a pointer event
53
+ // This works around iOS/Safari bugs where keydown is not fired for Tab
40
54
  const onFocusin = (ev) => {
41
- if (keyboardMode && ev.composedPath !== undefined) {
42
- const toFocus = ev.composedPath().filter((el) => {
43
- // TODO(FW-2832): type
44
- if (el.classList) {
45
- return el.classList.contains(ION_FOCUSABLE);
46
- }
47
- return false;
48
- });
55
+ const target = ev.target;
56
+ if (target.classList.contains(ION_FOCUSABLE) && !hadPointerEvent) {
57
+ const toFocus = ev
58
+ .composedPath()
59
+ .filter((el) => el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE));
49
60
  setFocus(toFocus);
50
61
  }
51
62
  };
@@ -57,14 +68,12 @@ export const startFocusVisible = (rootEl) => {
57
68
  ref.addEventListener('keydown', onKeydown);
58
69
  ref.addEventListener('focusin', onFocusin);
59
70
  ref.addEventListener('focusout', onFocusout);
60
- ref.addEventListener('touchstart', pointerDown, { passive: true });
61
- ref.addEventListener('mousedown', pointerDown);
71
+ ref.addEventListener('pointerdown', pointerDown, { passive: true });
62
72
  const destroy = () => {
63
73
  ref.removeEventListener('keydown', onKeydown);
64
74
  ref.removeEventListener('focusin', onFocusin);
65
75
  ref.removeEventListener('focusout', onFocusout);
66
- ref.removeEventListener('touchstart', pointerDown);
67
- ref.removeEventListener('mousedown', pointerDown);
76
+ ref.removeEventListener('pointerdown', pointerDown);
68
77
  };
69
78
  return {
70
79
  destroy,
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-07-01T14:08:28",
2
+ "timestamp": "2025-07-02T17:41:38",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.33.1",