@ionic/core 8.5.9-nightly.20250603 → 8.5.9

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.
@@ -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,66 @@ 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 newPosition 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('width');
88
+ cachedFooterEl.style.removeProperty('height');
89
+ cachedFooterEl.style.removeProperty('top');
90
+ cachedFooterEl.style.removeProperty('left');
91
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
92
+ // Move to page
93
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
83
94
  }
84
95
  else {
85
- const pagePadding = footerToShow.clientHeight;
86
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
96
+ // Get both the footer and document body positions
97
+ const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
98
+ const bodyRect = document.body.getBoundingClientRect();
99
+ // Add padding to the parent element to prevent content from being hidden
100
+ // when the footer is positioned absolutely. This has to be done before we
101
+ // make the footer absolutely positioned or we may accidentally cause the
102
+ // sheet to scroll.
103
+ const footerHeight = cachedFooterEl.clientHeight;
104
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
105
+ // Apply positioning styles to keep footer at bottom
106
+ cachedFooterEl.classList.add('modal-footer-moving');
107
+ // Calculate absolute position relative to body
108
+ // We need to subtract the body's offsetTop to get true position within document.body
109
+ const absoluteTop = cachedFooterElRect.top - bodyRect.top;
110
+ const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
111
+ // Capture the footer's current dimensions and hard code them during the drag
112
+ cachedFooterEl.style.setProperty('position', 'absolute');
113
+ cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
114
+ cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
115
+ cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
116
+ cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
117
+ // Also cache the footer Y position, which we use to determine if the
118
+ // sheet has been moved below the footer. When that happens, we need to swap
119
+ // the position back so it will collapse correctly.
120
+ cachedFooterYPosition = absoluteTop;
121
+ // If there's a toolbar, we need to combine the toolbar height with the footer position
122
+ // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
123
+ // we need to account for that.
124
+ const toolbar = baseEl.querySelector('ion-toolbar');
125
+ if (toolbar) {
126
+ cachedFooterYPosition -= toolbar.clientHeight;
127
+ }
128
+ document.body.appendChild(cachedFooterEl);
87
129
  }
88
- footerToHide.style.setProperty('display', 'none');
89
- footerToHide.setAttribute('aria-hidden', 'true');
90
130
  };
91
131
  /**
92
132
  * After the entering animation completes,
@@ -180,12 +220,11 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
180
220
  }
181
221
  /**
182
222
  * 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.
223
+ * the footer position to moving so that it doesn't shake
224
+ * while the sheet is being dragged.
186
225
  */
187
226
  if (!expandToScroll) {
188
- swapFooterVisibility('original');
227
+ swapFooterPosition('moving');
189
228
  }
190
229
  /**
191
230
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -204,6 +243,21 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
204
243
  animation.progressStart(true, 1 - currentBreakpoint);
205
244
  };
206
245
  const onMove = (detail) => {
246
+ /**
247
+ * If `expandToScroll` is disabled, we need to see if we're currently below
248
+ * the footer element and the footer is in a stationary position. If so,
249
+ * we need to make the stationary the original position so that the footer
250
+ * collapses with the sheet.
251
+ */
252
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
253
+ // Check if we need to swap the footer position
254
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
255
+ swapFooterPosition('stationary');
256
+ }
257
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
258
+ swapFooterPosition('moving');
259
+ }
260
+ }
207
261
  /**
208
262
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
209
263
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -337,14 +391,6 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
337
391
  * snapping animation completes.
338
392
  */
339
393
  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
394
  if (shouldPreventDismiss) {
349
395
  handleCanDismiss(baseEl, animation);
350
396
  }
@@ -361,10 +407,28 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
361
407
  if (contentEl && (snapToBreakpoint === breakpoints[breakpoints.length - 1] || !expandToScroll)) {
362
408
  contentEl.scrollY = true;
363
409
  }
