Skip to content

Commit 566d3db

Browse files
committed
feat(modal): improved implementation
1 parent 37c5832 commit 566d3db

File tree

8 files changed

+79
-56
lines changed

8 files changed

+79
-56
lines changed

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,33 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
3535

3636
contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
3737

38-
footerAnimation?.addElement(root.querySelector('ion-footer')!);
39-
4038
const baseAnimation = createAnimation('entering-base')
4139
.addElement(baseEl)
4240
.easing('cubic-bezier(0.32,0.72,0,1)')
4341
.duration(500)
44-
.addAnimation([wrapperAnimation]);
42+
.addAnimation([wrapperAnimation])
43+
.beforeAddWrite(() => {
44+
const ionFooter = baseEl.querySelector('ion-footer');
45+
if (ionFooter) {
46+
const footerHeight = ionFooter.clientHeight;
47+
const clonedFooter = ionFooter.cloneNode(true) as HTMLElement;
48+
baseEl.shadowRoot!.appendChild(clonedFooter);
49+
ionFooter.remove();
50+
51+
// add padding bottom to the .ion-page element to be
52+
// the same as the cloned footer height
53+
const page = baseEl.querySelector('.ion-page') as HTMLElement;
54+
page.style.setProperty('padding-bottom', `${footerHeight}px`);
55+
if (animateContentHeight && footerAnimation) {
56+
footerAnimation.addElement(root.querySelector('ion-footer')!);
57+
baseAnimation.addAnimation(footerAnimation);
58+
}
59+
}});
4560

4661
if (animateContentHeight && contentAnimation) {
4762
baseAnimation.addAnimation(contentAnimation);
4863
}
4964

