@ionic/core 8.5.7 → 8.5.8-dev.11748374591.17686118

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.
@@ -588,47 +588,7 @@ const iosEnterAnimation = (baseEl, opts) => {
588
588
  .addElement(baseEl)
589
589
  .easing('cubic-bezier(0.32,0.72,0,1)')
590
590
  .duration(500)
591
- .addAnimation([wrapperAnimation])
592
- .beforeAddWrite(() => {
593
- if (expandToScroll) {
594
- // Scroll can only be done when the modal is fully expanded.
595
- return;
596
- }
597
- /**
598
- * There are some browsers that causes flickering when
599
- * dragging the content when scroll is enabled at every
600
- * breakpoint. This is due to the wrapper element being
601
- * transformed off the screen and having a snap animation.
602
- *
603
- * A workaround is to clone the footer element and append
604
- * it outside of the wrapper element. This way, the footer
605
- * is still visible and the drag can be done without
606
- * flickering. The original footer is hidden until the modal
607
- * is dismissed. This maintains the animation of the footer
608
- * when the modal is dismissed.
609
- *
610
- * The workaround needs to be done before the animation starts
611
- * so there are no flickering issues.
612
- */
613
- const ionFooter = baseEl.querySelector('ion-footer');
614
- /**
615
- * This check is needed to prevent more than one footer
616
- * from being appended to the shadow root.
617
- * Otherwise, iOS and MD enter animations would append
618
- * the footer twice.
619
- */
620
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
621
- if (ionFooter && !ionFooterAlreadyAppended) {
622
- const footerHeight = ionFooter.clientHeight;
623
- const clonedFooter = ionFooter.cloneNode(true);
624
- baseEl.shadowRoot.appendChild(clonedFooter);
625
- ionFooter.style.setProperty('display', 'none');
626
- ionFooter.setAttribute('aria-hidden', 'true');
627
- // Padding is added to prevent some content from being hidden.
628
- const page = baseEl.querySelector('.ion-page');
629
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
630
- }
631
- });
591
+ .addAnimation([wrapperAnimation]);
632
592
  if (contentAnimation) {
633
593
  baseAnimation.addAnimation(contentAnimation);
634
594
  }
@@ -709,7 +669,7 @@ const createLeaveAnimation$1 = () => {
709
669
  * iOS Modal Leave Animation
710
670
  */
711
671
  const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
712
- const { presentingEl, currentBreakpoint, expandToScroll } = opts;
672
+ const { presentingEl, currentBreakpoint } = opts;
713
673
  const root = helpers.getElementRoot(baseEl);
714
674
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
715
675
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -718,29 +678,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
718
678
  .addElement(baseEl)
719
679
  .easing('cubic-bezier(0.32,0.72,0,1)')
720
680
  .duration(duration)
721
- .addAnimation(wrapperAnimation)
722
- .beforeAddWrite(() => {
723
- if (expandToScroll) {
724
- // Scroll can only be done when the modal is fully expanded.
725
- return;
726
- }
727
- /**
728
- * If expandToScroll is disabled, we need to swap
729
- * the visibility to the original, so the footer
730
- * dismisses with the modal and doesn't stay
731
- * until the modal is removed from the DOM.
732
- */
733
- const ionFooter = baseEl.querySelector('ion-footer');
734
- if (ionFooter) {
735
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
736
- ionFooter.style.removeProperty('display');
737
- ionFooter.removeAttribute('aria-hidden');
738
- clonedFooter.style.setProperty('display', 'none');
739
- clonedFooter.setAttribute('aria-hidden', 'true');
740
- const page = baseEl.querySelector('.ion-page');
741
- page.style.removeProperty('padding-bottom');
742
- }
743
- });
681
+ .addAnimation(wrapperAnimation);
744
682
  if (presentingEl) {
745
683
  const isMobile = window.innerWidth < 768;
746
684
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
@@ -836,47 +774,7 @@ const mdEnterAnimation = (baseEl, opts) => {
836
774
  .addElement(baseEl)
837
775
  .easing('cubic-bezier(0.36,0.66,0.04,1)')
838
776
  .duration(280)
839
- .addAnimation([backdropAnimation, wrapperAnimation])
840
- .beforeAddWrite(() => {
841
- if (expandToScroll) {
842
- // Scroll can only be done when the modal is fully expanded.
843
- return;
844
- }
845
- /**
846
- * There are some browsers that causes flickering when
847
- * dragging the content when scroll is enabled at every
848
- * breakpoint. This is due to the wrapper element being
849
- * transformed off the screen and having a snap animation.
850
- *
851
- * A workaround is to clone the footer element and append
852
- * it outside of the wrapper element. This way, the footer
853
- * is still visible and the drag can be done without
854
- * flickering. The original footer is hidden until the modal
855
- * is dismissed. This maintains the animation of the footer
856
- * when the modal is dismissed.
857
- *
858
- * The workaround needs to be done before the animation starts
859
- * so there are no flickering issues.
860
- */
861
- const ionFooter = baseEl.querySelector('ion-footer');
862
- /**
863
- * This check is needed to prevent more than one footer
864
- * from being appended to the shadow root.
865
- * Otherwise, iOS and MD enter animations would append
866
- * the footer twice.
867
- */
868
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
869
- if (ionFooter && !ionFooterAlreadyAppended) {
870
- const footerHeight = ionFooter.clientHeight;
871
- const clonedFooter = ionFooter.cloneNode(true);
872
- baseEl.shadowRoot.appendChild(clonedFooter);
873
- ionFooter.style.setProperty('display', 'none');
874
- ionFooter.setAttribute('aria-hidden', 'true');
875
- // Padding is added to prevent some content from being hidden.
876
- const page = baseEl.querySelector('.ion-page');
877
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
878
- }
879
- });
777
+ .addAnimation([backdropAnimation, wrapperAnimation]);
880
778
  if (contentAnimation) {
881
779
  baseAnimation.addAnimation(contentAnimation);
882
780
  }
@@ -895,7 +793,7 @@ const createLeaveAnimation = () => {
895
793
  * Md Modal Leave Animation
896
794
  */
897
795
  const mdLeaveAnimation = (baseEl, opts) => {
898
- const { currentBreakpoint, expandToScroll } = opts;
796
+ const { currentBreakpoint } = opts;
899
797
  const root = helpers.getElementRoot(baseEl);
900
798
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
901
799
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -903,29 +801,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
903
801
  const baseAnimation = animation.createAnimation()
904
802
  .easing('cubic-bezier(0.47,0,0.745,0.715)')
905
803
  .duration(200)
906
- .addAnimation([backdropAnimation, wrapperAnimation])
907
- .beforeAddWrite(() => {
908
- if (expandToScroll) {
909
- // Scroll can only be done when the modal is fully expanded.
910
- return;
911
- }
912
- /**
913
- * If expandToScroll is disabled, we need to swap
914
- * the visibility to the original, so the footer
915
- * dismisses with the modal and doesn't stay
916
- * until the modal is removed from the DOM.
917
- */
918
- const ionFooter = baseEl.querySelector('ion-footer');
919
- if (ionFooter) {
920
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
921
- ionFooter.style.removeProperty('display');
922
- ionFooter.removeAttribute('aria-hidden');
923
- clonedFooter.style.setProperty('display', 'none');
924
- clonedFooter.setAttribute('aria-hidden', 'true');
925
- const page = baseEl.querySelector('.ion-page');
926
- page.style.removeProperty('padding-bottom');
927
- }
928
- });
804
+ .addAnimation([backdropAnimation, wrapperAnimation]);
929
805
  return baseAnimation;
930
806
  };
931
807
 
@@ -957,6 +833,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
957
833
  let offset = 0;
958
834
  let canDismissBlocksGesture = false;
959
835
  let cachedScrollEl = null;
836
+ let cachedFooterEl = null;
837
+ let cachedFooterYPosition = null;
838
+ let currentFooterState = null;
960
839
  const canDismissMaxStep = 0.95;
961
840
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
962
841
  const minBreakpoint = breakpoints[0];
@@ -986,29 +865,45 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
986
865
  baseEl.classList.add(overlays.FOCUS_TRAP_DISABLE_CLASS);
987
866
  };
988
867
  /**
989
- * Toggles the visible modal footer when `expandToScroll` is disabled.
990
- * @param footer The footer to show.
868
+ * Toggles the footer to an absolute position while moving to prevent
869
+ * it from shaking while the sheet is being dragged.
870
+ * @param footer Whether the footer is in a moving or stationary position.
991
871
  */
992
- const swapFooterVisibility = (footer) => {
993
- const originalFooter = baseEl.querySelector('ion-footer');
994
- if (!originalFooter) {
995
- return;
872
+ const swapFooterPosition = (newPosition) => {
873
+ if (!cachedFooterEl) {
874
+ cachedFooterEl = baseEl.querySelector('ion-footer');
875
+ if (!cachedFooterEl) {
876
+ return;
877
+ }
996
878
  }
997
- const clonedFooter = wrapperEl.nextElementSibling;
998
- const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
999
- const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
1000
- footerToShow.style.removeProperty('display');
1001
- footerToShow.removeAttribute('aria-hidden');
1002
879
  const page = baseEl.querySelector('.ion-page');
1003
- if (footer === 'original') {
1004
- page.style.removeProperty('padding-bottom');
880
+ currentFooterState = newPosition;
881
+ if (newPosition === 'stationary') {
882
+ // Reset positioning styles to allow normal document flow
883
+ cachedFooterEl.classList.remove('modal-footer-moving');
884
+ cachedFooterEl.style.removeProperty('position');
885
+ cachedFooterEl.style.removeProperty('bottom');
886
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
887
+ // Move to page
888
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
1005
889
  }
1006
890
  else {
1007
- const pagePadding = footerToShow.clientHeight;
1008
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
891
+ // Add padding to the parent element to prevent content from being hidden
892
+ // when the footer is positioned absolutely. This has to be done before we
893
+ // make the footer absolutely positioned or we may accidentally cause the
894
+ // sheet to scroll.
895
+ const footerHeight = cachedFooterEl.clientHeight;
896
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
897
+ // Apply positioning styles to keep footer at bottom
898
+ cachedFooterEl.classList.add('modal-footer-moving');
899
+ cachedFooterEl.style.setProperty('position', 'absolute');
900
+ cachedFooterEl.style.setProperty('bottom', '0');
901
+ // Also cache the footer Y position, which we use to determine if the
902
+ // sheet has been moved below the footer. When that happens, we need to swap
903
+ // the position back so it will collapse correctly.
904
+ cachedFooterYPosition = cachedFooterEl.getBoundingClientRect().top + window.scrollY;
905
+ document.body.appendChild(cachedFooterEl);
1009
906
  }
1010
- footerToHide.style.setProperty('display', 'none');
1011
- footerToHide.setAttribute('aria-hidden', 'true');
1012
907
  };
1013
908
  /**
1014
909
  * After the entering animation completes,
@@ -1102,12 +997,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1102
997
  }
1103
998
  /**
1104
999
  * If expandToScroll is disabled, we need to swap
1105
- * the footer visibility to the original, so if the modal
1106
- * is dismissed, the footer dismisses with the modal
1107
- * and doesn't stay on the screen after the modal is gone.
1000
+ * the footer position to moving so that it doesn't shake
1001
+ * while the sheet is being dragged.
1108
1002
  */
1109
1003
  if (!expandToScroll) {
1110
- swapFooterVisibility('original');
1004
+ swapFooterPosition('moving');
1111
1005
  }
1112
1006
  /**
1113
1007
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -1126,6 +1020,21 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1126
1020
  animation.progressStart(true, 1 - currentBreakpoint);
1127
1021
  };
1128
1022
  const onMove = (detail) => {
1023
+ /**
1024
+ * If `expandToScroll` is disabled, we need to see if we're currently below
1025
+ * the footer element and the footer is in a stationary position. If so,
1026
+ * we need to make the stationary the original position so that the footer
1027
+ * collapses with the sheet.
1028
+ */
1029
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
1030
+ // Check if we need to swap the footer position
1031
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
1032
+ swapFooterPosition('stationary');
1033
+ }
1034
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
1035
+ swapFooterPosition('moving');
1036
+ }
1037
+ }
1129
1038
  /**
1130
1039
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
1131
1040
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -1259,14 +1168,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1259
1168
  * snapping animation completes.
1260
1169
  */
1261
1170
  gesture.enable(false);
1262
- /**
1263
- * If expandToScroll is disabled, we need to swap
1264
- * the footer visibility to the cloned one so the footer
1265
- * doesn't flicker when the sheet's height is animated.
1266
- */
1267
- if (!expandToScroll && shouldRemainOpen) {
1268
- swapFooterVisibility('cloned');
1269
- }
1270
1171
  if (shouldPreventDismiss) {
1271
1172
  handleCanDismiss(baseEl, animation);
1272
1173
  }
@@ -1287,6 +1188,14 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1287
1188
  animation
1288
1189
  .onFinish(() => {
1289
1190
  if (shouldRemainOpen) {
1191
+ /**
1192
+ * If expandToScroll is disabled, we need to swap
1193
+ * the footer position to stationary so that it
1194
+ * will act as it would by default
1195
+ */
1196
+ if (!expandToScroll) {
1197
+ swapFooterPosition('stationary');
1198
+ }
1290
1199
  /**
1291
1200
  * Once the snapping animation completes,
1292
1201
  * we need to reset the animation to go
@@ -31,47 +31,7 @@ export const iosEnterAnimation = (baseEl, opts) => {
31
31
  .addElement(baseEl)
32
32
  .easing('cubic-bezier(0.32,0.72,0,1)')
33
33
  .duration(500)
34
- .addAnimation([wrapperAnimation])
35
- .beforeAddWrite(() => {
36
- if (expandToScroll) {
37
- // Scroll can only be done when the modal is fully expanded.
38
- return;
39
- }
40
- /**
41
- * There are some browsers that causes flickering when
42
- * dragging the content when scroll is enabled at every
43
- * breakpoint. This is due to the wrapper element being
44
- * transformed off the screen and having a snap animation.
45
- *
46
- * A workaround is to clone the footer element and append
47
- * it outside of the wrapper element. This way, the footer
48
- * is still visible and the drag can be done without
49
- * flickering. The original footer is hidden until the modal
50
- * is dismissed. This maintains the animation of the footer
51
- * when the modal is dismissed.
52
- *
53
- * The workaround needs to be done before the animation starts
54
- * so there are no flickering issues.
55
- */
56
- const ionFooter = baseEl.querySelector('ion-footer');
57
- /**
58
- * This check is needed to prevent more than one footer
59
- * from being appended to the shadow root.
60
- * Otherwise, iOS and MD enter animations would append
61
- * the footer twice.
62
- */
63
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
64
- if (ionFooter && !ionFooterAlreadyAppended) {
65
- const footerHeight = ionFooter.clientHeight;
66
- const clonedFooter = ionFooter.cloneNode(true);
67
- baseEl.shadowRoot.appendChild(clonedFooter);
68
- ionFooter.style.setProperty('display', 'none');
69
- ionFooter.setAttribute('aria-hidden', 'true');
70
- // Padding is added to prevent some content from being hidden.
71
- const page = baseEl.querySelector('.ion-page');
72
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
73
- }
74
- });
34
+ .addAnimation([wrapperAnimation]);
75
35
  if (contentAnimation) {
76
36
  baseAnimation.addAnimation(contentAnimation);
77
37
  }
@@ -14,7 +14,7 @@ const createLeaveAnimation = () => {
14
14
  * iOS Modal Leave Animation
15
15
  */
16
16
  export const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
17
- const { presentingEl, currentBreakpoint, expandToScroll } = opts;
17
+ const { presentingEl, currentBreakpoint } = opts;
18
18
  const root = getElementRoot(baseEl);
19
19
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
20
20
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -23,29 +23,7 @@ export const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
23
23
  .addElement(baseEl)
24
24
  .easing('cubic-bezier(0.32,0.72,0,1)')
25
25
  .duration(duration)
26
- .addAnimation(wrapperAnimation)
27
- .beforeAddWrite(() => {
28
- if (expandToScroll) {
29
- // Scroll can only be done when the modal is fully expanded.
30
- return;
31
- }
32
- /**
33
- * If expandToScroll is disabled, we need to swap
34
- * the visibility to the original, so the footer
35
- * dismisses with the modal and doesn't stay
36
- * until the modal is removed from the DOM.
37
- */
38
- const ionFooter = baseEl.querySelector('ion-footer');
39
- if (ionFooter) {
40
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
41
- ionFooter.style.removeProperty('display');
42
- ionFooter.removeAttribute('aria-hidden');
43
- clonedFooter.style.setProperty('display', 'none');
44
- clonedFooter.setAttribute('aria-hidden', 'true');
45
- const page = baseEl.querySelector('.ion-page');
46
- page.style.removeProperty('padding-bottom');
47
- }
48
- });
26
+ .addAnimation(wrapperAnimation);
49
27
  if (presentingEl) {
50
28
  const isMobile = window.innerWidth < 768;
51
29
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
@@ -33,47 +33,7 @@ export const mdEnterAnimation = (baseEl, opts) => {
33
33
  .addElement(baseEl)
34
34
  .easing('cubic-bezier(0.36,0.66,0.04,1)')
35
35
  .duration(280)
36
- .addAnimation([backdropAnimation, wrapperAnimation])
37
- .beforeAddWrite(() => {
38
- if (expandToScroll) {
39
- // Scroll can only be done when the modal is fully expanded.
40
- return;
41
- }
42
- /**
43
- * There are some browsers that causes flickering when
44
- * dragging the content when scroll is enabled at every
45
- * breakpoint. This is due to the wrapper element being
46
- * transformed off the screen and having a snap animation.
47
- *
48
- * A workaround is to clone the footer element and append
49
- * it outside of the wrapper element. This way, the footer
50
- * is still visible and the drag can be done without
51
- * flickering. The original footer is hidden until the modal
52
- * is dismissed. This maintains the animation of the footer
53
- * when the modal is dismissed.
54
- *
55
- * The workaround needs to be done before the animation starts
56
- * so there are no flickering issues.
57
- */
58
- const ionFooter = baseEl.querySelector('ion-footer');
59
- /**
60
- * This check is needed to prevent more than one footer
61
- * from being appended to the shadow root.
62
- * Otherwise, iOS and MD enter animations would append
63
- * the footer twice.
64
- */
65
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
66
- if (ionFooter && !ionFooterAlreadyAppended) {
67
- const footerHeight = ionFooter.clientHeight;
68
- const clonedFooter = ionFooter.cloneNode(true);
69
- baseEl.shadowRoot.appendChild(clonedFooter);
70
- ionFooter.style.setProperty('display', 'none');
71
- ionFooter.setAttribute('aria-hidden', 'true');
72
- // Padding is added to prevent some content from being hidden.
73
- const page = baseEl.querySelector('.ion-page');
74
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
75
- }
76
- });
36
+ .addAnimation([backdropAnimation, wrapperAnimation]);
77
37
  if (contentAnimation) {
78
38
  baseAnimation.addAnimation(contentAnimation);
79
39
  }
@@ -16,7 +16,7 @@ const createLeaveAnimation = () => {
16
16
  * Md Modal Leave Animation
17
17
  */
18
18
  export const mdLeaveAnimation = (baseEl, opts) => {
19
- const { currentBreakpoint, expandToScroll } = opts;
19
+ const { currentBreakpoint } = opts;
20
20
  const root = getElementRoot(baseEl);
21
21
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
22
22
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -24,28 +24,6 @@ export const mdLeaveAnimation = (baseEl, opts) => {
24
24
  const baseAnimation = createAnimation()
25
25
  .easing('cubic-bezier(0.47,0,0.745,0.715)')
26
26
  .duration(200)
27
- .addAnimation([backdropAnimation, wrapperAnimation])
28
- .beforeAddWrite(() => {
29
- if (expandToScroll) {
30
- // Scroll can only be done when the modal is fully expanded.
31
- return;
32
- }
33
- /**
34
- * If expandToScroll is disabled, we need to swap
35
- * the visibility to the original, so the footer
36
- * dismisses with the modal and doesn't stay
37
- * until the modal is removed from the DOM.
38
- */
39
- const ionFooter = baseEl.querySelector('ion-footer');
40
- if (ionFooter) {
41
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
42
- ionFooter.style.removeProperty('display');
43
- ionFooter.removeAttribute('aria-hidden');
44
- clonedFooter.style.setProperty('display', 'none');
45
- clonedFooter.setAttribute('aria-hidden', 'true');
46
- const page = baseEl.querySelector('.ion-page');
47
- page.style.removeProperty('padding-bottom');
48
- }
49
- });
27
+ .addAnimation([backdropAnimation, wrapperAnimation]);
50
28
  return baseAnimation;
51
29
  };
@@ -35,6 +35,9 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
35
35
  let offset = 0;
36
36
  let canDismissBlocksGesture = false;
37
37
  let cachedScrollEl = null;
38
+ let cachedFooterEl = null;
39
+ let cachedFooterYPosition = null;
40
+ let currentFooterState = null;
38
41
  const canDismissMaxStep = 0.95;
39
42
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
40
43
  const minBreakpoint = breakpoints[0];
@@ -64,29 +67,45 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
64
67
  baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
65
68
  };
66
69
  /**
67
- * Toggles the visible modal footer when `expandToScroll` is disabled.
68
- * @param footer The footer to show.
70
+ * Toggles the footer to an absolute position while moving to prevent
71
+ * it from shaking while the sheet is being dragged.
72
+ * @param footer Whether the footer is in a moving or stationary position.
69
73
  */
70
- const swapFooterVisibility = (footer) => {
71
- const originalFooter = baseEl.querySelector('ion-footer');
72
- if (!originalFooter) {
73
- return;
74
+ const swapFooterPosition = (newPosition) => {
75
+ if (!cachedFooterEl) {
76
+ cachedFooterEl = baseEl.querySelector('ion-footer');
77
+ if (!cachedFooterEl) {
78
+ return;
79
+ }
74
80
  }
75
- const clonedFooter = wrapperEl.nextElementSibling;
76
- const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
77
- const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
78
- footerToShow.style.removeProperty('display');
79
- footerToShow.removeAttribute('aria-hidden');
80
81
  const page = baseEl.querySelector('.ion-page');
81
- if (footer === 'original') {
82
- page.style.removeProperty('padding-bottom');
82
+ currentFooterState = newPosition;
83
+ if (newPosition === 'stationary') {
84
+ // Reset positioning styles to allow normal document flow
85
+ cachedFooterEl.classList.remove('modal-footer-moving');
86
+ cachedFooterEl.style.removeProperty('position');
87
+ cachedFooterEl.style.removeProperty('bottom');
88
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
89
+ // Move to page
90
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
83
91
  }
84
92
  else {
85
- const pagePadding = footerToShow.clientHeight;
86
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
93
+ // Add padding to the parent element to prevent content from being hidden
94
+ // when the footer is positioned absolutely. This has to be done before we
95
+ // make the footer absolutely positioned or we may accidentally cause the
96
+ // sheet to scroll.
97
+ const footerHeight = cachedFooterEl.clientHeight;
98
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
99
+ // Apply positioning styles to keep footer at bottom
100
+ cachedFooterEl.classList.add('modal-footer-moving');
101
+ cachedFooterEl.style.setProperty('position', 'absolute');
102
+ cachedFooterEl.style.setProperty('bottom', '0');
103
+ // Also cache the footer Y position, which we use to determine if the
104
+ // sheet has been moved below the footer. When that happens, we need to swap
105
+ // the position back so it will collapse correctly.
106
+ cachedFooterYPosition = cachedFooterEl.getBoundingClientRect().top + window.scrollY;
107
+ document.body.appendChild(cachedFooterEl);
87
108
  }
88
- footerToHide.style.setProperty('display', 'none');
89
- footerToHide.setAttribute('aria-hidden', 'true');
90
109
  };
91
110
  /**
92
111
  * After the entering animation completes,
@@ -180,12 +199,11 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
180
199
  }
181
200
  /**
182
201
  * If expandToScroll is disabled, we need to swap
183
- * the footer visibility to the original, so if the modal
184
- * is dismissed, the footer dismisses with the modal
185
- * and doesn't stay on the screen after the modal is gone.
202
+ * the footer position to moving so that it doesn't shake
203
+ * while the sheet is being dragged.
186
204
  */
187
205
  if (!expandToScroll) {
188
- swapFooterVisibility('original');
206
+ swapFooterPosition('moving');
189
207
  }
190
208
  /**
191
209
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -204,6 +222,21 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
204
222
  animation.progressStart(true, 1 - currentBreakpoint);
205
223
  };
206
224
  const onMove = (detail) => {
225
+ /**
226
+ * If `expandToScroll` is disabled, we need to see if we're currently below
227
+ * the footer element and the footer is in a stationary position. If so,
228
+ * we need to make the stationary the original position so that the footer
229
+ * collapses with the sheet.
230
+ */
231
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
232
+ // Check if we need to swap the footer position
233
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
234
+ swapFooterPosition('stationary');
235
+ }
236
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
237
+ swapFooterPosition('moving');
238
+ }
239
+ }
207
240
  /**
208
241
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
209
242
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -337,14 +370,6 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
337
370
  * snapping animation completes.
338
371
  */
339
372
  gesture.enable(false);
340
- /**
341
- * If expandToScroll is disabled, we need to swap
342
- * the footer visibility to the cloned one so the footer
343
- * doesn't flicker when the sheet's height is animated.
344
- */
345
- if (!expandToScroll && shouldRemainOpen) {
346
- swapFooterVisibility('cloned');
347
- }
348
373
  if (shouldPreventDismiss) {
349
374
  handleCanDismiss(baseEl, animation);
350
375
  }
@@ -365,6 +390,14 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
365
390
  animation
366
391
  .onFinish(() => {
367
392
  if (shouldRemainOpen) {
393
+ /**
394
+ * If expandToScroll is disabled, we need to swap
395
+ * the footer position to stationary so that it
396
+ * will act as it would by default
397
+ */
398
+ if (!expandToScroll) {
399
+ swapFooterPosition('stationary');
400
+ }
368
401
  /**
369
402
  * Once the snapping animation completes,
370
403
  * we need to reset the animation to go
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-05-07T20:08:46",
2
+ "timestamp": "2025-05-27T19:38:24",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.20.0",