410
+ /**
411
+ * If expandToScroll is disabled and we're animating
412
+ * to close the sheet, we need to swap
413
+ * the footer position to stationary so that it
414
+ * will collapse correctly. We cannot just always swap
415
+ * here or it'll be jittery while animating movement.
416
+ */
417
+ if (!expandToScroll && snapToBreakpoint === 0) {
418
+ swapFooterPosition('stationary');
419
+ }
364
420
  return new Promise((resolve) => {
365
421
  animation
366
422
  .onFinish(() => {
367
423
  if (shouldRemainOpen) {
424
+ /**
425
+ * If expandToScroll is disabled, we need to swap
426
+ * the footer position to stationary so that it
427
+ * will act as it would by default.
428
+ */
429
+ if (!expandToScroll) {
430
+ swapFooterPosition('stationary');
431
+ }
368
432
  /**
369
433
  * Once the snapping animation completes,
370
434
  * we need to reset the animation to go
@@ -331,14 +331,4 @@ ion-backdrop {
331
331
  border-start-end-radius: var(--border-radius);
332
332
  border-end-end-radius: 0;
333
333
  border-end-start-radius: 0;
334
- }
335
-
336
- /**
337
- * Sheet modals require an additional padding as mentioned in the
338
- * `core.scss` file. However, there's a workaround that requires
339
- * a cloned footer to be added to the modal. This is only necessary
340
- * because the core styles are not being applied to the cloned footer.
341
- */
342
- :host(.modal-sheet.modal-no-expand-scroll) ion-footer ion-toolbar:first-of-type {
343
- padding-top: 6px;
344
334
  }
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-06-03T06:10:18",
2
+ "timestamp": "2025-06-04T13:41:42",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.20.0",
@@ -584,47 +584,7 @@ const iosEnterAnimation = (baseEl, opts) => {
584
584
  .addElement(baseEl)
585
585
  .easing('cubic-bezier(0.32,0.72,0,1)')
586
586
  .duration(500)
587
- .addAnimation([wrapperAnimation])
588
- .beforeAddWrite(() => {
589
- if (expandToScroll) {
590
- // Scroll can only be done when the modal is fully expanded.
591
- return;
592
- }
593
- /**
594
- * There are some browsers that causes flickering when
595
- * dragging the content when scroll is enabled at every
596
- * breakpoint. This is due to the wrapper element being
597
- * transformed off the screen and having a snap animation.
598
- *
599
- * A workaround is to clone the footer element and append
600
- * it outside of the wrapper element. This way, the footer
601
- * is still visible and the drag can be done without
602
- * flickering. The original footer is hidden until the modal
603
- * is dismissed. This maintains the animation of the footer
604
- * when the modal is dismissed.
605
- *
606
- * The workaround needs to be done before the animation starts
607
- * so there are no flickering issues.
608
- */
609
- const ionFooter = baseEl.querySelector('ion-footer');
610
- /**
611
- * This check is needed to prevent more than one footer
612
- * from being appended to the shadow root.
613
- * Otherwise, iOS and MD enter animations would append
614
- * the footer twice.
615
- */
616
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
617
- if (ionFooter && !ionFooterAlreadyAppended) {
618
- const footerHeight = ionFooter.clientHeight;
619
- const clonedFooter = ionFooter.cloneNode(true);
620
- baseEl.shadowRoot.appendChild(clonedFooter);
621
- ionFooter.style.setProperty('display', 'none');
622
- ionFooter.setAttribute('aria-hidden', 'true');
623
- // Padding is added to prevent some content from being hidden.
624
- const page = baseEl.querySelector('.ion-page');
625
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
626
- }
627
- });
587
+ .addAnimation([wrapperAnimation]);
628
588
  if (contentAnimation) {
629
589
  baseAnimation.addAnimation(contentAnimation);
630
590
  }
@@ -705,7 +665,7 @@ const createLeaveAnimation$1 = () => {
705
665
  * iOS Modal Leave Animation
706
666
  */
707
667
  const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
708
- const { presentingEl, currentBreakpoint, expandToScroll } = opts;
668
+ const { presentingEl, currentBreakpoint } = opts;
709
669
  const root = getElementRoot(baseEl);
710
670
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation$1();
711
671
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -714,29 +674,7 @@ const iosLeaveAnimation = (baseEl, opts, duration = 500) => {
714
674
  .addElement(baseEl)
715
675
  .easing('cubic-bezier(0.32,0.72,0,1)')
716
676
  .duration(duration)
717
- .addAnimation(wrapperAnimation)
718
- .beforeAddWrite(() => {
719
- if (expandToScroll) {
720
- // Scroll can only be done when the modal is fully expanded.
721
- return;
722
- }
723
- /**
724
- * If expandToScroll is disabled, we need to swap
725
- * the visibility to the original, so the footer
726
- * dismisses with the modal and doesn't stay
727
- * until the modal is removed from the DOM.
728
- */
729
- const ionFooter = baseEl.querySelector('ion-footer');
730
- if (ionFooter) {
731
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
732
- ionFooter.style.removeProperty('display');
733
- ionFooter.removeAttribute('aria-hidden');
734
- clonedFooter.style.setProperty('display', 'none');
735
- clonedFooter.setAttribute('aria-hidden', 'true');
736
- const page = baseEl.querySelector('.ion-page');
737
- page.style.removeProperty('padding-bottom');
738
- }
739
- });
677
+ .addAnimation(wrapperAnimation);
740
678
  if (presentingEl) {
741
679
  const isMobile = window.innerWidth < 768;
742
680
  const hasCardModal = presentingEl.tagName === 'ION-MODAL' && presentingEl.presentingElement !== undefined;
@@ -827,52 +765,12 @@ const mdEnterAnimation = (baseEl, opts) => {
827
765
  wrapperAnimation.addElement(root.querySelector('.modal-wrapper'));
828
766
  // The content animation is only added if scrolling is enabled for
829
767
  // all the breakpoints.
830
- expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
768
+ !expandToScroll && (contentAnimation === null || contentAnimation === void 0 ? void 0 : contentAnimation.addElement(baseEl.querySelector('.ion-page')));
831
769
  const baseAnimation = createAnimation()
832
770
  .addElement(baseEl)
833
771
  .easing('cubic-bezier(0.36,0.66,0.04,1)')
834
772
  .duration(280)
835
- .addAnimation([backdropAnimation, wrapperAnimation])
836
- .beforeAddWrite(() => {
837
- if (expandToScroll) {
838
- // Scroll can only be done when the modal is fully expanded.
839
- return;
840
- }
841
- /**
842
- * There are some browsers that causes flickering when
843
- * dragging the content when scroll is enabled at every
844
- * breakpoint. This is due to the wrapper element being
845
- * transformed off the screen and having a snap animation.
846
- *
847
- * A workaround is to clone the footer element and append
848
- * it outside of the wrapper element. This way, the footer
849
- * is still visible and the drag can be done without
850
- * flickering. The original footer is hidden until the modal
851
- * is dismissed. This maintains the animation of the footer
852
- * when the modal is dismissed.
853
- *
854
- * The workaround needs to be done before the animation starts
855
- * so there are no flickering issues.
856
- */
857
- const ionFooter = baseEl.querySelector('ion-footer');
858
- /**
859
- * This check is needed to prevent more than one footer
860
- * from being appended to the shadow root.
861
- * Otherwise, iOS and MD enter animations would append
862
- * the footer twice.
863
- */
864
- const ionFooterAlreadyAppended = baseEl.shadowRoot.querySelector('ion-footer');
865
- if (ionFooter && !ionFooterAlreadyAppended) {
866
- const footerHeight = ionFooter.clientHeight;
867
- const clonedFooter = ionFooter.cloneNode(true);
868
- baseEl.shadowRoot.appendChild(clonedFooter);
869
- ionFooter.style.setProperty('display', 'none');
870
- ionFooter.setAttribute('aria-hidden', 'true');
871
- // Padding is added to prevent some content from being hidden.
872
- const page = baseEl.querySelector('.ion-page');
873
- page.style.setProperty('padding-bottom', `${footerHeight}px`);
874
- }
875
- });
773
+ .addAnimation([backdropAnimation, wrapperAnimation]);
876
774
  if (contentAnimation) {
877
775
  baseAnimation.addAnimation(contentAnimation);
878
776
  }
@@ -891,7 +789,7 @@ const createLeaveAnimation = () => {
891
789
  * Md Modal Leave Animation
892
790
  */
893
791
  const mdLeaveAnimation = (baseEl, opts) => {
894
- const { currentBreakpoint, expandToScroll } = opts;
792
+ const { currentBreakpoint } = opts;
895
793
  const root = getElementRoot(baseEl);
896
794
  const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
897
795
  backdropAnimation.addElement(root.querySelector('ion-backdrop'));
@@ -899,29 +797,7 @@ const mdLeaveAnimation = (baseEl, opts) => {
899
797
  const baseAnimation = createAnimation()
900
798
  .easing('cubic-bezier(0.47,0,0.745,0.715)')
901
799
  .duration(200)
902
- .addAnimation([backdropAnimation, wrapperAnimation])
903
- .beforeAddWrite(() => {
904
- if (expandToScroll) {
905
- // Scroll can only be done when the modal is fully expanded.
906
- return;
907
- }
908
- /**
909
- * If expandToScroll is disabled, we need to swap
910
- * the visibility to the original, so the footer
911
- * dismisses with the modal and doesn't stay
912
- * until the modal is removed from the DOM.
913
- */
914
- const ionFooter = baseEl.querySelector('ion-footer');
915
- if (ionFooter) {
916
- const clonedFooter = baseEl.shadowRoot.querySelector('ion-footer');
917
- ionFooter.style.removeProperty('display');
918
- ionFooter.removeAttribute('aria-hidden');
919
- clonedFooter.style.setProperty('display', 'none');
920
- clonedFooter.setAttribute('aria-hidden', 'true');
921
- const page = baseEl.querySelector('.ion-page');
922
- page.style.removeProperty('padding-bottom');
923
- }
924
- });
800
+ .addAnimation([backdropAnimation, wrapperAnimation]);
925
801
  return baseAnimation;
926
802
  };
927
803
 
@@ -953,6 +829,9 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
953
829
  let offset = 0;
954
830
  let canDismissBlocksGesture = false;
955
831
  let cachedScrollEl = null;
832
+ let cachedFooterEl = null;
833
+ let cachedFooterYPosition = null;
834
+ let currentFooterState = null;
956
835
  const canDismissMaxStep = 0.95;
957
836
  const maxBreakpoint = breakpoints[breakpoints.length - 1];
958
837
  const minBreakpoint = breakpoints[0];
@@ -982,29 +861,66 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
982
861
  baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
983
862
  };
984
863
  /**
985
- * Toggles the visible modal footer when `expandToScroll` is disabled.
986
- * @param footer The footer to show.
864
+ * Toggles the footer to an absolute position while moving to prevent
865
+ * it from shaking while the sheet is being dragged.
866
+ * @param newPosition Whether the footer is in a moving or stationary position.
987
867
  */
988
- const swapFooterVisibility = (footer) => {
989
- const originalFooter = baseEl.querySelector('ion-footer');
990
- if (!originalFooter) {
991
- return;
868
+ const swapFooterPosition = (newPosition) => {
869
+ if (!cachedFooterEl) {
870
+ cachedFooterEl = baseEl.querySelector('ion-footer');
871
+ if (!cachedFooterEl) {
872
+ return;
873
+ }
992
874
  }
993
- const clonedFooter = wrapperEl.nextElementSibling;
994
- const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
995
- const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
996
- footerToShow.style.removeProperty('display');
997
- footerToShow.removeAttribute('aria-hidden');
998
875
  const page = baseEl.querySelector('.ion-page');
999
- if (footer === 'original') {
1000
- page.style.removeProperty('padding-bottom');
876
+ currentFooterState = newPosition;
877
+ if (newPosition === 'stationary') {
878
+ // Reset positioning styles to allow normal document flow
879
+ cachedFooterEl.classList.remove('modal-footer-moving');
880
+ cachedFooterEl.style.removeProperty('position');
881
+ cachedFooterEl.style.removeProperty('width');
882
+ cachedFooterEl.style.removeProperty('height');
883
+ cachedFooterEl.style.removeProperty('top');
884
+ cachedFooterEl.style.removeProperty('left');
885
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
886
+ // Move to page
887
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
1001
888
  }
1002
889
  else {
1003
- const pagePadding = footerToShow.clientHeight;
1004
- page.style.setProperty('padding-bottom', `${pagePadding}px`);
890
+ // Get both the footer and document body positions
891
+ const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
892
+ const bodyRect = document.body.getBoundingClientRect();
893
+ // Add padding to the parent element to prevent content from being hidden
894
+ // when the footer is positioned absolutely. This has to be done before we
895
+ // make the footer absolutely positioned or we may accidentally cause the
896
+ // sheet to scroll.
897
+ const footerHeight = cachedFooterEl.clientHeight;
898
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
899
+ // Apply positioning styles to keep footer at bottom
900
+ cachedFooterEl.classList.add('modal-footer-moving');
901
+ // Calculate absolute position relative to body
902
+ // We need to subtract the body's offsetTop to get true position within document.body
903
+ const absoluteTop = cachedFooterElRect.top - bodyRect.top;
904
+ const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
905
+ // Capture the footer's current dimensions and hard code them during the drag
906
+ cachedFooterEl.style.setProperty('position', 'absolute');
907
+ cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
908
+ cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
909
+ cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
910
+ cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
911
+ // Also cache the footer Y position, which we use to determine if the
912
+ // sheet has been moved below the footer. When that happens, we need to swap
913
+ // the position back so it will collapse correctly.
914
+ cachedFooterYPosition = absoluteTop;
915
+ // If there's a toolbar, we need to combine the toolbar height with the footer position
916
+ // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
917
+ // we need to account for that.
918
+ const toolbar = baseEl.querySelector('ion-toolbar');
919
+ if (toolbar) {
920
+ cachedFooterYPosition -= toolbar.clientHeight;
921
+ }
922
+ document.body.appendChild(cachedFooterEl);
1005
923
  }
1006
- footerToHide.style.setProperty('display', 'none');
1007
- footerToHide.setAttribute('aria-hidden', 'true');
1008
924
  };
1009
925
  /**
1010
926
  * After the entering animation completes,
@@ -1098,12 +1014,11 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1098
1014
  }
1099
1015
  /**
1100
1016
  * If expandToScroll is disabled, we need to swap
1101
- * the footer visibility to the original, so if the modal
1102
- * is dismissed, the footer dismisses with the modal
1103
- * and doesn't stay on the screen after the modal is gone.
1017
+ * the footer position to moving so that it doesn't shake
1018
+ * while the sheet is being dragged.
1104
1019
  */
1105
1020
  if (!expandToScroll) {
1106
- swapFooterVisibility('original');
1021
+ swapFooterPosition('moving');
1107
1022
  }
1108
1023
  /**
1109
1024
  * If we are pulling down, then it is possible we are pulling on the content.
@@ -1122,6 +1037,21 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1122
1037
  animation.progressStart(true, 1 - currentBreakpoint);
1123
1038
  };
1124
1039
  const onMove = (detail) => {
1040
+ /**
1041
+ * If `expandToScroll` is disabled, we need to see if we're currently below
1042
+ * the footer element and the footer is in a stationary position. If so,
1043
+ * we need to make the stationary the original position so that the footer
1044
+ * collapses with the sheet.
1045
+ */
1046
+ if (!expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null) {
1047
+ // Check if we need to swap the footer position
1048
+ if (detail.currentY >= cachedFooterYPosition && currentFooterState === 'moving') {
1049
+ swapFooterPosition('stationary');
1050
+ }
1051
+ else if (detail.currentY < cachedFooterYPosition && currentFooterState === 'stationary') {
1052
+ swapFooterPosition('moving');
1053
+ }
1054
+ }
1125
1055
  /**
1126
1056
  * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
1127
1057
  * the scrollable content, we should not allow the swipe gesture to continue.
@@ -1255,14 +1185,6 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1255
1185
  * snapping animation completes.
1256
1186
  */
1257
1187
  gesture.enable(false);
1258
- /**
1259
- * If expandToScroll is disabled, we need to swap
1260
- * the footer visibility to the cloned one so the footer
1261
- * doesn't flicker when the sheet's height is animated.
1262
- */
1263
- if (!expandToScroll && shouldRemainOpen) {
1264
- swapFooterVisibility('cloned');
1265
- }
1266
1188
  if (shouldPreventDismiss) {
1267
1189
  handleCanDismiss(baseEl, animation);
1268
1190
  }
@@ -1279,10 +1201,28 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1279
1201
  if (contentEl && (snapToBreakpoint === breakpoints[breakpoints.length - 1] || !expandToScroll)) {
1280
1202
  contentEl.scrollY = true;
1281
1203
  }
1204
+ /**
1205
+ * If expandToScroll is disabled and we're animating
1206
+ * to close the sheet, we need to swap
1207
+ * the footer position to stationary so that it
1208
+ * will collapse correctly. We cannot just always swap
1209
+ * here or it'll be jittery while animating movement.
1210
+ */
1211
+ if (!expandToScroll && snapToBreakpoint === 0) {
1212
+ swapFooterPosition('stationary');
1213
+ }
1282
1214
  return new Promise((resolve) => {
1283
1215
  animation
1284
1216
  .onFinish(() => {
1285
1217
  if (shouldRemainOpen) {
1218
+ /**
1219
+ * If expandToScroll is disabled, we need to swap
1220
+ * the footer position to stationary so that it
1221
+ * will act as it would by default.
1222
+ */
1223
+ if (!expandToScroll) {
1224
+ swapFooterPosition('stationary');
1225
+ }
1286
1226
  /**
1287
1227
  * Once the snapping animation completes,
1288
1228
  * we need to reset the animation to go
@@ -1347,7 +1287,7 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1347
1287
  };
1348
1288
  };
1349
1289
 
1350
- const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer ion-toolbar:first-of-type{padding-top:6px}";
1290
+ const modalIosCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.4)}:host(.modal-card),:host(.modal-sheet){--border-radius:10px}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:10px}}.modal-wrapper{-webkit-transform:translate3d(0, 100%, 0);transform:translate3d(0, 100%, 0)}@media screen and (max-width: 767px){@supports (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - max(30px, var(--ion-safe-area-top)) - 10px)}}@supports not (width: max(0px, 1px)){:host(.modal-card){--height:calc(100% - 40px)}}:host(.modal-card) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}:host(.modal-card){--backdrop-opacity:0;--width:100%;-ms-flex-align:end;align-items:flex-end}:host(.modal-card) .modal-shadow{display:none}:host(.modal-card) ion-backdrop{pointer-events:none}}@media screen and (min-width: 768px){:host(.modal-card){--width:calc(100% - 120px);--height:calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));--max-width:720px;--max-height:1000px;--backdrop-opacity:0;--box-shadow:0px 0px 30px 10px rgba(0, 0, 0, 0.1);-webkit-transition:all 0.5s ease-in-out;transition:all 0.5s ease-in-out}:host(.modal-card) .modal-wrapper{-webkit-box-shadow:none;box-shadow:none}:host(.modal-card) .modal-shadow{-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow)}}:host(.modal-sheet) .modal-wrapper{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius);border-end-end-radius:0;border-end-start-radius:0}";
1351
1291
  const IonModalIosStyle0 = modalIosCss;
1352
1292
 
1353
1293
  const modalMdCss = ":host{--width:100%;--min-width:auto;--max-width:auto;--height:100%;--min-height:auto;--max-height:auto;--overflow:hidden;--border-radius:0;--border-width:0;--border-style:none;--border-color:transparent;--background:var(--ion-background-color, #fff);--box-shadow:none;--backdrop-opacity:0;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:absolute;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);contain:strict}.modal-wrapper,ion-backdrop{pointer-events:auto}:host(.overlay-hidden){display:none}.modal-wrapper,.modal-shadow{border-radius:var(--border-radius);width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:var(--overflow);z-index:10}.modal-shadow{position:absolute;background:transparent}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--width:600px;--height:500px;--ion-safe-area-top:0px;--ion-safe-area-bottom:0px;--ion-safe-area-right:0px;--ion-safe-area-left:0px}}@media only screen and (min-width: 768px) and (min-height: 768px){:host{--width:600px;--height:600px}}.modal-handle{left:0px;right:0px;top:5px;border-radius:8px;-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;position:absolute;width:36px;height:5px;-webkit-transform:translateZ(0);transform:translateZ(0);border:0;background:var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));cursor:pointer;z-index:11}.modal-handle::before{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;position:absolute;width:36px;height:5px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);content:\"\"}:host(.modal-sheet){--height:calc(100% - (var(--ion-safe-area-top) + 10px))}:host(.modal-sheet) .modal-wrapper,:host(.modal-sheet) .modal-shadow{position:absolute;bottom:0}:host(.modal-sheet.modal-no-expand-scroll) ion-footer{position:absolute;bottom:0;width:var(--width)}:host{--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}@media only screen and (min-width: 768px) and (min-height: 600px){:host{--border-radius:2px;--box-shadow:0 28px 48px rgba(0, 0, 0, 0.4)}}.modal-wrapper{-webkit-transform:translate3d(0, 40px, 0);transform:translate3d(0, 40px, 0);opacity:0.01}";