Skip to content

Commit 07665ad

Browse files
committed
feat(sheet): changing the way sheet modal dragging works so the footer
is not duplicated, but the position is changed instead
1 parent b8c9d87 commit 07665ad

File tree

6 files changed

+68
-237
lines changed

6 files changed

+68
-237
lines changed

core/src/components/modal/animations/ios.enter.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -42,49 +42,6 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
4242
.easing('cubic-bezier(0.32,0.72,0,1)')
4343
.duration(500)
4444
.addAnimation([wrapperAnimation]);
45-
// .beforeAddWrite(() => {
46-
// if (expandToScroll) {
47-
// // Scroll can only be done when the modal is fully expanded.
48-
// return;
49-
// }
50-
51-
// /**
52-
// * There are some browsers that causes flickering when
53-
// * dragging the content when scroll is enabled at every
54-
// * breakpoint. This is due to the wrapper element being
55-
// * transformed off the screen and having a snap animation.
56-
// *
57-
// * A workaround is to clone the footer element and append
58-
// * it outside of the wrapper element. This way, the footer
59-
// * is still visible and the drag can be done without
60-
// * flickering. The original footer is hidden until the modal
61-
// * is dismissed. This maintains the animation of the footer
62-
// * when the modal is dismissed.
63-
// *
64-
// * The workaround needs to be done before the animation starts
65-
// * so there are no flickering issues.
66-
// */
67-
// const ionFooter = baseEl.querySelector('ion-footer');
68-
// /**
69-
// * This check is needed to prevent more than one footer
70-
// * from being appended to the shadow root.
71-
// * Otherwise, iOS and MD enter animations would append
72-
// * the footer twice.
73-
// */
74-
// const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
75-
// if (ionFooter && !ionFooterAlreadyAppended) {
76-
// const footerHeight = ionFooter.clientHeight;
77-
// const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
78-
79-
// baseEl.shadowRoot!.appendChild(clonedFooter);
80-
// ionFooter.style.setProperty('display', 'none');
81-
// ionFooter.setAttribute('aria-hidden', 'true');
82-
83-
// // Padding is added to prevent some content from being hidden.
84-
// const page = baseEl.querySelector('.ion-page') as HTMLElement;
85-
// page.style.setProperty('padding-bottom', `${footerHeight}px`);
86-
// }
87-
// });
8845

