@@ -84,6 +84,8 @@ export const createSheetGesture = (
8484 let offset = 0 ;
8585 let canDismissBlocksGesture = false ;
8686 let cachedScrollEl : HTMLElement | null = null ;
87+ let cachedFooterYPosition : number | null = null ;
88+ let currentFooterState : 'moving' | 'stationary' | null = null ;
8789 const canDismissMaxStep = 0.95 ;
8890 const maxBreakpoint = breakpoints [ breakpoints . length - 1 ] ;
8991 const minBreakpoint = breakpoints [ 0 ] ;
@@ -118,33 +120,39 @@ export const createSheetGesture = (
118120 } ;
119121
120122 /**
121- * Toggles the visible modal footer when `expandToScroll` is disabled.
122- * @param footer The footer to show.
123+ * Toggles the footer to an absolute position while moving to prevent
124+ * it from shaking while the sheet is being dragged.
125+ * @param footer Whether the footer is in a moving or stationary position.
123126 */
124- const swapFooterVisibility = ( footer : 'original ' | 'cloned ' ) => {
127+ const swapFooterPosition = ( newPosition : 'moving ' | 'stationary ' ) => {
125128 const originalFooter = baseEl . querySelector ( 'ion-footer' ) as HTMLIonFooterElement | null ;
126-
127129 if ( ! originalFooter ) {
128130 return ;
129131 }
130132
131- // const clonedFooter = wrapperEl.nextElementSibling as HTMLIonFooterElement;
132- // const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
133- // const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
134-
135- // footerToShow.style.removeProperty('display');
136- // footerToShow.removeAttribute('aria-hidden');
137-
138- // const page = baseEl.querySelector('.ion-page') as HTMLElement;
139- // if (footer === 'original') {
140- // page.style.removeProperty('padding-bottom');
141- // } else {
142- // const pagePadding = footerToShow.clientHeight;
143- // page.style.setProperty('padding-bottom', `${pagePadding}px`);
144- // }
145-
146- // footerToHide.style.setProperty('display', 'none');
147- // footerToHide.setAttribute('aria-hidden', 'true');
133+ currentFooterState = newPosition ;
134+ if ( newPosition === 'stationary' ) {
135+ // Reset positioning styles to allow normal document flow
136+ originalFooter . style . removeProperty ( 'position' ) ;
137+ originalFooter . style . removeProperty ( 'bottom' ) ;
138+ originalFooter . parentElement ?. style . removeProperty ( 'padding-bottom' ) ;
139+ } else {
140+ // Add padding to the parent element to prevent content from being hidden
141+ // when the footer is positioned absolutely. This has to be done before we
142+ // make the footer absolutely positioned or we may accidentally cause the
143+ // sheet to scroll.
144+ const footerHeight = originalFooter . clientHeight ;
145+ originalFooter . parentElement ?. style . setProperty ( 'padding-bottom' , `${ footerHeight } px` ) ;
146+
147+ // Apply positioning styles to keep footer at bottom
148+ originalFooter . style . setProperty ( 'position' , 'absolute' ) ;
149+ originalFooter . style . setProperty ( 'bottom' , '0' ) ;
150+
151+ // Also cache the footer Y position, which we use to determine if the
152+ // sheet has been moved below the footer. When that happens, we need to swap
153+ // the position back so it will collapse correctly.
154+ cachedFooterYPosition = originalFooter . getBoundingClientRect ( ) . top + window . scrollY ;
155+ }
148156 } ;
149157
150158 /**
@@ -247,12 +255,11 @@ export const createSheetGesture = (
247255
248256 /**
249257 * If expandToScroll is disabled, we need to swap
250- * the footer visibility to the original, so if the modal
251- * is dismissed, the footer dismisses with the modal
252- * and doesn't stay on the screen after the modal is gone.
258+ * the footer position to moving so that it doesn't shake
259+ * while the sheet is being dragged.
253260 */
254261 if ( ! expandToScroll ) {
255- swapFooterVisibility ( 'original ') ;
262+ swapFooterPosition ( 'moving ') ;
256263 }
257264
258265 /**
@@ -275,6 +282,21 @@ export const createSheetGesture = (
275282 } ;
276283
277284 const onMove = ( detail : GestureDetail ) => {
285+ /**
286+ * If `expandToScroll` is disabled, we need to see if we're currently below
287+ * the footer element and the footer is in a stationary position. If so,
288+ * we need to make the stationary the original position so that the footer
289+ * collapses with the sheet.
290+ */
291+ if ( ! expandToScroll && cachedFooterYPosition !== null && currentFooterState !== null ) {
292+ // Check if we need to swap the footer position
293+ if ( detail . currentY >= cachedFooterYPosition && currentFooterState === 'moving' ) {
294+ swapFooterPosition ( 'stationary' ) ;
295+ } else if ( detail . currentY < cachedFooterYPosition && currentFooterState === 'stationary' ) {
296+ swapFooterPosition ( 'moving' ) ;
297+ }
298+ }
299+
278300 /**
279301 * If `expandToScroll` is disabled, and an upwards swipe gesture is done within
280302 * the scrollable content, we should not allow the swipe gesture to continue.
@@ -431,15 +453,6 @@ export const createSheetGesture = (
431453 */
432454 gesture . enable ( false ) ;
433455
434- /**
435- * If expandToScroll is disabled, we need to swap
436- * the footer visibility to the cloned one so the footer
437- * doesn't flicker when the sheet's height is animated.
438- */
439- if ( ! expandToScroll && shouldRemainOpen ) {
440- swapFooterVisibility ( 'cloned' ) ;
441- }
442-
443456 if ( shouldPreventDismiss ) {
444457 handleCanDismiss ( baseEl , animation ) ;
445458 } else if ( ! shouldRemainOpen ) {
@@ -462,6 +475,15 @@ export const createSheetGesture = (
462475 . onFinish (
463476 ( ) => {
464477 if ( shouldRemainOpen ) {
478+ /**
479+ * If expandToScroll is disabled, we need to swap
480+ * the footer position to stationary so that it
481+ * will act as it would by default
482+ */
483+ if ( ! expandToScroll ) {
484+ swapFooterPosition ( 'stationary' ) ;
485+ }
486+
465487 /**
466488 * Once the snapping animation completes,
467489 * we need to reset the animation to go
0 commit comments