animate-presence
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -1,1 +0,6 @@ | ||
import{p as e,b as t}from"./p-bf4f5b96.js";e().then(e=>t([["p-cnydcalb",[[1,"animate-presence",{__presenceKey:[1,"__presence-key"],descendants:[16],observe:[1028],registerChild:[64],unregisterChild:[64],exit:[64],enter:[64]},[[0,"exitComplete","exitCompleteHandler"]]]]],["p-so2ggjcj",[[4,"animated-route-switch",{group:[513],routeViewsUpdated:[16],scrollTopOffset:[2,"scroll-top-offset"],location:[16]}]]],["p-kon4unsq",[[0,"context-consumer",{context:[16],renderer:[16],subscribe:[16],unsubscribe:[32]}]]],["p-8w5t9w62",[[0,"stencil-async-content",{documentLocation:[1,"document-location"],content:[32]}]]],["p-kyvbvkeu",[[0,"stencil-route",{group:[513],componentUpdated:[16],match:[1040],url:[1],component:[1],componentProps:[16],exact:[4],routeRender:[16],scrollTopOffset:[2,"scroll-top-offset"],routeViewsUpdated:[16],location:[16],history:[16],historyType:[1,"history-type"]}]]],["p-4v3h97my",[[4,"stencil-route-link",{url:[1],urlMatch:[1,"url-match"],activeClass:[1,"active-class"],exact:[4],strict:[4],custom:[1],anchorClass:[1,"anchor-class"],anchorRole:[1,"anchor-role"],anchorTitle:[1,"anchor-title"],anchorTabIndex:[1,"anchor-tab-index"],anchorId:[1,"anchor-id"],history:[16],location:[16],root:[1],ariaHaspopup:[1,"aria-haspopup"],ariaPosinset:[1,"aria-posinset"],ariaSetsize:[2,"aria-setsize"],ariaLabel:[1,"aria-label"],match:[32]}]]],["p-nx8hisar",[[4,"stencil-route-switch",{group:[513],scrollTopOffset:[2,"scroll-top-offset"],location:[16],routeViewsUpdated:[16]}]]],["p-lq6vkqqa",[[0,"stencil-route-title",{titleSuffix:[1,"title-suffix"],pageTitle:[1,"page-title"]}]]],["p-rnzvikhw",[[4,"stencil-router",{root:[1],historyType:[1,"history-type"],titleSuffix:[1,"title-suffix"],scrollTopOffset:[2,"scroll-top-offset"],location:[32],history:[32]}]]],["p-cstaclrc",[[0,"stencil-router-prompt",{when:[4],message:[1],history:[16],unblock:[32]}]]],["p-ixsapdha",[[0,"stencil-router-redirect",{history:[16],root:[1],url:[1]}]]]],e)); | ||
import { p as patchBrowser, g as globals, b as bootstrapLazy } from './core-c3774567.js'; | ||
patchBrowser().then(options => { | ||
globals(); | ||
return bootstrapLazy([["animate-presence",[[1,"animate-presence",{"__presenceKey":[1,"__presence-key"],"descendants":[16],"observe":[1028],"registerChild":[64],"unregisterChild":[64],"exit":[64],"enter":[64]},[[0,"exitComplete","exitCompleteHandler"]]]]],["animated-route-switch",[[4,"animated-route-switch",{"group":[513],"routeViewsUpdated":[16],"scrollTopOffset":[2,"scroll-top-offset"],"location":[16]}]]]], options); | ||
}); |
@@ -11,24 +11,4 @@ { | ||
}, | ||
"collections": [ | ||
{ | ||
"name": "@stencil/router", | ||
"tags": [ | ||
"stencil-async-content", | ||
"stencil-route", | ||
"stencil-route-link", | ||
"stencil-route-switch", | ||
"stencil-route-title", | ||
"stencil-router", | ||
"stencil-router-prompt", | ||
"stencil-router-redirect" | ||
] | ||
}, | ||
{ | ||
"name": "@stencil/state-tunnel", | ||
"tags": [ | ||
"context-consumer" | ||
] | ||
} | ||
], | ||
"collections": [], | ||
"bundles": [] | ||
} |
import { h, Host } from "@stencil/core"; | ||
import { setCustomProperties, isHTMLElement, hasData, presence, closest, enterChildren, exitChildren } from "../../utils"; | ||
import { setCustomProperties, isHTMLElement, hasData, presence, closest, enterChildren, exitChildren, injectGlobalStyle, } from '../../utils'; | ||
export class AnimatePresence { | ||
@@ -26,3 +26,3 @@ constructor() { | ||
attributes: true, | ||
attributeFilter: ["data-key"] | ||
attributeFilter: ['data-key'], | ||
}); | ||
@@ -36,6 +36,12 @@ } | ||
var _a, _b; | ||
injectGlobalStyle(); | ||
this.ancestor = this.getClosestParent(); | ||
if (typeof this.observe === "undefined") { | ||
if (typeof this.observe === 'undefined') { | ||
this.observe = (_b = (_a = this.ancestor) === null || _a === void 0 ? void 0 : _a.observe, (_b !== null && _b !== void 0 ? _b : true)); | ||
} | ||
Array.from(this.element.children).map((el, i) => { | ||
setCustomProperties(el, { i }); | ||
el.style.setProperty('animation-play-state', 'paused'); | ||
el.dataset.enter = ''; | ||
}); | ||
} | ||
@@ -46,5 +52,5 @@ async componentDidLoad() { | ||
(_a = this.ancestor) === null || _a === void 0 ? void 0 : _a.registerChild(this.element); | ||
Array.from(this.element.children).map(el => (el.dataset.initial = "")); | ||
if (!this.ancestor) | ||
if (!this.ancestor) { | ||
this.enter(); | ||
} | ||
} | ||
@@ -59,4 +65,9 @@ async componentDidUnload() { | ||
delete el.dataset.exit; | ||
el.dataset.initial = ""; | ||
el.dataset.enter = ""; | ||
const event = new CustomEvent('animatePresenceEnter', { | ||
bubbles: true, | ||
detail: { i }, | ||
}); | ||
el.dispatchEvent(event); | ||
el.style.removeProperty('animation-play-state'); | ||
el.dataset.enter = ''; | ||
setCustomProperties(el, { i }); | ||
@@ -67,21 +78,26 @@ await presence(el, { | ||
delete el.dataset.enter; | ||
el.style.removeProperty("--i"); | ||
} | ||
el.style.removeProperty('--i'); | ||
}, | ||
}); | ||
return enterChildren(el); | ||
} | ||
async exitNode(el, method = "remove", i = 0) { | ||
async exitNode(el, method = 'remove', i = 0) { | ||
await exitChildren(el); | ||
delete el.dataset.willExit; | ||
setCustomProperties(el, { i }); | ||
el.dataset.exit = ""; | ||
const event = new CustomEvent('animatePresenceExit', { | ||
bubbles: true, | ||
detail: { i }, | ||
}); | ||
el.dispatchEvent(event); | ||
el.dataset.exit = ''; | ||
await presence(el, { | ||
afterSelf: () => { | ||
if (method === "remove") { | ||
if (method === 'remove') { | ||
el.remove(); | ||
} | ||
else if (method === "hide") { | ||
el.style.setProperty("visibility", "hidden"); | ||
else if (method === 'hide') { | ||
el.style.setProperty('visibility', 'hidden'); | ||
} | ||
} | ||
}, | ||
}); | ||
@@ -93,6 +109,6 @@ return Promise.resolve(); | ||
return; | ||
if (hasData(node, "exit")) | ||
if (hasData(node, 'exit')) | ||
return; | ||
if (hasData(node, "willExit")) { | ||
return this.exitNode(node, "remove", i); | ||
if (hasData(node, 'willExit')) { | ||
return this.exitNode(node, 'remove', i); | ||
} | ||
@@ -106,9 +122,9 @@ else { | ||
return; | ||
if (hasData(node, "exit") || hasData(node, "willExit")) { | ||
if (hasData(node, 'exit') || hasData(node, 'willExit')) { | ||
return; | ||
} | ||
node.dataset.willExit = ""; | ||
i !== 0 && setCustomProperties(node, { i }); | ||
node.dataset.willExit = ''; | ||
setCustomProperties(node, { i }); | ||
if (isHTMLElement(record.previousSibling)) { | ||
record.previousSibling.insertAdjacentElement("afterend", node); | ||
record.previousSibling.insertAdjacentElement('afterend', node); | ||
} | ||
@@ -123,3 +139,3 @@ else if (isHTMLElement(record.target)) { | ||
if (record.addedNodes.length === 1) { | ||
this.handleEnter(record.addedNodes[0], record, records.length - i); | ||
this.handleEnter(record.addedNodes[0], record, records.length - 1 - i); | ||
} | ||
@@ -134,3 +150,3 @@ if (record.removedNodes.length === 1) { | ||
if (!this.mo) { | ||
if ("MutationObserver" in window) { | ||
if ('MutationObserver' in window) { | ||
this.mo = new MutationObserver(this.handleMutation); | ||
@@ -153,3 +169,3 @@ this.observeChanged(); | ||
...this.descendants.filter(element => element.__presenceKey !== key), | ||
el | ||
el, | ||
]; | ||
@@ -177,3 +193,3 @@ return; | ||
.reverse() | ||
.map((el, i) => this.exitNode(el, "hide", i))); | ||
.map((el, i) => this.exitNode(el, 'hide', i))); | ||
this.didExit = true; | ||
@@ -202,3 +218,3 @@ this.willExit = false; | ||
render() { | ||
return (h(Host, { style: { display: "contents" } }, | ||
return (h(Host, { style: { display: 'contents' } }, | ||
h("slot", null))); | ||
@@ -286,2 +302,32 @@ } | ||
} | ||
}, { | ||
"method": "animatePresenceEnter", | ||
"name": "animatePresenceEnter", | ||
"bubbles": true, | ||
"cancelable": true, | ||
"composed": true, | ||
"docs": { | ||
"tags": [], | ||
"text": "Dispatched on a child when it enters.\n\nThis event can be used as a hook to animate `event.target` with the Web Animations API." | ||
}, | ||
"complexType": { | ||
"original": "{ i: number }", | ||
"resolved": "{ i: number; }", | ||
"references": {} | ||
} | ||
}, { | ||
"method": "animatePresenceExit", | ||
"name": "animatePresenceExit", | ||
"bubbles": true, | ||
"cancelable": true, | ||
"composed": true, | ||
"docs": { | ||
"tags": [], | ||
"text": "Dispatched on a child when it exits.\n\nThis event can be used as a hook to animate `event.target` with the Web Animations API." | ||
}, | ||
"complexType": { | ||
"original": "{ i: number }", | ||
"resolved": "{ i: number; }", | ||
"references": {} | ||
} | ||
}]; } | ||
@@ -288,0 +334,0 @@ static get methods() { return { |
import { h } from "@stencil/core"; | ||
import { matchPath, } from "@stencil/router"; | ||
import { enterChildren, exitChildren } from "../../utils"; | ||
import { matchPath } from '../../utils/router'; | ||
import { enterChildren, exitChildren } from '../../utils'; | ||
const getUniqueId = () => { | ||
@@ -11,3 +11,3 @@ return ((Math.random() * 10e16).toString().match(/.{4}/g) || []).join('-'); | ||
exact: exact, | ||
strict: true | ||
strict: true, | ||
}); | ||
@@ -48,3 +48,3 @@ }; | ||
el: childElement, | ||
match: match | ||
match: match, | ||
}; | ||
@@ -81,3 +81,3 @@ }); | ||
if (index === this.activeIndex) { | ||
child.el.style.display = ""; | ||
child.el.style.display = ''; | ||
return enterChildren(child.el); | ||
@@ -90,3 +90,3 @@ } | ||
child.el.match = null; | ||
child.el.style.display = "none"; | ||
child.el.style.display = 'none'; | ||
}); | ||
@@ -169,3 +169,3 @@ }); | ||
"location": "import", | ||
"path": "@stencil/router" | ||
"path": "../../utils/router" | ||
} | ||
@@ -172,0 +172,0 @@ } |
@@ -6,27 +6,41 @@ export const nextTick = /*@__PURE__*/ (cb) => Promise.resolve().then(cb); | ||
var _a; | ||
const { animationName, animationDuration, transitionDuration } = window.getComputedStyle(element); | ||
if (animationName !== "none" && animationDuration !== "0s") { | ||
listen("animation"); | ||
} | ||
else if (transitionDuration !== "0s") { | ||
listen("transition"); | ||
} | ||
else { | ||
(_a = afterSelf) === null || _a === void 0 ? void 0 : _a(); | ||
resolve(); | ||
} | ||
function listen(name) { | ||
element.addEventListener(`${name}end`, onEnd(name)); | ||
} | ||
function onEnd(name) { | ||
return function (event) { | ||
// If WAAPI getAnimations exists, use that | ||
if (typeof element.getAnimations !== 'undefined') { | ||
Promise.all(element.getAnimations().map(anim => anim.finished)).then(() => { | ||
var _a; | ||
if (event.target !== element) | ||
return; | ||
element.removeEventListener(`${name}end`, this); | ||
(_a = afterSelf) === null || _a === void 0 ? void 0 : _a(); | ||
resolve(); | ||
return; | ||
}; | ||
}); | ||
} | ||
else { | ||
// Otherwise grab the computed style to check what listeners to attach | ||
// or bail out if there aren't any animations/transitions set | ||
const { animationName, animationDuration, transitionDuration, } = window.getComputedStyle(element); | ||
if (animationName !== 'none' && animationDuration !== '0s') { | ||
listen('animation'); | ||
} | ||
else if (transitionDuration !== '0s') { | ||
listen('transition'); | ||
} | ||
else { | ||
(_a = afterSelf) === null || _a === void 0 ? void 0 : _a(); | ||
resolve(); | ||
} | ||
// } | ||
function listen(name) { | ||
element.addEventListener(`${name}end`, onEnd(name)); | ||
} | ||
function onEnd(name) { | ||
return function (event) { | ||
var _a; | ||
if (event.target !== element) | ||
return; | ||
element.removeEventListener(`${name}end`, this); | ||
(_a = afterSelf) === null || _a === void 0 ? void 0 : _a(); | ||
resolve(); | ||
return; | ||
}; | ||
} | ||
} | ||
}); | ||
@@ -41,15 +55,15 @@ }; | ||
}; | ||
const convertToCustomProperties = (o, prefix = "--", result = {}) => { | ||
const convertToCustomProperties = (o, prefix = '--', result = {}) => { | ||
if (o == null) | ||
return result; | ||
switch (typeof o) { | ||
case "string": { | ||
case 'string': { | ||
result[kebab(prefix)] = o; | ||
return result; | ||
} | ||
case "number": { | ||
case 'number': { | ||
result[kebab(prefix)] = o.toString(10); | ||
return result; | ||
} | ||
case "boolean": { | ||
case 'boolean': { | ||
result[kebab(prefix)] = o ? `1` : `0`; | ||
@@ -61,11 +75,7 @@ return result; | ||
} | ||
if (Array.isArray(o) || typeof o === "object") { | ||
if (Array.isArray(o) || typeof o === 'object') { | ||
for (let [key, value] of Object.entries(o)) { | ||
const name = [ | ||
prefix, | ||
!prefix.endsWith("-") && '-', | ||
key | ||
] | ||
const name = [prefix, !prefix.endsWith('-') && '-', key] | ||
.filter(Boolean) | ||
.join(""); | ||
.join(''); | ||
convertToCustomProperties(value, name, result); | ||
@@ -79,4 +89,4 @@ } | ||
node.nodeType === node.ELEMENT_NODE && | ||
typeof node.tagName !== "undefined"; | ||
export const hasData = (el, key) => typeof el.dataset[key] !== "undefined"; | ||
typeof node.tagName !== 'undefined'; | ||
export const hasData = (el, key) => typeof el.dataset[key] !== 'undefined'; | ||
export function closest(selector, base = this) { | ||
@@ -88,3 +98,5 @@ try { | ||
let found = el.closest(selector); | ||
return found ? found : closestFrom(el.getRootNode().host); | ||
return found | ||
? found | ||
: closestFrom(el.getRootNode().host); | ||
} | ||
@@ -98,4 +110,4 @@ return closestFrom(base); | ||
export const getTopLevelChildren = (el) => { | ||
const all = Array.from(el.querySelectorAll("animate-presence")); | ||
const nested = Array.from(el.querySelectorAll(":scope animate-presence animate-presence")); | ||
const all = Array.from(el.querySelectorAll('animate-presence')); | ||
const nested = Array.from(el.querySelectorAll(':scope animate-presence animate-presence')); | ||
return all.filter(el => !nested.includes(el)); | ||
@@ -109,1 +121,10 @@ }; | ||
}; | ||
export const injectGlobalStyle = () => { | ||
let ss = document.head.querySelector('[data-ap-global]'); | ||
if (ss) | ||
return; | ||
ss = document.createElement('style'); | ||
ss.dataset.apGlobal = ''; | ||
ss.textContent = `animate-presence>[data-enter][style*="--i:"],animate-presence>[data-exit][style*="--i:"]{animation-fill-mode:both;}`; | ||
document.head.appendChild(ss); | ||
}; |
@@ -12,3 +12,3 @@ /* eslint-disable */ | ||
LocationSegments, | ||
} from '@stencil/router'; | ||
} from './utils/router'; | ||
@@ -69,2 +69,10 @@ export namespace Components { | ||
/** | ||
* Dispatched on a child when it enters. This event can be used as a hook to animate `event.target` with the Web Animations API. | ||
*/ | ||
'onAnimatePresenceEnter'?: (event: CustomEvent<{ i: number }>) => void; | ||
/** | ||
* Dispatched on a child when it exits. This event can be used as a hook to animate `event.target` with the Web Animations API. | ||
*/ | ||
'onAnimatePresenceExit'?: (event: CustomEvent<{ i: number }>) => void; | ||
/** | ||
* Fires when all exiting nodes have completed animating out. To simplify listener behavior, this event bubbles, but never beyond the closest `<animate-presence>` parent. | ||
@@ -71,0 +79,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import { EventEmitter } from "../../stencil.core"; | ||
import { EventEmitter } from '../../stencil.core'; | ||
export declare class AnimatePresence { | ||
@@ -45,2 +45,18 @@ element: HTMLAnimatePresenceElement; | ||
exitComplete: EventEmitter<void>; | ||
/** | ||
* Dispatched on a child when it enters. | ||
* | ||
* This event can be used as a hook to animate `event.target` with the Web Animations API. | ||
*/ | ||
animatePresenceEnter: EventEmitter<{ | ||
i: number; | ||
}>; | ||
/** | ||
* Dispatched on a child when it exits. | ||
* | ||
* This event can be used as a hook to animate `event.target` with the Web Animations API. | ||
*/ | ||
animatePresenceExit: EventEmitter<{ | ||
i: number; | ||
}>; | ||
private willExit; | ||
@@ -47,0 +63,0 @@ private didExit; |
@@ -1,5 +0,5 @@ | ||
import { QueueApi } from "@stencil/core/dist/declarations"; | ||
import { MatchResults, LocationSegments } from "@stencil/router"; | ||
import { QueueApi } from '@stencil/core/dist/declarations'; | ||
import { MatchResults, LocationSegments } from '../../utils/router'; | ||
interface Child { | ||
el: HTMLStencilRouteElement; | ||
el: any; | ||
match: MatchResults | null; | ||
@@ -6,0 +6,0 @@ } |
@@ -12,1 +12,2 @@ export declare const nextTick: (cb: () => void) => Promise<void>; | ||
export declare const enterChildren: (el: HTMLElement) => Promise<void[]>; | ||
export declare const injectGlobalStyle: () => void; |
{ | ||
"name": "animate-presence", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Effortless element entrance/exit animations", | ||
@@ -24,7 +24,5 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@stencil/core": "^1.8.8" | ||
"@stencil/core": "^1.8.8", | ||
"workbox-build": "4.3.1" | ||
}, | ||
"peerDependencies": { | ||
"@stencil/router": "^1.0.1" | ||
}, | ||
"license": "MIT", | ||
@@ -31,0 +29,0 @@ "repository": { |
127
readme.md
@@ -1,8 +0,26 @@ | ||
![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) | ||
<div align="center"> | ||
<img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo.svg?sanitize=true" alt="Animate Presence" width="175" style="margin:0 auto;"/> | ||
</div> | ||
<h3 align="center" style="text-align:center;">Effortless element entrance/exit animations.</h3> | ||
# Animate Presence | ||
### Effortless element entrance/exit animations. | ||
<h3 align="center" style="text-align:center;color:#eee"> | ||
<a href="https://github.com/natemoo-re/animate-presence"> | ||
<img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo-github.svg?sanitize=true" alt="GitHub" height="24"/> | ||
</a> | ||
/ | ||
<a href="https://www.npmjs.com/package/animate-presence"> | ||
<img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo-npm.svg?sanitize=true" alt="NPM" height="24"/> | ||
</a> | ||
/ | ||
<a href="https://animate-presence.now.sh/"> | ||
<img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/learn.svg?sanitize=true" alt="Examples" height="24"/> | ||
</a> | ||
</h3> | ||
Unlike most animation libraries, there's no new API to learn—just use CSS. | ||
<br /> | ||
--- | ||
Unlike most animation libraries, there's no new API to learn—just use CSS or the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API). | ||
Here's a basic example: | ||
@@ -12,18 +30,22 @@ | ||
<animate-presence> | ||
<div class="item">Item A</div> | ||
<div class="item">Item B</div> | ||
<div class="item">Item C</div> | ||
<div class="item">Item A</div> | ||
<div class="item">Item B</div> | ||
<div class="item">Item C</div> | ||
</animate-presence> | ||
<style> | ||
.item[data-enter] { | ||
animation: fade 1s ease-in; | ||
.item[data-enter] { | ||
animation: fade 1s ease-in; | ||
} | ||
.item[data-exit] { | ||
animation: fade 1s ease-out reverse; | ||
} | ||
@keyframes fade { | ||
from { | ||
opacity: 0; | ||
} | ||
.item[data-exit] { | ||
animation: fade 1s ease-out reverse; | ||
to { | ||
opacity: 1; | ||
} | ||
@keyframes fade { | ||
from { opacity: 0; } | ||
to { opacity: 1; } | ||
} | ||
} | ||
</style> | ||
@@ -40,8 +62,13 @@ ``` | ||
| Attribute | Description | | ||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | | ||
| `data-enter` | Applied immediately when a node enters. Trigger your entrance animation here. | | ||
| `data-exit` | Applied when a node will be removed. Trigger your exit animations here. The node will be removed when the animation completes. | | ||
| `data-initial` | Applied immediately when a node enters. Useful for hiding an element before a delayed entrance. | | ||
| Attribute | Description | | ||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ | | ||
| `data-enter` | Applied immediately when a node enters. Trigger your entrance animation here. | | ||
| `data-exit` | Applied when a node will be removed. Trigger your exit animations here. The node will be removed when the animation completes. | | ||
> **Note** Animate Presence overrides the default `animation-fill-mode` of animating children to `both` (rather than `none`). | ||
> This provides more reasonable default behavior in most situations, so the rule is injected with a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) (`021`) high enough to override the `animation` shorthand for most rules (like `.item[data-enter]`). | ||
> If you experience visual flickering at the start or end of an animation, you may be using a selector with a higher specificity than `021`. | ||
> The `animation` shorthand resets the `animation-fill-mode` to `none`, so you likely need to manually specify `both`. | ||
## Stagger | ||
@@ -55,14 +82,13 @@ | ||
[data-enter] { | ||
animation-delay: calc(var(--i, 0) * 50ms); | ||
animation-delay: calc(var(--i, 0) * 50ms); | ||
} | ||
``` | ||
> Note: you'll likely want to use `[data-initial]` to hide the element before the `[data-enter]` animation is triggered | ||
## Nesting | ||
## Nesting | ||
Animate Presence uses a tree-based approach, meaning that nested `animate-presence` elements are aware of their parents and children. | ||
__Enter__ animations are applied top-down, meaning the top-level parent enters, which triggers the next child entrance, and so on. | ||
**Enter** animations are applied top-down, meaning the top-level parent enters, which triggers the next child entrance, and so on. | ||
__Exit__ animations are applied bottom-up, meaning the deepest child exits, which triggers the next parent exit, and so on. | ||
**Exit** animations are applied bottom-up, meaning the deepest child exits, which triggers the next parent exit, and so on. | ||
@@ -73,2 +99,3 @@ > Animate Presence relies on `querySelectorAll` to construct the internal tree, so it does not (yet) work with Shadow Roots. | ||
## Usage with [@stencil/router](https://github.com/ionic-team/stencil-router) | ||
For Stencil apps using `@stencil/router`, Animate Presence has an `<animated-route-switch>` component which allows you to smoothly animate between routes. | ||
@@ -79,3 +106,3 @@ | ||
2. Due to a current bug in `@stencil/router`, you will need to use `injectHistory` to pass `location` down to `animated-route-switch`. | ||
Hopefully this will be fixed soon, but there's a simple work around for now. | ||
@@ -88,29 +115,29 @@ | ||
@Component({ | ||
tag: "app-root", | ||
styleUrl: "app-root.css", | ||
scoped: true | ||
tag: 'app-root', | ||
styleUrl: 'app-root.css', | ||
scoped: true, | ||
}) | ||
export class AppRoot { | ||
@Prop() location: LocationSegments; | ||
@Prop() location: LocationSegments; | ||
render() { | ||
return ( | ||
<div> | ||
<header> | ||
<h1>Stencil App Starter</h1> | ||
</header> | ||
render() { | ||
return ( | ||
<div> | ||
<header> | ||
<h1>Stencil App Starter</h1> | ||
</header> | ||
<main> | ||
<stencil-router> | ||
{/* Pass `location` to animated-route-switch */} | ||
<animated-route-switch location={this.location}> | ||
{/* <animate-presence> should exist somewhere within these components */} | ||
<stencil-route url="/" exact={true} component="app-home" /> | ||
<stencil-route url="/profile/:name" component="app-profile" /> | ||
</animated-route-switch> | ||
</stencil-router> | ||
</main> | ||
</div> | ||
); | ||
} | ||
<main> | ||
<stencil-router> | ||
{/* Pass `location` to animated-route-switch */} | ||
<animated-route-switch location={this.location}> | ||
{/* <animate-presence> should exist somewhere within these components */} | ||
<stencil-route url="/" exact={true} component="app-home" /> | ||
<stencil-route url="/profile/:name" component="app-profile" /> | ||
</animated-route-switch> | ||
</stencil-router> | ||
</main> | ||
</div> | ||
); | ||
} | ||
} | ||
@@ -123,2 +150,2 @@ injectHistory(AppRoot); | ||
> As noted above, if your Stencil components rely on `shadow` encapsulation, Animate Presence won't work as expected (yet). I'm working on it. | ||
> For now, you can either use `scoped` encapsulation or stay tuned for updates! | ||
> For now, you can either use `scoped` encapsulation or stay tuned for updates! |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
0
145
2
309477
2
35
7516