Compare commits

...

3 Commits

Author SHA1 Message Date
Liam DeBeasi
65c94b16d5 add custom anim support to nav outlet 2023-06-09 14:37:38 -04:00
Liam DeBeasi
fb0b0ae46e add modal sample 2023-06-09 12:39:38 -04:00
Liam DeBeasi
a3bc83a1e6 poc using animations with any lib 2023-06-09 09:27:55 -04:00
8 changed files with 180 additions and 55 deletions

View File

@@ -11,6 +11,13 @@
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<script type="module">
import * as motion from 'https://esm.run/motion';
window.motionOne = motion;
</script>
</head>
<body>
@@ -23,25 +30,47 @@
<ion-content class="ion-padding">
<ion-button id="default">Open Alert</ion-button>
<ion-button id="timeout">Open Alert, Close After 500ms</ion-button>
<ion-alert id="default-alert" trigger="default" header="Alert" message="Hello World!"></ion-alert>
<ion-alert id="timeout-alert" trigger="timeout" header="Alert" message="Hello World!"></ion-alert>
<ion-alert id="custom-alert" trigger="default" header="Alert" message="Hello World!"></ion-alert>
</ion-content>
</ion-app>
<script>
const defaultAlert = document.querySelector('#default-alert');
const timeoutAlert = document.querySelector('#timeout-alert');
const customAlert = document.querySelector('#custom-alert');
defaultAlert.buttons = ['OK'];
timeoutAlert.buttons = ['OK'];
customAlert.enterAnimation = async (el, opts, done) => {
const { animate } = window.motionOne;
const backdropAni = animate('#custom-alert ion-backdrop', {
opacity: 'var(--backdrop-opacity)'
});
timeoutAlert.addEventListener('didPresent', () => {
setTimeout(() => {
timeoutAlert.dismiss();
}, 500);
});
const wrapperAni = animate('#custom-alert .alert-wrapper', {
opacity: 1,
transform: ['scale(1.5)', 'scale(1)']
})
await Promise.all([wrapperAni.finished, backdropAni.finished]);
done();
}
customAlert.leaveAnimation = async (el, opts, done) => {
const { animate } = window.motionOne;
const backdropAni = animate('#custom-alert ion-backdrop', {
opacity: 0
});
const wrapperAni = animate('#custom-alert .alert-wrapper', {
opacity: 0,
transform: 'scale(0.5)'
})
await Promise.all([wrapperAni.finished, backdropAni.finished]);
done();
}
customAlert.buttons = ['OK'];
</script>
</body>
</html>

View File

@@ -17,7 +17,8 @@ const createLeaveAnimation = () => {
/**
* iOS Modal Leave Animation
*/
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
const duration = 500;
const { presentingEl, currentBreakpoint } = opts;
const root = getElementRoot(baseEl);
const { wrapperAnimation, backdropAnimation } =

View File

@@ -479,7 +479,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
presentingEl: presentingElement,
currentBreakpoint: this.initialBreakpoint,
backdropBreakpoint: this.backdropBreakpoint,
});
backdropElement: this.backdropEl,
wrapperElement: this.wrapperEl
} as any);
/* tslint:disable-next-line */
if (typeof window !== 'undefined') {
@@ -702,7 +704,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
presentingEl: presentingElement,
currentBreakpoint: this.currentBreakpoint ?? this.initialBreakpoint,
backdropBreakpoint: this.backdropBreakpoint,
}
backdropElement: this.backdropEl,
wrapperElement: this.wrapperEl
} as any
);
const dismissed = await this.currentTransition;

View File

@@ -33,6 +33,11 @@
margin-left: 5px;
}
</style>
<script type="module">
import * as motion from 'https://esm.run/motion';
window.motionOne = motion;
</script>
</head>
<body>
<ion-app>
@@ -47,12 +52,50 @@
<div class="grid-item">
<h2>Click</h2>
<ion-button id="left-click-trigger">Trigger</ion-button>
<ion-modal class="left-click-modal" trigger="left-click-trigger">
<ion-modal id="custom-modal" trigger="left-click-trigger">
<ion-content class="ion-padding"> Modal Content </ion-content>
</ion-modal>
</div>
</div>
</ion-content>
</ion-app>
<script>
const customModal = document.querySelector('#custom-modal');
customModal.enterAnimation = async (el, opts, done) => {
const { backdropElement, wrapperElement } = opts;
const { animate } = window.motionOne;
const backdropAni = animate(backdropElement, {
opacity: 'var(--backdrop-opacity)'
});
const wrapperAni = animate(wrapperElement, {
opacity: 1,
transform: ['scale(1.5)', 'scale(1)']
})
await Promise.all([wrapperAni.finished, backdropAni.finished]);
done();
}
customModal.leaveAnimation = async (el, opts, done) => {
const { backdropElement, wrapperElement } = opts;
const { animate } = window.motionOne;
const backdropAni = animate(backdropElement, {
opacity: 0
});
const wrapperAni = animate(wrapperElement, {
opacity: 0,
transform: 'scale(0.5)'
})
await Promise.all([wrapperAni.finished, backdropAni.finished]);
done();
}
</script>
</body>
</html>

View File

