New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(modal): update iOS design, add swipe gesture #19428
Merged
Merged
Changes from 35 commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
90280a8
update animation
manucorporat 32dc3ae
merge 1
manucorporat 9007a46
commit
manucorporat 1b41551
wip
manucorporat efb479a
wip
manucorporat ffda359
revert menu
manucorporat 5712020
wip
manucorporat a6fa6e5
Merge branch 'master' into fix-animation-manu-2
manucorporat 6a48397
wip
manucorporat ed98612
Merge branch 'master' into fix-animation-manu-2
manucorporat a1ccbd8
wip
manucorporat 6f41212
wip
manucorporat 0c95251
feat(modal): new modal gestures
manucorporat e021370
sync with master
liamdebeasi a78504c
one more thing
liamdebeasi e4e1509
begin fixing swipe modal
liamdebeasi 853948c
rounding errors
liamdebeasi 483ee17
fix test
liamdebeasi dddd619
sync with master:
liamdebeasi c21df96
revert hide caret changes
liamdebeasi ff1578d
remove non-modal css changes
liamdebeasi da3d9d8
one more fix
liamdebeasi c5e27c8
more clean up
liamdebeasi df80e53
fix a few small issues
liamdebeasi 78d2533
remove old code
liamdebeasi 0c2282a
remove running animations
liamdebeasi 78c3830
add initial animation clean up fix
liamdebeasi 88f8376
clamp out of bounds values
liamdebeasi e8e00e5
fix safari bug, fix dismiss via modalcontroller
liamdebeasi 7d33588
clear styles after in case user dismisses quickly
liamdebeasi 8d39031
update transition effect to avoid additional paints on modal
liamdebeasi 894100a
remove unused velocity
liamdebeasi edfdec3
add role
liamdebeasi f6d2750
revert old style changes
liamdebeasi 74e5139
remove old styles
liamdebeasi 703c2a5
update tests
liamdebeasi ae44cf7
fix dir
liamdebeasi 46d5c1e
update api
liamdebeasi e7025bf
make native el public
liamdebeasi 11eab87
update API, fix bug with multiple modals
liamdebeasi d51e8b2
fix interface
liamdebeasi 5cb91d9
usage docs
liamdebeasi e5212ec
chore(docs): updating react swipable modal doc
e60f230
clean up code, remove online imgs, run build
liamdebeasi eec775e
only modify transform on card style modals
liamdebeasi 6723751
update comment
liamdebeasi 67654ba
cleanup
liamdebeasi 3dab6ab
add border radius animation
liamdebeasi 4e3bb95
Merge remote-tracking branch 'origin/master' into new-modal-swipe
liamdebeasi c35c97d
fix background color removal
liamdebeasi File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,90 +1,62 @@ | ||||||
import { Animation } from '../../../interface'; | ||||||
import { createAnimation } from '../../../utils/animation/animation'; | ||||||
import { SwipeToCloseDefaults } from '../gestures/swipe-to-close'; | ||||||
|
||||||
/** | ||||||
* iOS Modal Enter Animation | ||||||
* iOS Modal Enter Animation for the Card presentation style | ||||||
*/ | ||||||
export const iosEnterAnimation = (baseEl: HTMLElement): Animation => { | ||||||
const baseAnimation = createAnimation(); | ||||||
const backdropAnimation = createAnimation(); | ||||||
const wrapperAnimation = createAnimation(); | ||||||
|
||||||
backdropAnimation | ||||||
export const iosEnterAnimation = ( | ||||||
baseEl: HTMLElement, | ||||||
presentingEl?: HTMLElement, | ||||||
): Animation => { | ||||||
// The top translate Y for the presenting element | ||||||
const backdropAnimation = createAnimation() | ||||||
.addElement(baseEl.querySelector('ion-backdrop')!) | ||||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)'); | ||||||
|
||||||
wrapperAnimation | ||||||
const wrapperAnimation = createAnimation() | ||||||
.addElement(baseEl.querySelector('.modal-wrapper')!) | ||||||
.beforeStyles({ 'opacity': 1 }) | ||||||
.fromTo('transform', 'translateY(100%)', 'translateY(0%)'); | ||||||
|
||||||
return baseAnimation | ||||||
const baseAnimation = createAnimation() | ||||||
.addElement(baseEl) | ||||||
.easing('cubic-bezier(0.36,0.66,0.04,1)') | ||||||
.duration(400) | ||||||
.easing('cubic-bezier(0.32,0.72,0,1)') | ||||||
.duration(500) | ||||||
.beforeAddClass('show-modal') | ||||||
.addAnimation([backdropAnimation, wrapperAnimation]); | ||||||
}; | ||||||
|
||||||
/** | ||||||
* Animations for modals | ||||||
*/ | ||||||
// export function modalSlideIn(rootEl: HTMLElement) { | ||||||
|
||||||
// } | ||||||
|
||||||
// export class ModalSlideOut { | ||||||
// constructor(el: HTMLElement) { | ||||||
// let backdrop = new Animation(this.plt, el.querySelector('ion-backdrop')); | ||||||
// let wrapperEle = <HTMLElement>el.querySelector('.modal-wrapper'); | ||||||
// let wrapperEleRect = wrapperEle.getBoundingClientRect(); | ||||||
// let wrapper = new Animation(this.plt, wrapperEle); | ||||||
|
||||||
// // height of the screen - top of the container tells us how much to scoot it down | ||||||
// // so it's off-screen | ||||||
// wrapper.fromTo('translateY', '0px', `${this.plt.height() - wrapperEleRect.top}px`); | ||||||
// backdrop.fromTo('opacity', 0.4, 0.0); | ||||||
|
||||||
// this | ||||||
// .element(this.leavingView.pageRef()) | ||||||
// .easing('ease-out') | ||||||
// .duration(250) | ||||||
// .add(backdrop) | ||||||
// .add(wrapper); | ||||||
// } | ||||||
// } | ||||||
|
||||||
// export class ModalMDSlideIn { | ||||||
// constructor(el: HTMLElement) { | ||||||
// const backdrop = new Animation(this.plt, el.querySelector('ion-backdrop')); | ||||||
// const wrapper = new Animation(this.plt, el.querySelector('.modal-wrapper')); | ||||||
|
||||||
// backdrop.fromTo('opacity', 0.01, 0.4); | ||||||
// wrapper.fromTo('translateY', '40px', '0px'); | ||||||
// wrapper.fromTo('opacity', 0.01, 1); | ||||||
|
||||||
// const DURATION = 280; | ||||||
// const EASING = 'cubic-bezier(0.36,0.66,0.04,1)'; | ||||||
// this.element(this.enteringView.pageRef()).easing(EASING).duration(DURATION) | ||||||
// .add(backdrop) | ||||||
// .add(wrapper); | ||||||
// } | ||||||
// } | ||||||
|
||||||
// export class ModalMDSlideOut { | ||||||
// constructor(el: HTMLElement) { | ||||||
// const backdrop = new Animation(this.plt, el.querySelector('ion-backdrop')); | ||||||
// const wrapper = new Animation(this.plt, el.querySelector('.modal-wrapper')); | ||||||
|
||||||
// backdrop.fromTo('opacity', 0.4, 0.0); | ||||||
// wrapper.fromTo('translateY', '0px', '40px'); | ||||||
// wrapper.fromTo('opacity', 0.99, 0); | ||||||
|
||||||
// this | ||||||
// .element(this.leavingView.pageRef()) | ||||||
// .duration(200) | ||||||
// .easing('cubic-bezier(0.47,0,0.745,0.715)') | ||||||
// .add(wrapper) | ||||||
// .add(backdrop); | ||||||
// } | ||||||
// } | ||||||
// BEFORE 0->1 AFTER | ||||||
// BEFORE 1->0 AFTER | ||||||
|
||||||
// onstart(0.5) 0.5 -> 1 onFinish(1) | ||||||
// onstart(0.5) 0.5 -> 0 onFinish(0) | ||||||
// onstart(false) START 0->1 END onFInish(true) | ||||||
// onstart(true) END 1->0 START onFinish(false) | ||||||
|
||||||
// onstart(false) START 0->1 END onFInish(true) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// onstart(true) END 1->0 START onFinish(false) | ||||||
|
||||||
if (presentingEl) { | ||||||
const bodyEl = document.body; | ||||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE; | ||||||
const presentingToY = SwipeToCloseDefaults.MIN_PRESENTING_Y; | ||||||
const finalTransform = `translateY(${presentingToY}%) scale(${toPresentingScale})`; | ||||||
const presentingAnimation = createAnimation() | ||||||
.beforeStyles({ | ||||||
'transform': 'translateY(0)', | ||||||
'border-radius': '10px 10px 0 0' | ||||||
}) | ||||||
.afterStyles({ | ||||||
'transform': finalTransform | ||||||
}) | ||||||
.addElement(presentingEl) | ||||||
.fromTo('transform', 'translateY(0px) scale(1)', finalTransform); | ||||||
|
||||||
// Wrap around animation code | ||||||
bodyEl.style.backgroundColor = 'black'; | ||||||
baseAnimation.addAnimation(presentingAnimation); | ||||||
} | ||||||
|
||||||
return baseAnimation; | ||||||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,59 @@ | ||
import { Animation } from '../../../interface'; | ||
import { createAnimation } from '../../../utils/animation/animation'; | ||
import { raf } from '../../../utils/helpers'; | ||
import { SwipeToCloseDefaults } from '../gestures/swipe-to-close'; | ||
|
||
/** | ||
* iOS Modal Leave Animation | ||
*/ | ||
export const iosLeaveAnimation = (baseEl: HTMLElement): Animation => { | ||
const baseAnimation = createAnimation(); | ||
const backdropAnimation = createAnimation(); | ||
const wrapperAnimation = createAnimation(); | ||
const wrapperEl = baseEl.querySelector('.modal-wrapper'); | ||
const wrapperElRect = wrapperEl!.getBoundingClientRect(); | ||
export const iosLeaveAnimation = ( | ||
baseEl: HTMLElement, | ||
presentingEl?: HTMLElement, | ||
duration = 500 | ||
): Animation => { | ||
|
||
backdropAnimation | ||
const backdropAnimation = createAnimation() | ||
.addElement(baseEl.querySelector('ion-backdrop')!) | ||
.fromTo('opacity', 'var(--backdrop-opacity)', 0.0); | ||
|
||
wrapperAnimation | ||
.addElement(wrapperEl!) | ||
const wrapperAnimation = createAnimation() | ||
.addElement(baseEl.querySelector('.modal-wrapper')!) | ||
.beforeStyles({ 'opacity': 1 }) | ||
.fromTo('transform', 'translateY(0%)', `translateY(${(baseEl.ownerDocument as any).defaultView.innerHeight - wrapperElRect.top}px)`); | ||
.fromTo('transform', `translateY(0%)`, 'translateY(100%)'); | ||
|
||
return baseAnimation | ||
const baseAnimation = createAnimation() | ||
.addElement(baseEl) | ||
.easing('ease-out') | ||
.duration(250) | ||
.easing('cubic-bezier(0.32,0.72,0,1)') | ||
.duration(duration) | ||
.addAnimation([backdropAnimation, wrapperAnimation]); | ||
|
||
if (presentingEl) { | ||
const currentPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE; | ||
const presentingFromY = SwipeToCloseDefaults.MIN_PRESENTING_Y; | ||
const presentingAnimation = createAnimation() | ||
.addElement(presentingEl) | ||
.beforeClearStyles(['transform']) | ||
.afterClearStyles(['transform']) | ||
.onFinish(currentStep => { | ||
if (currentStep === 1) { | ||
/** | ||
* This is a hack to work around an issue in Safari | ||
* where the border-radius change is not rendered | ||
* unless a layout is forced | ||
*/ | ||
raf(() => { | ||
presentingEl.style.removeProperty('border-radius'); | ||
presentingEl.style.setProperty('overflow', 'unset'); | ||
raf(() => { | ||
presentingEl.style.setProperty('overflow', ''); | ||
}); | ||
}); | ||
} | ||
}) | ||
.fromTo('transform', `translateY(${presentingFromY}px) scale(${currentPresentingScale})`, 'translateY(0px) scale(1)'); | ||
|
||
baseAnimation.addAnimation(presentingAnimation); | ||
} | ||
|
||
return baseAnimation; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { Animation } from '../../../interface'; | ||
import { getTimeGivenProgression } from '../../../utils/animation/cubic-bezier'; | ||
import { GestureDetail, createGesture } from '../../../utils/gesture'; | ||
import { clamp } from '../../../utils/helpers'; | ||
|
||
// Defaults for the card swipe animation | ||
export const SwipeToCloseDefaults = { | ||
MIN_BACKDROP_OPACITY: 0.4, | ||
MIN_PRESENTING_SCALE: 0.95, | ||
MIN_Y_CARD: 44, | ||
MIN_Y_FULLSCREEN: 0, | ||
MIN_PRESENTING_Y: 0 | ||
}; | ||
|
||
export const createSwipeToCloseGesture = ( | ||
el: HTMLIonModalElement, | ||
animation: Animation, | ||
onDismiss: () => void | ||
) => { | ||
const height = el.offsetHeight; | ||
let isOpen = false; | ||
|
||
const canStart = (detail: GestureDetail) => { | ||
const target = detail.event.target as HTMLElement | null; | ||
|
||
if (target === null || | ||
!(target as any).closest) { | ||
return true; | ||
} | ||
|
||
const content = target.closest('ion-content'); | ||
if (content === null) { | ||
return true; | ||
} | ||
// Target is in the content so we don't start the gesture. | ||
// We could be more nuanced here and allow it for content that | ||
// does not need to scroll. | ||
return false; | ||
}; | ||
|
||
const onStart = () => { | ||
animation.progressStart(true, (isOpen) ? 1 : 0); | ||
}; | ||
|
||
const onMove = (detail: GestureDetail) => { | ||
const step = detail.deltaY / height; | ||
if (step < 0) { return; } | ||
|
||
animation.progressStep(step); | ||
}; | ||
|
||
const onEnd = (detail: GestureDetail) => { | ||
const velocity = detail.velocityY; | ||
const step = detail.deltaY / height; | ||
if (step < 0) { return; } | ||
|
||
const threshold = (detail.deltaY + velocity * 1000) / height; | ||
|
||
const shouldComplete = threshold >= 0.5; | ||
let newStepValue = (shouldComplete) ? -0.001 : 0.001; | ||
|
||
if (!shouldComplete) { | ||
animation.easing('cubic-bezier(1, 0, 0.68, 0.28)'); | ||
newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], step)[0]; | ||
} else { | ||
animation.easing('cubic-bezier(0.32, 0.72, 0, 1)'); | ||
newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], step)[0]; | ||
} | ||
|
||
const duration = (shouldComplete) ? computeDuration(step * height, velocity) : computeDuration((1 - step) * height, velocity); | ||
isOpen = shouldComplete; | ||
|
||
animation | ||
.onFinish(() => { | ||
if (shouldComplete) { | ||
onDismiss(); | ||
} | ||
}) | ||
.progressEnd((shouldComplete) ? 1 : 0, newStepValue, duration); | ||
}; | ||
|
||
return createGesture({ | ||
el, | ||
gestureName: 'modalSwipeToClose', | ||
gesturePriority: 40, | ||
direction: 'y', | ||
threshold: 10, | ||
canStart, | ||
onStart, | ||
onMove, | ||
onEnd | ||
}); | ||
}; | ||
|
||
const computeDuration = (remaining: number, velocity: number) => { | ||
return clamp(100, remaining / Math.abs(velocity * 1.1), 400); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@manucorporat do you still want these comments in here, or can I remove then?