vanimate-presence 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # VanimatePresence
2
+
3
+ `VanimatePresence` is a tiny TypeScript helper that keeps a removed node in the DOM until its exit animation completes.
4
+ It works with plain DOM code and any framework. VanJS usage is supported, but optional.
5
+
6
+ ## API
7
+
8
+ ```ts
9
+ import { VanimatePresence, webExit, cssExit } from "vanimate-presence";
10
+
11
+ const el = document.createElement("div");
12
+ el.textContent = "Loading...";
13
+
14
+ VanimatePresence(el, {
15
+ exit: webExit(
16
+ [{ opacity: 1 }, { opacity: 0 }],
17
+ { duration: 400, fill: "forwards" },
18
+ ),
19
+ });
20
+
21
+ document.body.append(el);
22
+ el.remove();
23
+ ```
24
+
25
+ CSS exit option:
26
+
27
+ ```ts
28
+ VanimatePresence(el, {
29
+ exit: cssExit("fade-out", { waitFor: "animationend" }),
30
+ });
31
+ ```
32
+
33
+ Attach to an existing element:
34
+
35
+ ```ts
36
+ document.querySelector("#target")?.VanimatePresence({
37
+ exit: cssExit("fade-out", { waitFor: "animationend" }),
38
+ });
39
+ ```
40
+
41
+ VanJS usage (optional):
42
+
43
+ ```ts
44
+ import van from "vanjs-core";
45
+ import { VanimatePresence, webExit } from "vanimate-presence";
46
+
47
+ const visible = van.state(true);
48
+ const { div } = van.tags;
49
+
50
+ const view = () =>
51
+ visible.val
52
+ ? VanimatePresence(div("Loading..."), {
53
+ exit: webExit(
54
+ [{ opacity: 1 }, { opacity: 0 }],
55
+ { duration: 400, fill: "forwards" },
56
+ ),
57
+ })
58
+ : "";
59
+ ```
60
+
61
+ ## Build
62
+
63
+ ```bash
64
+ npm run build
65
+ ```
66
+
67
+ ## Lint and Format (Biome)
68
+
69
+ ```bash
70
+ npm run lint
71
+ npm run format
72
+ npm run check
73
+ ```
74
+
75
+ ## Demo
76
+
77
+ ```bash
78
+ npm run demo
79
+ ```
80
+
81
+ Then open:
82
+
83
+ ```text
84
+ http://127.0.0.1:8000/demo/index.html
85
+ ```
86
+
87
+ Optional:
88
+ - use `PORT=9000 npm run demo` to pick a different port.
89
+
90
+ The demo compares:
91
+ - immediate removal (plain conditional VanJS render)
92
+ - delayed removal with Web Animations
93
+ - delayed removal with CSS animation classes
94
+ - attachment to an existing DOM element via prototype method
@@ -0,0 +1,3 @@
1
+ export type { PresenceCssExit, PresenceExit, PresenceExitHandler, PresenceWebExit, VanimatePresenceOptions, } from "./vanimate-presence";
2
+ export { cssExit, installVanimatePresencePrototype, VanimatePresence, webExit, } from "./vanimate-presence";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,OAAO,EACP,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var G=new WeakMap,Q=new WeakMap;function X(P,H){if(!H||!H.exit)throw Error("VanimatePresence requires an exit option.");if(G.get(P)?.exiting)return P;G.set(P,{options:H,exiting:!1});let O=P.ownerDocument??(typeof document>"u"?null:document);if(O)$(O);return P}function Y(){if(typeof Element>"u")return;if(typeof Element.prototype.VanimatePresence==="function")return;Object.defineProperty(Element.prototype,"VanimatePresence",{configurable:!0,enumerable:!1,writable:!0,value(H){if(!(this instanceof HTMLElement))throw Error("VanimatePresence can only be attached to HTMLElement.");return X(this,H)}})}Y();function Z(P,H,j){return{type:"web",keyframes:P,options:H,cancelRunning:j?.cancelRunning??!0}}function _(P,H){return{type:"css",exitClass:P,waitFor:H?.waitFor,timeoutMs:H?.timeoutMs}}function $(P){if(Q.has(P))return;let H=new MutationObserver((j)=>{for(let O of j){if(O.type!=="childList"||O.removedNodes.length===0)continue;for(let z=0;z<O.removedNodes.length;z+=1){let q=O.removedNodes.item(z);if(!q)continue;R(q,O.target,O.nextSibling)}}});H.observe(P,{childList:!0,subtree:!0}),Q.set(P,H)}function R(P,H,j){if(!(P instanceof HTMLElement))return;let O=G.get(P);if(!O)return;if(!H.isConnected)return;try{H.insertBefore(P,j)}catch{G.delete(P);return}if(O.exiting)return;O.exiting=!0,k(P,O).finally(()=>{G.delete(P),P.remove()})}async function k(P,H){let j=H.options.exit;if(typeof j==="function"){await Promise.resolve(j(P));return}if(j.type==="web"){await F(P,j);return}await L(P,j)}async function F(P,H){if(H.cancelRunning??!0)for(let O of P.getAnimations())O.cancel();await P.animate(H.keyframes,H.options).finished.catch(()=>{return})}function L(P,H){let j=H.waitFor??"animationend";return new Promise((O)=>{let z=!1,q,A=j==="both"?["animationend","transitionend"]:[j],B=()=>{if(z)return;if(z=!0,q!==void 0)window.clearTimeout(q);for(let E of A)P.removeEventListener(E,J);O()},J=(E)=>{if(E.target!==P)return;B()};for(let E of A)P.addEventListener(E,J);P.classList.add(H.exitClass);let K=H.timeoutMs??S(P);if(K<=0){queueMicrotask(B);return}q=window.setTimeout(B,K+34)})}function S(P){let H=window.getComputedStyle(P),j=U(H.transitionDuration,H.transitionDelay),O=U(H.animationDuration,H.animationDelay);return Math.max(j,O)}function U(P,H){let j=P.split(",").map(W),O=H.split(",").map(W),z=Math.max(j.length,O.length),q=0;for(let A=0;A<z;A+=1){let B=j[A%j.length]??0,J=O[A%O.length]??0;q=Math.max(q,B+J)}return q}function W(P){let H=P.trim();if(!H)return 0;if(H.endsWith("ms"))return Number.parseFloat(H);if(H.endsWith("s"))return Number.parseFloat(H)*1000;return 0}export{Z as webExit,Y as installVanimatePresencePrototype,_ as cssExit,X as VanimatePresence};
@@ -0,0 +1,32 @@
1
+ export type PresenceExitHandler<TElement extends HTMLElement> = (element: TElement) => unknown;
2
+ export interface PresenceWebExit {
3
+ type: "web";
4
+ keyframes: Keyframe[] | PropertyIndexedKeyframes;
5
+ options: KeyframeAnimationOptions;
6
+ cancelRunning?: boolean;
7
+ }
8
+ export interface PresenceCssExit {
9
+ type: "css";
10
+ exitClass: string;
11
+ waitFor?: "animationend" | "transitionend" | "both";
12
+ timeoutMs?: number;
13
+ }
14
+ export type PresenceExit<TElement extends HTMLElement> = PresenceExitHandler<TElement> | PresenceWebExit | PresenceCssExit;
15
+ export interface VanimatePresenceOptions<TElement extends HTMLElement> {
16
+ exit: PresenceExit<TElement>;
17
+ }
18
+ declare global {
19
+ interface Element {
20
+ VanimatePresence(options: VanimatePresenceOptions<HTMLElement>): this;
21
+ }
22
+ }
23
+ export declare function VanimatePresence<TElement extends HTMLElement>(element: TElement, options: VanimatePresenceOptions<TElement>): TElement;
24
+ export declare function installVanimatePresencePrototype(): void;
25
+ export declare function webExit(keyframes: Keyframe[] | PropertyIndexedKeyframes, options: KeyframeAnimationOptions, config?: {
26
+ cancelRunning?: boolean;
27
+ }): PresenceWebExit;
28
+ export declare function cssExit(exitClass: string, options?: {
29
+ waitFor?: "animationend" | "transitionend" | "both";
30
+ timeoutMs?: number;
31
+ }): PresenceCssExit;
32
+ //# sourceMappingURL=vanimate-presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vanimate-presence.d.ts","sourceRoot":"","sources":["../src/vanimate-presence.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW,IAAI,CAC9D,OAAO,EAAE,QAAQ,KACd,OAAO,CAAC;AAEb,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,CAAC;IACZ,SAAS,EAAE,QAAQ,EAAE,GAAG,wBAAwB,CAAC;IACjD,OAAO,EAAE,wBAAwB,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,CAAC,QAAQ,SAAS,WAAW,IACjD,mBAAmB,CAAC,QAAQ,CAAC,GAC7B,eAAe,GACf,eAAe,CAAC;AAEpB,MAAM,WAAW,uBAAuB,CAAC,QAAQ,SAAS,WAAW;IACnE,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;CAC9B;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO;QACf,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;KACvE;CACF;AAWD,wBAAgB,gBAAgB,CAAC,QAAQ,SAAS,WAAW,EAC3D,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,uBAAuB,CAAC,QAAQ,CAAC,GACzC,QAAQ,CAsBV;AAED,wBAAgB,gCAAgC,IAAI,IAAI,CA6BvD;AAID,wBAAgB,OAAO,CACrB,SAAS,EAAE,QAAQ,EAAE,GAAG,wBAAwB,EAChD,OAAO,EAAE,wBAAwB,EACjC,MAAM,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,eAAe,CAOjB;AAED,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,eAAe,CAOjB"}
@@ -0,0 +1 @@
1
+ var O=new WeakMap,X=new WeakMap;function _(j,q){if(!q||!q.exit)throw Error("VanimatePresence requires an exit option.");if(O.get(j)?.exiting)return j;O.set(j,{options:q,exiting:!1});let z=j.ownerDocument??(typeof document>"u"?null:document);if(z)R(z);return j}function $(){if(typeof Element>"u")return;if(typeof Element.prototype.VanimatePresence==="function")return;Object.defineProperty(Element.prototype,"VanimatePresence",{configurable:!0,enumerable:!1,writable:!0,value(q){if(!(this instanceof HTMLElement))throw Error("VanimatePresence can only be attached to HTMLElement.");return _(this,q)}})}$();function S(j,q,A){return{type:"web",keyframes:j,options:q,cancelRunning:A?.cancelRunning??!0}}function h(j,q){return{type:"css",exitClass:j,waitFor:q?.waitFor,timeoutMs:q?.timeoutMs}}function R(j){if(X.has(j))return;let q=new MutationObserver((A)=>{for(let z of A){if(z.type!=="childList"||z.removedNodes.length===0)continue;for(let G=0;G<z.removedNodes.length;G+=1){let B=z.removedNodes.item(G);if(!B)continue;W(B,z.target,z.nextSibling)}}});q.observe(j,{childList:!0,subtree:!0}),X.set(j,q)}function W(j,q,A){if(!(j instanceof HTMLElement))return;let z=O.get(j);if(!z)return;if(!q.isConnected)return;try{q.insertBefore(j,A)}catch{O.delete(j);return}if(z.exiting)return;z.exiting=!0,k(j,z).finally(()=>{O.delete(j),j.remove()})}async function k(j,q){let A=q.options.exit;if(typeof A==="function"){await Promise.resolve(A(j));return}if(A.type==="web"){await F(j,A);return}await L(j,A)}async function F(j,q){if(q.cancelRunning??!0)for(let z of j.getAnimations())z.cancel();await j.animate(q.keyframes,q.options).finished.catch(()=>{return})}function L(j,q){let A=q.waitFor??"animationend";return new Promise((z)=>{let G=!1,B,H=A==="both"?["animationend","transitionend"]:[A],J=()=>{if(G)return;if(G=!0,B!==void 0)window.clearTimeout(B);for(let K of H)j.removeEventListener(K,Q);z()},Q=(K)=>{if(K.target!==j)return;J()};for(let K of H)j.addEventListener(K,Q);j.classList.add(q.exitClass);let U=q.timeoutMs??P(j);if(U<=0){queueMicrotask(J);return}B=window.setTimeout(J,U+34)})}function P(j){let q=window.getComputedStyle(j),A=Y(q.transitionDuration,q.transitionDelay),z=Y(q.animationDuration,q.animationDelay);return Math.max(A,z)}function Y(j,q){let A=j.split(",").map(Z),z=q.split(",").map(Z),G=Math.max(A.length,z.length),B=0;for(let H=0;H<G;H+=1){let J=A[H%A.length]??0,Q=z[H%z.length]??0;B=Math.max(B,J+Q)}return B}function Z(j){let q=j.trim();if(!q)return 0;if(q.endsWith("ms"))return Number.parseFloat(q);if(q.endsWith("s"))return Number.parseFloat(q)*1000;return 0}export{S as webExit,$ as installVanimatePresencePrototype,h as cssExit,_ as VanimatePresence};
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "vanimate-presence",
3
+ "version": "0.1.0",
4
+ "description": "Simple AnimatePresence-like DOM persistence for VanJS",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "npm run clean && npm run build:js && npm run build:types",
19
+ "build:js": "bun build ./src/index.ts ./src/vanimate-presence.ts --outdir ./dist --format esm --target browser --minify",
20
+ "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
21
+ "check": "biome check .",
22
+ "clean": "rm -rf dist",
23
+ "demo:serve": "node ./scripts/serve-demo.mjs",
24
+ "demo": "npm run build && npm run demo:serve",
25
+ "format": "biome format --write .",
26
+ "lint": "biome lint ."
27
+ },
28
+ "keywords": [
29
+ "vanjs",
30
+ "animation",
31
+ "presence"
32
+ ],
33
+ "license": "MIT",
34
+ "devDependencies": {
35
+ "@biomejs/biome": "2.3.15"
36
+ }
37
+ }