8946
if (contentAnimation) {
9047
baseAnimation.addAnimation(contentAnimation);

core/src/components/modal/animations/ios.leave.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const createLeaveAnimation = () => {
1919
* iOS Modal Leave Animation
2020
*/
2121
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
22-
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
22+
const { presentingEl, currentBreakpoint } = opts;
2323
const root = getElementRoot(baseEl);
2424
const { wrapperAnimation, backdropAnimation } =
2525
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
@@ -33,32 +33,6 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
3333
.easing('cubic-bezier(0.32,0.72,0,1)')
3434
.duration(duration)
3535
.addAnimation(wrapperAnimation);
36-
// .beforeAddWrite(() => {
37-
// if (expandToScroll) {
38-
// // Scroll can only be done when the modal is fully expanded.
39-
// return;
40-
// }
41-
42-
// /**
43-
// * If expandToScroll is disabled, we need to swap
44-
// * the visibility to the original, so the footer
45-
// * dismisses with the modal and doesn't stay
46-
// * until the modal is removed from the DOM.
47-
// */
48-
// const ionFooter = baseEl.querySelector('ion-footer');
49-
// if (ionFooter) {
50-
// const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
51-
52-
// ionFooter.style.removeProperty('display');
53-
// ionFooter.removeAttribute('aria-hidden');
54-
55-
// clonedFooter.style.setProperty('display', 'none');
56-
// clonedFooter.setAttribute('aria-hidden', 'true');
57-
58-
// const page = baseEl.querySelector('.ion-page') as HTMLElement;
59-
// page.style.removeProperty('padding-bottom');
60-
// }
61-
// });
6236

6337
if (presentingEl) {
6438
const isMobile = window.innerWidth < 768;

core/src/components/modal/animations/md.enter.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,49 +44,6 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOption
4444
.easing('cubic-bezier(0.36,0.66,0.04,1)')
4545
.duration(280)
4646
.addAnimation([backdropAnimation, wrapperAnimation]);
47-
// .beforeAddWrite(() => {
48-
// if (expandToScroll) {
49-
// // Scroll can only be done when the modal is fully expanded.
50-
// return;
51-
// }
52-
53-
// /**
54-
// * There are some browsers that causes flickering when
55-
// * dragging the content when scroll is enabled at every
56-
// * breakpoint. This is due to the wrapper element being
57-
// * transformed off the screen and having a snap animation.
58-
// *
59-
// * A workaround is to clone the footer element and append
60-
// * it outside of the wrapper element. This way, the footer
61-
// * is still visible and the drag can be done without
62-
// * flickering. The original footer is hidden until the modal
63-
// * is dismissed. This maintains the animation of the footer
64-
// * when the modal is dismissed.
65-
// *
66-
// * The workaround needs to be done before the animation starts
67-
// * so there are no flickering issues.
68-
// */
69-
// const ionFooter = baseEl.querySelector('ion-footer');
70-
// /**
71-
// * This check is needed to prevent more than one footer
72-
// * from being appended to the shadow root.
73-
// * Otherwise, iOS and MD enter animations would append
74-
// * the footer twice.
75-
// */
76-
// const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
77-
// if (ionFooter && !ionFooterAlreadyAppended) {
78-
// const footerHeight = ionFooter.clientHeight;
79-
// const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
80-
81-
// baseEl.shadowRoot!.appendChild(clonedFooter);
82-
// ionFooter.style.setProperty('display', 'none');
83-
// ionFooter.setAttribute('aria-hidden', 'true');
84-
85-
// // Padding is added to prevent some content from being hidden.
86-
// const page = baseEl.querySelector('.ion-page') as HTMLElement;
87-
// page.style.setProperty('padding-bottom', `${footerHeight}px`);
88-
// }
89-
// });
9047

9148
if (contentAnimation) {
9249
baseAnimation.addAnimation(contentAnimation);

core/src/components/modal/animations/md.leave.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const createLeaveAnimation = () => {
2121
* Md Modal Leave Animation
2222
*/
2323
export const mdLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
24-
const { currentBreakpoint, expandToScroll } = opts;
24+
const { currentBreakpoint } = opts;
2525
const root = getElementRoot(baseEl);
2626
const { wrapperAnimation, backdropAnimation } =
2727
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
@@ -33,32 +33,6 @@ export const mdLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOption
3333
.easing('cubic-bezier(0.47,0,0.745,0.715)')
3434
.duration(200)
3535
.addAnimation([backdropAnimation, wrapperAnimation]);
36-
// .beforeAddWrite(() => {
37-
// if (expandToScroll) {
38-
// // Scroll can only be done when the modal is fully expanded.
39-
// return;
40-
// }
41-
42-
// /**
43-
// * If expandToScroll is disabled, we need to swap
44-
// * the visibility to the original, so the footer
45-
// * dismisses with the modal and doesn't stay
46-
// * until the modal is removed from the DOM.
47-
// */
48-
// const ionFooter = baseEl.querySelector('ion-footer');
49-
// if (ionFooter) {
50-
// const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
51-
52-
// ionFooter.style.removeProperty('display');
53-
// ionFooter.removeAttribute('aria-hidden');
54-
55-
// clonedFooter.style.setProperty('display', 'none');
56-
// clonedFooter.setAttribute('aria-hidden', 'true');
57-
58-
// const page = baseEl.querySelector('.ion-page') as HTMLElement;
59-
// page.style.removeProperty('padding-bottom');
60-
// }
61-
// });
6236

6337
return baseAnimation;
6438
};

core/src/components/modal/gestures/sheet.ts

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)