@ionic/core 8.5.7 → 8.5.8-dev.11748364689.107665ad

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.
@@ -582,47 +582,7 @@ const iosEnterAnimation = (baseEl, opts) => {
582
582
  .addElement(baseEl)
583
583
  .easing('cubic-bezier(0.32,0.72,0,1)')
584
584
  .duration(500)
585
- .addAnimation([wrapperAnimation])
586
- .beforeAddWrite(() => {
587
- if (expandToScroll) {
588
- // Scroll can only be done when the modal is fully expanded.
589
- return;
590
- }
591
- /**
592
- * There are some browsers that causes flickering when
593
- * dragging the content when scroll is enabled at every
594
- * breakpoint. This is due to the wrapper element being
595
- * transformed off the screen and having a snap animation.
596
- *
597
- * A workaround is to clone the footer element and append
598
- * it outside of the wrapper element. This way, the footer
599
- * is still visible and the drag can be done without
600
- * flickering. The original footer is hidden until the modal
601
- * is dismissed. This maintains the animation of the footer
602
- * when the modal is dismissed.
603
- *
604
- * The workaround needs to be done before the animation starts
605
- * so there are no flickering issues.
606
- */
607
- const ionFooter = baseEl.querySelector('ion-footer');
608
- /**
609
- * This check is needed to prevent more than one footer
610
- * from being appended to the shadow root.
611
- * Otherwise, iOS and MD enter animations would append
612
- * the footer twice.
613
- */
614
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
615
- if (ionFooter && !ionFooterAlreadyAppended) {
616
- const footerHeight = ionFooter.clientHeight;
617
- const clonedFooter = ionFooter.cloneNode(true);
618
- baseEl.shadowRoot.appendChild(clonedFooter);
619
- ionFooter.style.setProperty('display', 'none');
620
- ionFooter.setAttribute('aria-hidden', 'true');
621
- // Padding is added to prevent some content from being hidden.
622
- const page = baseEl.querySelector('.ion-page');
623
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
624
- }
625
- });
585
+ .addAnimation([wrapperAnimation]);
626
586
  if (contentAnimation) {
627
587
  baseAnimation.addAnimation(contentAnimation);
628
588
  }
@@ -703,7 +663,7 @@ const createLeaveAnimation$1 = () => {
703
663
  * iOS Modal Leave Animation
704
664
  */
705
665
  const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
706
- const { presentingEl, currentBreakpoint, expandToScroll } = opts;
666
+ const { presentingEl, currentBreakpoint } = opts;
707
667
  const root = getElementRoot(baseEl);
708
668
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
709
669
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -712,29 +672,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
712
672
  .addElement(baseEl)
713
673
  .easing('cubic-bezier(0.32,0.72,0,1)')
714
674
  .duration(duration)
715
- .addAnimation(wrapperAnimation)
716
- .beforeAddWrite(() => {
717
- if (expandToScroll) {
718
- // Scroll can only be done when the modal is fully expanded.
719
- return;
720
- }
721
- /**
722
- * If expandToScroll is disabled, we need to swap
723
- * the visibility to the original, so the footer
724
- * dismisses with the modal and doesn't stay
725
- * until the modal is removed from the DOM.
726
- */
727
- const ionFooter = baseEl.querySelector('ion-footer');
728
- if (ionFooter) {
729
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
730
- ionFooter.style.removeProperty('display');
731
- ionFooter.removeAttribute('aria-hidden');
732
- clonedFooter.style.setProperty('display', 'none');
733
- clonedFooter.setAttribute('aria-hidden', 'true');
734
- const page = baseEl.querySelector('.ion-page');
735
- page.style.removeProperty('padding-bottom');
736
- }
737
- });
675
+ .addAnimation(wrapperAnimation);
738
676
  if (presentingEl) {
739
677
  const isMobile = window.innerWidth < 768;
740
678
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
@@ -830,47 +768,7 @@ const mdEnterAnimation = (baseEl, opts) => {
830
768
  .addElement(baseEl)
831
769
  .easing('cubic-bezier(0.36,0.66,0.04,1)')
832
770
  .duration(280)
833
- .addAnimation([backdropAnimation, wrapperAnimation])
834
- .beforeAddWrite(() => {
835
- if (expandToScroll) {
836
- // Scroll can only be done when the modal is fully expanded.
837
- return;
838
- }
839
- /**
840
- * There are some browsers that causes flickering when
841
- * dragging the content when scroll is enabled at every
842
- * breakpoint. This is due to the wrapper element being
843
- * transformed off the screen and having a snap animation.
844
- *
845
- * A workaround is to clone the footer element and append
846
- * it outside of the wrapper element. This way, the footer
847
- * is still visible and the drag can be done without
848
- * flickering. The original footer is hidden until the modal
849
- * is dismissed. This maintains the animation of the footer
850
- * when the modal is dismissed.
851
- *
852
- * The workaround needs to be done before the animation starts
853
- * so there are no flickering issues.
854
- */
855
- const ionFooter = baseEl.querySelector('ion-footer');
856
- /**
857
- * This check is needed to prevent more than one footer
858
- * from being appended to the shadow root.
859
- * Otherwise, iOS and MD enter animations would append
860
- * the footer twice.
861
- */
862
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
863
- if (ionFooter && !ionFooterAlreadyAppended) {
864
- const footerHeight = ionFooter.clientHeight;
865
- const clonedFooter = ionFooter.cloneNode(true);
866
- baseEl.shadowRoot.appendChild(clonedFooter);
867
- ionFooter.style.setProperty('display', 'none');
868
- ionFooter.setAttribute('aria-hidden', 'true');
869
- // Padding is added to prevent some content from being hidden.
870
- const page = baseEl.querySelector('.ion-page');
871
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
872
- }
873
- });
771
+ .addAnimation([backdropAnimation, wrapperAnimation]);
874
772
  if (contentAnimation) {
875
773
  baseAnimation.addAnimation(contentAnimation);
876
774
  }
@@ -889,7 +787,7 @@ const createLeaveAnimation = () => {
889
787
  * Md Modal Leave Animation
890
788
  */
891
789
  const mdLeaveAnimation = (baseEl, opts) => {
892
- const { currentBreakpoint, expandToScroll } = opts;
790
+ const { currentBreakpoint } = opts;
893
791
  const root = getElementRoot(baseEl);
894
792
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
895
793
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -897,29 +795,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
897
795
  const baseAnimation = createAnimation()
898
796
  .easing('cubic-bezier(0.47,0,0.745,0.715)')
899
797
  .duration(200)
900
- .addAnimation([backdropAnimation, wrapperAnimation])
901
- .beforeAddWrite(() => {
902
- if (expandToScroll) {
903
- // Scroll can only be done when the modal is fully expanded.
904
- return;
905
- }
906
- /**
907
- * If expandToScroll is disabled, we need to swap
908
- * the visibility to the original, so the footer
909
- * dismisses with the modal and doesn't stay
910
- * until the modal is removed from the DOM.
911
- */
912
- const ionFooter = baseEl.querySelector('ion-footer');
913
- if (ionFooter) {
914
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
915
- ionFooter.style.removeProperty('display');
916
- ionFooter.removeAttribute('aria-hidden');
917
- clonedFooter.style.setProperty('display', 'none');
918
- clonedFooter.setAttribute('aria-hidden', 'true');
919
- const page = baseEl.querySelector('.ion-page');
920
- page.style.removeProperty('padding-bottom');
921
- }
922
- });
798
+ .addAnimation([backdropAnimation, wrapperAnimation]);
923
799
  return baseAnimation;
924
800
  };
925
801
 
@@ -951,6 +827,8 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
951
827
  let offset = 0;
952
828
  let canDismissBlocksGesture = false;
953
829
  let cachedScrollEl = null;
830
+ let cachedFooterYPosition = null;
831
+ let currentFooterState = null;
954
832
  const canDismissMaxStep = 0.95;
955
833
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
956
834
  const minBreakpoint = breakpoints[0];
@@ -980,29 +858,38 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
980
858
  baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
981
859
  };
982
860
  /**
983
- * Toggles the visible modal footer when `expandToScroll` is disabled.
984
- * @param footer The footer to show.
861
+ * Toggles the footer to an absolute position while moving to prevent
862
+ * it from shaking while the sheet is being dragged.
863
+ * @param footer Whether the footer is in a moving or stationary position.
985
864
  */
986
- const swapFooterVisibility = (footer) => {
865
+ const swapFooterPosition = (newPosition) => {
866
+ var _a, _b;
987
867
  const originalFooter = baseEl.querySelector('ion-footer');
988
868
  if (!originalFooter) {
989
869
  return;
990
870
  }
991
- const clonedFooter = wrapperEl.nextElementSibling;
992
- const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
993
- const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
994
- footerToShow.style.removeProperty('display');
995
- footerToShow.removeAttribute('aria-hidden');
996
- const page = baseEl.querySelector('.ion-page');
997
- if (footer === 'original') {
998
- page.style.removeProperty('padding-bottom');
871
+ currentFooterState = newPosition;
872
+ if (newPosition === 'stationary') {
873
+ // Reset positioning styles to allow normal document flow
874
+ originalFooter.style.removeProperty('position');
875
+ originalFooter.style.removeProperty('bottom');
876
+ (_a = originalFooter.parentElement) === null || _a === void 0 ? void 0 : _a.style.removeProperty('padding-bottom');
999
877
  }
1000
878
  else {
1001
- const pagePadding = footerToShow.clientHeight;
1002
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
879
+ // Add padding to the parent element to prevent content from being hidden
880
+ // when the footer is positioned absolutely. This has to be done before we
881
+ // make the footer absolutely positioned or we may accidentally cause the
882
+ // sheet to scroll.
883
+ const footerHeight = originalFooter.clientHeight;
884
+ (_b = originalFooter.parentElement) === null || _b === void 0 ? void 0 : _b.style.setProperty('padding-bottom', `${footerHeight}px`);
885
+ // Apply positioning styles to keep footer at bottom
886
+ originalFooter.style.setProperty('position', 'absolute');
887
+ originalFooter.style.setProperty('bottom', '0');
888
+ // Also cache the footer Y position, which we use to determine if the
889
+ // sheet has been moved below the footer. When that happens, we need to swap
890
+ // the position back so it will collapse correctly.
891
+ cachedFooterYPosition = originalFooter.getBoundingClientRect().top + window.scrollY;
1003
892
  }
1004
- footerToHide.style.setProperty('display', 'none');
1005
- footerToHide.setAttribute('aria-hidden', 'true');
1006
893
  };
1007
894
  /**
1008
895
  * After the entering animation completes,
@@ -1096,12 +983,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1096
983
  }
1097
984
  /**
1098
985
  * If expandToScroll is disabled, we need to swap
1099
- * the footer visibility to the original, so if the modal
1100
- * is dismissed, the footer dismisses with the modal
1101
- * and doesn't stay on the screen after the modal is gone.
986
+ * the footer position to moving so that it doesn't shake
987
+ * while the sheet is being dragged.
1102
988
  */
1103
989
  if (!expandToScroll) {
1104
- swapFooterVisibility('original');
990
+ swapFooterPosition('moving');
1105
991
  }
1106
992
  /**
1107
993
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -1120,6 +1006,21 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1120
1006
  animation.progressStart(true, 1 - currentBreakpoint);
1121
1007
  };
1122
1008
  const onMove = (detail) => {
1009
+ /**
1010
+ * If `expandToScroll` is disabled, we need to see if we're currently below
1011
+ * the footer element and the footer is in a stationary position. If so,
1012
+ * we need to make the stationary the original position so that the footer
1013
+ * collapses with the sheet.
1014
+ */
1015
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
1016
+ // Check if we need to swap the footer position
1017
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
1018
+ swapFooterPosition('stationary');
1019
+ }
1020
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
1021
+ swapFooterPosition('moving');
1022
+ }
1023
+ }
1123
1024
  /**
1124
1025
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
1125
1026
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -1253,14 +1154,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1253
1154
  * snapping animation completes.
1254
1155
  */
1255
1156
  gesture.enable(false);
1256
- /**
1257
- * If expandToScroll is disabled, we need to swap
1258
- * the footer visibility to the cloned one so the footer
1259
- * doesn't flicker when the sheet's height is animated.
1260
- */
1261
- if (!expandToScroll && shouldRemainOpen) {
1262
- swapFooterVisibility('cloned');
1263
- }
1264
1157
  if (shouldPreventDismiss) {
1265
1158
  handleCanDismiss(baseEl, animation);
1266
1159
  }
@@ -1281,6 +1174,14 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1281
1174
  animation
1282
1175
  .onFinish(() => {
1283
1176
  if (shouldRemainOpen) {
1177
+ /**
1178
+ * If expandToScroll is disabled, we need to swap
1179
+ * the footer position to stationary so that it
1180
+ * will act as it would by default
1181
+ */
1182
+ if (!expandToScroll) {
1183
+ swapFooterPosition('stationary');
1184
+ }
1284
1185
  /**
1285
1186
  * Once the snapping animation completes,
1286
1187
  * we need to reset the animation to go
@@ -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,8 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
957
833
  let offset = 0;
958
834
  let canDismissBlocksGesture = false;
959
835
  let cachedScrollEl = null;
836
+ let cachedFooterYPosition = null;
837
+ let currentFooterState = null;
960
838
  const canDismissMaxStep = 0.95;
961
839
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
962
840
  const minBreakpoint = breakpoints[0];
@@ -986,29 +864,38 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
986
864
  baseEl.classList.add(overlays.FOCUS_TRAP_DISABLE_CLASS);
987
865
  };
988
866
  /**
989
- * Toggles the visible modal footer when `expandToScroll` is disabled.
990
- * @param footer The footer to show.
867
+ * Toggles the footer to an absolute position while moving to prevent
868
+ * it from shaking while the sheet is being dragged.
869
+ * @param footer Whether the footer is in a moving or stationary position.
991
870
  */
992
- const swapFooterVisibility = (footer) => {
871
+ const swapFooterPosition = (newPosition) => {
872
+ var _a, _b;
993
873
  const originalFooter = baseEl.querySelector('ion-footer');
994
874
  if (!originalFooter) {
995
875
  return;
996
876
  }
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
- const page = baseEl.querySelector('.ion-page');
1003
- if (footer === 'original') {
1004
- page.style.removeProperty('padding-bottom');
877
+ currentFooterState = newPosition;
878
+ if (newPosition === 'stationary') {
879
+ // Reset positioning styles to allow normal document flow
880
+ originalFooter.style.removeProperty('position');
881
+ originalFooter.style.removeProperty('bottom');
882
+ (_a = originalFooter.parentElement) === null || _a === void 0 ? void 0 : _a.style.removeProperty('padding-bottom');
1005
883
  }
1006
884
  else {
1007
- const pagePadding = footerToShow.clientHeight;
1008
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
885
+ // Add padding to the parent element to prevent content from being hidden
886
+ // when the footer is positioned absolutely. This has to be done before we
887
+ // make the footer absolutely positioned or we may accidentally cause the
888
+ // sheet to scroll.
889
+ const footerHeight = originalFooter.clientHeight;
890
+ (_b = originalFooter.parentElement) === null || _b === void 0 ? void 0 : _b.style.setProperty('padding-bottom', `${footerHeight}px`);
891
+ // Apply positioning styles to keep footer at bottom
892
+ originalFooter.style.setProperty('position', 'absolute');
893
+ originalFooter.style.setProperty('bottom', '0');
894
+ // Also cache the footer Y position, which we use to determine if the
895
+ // sheet has been moved below the footer. When that happens, we need to swap
896
+ // the position back so it will collapse correctly.
897
+ cachedFooterYPosition = originalFooter.getBoundingClientRect().top + window.scrollY;
1009
898
  }
1010
- footerToHide.style.setProperty('display', 'none');
1011
- footerToHide.setAttribute('aria-hidden', 'true');
1012
899
  };
1013
900
  /**
1014
901
  * After the entering animation completes,
@@ -1102,12 +989,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1102
989
  }
1103
990
  /**
1104
991
  * 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.
992
+ * the footer position to moving so that it doesn't shake
993
+ * while the sheet is being dragged.
1108
994
  */
1109
995
  if (!expandToScroll) {
1110
- swapFooterVisibility('original');
996
+ swapFooterPosition('moving');
1111
997
  }
1112
998
  /**
1113
999
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -1126,6 +1012,21 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1126
1012
  animation.progressStart(true, 1 - currentBreakpoint);
1127
1013
  };
1128
1014
  const onMove = (detail) => {
1015
+ /**
1016
+ * If `expandToScroll` is disabled, we need to see if we're currently below
1017
+ * the footer element and the footer is in a stationary position. If so,
1018
+ * we need to make the stationary the original position so that the footer
1019
+ * collapses with the sheet.
1020
+ */
1021
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
1022
+ // Check if we need to swap the footer position
1023
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
1024
+ swapFooterPosition('stationary');
1025
+ }
1026
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
1027
+ swapFooterPosition('moving');
1028
+ }
1029
+ }
1129
1030
  /**
1130
1031
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
1131
1032
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -1259,14 +1160,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1259
1160
  * snapping animation completes.
1260
1161
  */
1261
1162
  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
1163
  if (shouldPreventDismiss) {
1271
1164
  handleCanDismiss(baseEl, animation);
1272
1165
  }
@@ -1287,6 +1180,14 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1287
1180
  animation
1288
1181
  .onFinish(() => {
1289
1182
  if (shouldRemainOpen) {
1183
+ /**
1184
+ * If expandToScroll is disabled, we need to swap
1185
+ * the footer position to stationary so that it
1186
+ * will act as it would by default
1187
+ */
1188
+ if (!expandToScroll) {
1189
+ swapFooterPosition('stationary');
1190
+ }
1290
1191
  /**
1291
1192
  * Once the snapping animation completes,
1292
1193
  * 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
  }