@ionic/core 8.6.3-dev.11751376263.167f1d05 → 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 -211
  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 -211
  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 -78
  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 -211
  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 -232
  28. package/hydrate/index.mjs +38 -232
  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-85cedfd0.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
@@ -19,33 +19,44 @@ const FOCUS_KEYS = [
19
19
  ];
20
20
  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 @@ 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,
@@ -235,7 +235,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
235
235
  const scrollSelectedIntoView = () => {
236
236
  const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
237
237
  if (indexOfSelected > -1) {
238
- const selectedItem = overlay.querySelector(`.select-interface-option:nth-child(${indexOfSelected + 1})`);
238
+ const selectedItem = overlay.querySelector(`.select-interface-option:nth-of-type(${indexOfSelected + 1})`);
239
239
  if (selectedItem) {
240
240
  /**
241
241
  * Browsers such as Firefox do not
@@ -755,7 +755,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
755
755
  * TODO(FW-5592): Remove hasStartEndSlots condition
756
756
  */
757
757
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
758
- return (h(Host, { key: 'e33253af9b9daa14460c7c259fb138b4d2b875ae', onClick: this.onClick, class: createColorClasses(this.color, {
758
+ return (h(Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: createColorClasses(this.color, {
759
759
  [mode]: true,
760
760
  'in-item': inItem,
761
761
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -773,7 +773,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
773
773
  [`select-justify-${justify}`]: justifyEnabled,
774
774
  [`select-shape-${shape}`]: shape !== undefined,
775
775
  [`select-label-placement-${labelPlacement}`]: true,
776
- }) }, 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()));
776
+ }) }, 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()));
777
777
  }
778
778
  get el() { return this; }
779
779
  static get watchers() { return {
@@ -587,7 +587,7 @@ const iosEnterAnimation = (baseEl, opts) => {
587
587
  baseAnimation.addAnimation(contentAnimation);
588
588
  }
589
589
  if (presentingEl) {
590
- const isPortrait = window.innerWidth < 768;
590
+ const isMobile = window.innerWidth < 768;
591
591
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
592
592
  const presentingElRoot = getElementRoot(presentingEl);
593
593
  const presentingAnimation = createAnimation().beforeStyles({
@@ -596,7 +596,7 @@ const iosEnterAnimation = (baseEl, opts) => {
596
596
  overflow: 'hidden',
597
597
  });
598
598
  const bodyEl = document.body;
599
- if (isPortrait) {
599
+ if (isMobile) {
600
600
  /**
601
601
  * Fallback for browsers that does not support `max()` (ex: Firefox)
602
602
  * No need to worry about statusbar padding since engines like Gecko
@@ -674,7 +674,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
674
674
  .duration(duration)
675
675
  .addAnimation(wrapperAnimation);
676
676
  if (presentingEl) {
677
- const isPortrait = window.innerWidth < 768;
677
+ const isMobile = window.innerWidth < 768;
678
678
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
679
679
  const presentingElRoot = getElementRoot(presentingEl);
680
680
  const presentingAnimation = createAnimation()
@@ -692,7 +692,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
692
692
  }
693
693
  });
694
694
  const bodyEl = document.body;
695
- if (isPortrait) {
695
+ if (isMobile) {
696
696
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
697
697
  const modalTransform = hasCardModal ? '-10px' : transformOffset;
698
698
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
@@ -739,144 +739,6 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
739
739
  return baseAnimation;
740
740
  };
741
741
 
742
- /**
743
- * Transition animation from portrait view to landscape view
744
- * This handles the case where a card modal is open in portrait view
745
- * and the user switches to landscape view
746
- */
747
- const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
748
- const { presentingEl } = opts;
749
- if (!presentingEl) {
750
- // No transition needed for non-card modals
751
- return createAnimation('portrait-to-landscape-transition');
752
- }
753
- const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
754
- const presentingElRoot = getElementRoot(presentingEl);
755
- const bodyEl = document.body;
756
- const baseAnimation = createAnimation('portrait-to-landscape-transition')
757
- .addElement(baseEl)
758
- .easing('cubic-bezier(0.32,0.72,0,1)')
759
- .duration(duration);
760
- const presentingAnimation = createAnimation().beforeStyles({
761
- transform: 'translateY(0)',
762
- 'transform-origin': 'top center',
763
- overflow: 'hidden',
764
- });
765
- if (!hasCardModal) {
766
- // Non-card modal: transition from portrait state to landscape state
767
- // Portrait: presentingEl has transform and body has black background
768
- // Landscape: no transform, no body background, modal wrapper opacity changes
769
- const root = getElementRoot(baseEl);
770
- const wrapperAnimation = createAnimation()
771
- .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
772
- .fromTo('opacity', '1', '1'); // Keep wrapper visible in landscape
773
- const backdropAnimation = createAnimation()
774
- .addElement(root.querySelector('ion-backdrop'))
775
- .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
776
- // Animate presentingEl from portrait state back to normal
777
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
778
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
779
- const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
780
- presentingAnimation
781
- .addElement(presentingEl)
782
- .afterStyles({
783
- transform: 'translateY(0px) scale(1)',
784
- 'border-radius': '0px',
785
- })
786
- .beforeAddWrite(() => bodyEl.style.setProperty('background-color', ''))
787
- .fromTo('transform', fromTransform, 'translateY(0px) scale(1)')
788
- .fromTo('filter', 'contrast(0.85)', 'contrast(1)')
789
- .fromTo('border-radius', '10px 10px 0 0', '0px');
790
- baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
791
- }
792
- else {
793
- // Card modal: transition from portrait card state to landscape card state
794
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
795
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
796
- const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
797
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
798
- presentingAnimation
799
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
800
- .fromTo('transform', fromTransform, toTransform)
801
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
802
- const shadowAnimation = createAnimation()
803
- .addElement(presentingElRoot.querySelector('.modal-shadow'))
804
- .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
805
- .fromTo('transform', fromTransform, toTransform);
806
- baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
807
- }
808
- return baseAnimation;
809
- };
810
- /**
811
- * Transition animation from landscape view to portrait view
812
- * This handles the case where a card modal is open in landscape view
813
- * and the user switches to portrait view
814
- */
815
- const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
816
- const { presentingEl } = opts;
817
- if (!presentingEl) {
818
- // No transition needed for non-card modals
819
- return createAnimation('landscape-to-portrait-transition');
820
- }
821
- const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
822
- const presentingElRoot = getElementRoot(presentingEl);
823
- const bodyEl = document.body;
824
- const baseAnimation = createAnimation('landscape-to-portrait-transition')
825
- .addElement(baseEl)
826
- .easing('cubic-bezier(0.32,0.72,0,1)')
827
- .duration(duration);
828
- const presentingAnimation = createAnimation().beforeStyles({
829
- transform: 'translateY(0)',
830
- 'transform-origin': 'top center',
831
- overflow: 'hidden',
832
- });
833
- if (!hasCardModal) {
834
- // Non-card modal: transition from landscape state to portrait state
835
- const root = getElementRoot(baseEl);
836
- const wrapperAnimation = createAnimation()
837
- .addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow'))
838
- .fromTo('opacity', '1', '1'); // Keep wrapper visible
839
- const backdropAnimation = createAnimation()
840
- .addElement(root.querySelector('ion-backdrop'))
841
- .fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
842
- // Animate presentingEl from normal state to portrait state
843
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
844
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
845
- const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
846
- presentingAnimation
847
- .addElement(presentingEl)
848
- .afterStyles({
849
- transform: toTransform,
850
- 'border-radius': '10px 10px 0 0',
851
- filter: 'contrast(0.85)',
852
- overflow: 'hidden',
853
- 'transform-origin': 'top center',
854
- })
855
- .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
856
- .fromTo('transform', 'translateY(0px) scale(1)', toTransform)
857
- .fromTo('filter', 'contrast(1)', 'contrast(0.85)')
858
- .fromTo('border-radius', '0px', '10px 10px 0 0');
859
- baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
860
- }
861
- else {
862
- // Card modal: transition from landscape card state to portrait card state
863
- const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
864
- const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
865
- const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
866
- const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
867
- presentingAnimation
868
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
869
- .fromTo('transform', fromTransform, toTransform)
870
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
871
- const shadowAnimation = createAnimation()
872
- .addElement(presentingElRoot.querySelector('.modal-shadow'))
873
- .fromTo('opacity', '0', '0') // Shadow stays hidden
874
- .fromTo('transform', fromTransform, toTransform);
875
- baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
876
- }
877
- return baseAnimation;
878
- };
879
-
880
742
  const createEnterAnimation = () => {
881
743
  const backdropAnimation = createAnimation()
882
744
  .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
@@ -1660,7 +1522,6 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
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 = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
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 = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
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,67 +1960,6 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
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 (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
- if (this.resizeListener) {
2158
- window.removeEventListener('resize', this.resizeListener);
2159
- this.resizeListener = undefined;
2160
- }
2161
- if (this.viewTransitionAnimation) {
2162
- this.viewTransitionAnimation.destroy();
2163
- this.viewTransitionAnimation = undefined;
2164
- }
2165
- }
2166
1963
  render() {
2167
1964
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
2168
1965
  const showHandle = handle !== false && isSheetModal;
@@ -2170,20 +1967,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2170
1967
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2171
1968
  const isHandleCycle = handleBehavior === 'cycle';
2172
1969
  const isSheetModalWithHandle = isSheetModal && showHandle;
2173
- return (h(Host, Object.assign({ key: 'd5bcaa588471573e1bcbd99997b306c8ecdd124f', "no-router": true,
1970
+ return (h(Host, Object.assign({ key: '8add05bb43a2cdb5e3cf180147d31eb85a018fe0', "no-router": true,
2174
1971
  // Allow the modal to be navigable when the handle is focusable
2175
1972
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2176
1973
  zIndex: `${20000 + this.overlayIndex}`,
2177
- }, 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: '78b954a1c50a0325eb51db04314e86701c66d840', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '21887227c272f5eba1b3d164be3572bd710d415c', class: "modal-shadow" }), h("div", Object.assign({ key: 'a40472a9065c04ad12fb7b2b2a05e5021f077ad5',
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, [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',
2178
1975
  /*
2179
1976
  role and aria-modal must be used on the
2180
1977
  same element. They must also be set inside the
2181
1978
  shadow DOM otherwise ion-button will not be highlighted
2182
1979
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2183
1980
  */
2184
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'a4973f66587546031d1cc0197e1ba7c2521a3fbc', class: "modal-handle",
1981
+ 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",
2185
1982
  // Prevents the handle from receiving keyboard focus when it does not cycle
2186
- 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: 'ee5e007e3a4e592d98f1b5a5d31ad55e6faa3bdb' }))));
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) })), h("slot", { key: '394370d0ed03ee03152f8f8abae7ff7664ca5c13' }))));
2187
1984
  }
2188
1985
  get el() { return this; }
2189
1986
  static get watchers() { return {
@@ -21,33 +21,44 @@ const FOCUS_KEYS = [
21
21
  ];
22
22
  const startFocusVisible = (rootEl) => {
23
23
  let currentFocus = [];
24
- let keyboardMode = true;
24
+ // Tracks if the last interaction was a pointer event (mouse, touch, pen)
25
+ // Used to distinguish between pointer and keyboard navigation for focus styling
26
+ let hadPointerEvent = false;
25
27
  const ref = rootEl ? rootEl.shadowRoot : document;
26
28
  const root = rootEl ? rootEl : document.body;
29
+ // Adds or removes the focused class for styling
27
30
  const setFocus = (elements) => {
28
31
  currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED));
29
32
  elements.forEach((el) => el.classList.add(ION_FOCUSED));
30
33
  currentFocus = elements;
31
34
  };
32
- const pointerDown = () => {
33
- keyboardMode = false;
34
- setFocus([]);
35
+ // Do not set focus on pointer interactions
36
+ const pointerDown = (ev) => {
37
+ if (ev instanceof PointerEvent && ev.pointerType !== '') {
38
+ hadPointerEvent = true;
39
+ // Reset after the event loop so only the immediate focusin is suppressed
40
+ setTimeout(() => {
41
+ hadPointerEvent = false;
42
+ }, 0);
43
+ }
35
44
  };
45
+ // Clear hadPointerEvent so keyboard navigation shows focus
46
+ // Also, clear focus if the key is not a navigation key
36
47
  const onKeydown = (ev) => {
37
- keyboardMode = FOCUS_KEYS.includes(ev.key);
38
- if (!keyboardMode) {
48
+ hadPointerEvent = false;
49
+ const keyboardEvent = ev;
50
+ if (!FOCUS_KEYS.includes(keyboardEvent.key)) {
39
51
  setFocus([]);
40
52
  }
41
53
  };
54
+ // Set focus if the last interaction was NOT a pointer event
55
+ // This works around iOS/Safari bugs where keydown is not fired for Tab
42
56
  const onFocusin = (ev) => {
43
- if (keyboardMode && ev.composedPath !== undefined) {
44
- const toFocus = ev.composedPath().filter((el) => {
45
- // TODO(FW-2832): type
46
- if (el.classList) {
47
- return el.classList.contains(ION_FOCUSABLE);
48
- }
49
- return false;
50
- });
57
+ const target = ev.target;
58
+ if (target.classList.contains(ION_FOCUSABLE) && !hadPointerEvent) {
59
+ const toFocus = ev
60
+ .composedPath()
61
+ .filter((el) => el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE));
51
62
  setFocus(toFocus);
52
63
  }
53
64
  };
@@ -59,14 +70,12 @@ const startFocusVisible = (rootEl) => {
59
70
  ref.addEventListener('keydown', onKeydown);
60
71
  ref.addEventListener('focusin', onFocusin);
61
72
  ref.addEventListener('focusout', onFocusout);
62
- ref.addEventListener('touchstart', pointerDown, { passive: true });
63
- ref.addEventListener('mousedown', pointerDown);
73
+ ref.addEventListener('pointerdown', pointerDown, { passive: true });
64
74
  const destroy = () => {
65
75
  ref.removeEventListener('keydown', onKeydown);
66
76
  ref.removeEventListener('focusin', onFocusin);
67
77
  ref.removeEventListener('focusout', onFocusout);
68
- ref.removeEventListener('touchstart', pointerDown);
69
- ref.removeEventListener('mousedown', pointerDown);
78
+ ref.removeEventListener('pointerdown', pointerDown);
70
79
  };
71
80
  return {
72
81
  destroy,
@@ -60,7 +60,7 @@ const App = class {
60
60
  if (typeof window !== 'undefined') {
61
61
  Promise.resolve().then(function () { return require('./keyboard-hHzlEQpk.js'); }).then((module) => module.startKeyboardAssist(window));
62
62
  }
63
- Promise.resolve().then(function () { return require('./focus-visible-CCvKiLh3.js'); }).then((module) => (this.focusVisible = module.startFocusVisible()));
63
+ Promise.resolve().then(function () { return require('./focus-visible-ZV7jHMPt.js'); }).then((module) => (this.focusVisible = module.startFocusVisible()));
64
64
  });
65
65
  }
66
66
  }
@@ -4,7 +4,7 @@
4
4
  'use strict';
5
5
 
6
6
  var index = require('./index-DODXXb_r.js');
7
- var focusVisible = require('./focus-visible-CCvKiLh3.js');
7
+ var focusVisible = require('./focus-visible-ZV7jHMPt.js');
8
8
  var helpers = require('./helpers-BITAzJfi.js');
9
9
  var dir = require('./dir-Cn0z1rJH.js');
10
10
  var theme = require('./theme-CeDs6Hcv.js');