50-
if (animateContentHeight && footerAnimation) {
51-
baseAnimation.addAnimation(footerAnimation);
52-
}
53-
5465
if (presentingEl) {
5566
const isMobile = window.innerWidth < 768;
5667
const hasCardModal =

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const createLeaveAnimation = () => {
1212

1313
const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
1414

15-
return { backdropAnimation, wrapperAnimation };
15+
return { backdropAnimation, wrapperAnimation, footerAnimation: undefined };
1616
};
1717

1818
/**
@@ -21,19 +21,25 @@ const createLeaveAnimation = () => {
2121
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
2222
const { presentingEl, currentBreakpoint } = opts;
2323
const root = getElementRoot(baseEl);
24-
const { wrapperAnimation, backdropAnimation } =
25-
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
24+
const { wrapperAnimation, backdropAnimation, footerAnimation } =
25+
currentBreakpoint !== undefined ? createSheetLeaveAnimation(baseEl, opts) : createLeaveAnimation();
2626

2727
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
2828

2929
wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!).beforeStyles({ opacity: 1 });
3030

31+
footerAnimation?.addElement(root.querySelector('ion-footer')!);
32+
3133
const baseAnimation = createAnimation('leaving-base')
3234
.addElement(baseEl)
3335
.easing('cubic-bezier(0.32,0.72,0,1)')
3436
.duration(duration)
3537
.addAnimation(wrapperAnimation);
3638

39+
if (footerAnimation) {
40+
baseAnimation.addAnimation(footerAnimation);
41+
}
42+
3743
if (presentingEl) {
3844
const isMobile = window.innerWidth < 768;
3945
const hasCardModal =

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,32 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOption
3737

3838
contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
3939

40-
footerAnimation?.addElement(root.querySelector('ion-footer')!);
41-
4240
const baseAnimation = createAnimation()
4341
.addElement(baseEl)
4442
.easing('cubic-bezier(0.36,0.66,0.04,1)')
4543
.duration(280)
46-
.addAnimation([backdropAnimation, wrapperAnimation]);
44+
.addAnimation([backdropAnimation, wrapperAnimation])
45+
.beforeAddWrite(() => {
46+
const ionFooter = baseEl.querySelector('ion-footer');
47+
if (ionFooter) {
48+
const footerHeight = ionFooter.clientHeight;
49+
const clonedFooter = ionFooter.cloneNode(true) as HTMLElement;
50+
baseEl.shadowRoot!.appendChild(clonedFooter);
51+
ionFooter.remove();
52+
53+
// add padding bottom to the .ion-page element to be
54+
// the same as the cloned footer height
55+
const page = baseEl.querySelector('.ion-page') as HTMLElement;
56+
page.style.setProperty('padding-bottom', `${footerHeight}px`);
57+
if (animateContentHeight && footerAnimation) {
58+
footerAnimation.addElement(root.querySelector('ion-footer')!);
59+
baseAnimation.addAnimation(footerAnimation);
60+
}
61+
}});
4762

4863
if (animateContentHeight && contentAnimation) {
4964
baseAnimation.addAnimation(contentAnimation);
5065
}
5166

52-
if (animateContentHeight && footerAnimation) {
53-
baseAnimation.addAnimation(footerAnimation);
54-
}
55-
5667
return baseAnimation;
5768
};

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const createLeaveAnimation = () => {
1414
{ offset: 1, opacity: 0, transform: 'translateY(40px)' },
1515
]);
1616

17-
return { backdropAnimation, wrapperAnimation };
17+
return { backdropAnimation, wrapperAnimation, footerAnimation: undefined };
1818
};
1919

2020
/**
@@ -23,14 +23,22 @@ const createLeaveAnimation = () => {
2323
export const mdLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
2424
const { currentBreakpoint } = opts;
2525
const root = getElementRoot(baseEl);
26-
const { wrapperAnimation, backdropAnimation } =
27-
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
26+
const { wrapperAnimation, backdropAnimation, footerAnimation } =
27+
currentBreakpoint !== undefined ? createSheetLeaveAnimation(baseEl, opts) : createLeaveAnimation();
2828

2929
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
3030
wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!);
31+
footerAnimation?.addElement(root.querySelector('ion-footer')!);
32+
console.log(footerAnimation);
3133

32-
return createAnimation()
34+
const baseAnimation = createAnimation()
3335
.easing('cubic-bezier(0.47,0,0.745,0.715)')
3436
.duration(200)
3537
.addAnimation([backdropAnimation, wrapperAnimation]);
38+
39+
if (footerAnimation) {
40+
baseAnimation.addAnimation(footerAnimation);
41+
}
42+
43+
return baseAnimation;
3644
};

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ export const createSheetEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimat
3434
{ offset: 1, opacity: 1, maxHeight: `${currentBreakpoint! * 100}%` },
3535
]);
3636

37-
const wrapperHeight = baseEl.shadowRoot?.querySelector('.modal-wrapper, .modal-shadow')?.clientHeight;
37+
const footerHeight = baseEl.querySelector('ion-footer')?.clientHeight;
3838
const footerAnimation = createAnimation('footerAnimation').keyframes([
39-
{ offset: 0, opacity: 1, transform: `translateY(-${wrapperHeight}px)` },
40-
{ offset: 1, opacity: 1, transform: `translateY(-${wrapperHeight! * (1 - currentBreakpoint!)}px)` },
39+
{ offset: 0, opacity: 1, transform: `translateY(${footerHeight}px)` },
40+
{ offset: 0.2, opacity: 1, transform: `translateY(0)` },
4141
]);
4242

4343
return { wrapperAnimation, backdropAnimation, contentAnimation, footerAnimation };
4444
};
4545

46-
export const createSheetLeaveAnimation = (opts: ModalAnimationOptions) => {
46+
export const createSheetLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions) => {
4747
const { currentBreakpoint, backdropBreakpoint } = opts;
4848

4949
/**
@@ -75,5 +75,12 @@ export const createSheetLeaveAnimation = (opts: ModalAnimationOptions) => {
7575
{ offset: 1, opacity: 1, transform: `translateY(100%)` },
7676
]);
7777

78-
return { wrapperAnimation, backdropAnimation };
78+
const footerHeight = baseEl.shadowRoot!.querySelector('ion-footer')?.clientHeight;
79+
const footerAnimation = createAnimation('footerAnimation').keyframes([
80+
{ offset: 0, opacity: 1, transform: `translateY(0)` },
81+
{ offset: 0.7, opacity: 1, transform: `translateY(0)` },
82+
{ offset: 1, opacity: 1, transform: `translateY(${footerHeight}px)` },
83+
]);
84+
85+
return { wrapperAnimation, backdropAnimation, footerAnimation };
7986
};

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

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,7 @@ export const createSheetGesture = (
7575
CONTENT_KEYFRAMES: [
7676
{ offset: 0, maxHeight: '100%' },
7777
{ offset: 1, maxHeight: '0%' },
78-
],
79-
FOOTER_KEYFRAMES: [
80-
{ offset: 0, transform: `translateY(0)` },
81-
{ offset: 1, transform: `translateY(-${wrapperEl.clientHeight}px)` },
82-
],
78+
]
8379
};
8480

8581
const contentEl = baseEl.querySelector('ion-content');
@@ -93,7 +89,7 @@ export const createSheetGesture = (
9389
const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
9490
const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
9591
const contentAnimation = animation.childAnimations.find((ani) => ani.id === 'contentAnimation');
96-
const footerAnimation = animation.childAnimations.find((ani) => ani.id === 'footerAnimation');
92+
animation.childAnimations.find((ani) => ani.id === 'footerAnimation')?.destroy();
9793

9894
const enableBackdrop = () => {
9995
baseEl.style.setProperty('pointer-events', 'auto');
@@ -133,7 +129,6 @@ export const createSheetGesture = (
133129
wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
134130
backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
135131
contentAnimation?.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
136-
footerAnimation?.keyframes([...SheetDefaults.FOOTER_KEYFRAMES]);
137132

138133
animation.progressStart(true, 1 - currentBreakpoint);
139134

@@ -345,7 +340,7 @@ export const createSheetGesture = (
345340
},
346341
]);
347342

348-
if (contentAnimation && footerAnimation) {
343+
if (contentAnimation) {
349344
/**
350345
* The modal content should scroll at any breakpoint when scrollAtEdge
351346
* is disabled. In order to do this, the content needs to be completely
@@ -357,11 +352,6 @@ export const createSheetGesture = (
357352
{ offset: 0, maxHeight: `${(1 - breakpointOffset) * 100}%` },
358353
{ offset: 1, maxHeight: `${snapToBreakpoint * 100}%` },
359354
]);
360-
361-
footerAnimation.keyframes([
362-
{ offset: 0, transform: `translateY(-${height * (breakpointOffset)}px)` },
363-
{ offset: 1, transform: `translateY(-${height * (1 - snapToBreakpoint)}px)` },
364-
])
365355
}
366356

367357
animation.progressStep(0);
@@ -408,7 +398,6 @@ export const createSheetGesture = (
408398
wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
409399
backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
410400
contentAnimation?.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
411-
footerAnimation?.keyframes([...SheetDefaults.FOOTER_KEYFRAMES]);
412401
animation.progressStart(true, 1 - snapToBreakpoint);
413402
currentBreakpoint = snapToBreakpoint;
414403
onBreakpointChange(currentBreakpoint);

core/src/components/modal/modal.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,10 @@ ion-backdrop {
166166
position: absolute;
167167
bottom: 0;
168168
}
169+
170+
:host(.modal-sheet) ion-footer {
171+
position: absolute;
172+
bottom: 0;
173+
174+
width: var(--width);
175+
}

core/src/components/modal/modal.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,22 +532,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
532532

533533
this.usersElement = await attachComponent(delegate, el, this.component, ['ion-page'], this.componentProps, inline);
534534

535-
/**
536-
* If scrollAtEdge is false, footer must be fixed to
537-
* the bottom of the modal and be always visible.
538-
*/
539-
if (this.scrollAtEdge === false){
540-
const footer = this.usersElement.querySelector('ion-footer');
541-
if (!footer) return;
542-
footer.style.position = 'fixed';
543-
footer.style.bottom = '0';
544-
const fixedFooter = footer.cloneNode(true) as HTMLIonFooterElement;
545-
footer.remove();
546-
this.usersElement.style.setProperty('padding-bottom', `${fixedFooter.clientHeight}px`);
547-
el.shadowRoot!.querySelector('.modal-wrapper')!.appendChild(footer);
548-
}
549-
550-
551535
/**
552536
* When using the lazy loaded build of Stencil, we need to wait
553537
* for every Stencil component instance to be ready before presenting

0 commit comments

Comments
 (0)