@@ -12,6 +12,11 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<script type="module">
import * as motion from 'https://esm.run/motion';
window.motionOne = motion;
</script>
<script>
class PageOne extends HTMLElement {
connectedCallback() {
@@ -127,5 +132,26 @@
<script>
document.querySelector('ion-route[component=page-three]').componentProps = { param: 'route' };
const routerOutlet = document.querySelector('ion-router-outlet');
routerOutlet.animation = async (el, opts, done) => {
const { enteringEl, leavingEl, direction } = opts;
const { animate, spring } = window.motionOne;
const enteringAni = animate(enteringEl, {
opacity: [1, 1],
x: ['100%', '0%']
}, { easing: spring() });
const leavingAni = animate(leavingEl, {
opacity: [1, 1],
easing: spring(),
x: '-100%'
}, { easing: spring() })
await Promise.all([enteringAni.finished, leavingAni.finished]);
done();
}
</script>
</html>

View File

@@ -264,4 +264,4 @@ export type AnimationPlayTo = 'start' | 'end';
export type AnimationDirection = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
export type AnimationFill = 'auto' | 'none' | 'forwards' | 'backwards' | 'both';
export type AnimationBuilder = (baseEl: any, opts?: any) => Animation;
export type AnimationBuilder = (baseEl: any, opts?: any, done?: () => void) => Animation;

View File

@@ -583,26 +583,35 @@ const overlayAnimation = async (
baseEl.classList.remove('overlay-hidden');
const aniRoot = overlay.el;
const animation = animationBuilder(aniRoot, opts);
if (!overlay.animated || !config.getBoolean('animated', true)) {
animation.duration(0);
let resolvePromise;
let promise = new Promise((resolve) => {
resolvePromise = () => { resolve(true) };
})
const animation = animationBuilder(aniRoot, opts, resolvePromise);
if (animation.beforeAddWrite === undefined) {
await promise;
} else {
if (!overlay.animated || !config.getBoolean('animated', true)) {
animation.duration(0);
}
if (overlay.keyboardClose) {
animation.beforeAddWrite(() => {
const activeElement = baseEl.ownerDocument!.activeElement as HTMLElement;
if (activeElement?.matches('input,ion-input, ion-textarea')) {
activeElement.blur();
}
});
}
const activeAni = activeAnimations.get(overlay) || [];
activeAnimations.set(overlay, [...activeAni, animation]);
await animation.play();
}
if (overlay.keyboardClose) {
animation.beforeAddWrite(() => {
const activeElement = baseEl.ownerDocument!.activeElement as HTMLElement;
if (activeElement?.matches('input,ion-input, ion-textarea')) {
activeElement.blur();
}
});
}
const activeAni = activeAnimations.get(overlay) || [];
activeAnimations.set(overlay, [...activeAni, animation]);
await animation.play();
return true;
};

View File

@@ -21,7 +21,7 @@ export const transition = (opts: TransitionOptions): Promise<TransitionResult> =
beforeTransition(opts);
runTransition(opts).then(
(result) => {
if (result.animation) {
if (result.animation && result.animation.destroy) {
result.animation.destroy();
}
afterTransition(opts);
@@ -102,11 +102,9 @@ const getAnimationBuilder = async (opts: TransitionOptions): Promise<AnimationBu
const animation = async (animationBuilder: AnimationBuilder, opts: TransitionOptions): Promise<TransitionResult> => {
await waitForReady(opts, true);
const trans = animationBuilder(opts.baseEl, opts);
fireWillEvents(opts.enteringEl, opts.leavingEl);
const didComplete = await playTransition(trans, opts);
const { animation, didComplete } = await playTransition(animationBuilder, opts);
if (opts.progressCallback) {
opts.progressCallback(undefined);
@@ -118,7 +116,7 @@ const animation = async (animationBuilder: AnimationBuilder, opts: TransitionOpt
return {
hasCompleted: didComplete,
animation: trans,
animation,
};
};
@@ -155,27 +153,42 @@ const notifyViewReady = async (
}
};
const playTransition = (trans: Animation, opts: TransitionOptions): Promise<boolean> => {
const progressCallback = opts.progressCallback;
const playTransition = async (animationBuilder: AnimationBuilder, opts: TransitionOptions): Promise<{ animation: Animation, didComplete: boolean}> => {
const promise = new Promise<boolean>((resolve) => {
trans.onFinish((currentStep: any) => resolve(currentStep === 1));
});
let resolvePromise;
let promise: Promise<boolean> = new Promise((resolve) => {
resolvePromise = () => { resolve(true) };
})
// cool, let's do this, start the transition
if (progressCallback) {
// this is a swipe to go back, just get the transition progress ready
// kick off the swipe animation start
trans.progressStart(true);
progressCallback(trans);
const animation = animationBuilder(opts.baseEl, opts, resolvePromise);
let didComplete = false;
if (animation.beforeAddWrite === undefined) {
didComplete = await promise;
} else {
// only the top level transition should actually start "play"
// kick it off and let it play through
// ******** DOM WRITE ****************
trans.play();
const progressCallback = opts.progressCallback;
promise = new Promise<boolean>((resolve) => {
animation.onFinish((currentStep: any) => resolve(currentStep === 1));
});
// cool, let's do this, start the transition
if (progressCallback) {
// this is a swipe to go back, just get the transition progress ready
// kick off the swipe animation start
animation.progressStart(true);
progressCallback(animation);
} else {
// only the top level transition should actually start "play"
// kick it off and let it play through
// ******** DOM WRITE ****************
animation.play();
didComplete = await promise;
}
}
// create a callback for when the animation is done
return promise;
return { didComplete, animation };
};
const fireWillEvents = (enteringEl: HTMLElement | undefined, leavingEl: HTMLElement | undefined) => {