@vielzeug/necromancer 2.0.0 → 2.0.1

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/dist/_handle.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function t(t,n,r=!1){let i=!1,a,o=!1,s,c=e(t,()=>{s?.(),s=void 0},r).then(e=>i&&o&&e.status===`cancelled`?{reason:a,status:`cancelled`}:e),l=e=>{i||(i=!0,a=e,o=e!==void 0,s?.(),s=void 0,t.cancel())};if(n){let e=()=>l(n.reason);n.addEventListener(`abort`,e,{once:!0}),s=()=>n.removeEventListener(`abort`,e)}return{animation:t,dispose:l,get disposed(){return i},result:c,[Symbol.dispose](){this.dispose()}}}function n(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),a=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};if(t){let e=()=>a(t.reason);t.addEventListener(`abort`,e,{once:!0}),r=()=>t.removeEventListener(`abort`,e)}return{dispose:a,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}exports.createAnimationGroup=n,exports.createAnimationHandle=t;
1
+ function e(e,t){let n=()=>t(e.reason);return e.addEventListener(`abort`,n,{once:!0}),()=>e.removeEventListener(`abort`,n)}function t(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function n(n,r,i=!1){let a=!1,o,s=!1,c,l=t(n,()=>{c?.(),c=void 0},i).then(e=>a&&s&&e.status===`cancelled`?{reason:o,status:`cancelled`}:e),u=e=>{a||(a=!0,o=e,s=e!==void 0,c?.(),c=void 0,n.cancel())};return r&&(c=e(r,u)),{animation:n,dispose:u,get disposed(){return a},result:l,[Symbol.dispose](){this.dispose()}}}function r(t,n){let r=!1,i,a=Promise.all(t.map(e=>e.result)).then(e=>(i?.(),i=void 0,e)),o=e=>{if(!r){r=!0,i?.(),i=void 0;for(let n of t)n.dispose(e)}};return n&&(i=e(n,o)),{dispose:o,get disposed(){return r},handles:t,results:a,[Symbol.dispose](){this.dispose()}}}exports.createAnimationGroup=r,exports.createAnimationHandle=n;
2
2
  //# sourceMappingURL=_handle.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.cjs","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":"AAEA,SAAS,EAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,EAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,EACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,EAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAEA,GAAI,EAAQ,CACV,IAAM,MAAc,EAAQ,EAAO,MAAM,EAEzC,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACtD,MAA4B,EAAO,oBAAoB,QAAS,CAAK,CACvE,CAEA,MAAO,CACL,YACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,EAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,EAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAEA,GAAI,EAAQ,CACV,IAAM,MAAc,EAAQ,EAAO,MAAM,EAEzC,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACtD,MAA4B,EAAO,oBAAoB,QAAS,CAAK,CACvE,CAEA,MAAO,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF"}
1
+ {"version":3,"file":"_handle.cjs","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":"AAMA,SAAS,EAA0B,EAAqB,EAAiD,CACvG,IAAM,MAAc,EAAQ,EAAO,MAAM,EAIzC,OAFA,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,MAEzC,EAAO,oBAAoB,QAAS,CAAK,CACxD,CAEA,SAAS,EAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,EAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,EACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,EAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,YACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,EAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,EAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.d.ts","sourceRoot":"","sources":["../src/_handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,MAAM,SAAS,CAAC;AAiBhF,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,UAAQ,GAAG,eAAe,CAkDlH;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAwC9G"}
1
+ {"version":3,"file":"_handle.d.ts","sourceRoot":"","sources":["../src/_handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,MAAM,SAAS,CAAC;AA6BhF,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,UAAQ,GAAG,eAAe,CA+ClH;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAqC9G"}
package/dist/_handle.js CHANGED
@@ -1,59 +1,55 @@
1
1
  //#region src/_handle.ts
2
- function e(e, t, n) {
2
+ function e(e, t) {
3
+ let n = () => t(e.reason);
4
+ return e.addEventListener("abort", n, { once: !0 }), () => e.removeEventListener("abort", n);
5
+ }
6
+ function t(e, t, n) {
3
7
  return e.finished.then(() => (t(), { status: n ? "reduced" : "finished" }), (e) => (t(), {
4
8
  reason: e,
5
9
  status: "cancelled"
6
10
  }));
7
11
  }
8
- function t(t, n, r = !1) {
9
- let i = !1, a, o = !1, s, c = e(t, () => {
10
- s?.(), s = void 0;
11
- }, r).then((e) => i && o && e.status === "cancelled" ? {
12
- reason: a,
12
+ function n(n, r, i = !1) {
13
+ let a = !1, o, s = !1, c, l = t(n, () => {
14
+ c?.(), c = void 0;
15
+ }, i).then((e) => a && s && e.status === "cancelled" ? {
16
+ reason: o,
13
17
  status: "cancelled"
14
- } : e), l = (e) => {
15
- i || (i = !0, a = e, o = e !== void 0, s?.(), s = void 0, t.cancel());
18
+ } : e), u = (e) => {
19
+ a || (a = !0, o = e, s = e !== void 0, c?.(), c = void 0, n.cancel());
16
20
  };
17
- if (n) {
18
- let e = () => l(n.reason);
19
- n.addEventListener("abort", e, { once: !0 }), s = () => n.removeEventListener("abort", e);
20
- }
21
- return {
22
- animation: t,
23
- dispose: l,
21
+ return r && (c = e(r, u)), {
22
+ animation: n,
23
+ dispose: u,
24
24
  get disposed() {
25
- return i;
25
+ return a;
26
26
  },
27
- result: c,
27
+ result: l,
28
28
  [Symbol.dispose]() {
29
29
  this.dispose();
30
30
  }
31
31
  };
32
32
  }
33
- function n(e, t) {
34
- let n = !1, r, i = Promise.all(e.map((e) => e.result)).then((e) => (r?.(), r = void 0, e)), a = (t) => {
35
- if (!n) {
36
- n = !0, r?.(), r = void 0;
37
- for (let n of e) n.dispose(t);
33
+ function r(t, n) {
34
+ let r = !1, i, a = Promise.all(t.map((e) => e.result)).then((e) => (i?.(), i = void 0, e)), o = (e) => {
35
+ if (!r) {
36
+ r = !0, i?.(), i = void 0;
37
+ for (let n of t) n.dispose(e);
38
38
  }
39
39
  };
40
- if (t) {
41
- let e = () => a(t.reason);
42
- t.addEventListener("abort", e, { once: !0 }), r = () => t.removeEventListener("abort", e);
43
- }
44
- return {
45
- dispose: a,
40
+ return n && (i = e(n, o)), {
41
+ dispose: o,
46
42
  get disposed() {
47
- return n;
43
+ return r;
48
44
  },
49
- handles: e,
50
- results: i,
45
+ handles: t,
46
+ results: a,
51
47
  [Symbol.dispose]() {
52
48
  this.dispose();
53
49
  }
54
50
  };
55
51
  }
56
52
  //#endregion
57
- export { n as createAnimationGroup, t as createAnimationHandle };
53
+ export { r as createAnimationGroup, n as createAnimationHandle };
58
54
 
59
55
  //# sourceMappingURL=_handle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.js","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":";AAEA,SAAS,EAAoB,GAAsB,GAAuB,GAA4C;CACpH,OAAO,EAAU,SAAS,YAEtB,EAAU,GAEH,EAAE,QAAQ,IAAU,YAAY,WAAW,KAEnD,OACC,EAAU,GAEH;EAAE;EAAQ,QAAQ;CAAY,EAEzC;AACF;AAGA,SAAgB,EAAsB,GAAsB,GAAsB,IAAU,IAAwB;CAClH,IAAI,IAAW,IACX,GACA,IAAmB,IACnB,GAEE,IAAmC,EACvC,SACM;EAEJ,AADA,IAAsB,GACtB,IAAsB,KAAA;CACxB,GACA,CACF,CAAC,CAAC,MAAM,MACF,KAAY,KAAoB,EAAQ,WAAW,cAC9C;EAAE,QAAQ;EAAe,QAAQ;CAAY,IAG/C,CACR,GAEK,KAAW,MAA2B;EACtC,MAEJ,IAAW,IACX,IAAgB,GAChB,IAAmB,MAAW,KAAA,GAC9B,IAAsB,GACtB,IAAsB,KAAA,GACtB,EAAU,OAAO;CACnB;CAEA,IAAI,GAAQ;EACV,IAAM,UAAc,EAAQ,EAAO,MAAM;EAGzC,AADA,EAAO,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC,GACtD,UAA4B,EAAO,oBAAoB,SAAS,CAAK;CACvE;CAEA,OAAO;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF;AAGA,SAAgB,EAAqB,GAAqC,GAAsC;CAC9G,IAAI,IAAW,IACX,GAEE,IAA+C,QAAQ,IAAI,EAAQ,KAAK,MAAW,EAAO,MAAM,CAAC,CAAC,CAAC,MACtG,OACC,IAAsB,GACtB,IAAsB,KAAA,GAEf,EAEX,GAEM,KAAW,MAA2B;EACtC,QAIJ;GAFA,IAAW,IACX,IAAsB,GACtB,IAAsB,KAAA;GACtB,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAM;EAD7B;CAExB;CAEA,IAAI,GAAQ;EACV,IAAM,UAAc,EAAQ,EAAO,MAAM;EAGzC,AADA,EAAO,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC,GACtD,UAA4B,EAAO,oBAAoB,SAAS,CAAK;CACvE;CAEA,OAAO;EACL;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF"}
1
+ {"version":3,"file":"_handle.js","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":";AAMA,SAAS,EAA0B,GAAqB,GAAiD;CACvG,IAAM,UAAc,EAAQ,EAAO,MAAM;CAIzC,OAFA,EAAO,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC,SAEzC,EAAO,oBAAoB,SAAS,CAAK;AACxD;AAEA,SAAS,EAAoB,GAAsB,GAAuB,GAA4C;CACpH,OAAO,EAAU,SAAS,YAEtB,EAAU,GAEH,EAAE,QAAQ,IAAU,YAAY,WAAW,KAEnD,OACC,EAAU,GAEH;EAAE;EAAQ,QAAQ;CAAY,EAEzC;AACF;AAGA,SAAgB,EAAsB,GAAsB,GAAsB,IAAU,IAAwB;CAClH,IAAI,IAAW,IACX,GACA,IAAmB,IACnB,GAEE,IAAmC,EACvC,SACM;EAEJ,AADA,IAAsB,GACtB,IAAsB,KAAA;CACxB,GACA,CACF,CAAC,CAAC,MAAM,MACF,KAAY,KAAoB,EAAQ,WAAW,cAC9C;EAAE,QAAQ;EAAe,QAAQ;CAAY,IAG/C,CACR,GAEK,KAAW,MAA2B;EACtC,MAEJ,IAAW,IACX,IAAgB,GAChB,IAAmB,MAAW,KAAA,GAC9B,IAAsB,GACtB,IAAsB,KAAA,GACtB,EAAU,OAAO;CACnB;CAMA,OAJI,MACF,IAAsB,EAA0B,GAAQ,CAAO,IAG1D;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF;AAGA,SAAgB,EAAqB,GAAqC,GAAsC;CAC9G,IAAI,IAAW,IACX,GAEE,IAA+C,QAAQ,IAAI,EAAQ,KAAK,MAAW,EAAO,MAAM,CAAC,CAAC,CAAC,MACtG,OACC,IAAsB,GACtB,IAAsB,KAAA,GAEf,EAEX,GAEM,KAAW,MAA2B;EACtC,QAIJ;GAFA,IAAW,IACX,IAAsB,GACtB,IAAsB,KAAA;GACtB,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAM;EAD7B;CAExB;CAMA,OAJI,MACF,IAAsB,EAA0B,GAAQ,CAAO,IAG1D;EACL;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"_motion.cjs","names":[],"sources":["../src/_motion.ts"],"sourcesContent":["import type { AnimateOptions, MotionMode } from './types';\n\nimport { NecromancerConfigError } from './errors';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n"],"mappings":"gCAIA,IAAM,EAAmB,IAGzB,SAAgB,EAAmB,EAA2B,CAK5D,OAJI,IAAS,OAAe,GAExB,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,CAGA,SAAgB,EAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,WAAU,UAAW,EAAY,OAAQ,EAAU,SAAU,OAAQ,EAAS,GAAG,GAAkB,EACrG,EACJ,IAAa,IAAA,GAAY,CAAE,GAAG,EAAe,SAAU,CAAiB,EAAI,CAAE,GAAG,EAAe,UAAS,EAE3G,OAAO,EAAU,CAAE,GAAG,EAAQ,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAI,CACtF,CAGA,SAAgB,EAAmB,EAAyB,EAAgC,CAC1F,GAAI,IAAW,EAAG,OAAO,EAEzB,IAAM,EAAQ,EAAQ,OAAS,EAE/B,GAAI,OAAO,GAAU,SACnB,MAAM,IAAI,EAAA,uBAAuB,yDAAyD,OAAO,EAAM,EAAE,EAG3G,MAAO,CAAE,GAAG,EAAS,MAAO,EAAQ,CAAO,CAC7C"}
1
+ {"version":3,"file":"_motion.cjs","names":[],"sources":["../src/_motion.ts"],"sourcesContent":["import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n"],"mappings":"gCAGA,IAAM,EAAmB,IAGzB,SAAgB,EAAmB,EAA2B,CAK5D,OAJI,IAAS,OAAe,GAExB,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,CAGA,SAAgB,EAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,WAAU,UAAW,EAAY,OAAQ,EAAU,SAAU,OAAQ,EAAS,GAAG,GAAkB,EACrG,EACJ,IAAa,IAAA,GAAY,CAAE,GAAG,EAAe,SAAU,CAAiB,EAAI,CAAE,GAAG,EAAe,UAAS,EAE3G,OAAO,EAAU,CAAE,GAAG,EAAQ,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAI,CACtF,CAGA,SAAgB,EAAmB,EAAyB,EAAgC,CAC1F,GAAI,IAAW,EAAG,OAAO,EAEzB,IAAM,EAAQ,EAAQ,OAAS,EAE/B,GAAI,OAAO,GAAU,SACnB,MAAM,IAAI,EAAA,uBAAuB,yDAAyD,OAAO,EAAM,EAAE,EAG3G,MAAO,CAAE,GAAG,EAAS,MAAO,EAAQ,CAAO,CAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"_motion.d.ts","sourceRoot":"","sources":["../src/_motion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAM1D,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAM5D;AAED,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,EAAE,OAAO,UAAQ,GAAG,wBAAwB,CAM/G;AAED,2GAA2G;AAC3G,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAU1F"}
1
+ {"version":3,"file":"_motion.d.ts","sourceRoot":"","sources":["../src/_motion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAI1D,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAM5D;AAED,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,EAAE,OAAO,UAAQ,GAAG,wBAAwB,CAM/G;AAED,2GAA2G;AAC3G,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAU1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"_motion.js","names":[],"sources":["../src/_motion.ts"],"sourcesContent":["import type { AnimateOptions, MotionMode } from './types';\n\nimport { NecromancerConfigError } from './errors';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n"],"mappings":";;AAIA,IAAM,IAAmB;AAGzB,SAAgB,EAAmB,GAA2B;CAK5D,OAJI,MAAS,SAAe,KAExB,MAAS,aAEN,OAAO,cAAe,cAAc,WAAW,kCAAkC,CAAC,CAAC;AAC5F;AAGA,SAAgB,EAAwB,IAA0B,CAAC,GAAG,IAAU,IAAiC;CAC/G,IAAM,EAAE,aAAU,WAAW,GAAY,QAAQ,IAAU,UAAU,QAAQ,GAAS,GAAG,MAAkB,GACrG,IACJ,MAAa,KAAA,IAAY;EAAE,GAAG;EAAe,UAAU;CAAiB,IAAI;EAAE,GAAG;EAAe;CAAS;CAE3G,OAAO,IAAU;EAAE,GAAG;EAAQ,OAAO;EAAG,UAAU;EAAG,UAAU;EAAG,YAAY;CAAE,IAAI;AACtF;AAGA,SAAgB,EAAmB,GAAyB,GAAgC;CAC1F,IAAI,MAAW,GAAG,OAAO;CAEzB,IAAM,IAAQ,EAAQ,SAAS;CAE/B,IAAI,OAAO,KAAU,UACnB,MAAM,IAAI,EAAuB,yDAAyD,OAAO,EAAM,EAAE;CAG3G,OAAO;EAAE,GAAG;EAAS,OAAO,IAAQ;CAAO;AAC7C"}
1
+ {"version":3,"file":"_motion.js","names":[],"sources":["../src/_motion.ts"],"sourcesContent":["import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n"],"mappings":";;AAGA,IAAM,IAAmB;AAGzB,SAAgB,EAAmB,GAA2B;CAK5D,OAJI,MAAS,SAAe,KAExB,MAAS,aAEN,OAAO,cAAe,cAAc,WAAW,kCAAkC,CAAC,CAAC;AAC5F;AAGA,SAAgB,EAAwB,IAA0B,CAAC,GAAG,IAAU,IAAiC;CAC/G,IAAM,EAAE,aAAU,WAAW,GAAY,QAAQ,IAAU,UAAU,QAAQ,GAAS,GAAG,MAAkB,GACrG,IACJ,MAAa,KAAA,IAAY;EAAE,GAAG;EAAe,UAAU;CAAiB,IAAI;EAAE,GAAG;EAAe;CAAS;CAE3G,OAAO,IAAU;EAAE,GAAG;EAAQ,OAAO;EAAG,UAAU;EAAG,UAAU;EAAG,YAAY;CAAE,IAAI;AACtF;AAGA,SAAgB,EAAmB,GAAyB,GAAgC;CAC1F,IAAI,MAAW,GAAG,OAAO;CAEzB,IAAM,IAAQ,EAAQ,SAAS;CAE/B,IAAI,OAAO,KAAU,UACnB,MAAM,IAAI,EAAuB,yDAAyD,OAAO,EAAM,EAAE;CAG3G,OAAO;EAAE,GAAG;EAAS,OAAO,IAAQ;CAAO;AAC7C"}
@@ -1,2 +1,2 @@
1
- const e=require("./_elements.cjs"),t=require("./_handle.cjs"),n=require("./errors.cjs"),r=require("./_motion.cjs"),i=require("./animate.cjs");function a(e){if(!Number.isFinite(e)||e<0)throw new n.NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function o(n,o,s={}){let{signal:c,stagger:l=0,...u}=s;if(a(l),c?.aborted)throw c.reason;let d=e.uniqueElements(n),f=d.map((e,t)=>typeof o==`function`?o(e,t,d.length):o),p=[];try{for(let[e,t]of d.entries()){let n=f[e];p.push(i.animate(t,n,r.withStaggeredDelay(u,e*l)))}}catch(e){for(let t of p)t.dispose(e);throw e}return t.createAnimationGroup(p,c)}exports.animateEach=o;
1
+ const e=require("./_handle.cjs"),t=require("./errors.cjs"),n=require("./_motion.cjs"),r=require("./animate.cjs"),i=require("./_elements.cjs");function a(e){if(!Number.isFinite(e)||e<0)throw new t.NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function o(t,o,s={}){let{signal:c,stagger:l=0,...u}=s;if(a(l),c?.aborted)throw c.reason;let d=i.uniqueElements(t),f=d.map((e,t)=>typeof o==`function`?o(e,t,d.length):o),p=[];try{for(let[e,t]of d.entries()){let i=f[e];p.push(r.animate(t,i,n.withStaggeredDelay(u,e*l)))}}catch(e){for(let t of p)t.dispose(e);throw e}return e.createAnimationGroup(p,c)}exports.animateEach=o;
2
2
  //# sourceMappingURL=animate-each.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.cjs","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":"8IAQA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAA,uBAAuB,gDAAgD,CAErF,CAQA,SAAgB,EACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,EAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAA,eAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EACM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAA,QAAQ,EAAS,EAAe,EAAA,mBAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAA,qBAAqB,EAAS,CAAM,CAC7C"}
1
+ {"version":3,"file":"animate-each.cjs","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":"8IAOA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAA,uBAAuB,gDAAgD,CAErF,CAQA,SAAgB,EACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,EAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAA,eAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EACM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAA,QAAQ,EAAS,EAAe,EAAA,mBAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAA,qBAAqB,EAAS,CAAM,CAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.d.ts","sourceRoot":"","sources":["../src/animate-each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAmB,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAc/G;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,EACtC,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CA2BhB"}
1
+ {"version":3,"file":"animate-each.d.ts","sourceRoot":"","sources":["../src/animate-each.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAmB,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAQ/G;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,EACtC,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CA2BhB"}
@@ -1,26 +1,26 @@
1
- import { uniqueElements as e } from "./_elements.js";
2
- import { createAnimationGroup as t } from "./_handle.js";
3
- import { NecromancerConfigError as n } from "./errors.js";
4
- import { withStaggeredDelay as r } from "./_motion.js";
5
- import { animate as i } from "./animate.js";
1
+ import { createAnimationGroup as e } from "./_handle.js";
2
+ import { NecromancerConfigError as t } from "./errors.js";
3
+ import { withStaggeredDelay as n } from "./_motion.js";
4
+ import { animate as r } from "./animate.js";
5
+ import { uniqueElements as i } from "./_elements.js";
6
6
  //#region src/animate-each.ts
7
7
  function a(e) {
8
- if (!Number.isFinite(e) || e < 0) throw new n("stagger must be a finite, non-negative number.");
8
+ if (!Number.isFinite(e) || e < 0) throw new t("stagger must be a finite, non-negative number.");
9
9
  }
10
- function o(n, o, s = {}) {
10
+ function o(t, o, s = {}) {
11
11
  let { signal: c, stagger: l = 0, ...u } = s;
12
12
  if (a(l), c?.aborted) throw c.reason;
13
- let d = e(n), f = d.map((e, t) => typeof o == "function" ? o(e, t, d.length) : o), p = [];
13
+ let d = i(t), f = d.map((e, t) => typeof o == "function" ? o(e, t, d.length) : o), p = [];
14
14
  try {
15
15
  for (let [e, t] of d.entries()) {
16
- let n = f[e];
17
- p.push(i(t, n, r(u, e * l)));
16
+ let i = f[e];
17
+ p.push(r(t, i, n(u, e * l)));
18
18
  }
19
19
  } catch (e) {
20
20
  for (let t of p) t.dispose(e);
21
21
  throw e;
22
22
  }
23
- return t(p, c);
23
+ return e(p, c);
24
24
  }
25
25
  //#endregion
26
26
  export { o as animateEach };
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.js","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":";;;;;;AAQA,SAAS,EAAgB,GAAuB;CAC9C,IAAI,CAAC,OAAO,SAAS,CAAO,KAAK,IAAU,GACzC,MAAM,IAAI,EAAuB,gDAAgD;AAErF;AAQA,SAAgB,EACd,GACA,GACA,IAA8B,CAAC,GACf;CAChB,IAAM,EAAE,WAAQ,aAAU,GAAG,GAAG,MAAqB;CAIrD,IAFA,EAAgB,CAAO,GAEnB,GAAQ,SAAS,MAAM,EAAO;CAElC,IAAM,IAAS,EAAe,CAAQ,GAChC,IAAS,EAAO,KAAK,GAAS,MAClC,OAAO,KAAc,aAAa,EAAU,GAAS,GAAO,EAAO,MAAM,IAAI,CAC/E,GACM,IAA6B,CAAC;CAEpC,IAAI;EACF,KAAK,IAAM,CAAC,GAAO,MAAY,EAAO,QAAQ,GAAG;GAE/C,IAAM,IAAgB,EAAO;GAE7B,EAAQ,KAAK,EAAQ,GAAS,GAAe,EAAmB,GAAkB,IAAQ,CAAO,CAAC,CAAC;EACrG;CACF,SAAS,GAAO;EACd,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAK;EAElD,MAAM;CACR;CAEA,OAAO,EAAqB,GAAS,CAAM;AAC7C"}
1
+ {"version":3,"file":"animate-each.js","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":";;;;;;AAOA,SAAS,EAAgB,GAAuB;CAC9C,IAAI,CAAC,OAAO,SAAS,CAAO,KAAK,IAAU,GACzC,MAAM,IAAI,EAAuB,gDAAgD;AAErF;AAQA,SAAgB,EACd,GACA,GACA,IAA8B,CAAC,GACf;CAChB,IAAM,EAAE,WAAQ,aAAU,GAAG,GAAG,MAAqB;CAIrD,IAFA,EAAgB,CAAO,GAEnB,GAAQ,SAAS,MAAM,EAAO;CAElC,IAAM,IAAS,EAAe,CAAQ,GAChC,IAAS,EAAO,KAAK,GAAS,MAClC,OAAO,KAAc,aAAa,EAAU,GAAS,GAAO,EAAO,MAAM,IAAI,CAC/E,GACM,IAA6B,CAAC;CAEpC,IAAI;EACF,KAAK,IAAM,CAAC,GAAO,MAAY,EAAO,QAAQ,GAAG;GAE/C,IAAM,IAAgB,EAAO;GAE7B,EAAQ,KAAK,EAAQ,GAAS,GAAe,EAAmB,GAAkB,IAAQ,CAAO,CAAC,CAAC;EACrG;CACF,SAAS,GAAO;EACd,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAK;EAElD,MAAM;CACR;CAEA,OAAO,EAAqB,GAAS,CAAM;AAC7C"}
package/dist/animate.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_handle.cjs"),t=require("./errors.cjs"),n=require("./_motion.cjs"),r=require("./_active.cjs");function i(i,a,o={}){if(o.signal?.aborted)throw o.signal.reason;if(typeof i.animate!=`function`)throw new t.NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);o.interrupt===`cancel`&&r.interruptAnimations(i);let s=n.shouldReduceMotion(o.motion??`system`),c=i.animate(a,n.resolveAnimationOptions(o,s)),l=e.createAnimationHandle(c,o.signal,s);return r.trackAnimation(i,l),l}exports.animate=i;
1
+ const e=require("./_active.cjs"),t=require("./_handle.cjs"),n=require("./errors.cjs"),r=require("./_motion.cjs");function i(i,a,o={}){if(o.signal?.aborted)throw o.signal.reason;if(typeof i.animate!=`function`)throw new n.NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);o.interrupt===`cancel`&&e.interruptAnimations(i);let s=r.shouldReduceMotion(o.motion??`system`),c=i.animate(a,r.resolveAnimationOptions(o,s)),l=t.createAnimationHandle(c,o.signal,s);return e.trackAnimation(i,l),l}exports.animate=i;
2
2
  //# sourceMappingURL=animate.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate.cjs","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\nimport { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":"iHAaA,SAAgB,EAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAAA,4BAA4B,8DAA8D,EAGlG,EAAQ,YAAc,UAAU,EAAA,oBAAoB,CAAO,EAE/D,IAAM,EAAU,EAAA,mBAAmB,EAAQ,QAAU,QAAQ,EACvD,EAAkB,EAAQ,QAC9B,EACA,EAAA,wBAAwB,EAAS,CAAO,CAC1C,EACM,EAAS,EAAA,sBAAsB,EAAiB,EAAQ,OAAQ,CAAO,EAI7E,OAFA,EAAA,eAAe,EAAS,CAAM,EAEvB,CACT"}
1
+ {"version":3,"file":"animate.cjs","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":"iHAYA,SAAgB,EAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAAA,4BAA4B,8DAA8D,EAGlG,EAAQ,YAAc,UAAU,EAAA,oBAAoB,CAAO,EAE/D,IAAM,EAAU,EAAA,mBAAmB,EAAQ,QAAU,QAAQ,EACvD,EAAkB,EAAQ,QAC9B,EACA,EAAA,wBAAwB,EAAS,CAAO,CAC1C,EACM,EAAS,EAAA,sBAAsB,EAAiB,EAAQ,OAAQ,CAAO,EAI7E,OAFA,EAAA,eAAe,EAAS,CAAM,EAEvB,CACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAO1E;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,eAAe,CAmB7G"}
1
+ {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,eAAe,CAmB7G"}
package/dist/animate.js CHANGED
@@ -1,14 +1,14 @@
1
- import { createAnimationHandle as e } from "./_handle.js";
2
- import { NecromancerUnsupportedError as t } from "./errors.js";
3
- import { resolveAnimationOptions as n, shouldReduceMotion as r } from "./_motion.js";
4
- import { interruptAnimations as i, trackAnimation as a } from "./_active.js";
1
+ import { interruptAnimations as e, trackAnimation as t } from "./_active.js";
2
+ import { createAnimationHandle as n } from "./_handle.js";
3
+ import { NecromancerUnsupportedError as r } from "./errors.js";
4
+ import { resolveAnimationOptions as i, shouldReduceMotion as a } from "./_motion.js";
5
5
  //#region src/animate.ts
6
6
  function o(o, s, c = {}) {
7
7
  if (c.signal?.aborted) throw c.signal.reason;
8
- if (typeof o.animate != "function") throw new t("The Web Animations API is not available in this environment.");
9
- c.interrupt === "cancel" && i(o);
10
- let l = r(c.motion ?? "system"), u = o.animate(s, n(c, l)), d = e(u, c.signal, l);
11
- return a(o, d), d;
8
+ if (typeof o.animate != "function") throw new r("The Web Animations API is not available in this environment.");
9
+ c.interrupt === "cancel" && e(o);
10
+ let l = a(c.motion ?? "system"), u = o.animate(s, i(c, l)), d = n(u, c.signal, l);
11
+ return t(o, d), d;
12
12
  }
13
13
  //#endregion
14
14
  export { o as animate };
@@ -1 +1 @@
1
- {"version":3,"file":"animate.js","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\nimport { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":";;;;;AAaA,SAAgB,EAAQ,GAAkB,GAAsB,IAA0B,CAAC,GAAoB;CAC7G,IAAI,EAAQ,QAAQ,SAAS,MAAM,EAAQ,OAAO;CAElD,IAAI,OAAO,EAAQ,WAAY,YAC7B,MAAM,IAAI,EAA4B,8DAA8D;CAGtG,AAAI,EAAQ,cAAc,YAAU,EAAoB,CAAO;CAE/D,IAAM,IAAU,EAAmB,EAAQ,UAAU,QAAQ,GACvD,IAAkB,EAAQ,QAC9B,GACA,EAAwB,GAAS,CAAO,CAC1C,GACM,IAAS,EAAsB,GAAiB,EAAQ,QAAQ,CAAO;CAI7E,OAFA,EAAe,GAAS,CAAM,GAEvB;AACT"}
1
+ {"version":3,"file":"animate.js","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":";;;;;AAYA,SAAgB,EAAQ,GAAkB,GAAsB,IAA0B,CAAC,GAAoB;CAC7G,IAAI,EAAQ,QAAQ,SAAS,MAAM,EAAQ,OAAO;CAElD,IAAI,OAAO,EAAQ,WAAY,YAC7B,MAAM,IAAI,EAA4B,8DAA8D;CAGtG,AAAI,EAAQ,cAAc,YAAU,EAAoB,CAAO;CAE/D,IAAM,IAAU,EAAmB,EAAQ,UAAU,QAAQ,GACvD,IAAkB,EAAQ,QAC9B,GACA,EAAwB,GAAS,CAAO,CAC1C,GACM,IAAS,EAAsB,GAAiB,EAAQ,QAAQ,CAAO;CAI7E,OAFA,EAAe,GAAS,CAAM,GAEvB;AACT"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { animateEach } from './animate-each';
2
1
  export { animate } from './animate';
2
+ export { animateEach } from './animate-each';
3
3
  export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';
4
4
  export { captureLayout } from './layout';
5
5
  export type { AnimateEachOptions, AnimateOptions, AnimationGroup, AnimationHandle, AnimationResult, KeyframeFactory, Keyframes, LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition, MotionMode, } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,SAAS,CAAC"}
package/dist/layout.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_elements.cjs"),t=require("./errors.cjs"),n=require("./animate-each.cjs");function r(e,t){let n=10**t;return Math.round(e*n)/n}function i(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function a(e){let t=e.getBoundingClientRect(),n=i(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function o(e,t){return t===0?1:e/t}function s(e,n,r){let i=n(e);if(typeof i!=`string`||i.length===0)throw new t.NecromancerConfigError(`getKey must return a non-empty string for every ${r} element.`);return i}function c(e,n,r){let i=new Map;for(let a of e){let e=s(a.element,n,r);if(i.has(e))throw new t.NecromancerConfigError(`getKey returned duplicate keys for the ${r} elements.`);i.set(e,a)}return i}function l(e,t){if(!e.isConnected)return;let n=a(e),r=t.x-n.x,i=t.y-n.y,s=o(t.width,n.width),c=o(t.height,n.height);return r===0&&i===0&&s===1&&c===1?void 0:{element:e,scaleX:s,scaleY:c,x:r,y:i}}function u(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[l(e,t.rect)].filter(e=>!!e):[]})}function d(e,n,r){let i=new Set;return n.flatMap(n=>{let a=s(n,r,`committed`),o=e.get(a);if(i.has(a))throw new t.NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return i.add(a),o?[l(n,o.rect)].filter(e=>!!e):[]})}function f(i,o={}){let s=e.uniqueElements(i).map(e=>({element:e,rect:a(e)})),l=o.getKey,f=l?c(s,l,`captured`):void 0,p=!1;return{animate(i={}){if(p)throw new t.NecromancerConfigError(`This layout transition has already been animated.`);let{elements:a,...o}=i,c=e.uniqueElements(a??s.map(({element:e})=>e)),m=l&&f?d(f,c,l):u(s,c);return p=!0,n.animateEach(m.map(({element:e})=>e),(e,t)=>{let n=m[t],i=`${r(n.x,2)}px ${r(n.y,2)}px`;return[{composite:`add`,scale:`${r(n.scaleX,4)} ${r(n.scaleY,4)}`,translate:i},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.captureLayout=f;
1
+ const e=require("./errors.cjs"),t=require("./_elements.cjs"),n=require("./animate-each.cjs");function r(e,t){let n=10**t;return Math.round(e*n)/n}function i(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function a(e){let t=e.getBoundingClientRect(),n=i(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function o(e,t){return t===0?1:e/t}function s(t,n,r){let i=n(t);if(typeof i!=`string`||i.length===0)throw new e.NecromancerConfigError(`getKey must return a non-empty string for every ${r} element.`);return i}function c(t,n,r){let i=new Map;for(let a of t){let t=s(a.element,n,r);if(i.has(t))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the ${r} elements.`);i.set(t,a)}return i}function l(e,t){if(!e.isConnected)return;let n=a(e),r=t.x-n.x,i=t.y-n.y,s=o(t.width,n.width),c=o(t.height,n.height);return r===0&&i===0&&s===1&&c===1?void 0:{element:e,scaleX:s,scaleY:c,x:r,y:i}}function u(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[l(e,t.rect)].filter(e=>!!e):[]})}function d(t,n,r){let i=new Set;return n.flatMap(n=>{let a=s(n,r,`committed`),o=t.get(a);if(i.has(a))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return i.add(a),o?[l(n,o.rect)].filter(e=>!!e):[]})}function f(i,o={}){let s=t.uniqueElements(i).map(e=>({element:e,rect:a(e)})),l=o.getKey,f=l?c(s,l,`captured`):void 0,p=!1;return{animate(i={}){if(p)throw new e.NecromancerConfigError(`This layout transition has already been animated.`);let{elements:a,...o}=i,c=t.uniqueElements(a??s.map(({element:e})=>e)),m=l&&f?d(f,c,l):u(s,c);return p=!0,n.animateEach(m.map(({element:e})=>e),(e,t)=>{let n=m[t],i=`${r(n.x,2)}px ${r(n.y,2)}px`;return[{composite:`add`,scale:`${r(n.scaleX,4)} ${r(n.scaleY,4)}`,translate:i},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.captureLayout=f;
2
2
  //# sourceMappingURL=layout.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.cjs","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"6FAaA,SAAS,EAAM,EAAe,EAA2B,CACvD,IAAM,EAAS,IAAM,EAErB,OAAO,KAAK,MAAM,EAAQ,CAAM,EAAI,CACtC,CAMA,SAAS,EAAY,EAA+D,CAClF,GAAI,gBAAiB,GAAW,iBAAkB,EAChD,MAAO,CAAE,OAAS,EAAwB,aAAc,MAAQ,EAAwB,WAAY,EAGtG,IAAM,EAAO,EAAQ,sBAAsB,EAE3C,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,KAAM,CAClD,CAEA,SAAS,EAAc,EAA8B,CACnD,IAAM,EAAO,EAAQ,sBAAsB,EACrC,EAAO,EAAY,CAAO,EAEhC,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,MAAO,EAAG,EAAK,EAAG,EAAG,EAAK,CAAE,CACxE,CAIA,SAAS,EAAa,EAAkB,EAAsB,CAC5D,OAAO,IAAS,EAAI,EAAI,EAAW,CACrC,CAEA,SAAS,EAAQ,EAAkB,EAAsC,EAAuB,CAC9F,IAAM,EAAM,EAAO,CAAO,EAE1B,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAC5C,MAAM,IAAI,EAAA,uBAAuB,mDAAmD,EAAM,UAAU,EAGtG,OAAO,CACT,CAEA,SAAS,EAAa,EAAiC,EAAsC,EAAe,CAC1G,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAM,EAAQ,EAAM,QAAS,EAAQ,CAAK,EAEhD,GAAI,EAAK,IAAI,CAAG,EACd,MAAM,IAAI,EAAA,uBAAuB,0CAA0C,EAAM,WAAW,EAG9F,EAAK,IAAI,EAAK,CAAK,CACrB,CAEA,OAAO,CACT,CAEA,SAAS,EAAe,EAAkB,EAA4C,CACpF,GAAI,CAAC,EAAQ,YAAa,OAE1B,IAAM,EAAO,EAAc,CAAO,EAC5B,EAAI,EAAK,EAAI,EAAK,EAClB,EAAI,EAAK,EAAI,EAAK,EAClB,EAAS,EAAa,EAAK,MAAO,EAAK,KAAK,EAC5C,EAAS,EAAa,EAAK,OAAQ,EAAK,MAAM,EAEpD,OAAO,IAAM,GAAK,IAAM,GAAK,IAAW,GAAK,IAAW,EAAI,IAAA,GAAY,CAAE,UAAS,SAAQ,SAAQ,IAAG,GAAE,CAC1G,CAEA,SAAS,EAAgB,EAAiC,EAA8C,CACtG,IAAM,EAAW,IAAI,IAAI,EAAQ,IAAK,GAAU,CAAC,EAAM,QAAS,CAAK,CAAC,CAAC,EAEvE,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAQ,EAAS,IAAI,CAAO,EAElC,OAAO,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAEA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAY,IAAI,IAEtB,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAM,EAAQ,EAAS,EAAQ,WAAW,EAC1C,EAAQ,EAAS,IAAI,CAAG,EAE9B,GAAI,EAAU,IAAI,CAAG,EACnB,MAAM,IAAI,EAAA,uBAAuB,4DAA4D,EAK/F,OAFA,EAAU,IAAI,CAAG,EAEV,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAWA,SAAgB,EAAc,EAA6B,EAAgC,CAAC,EAAqB,CAC/G,IAAM,EAAyB,EAAA,eAAe,CAAQ,CAAC,CAAC,IAAK,IAAa,CACxE,UACA,KAAM,EAAc,CAAO,CAC7B,EAAE,EACI,EAAS,EAAQ,OACjB,EAAW,EAAS,EAAa,EAAS,EAAQ,UAAU,EAAI,IAAA,GAElE,EAAW,GAEf,MAAO,CACL,QAAQ,EAAkC,CAAC,EAAG,CAC5C,GAAI,EAAU,MAAM,IAAI,EAAA,uBAAuB,mDAAmD,EAElG,GAAM,CAAE,SAAU,EAAmB,GAAG,GAAqB,EACvD,EAAW,EAAA,eAAe,GAAqB,EAAQ,KAAK,CAAE,aAAc,CAAO,CAAC,EACpF,EAAU,GAAU,EAAW,EAAW,EAAU,EAAU,CAAM,EAAI,EAAgB,EAAS,CAAQ,EAI/G,MAFA,GAAW,GAEJ,EAAA,YACL,EAAQ,KAAK,CAAE,aAAc,CAAO,GACnC,EAAG,IAAU,CAEZ,IAAM,EAAS,EAAQ,GACjB,EAAY,GAAG,EAAM,EAAO,EAAG,CAAC,EAAE,KAAK,EAAM,EAAO,EAAG,CAAC,EAAE,IAGhE,MAAO,CACL,CAAE,UAAW,MAAO,MAAA,GAHL,EAAM,EAAO,OAAQ,CAAC,EAAE,GAAG,EAAM,EAAO,OAAQ,CAAC,IAGrC,WAAU,EACrC,CAAE,UAAW,MAAO,MAAO,MAAO,UAAW,SAAU,CACzD,CACF,EACA,CACF,CACF,CACF,CACF"}
1
+ {"version":3,"file":"layout.cjs","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAuH9F;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CAqC/G"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA0B,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAmH9F;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CAqC/G"}
package/dist/layout.js CHANGED
@@ -1,5 +1,5 @@
1
- import { uniqueElements as e } from "./_elements.js";
2
- import { NecromancerConfigError as t } from "./errors.js";
1
+ import { NecromancerConfigError as e } from "./errors.js";
2
+ import { uniqueElements as t } from "./_elements.js";
3
3
  import { animateEach as n } from "./animate-each.js";
4
4
  //#region src/layout.ts
5
5
  function r(e, t) {
@@ -29,17 +29,17 @@ function a(e) {
29
29
  function o(e, t) {
30
30
  return t === 0 ? 1 : e / t;
31
31
  }
32
- function s(e, n, r) {
33
- let i = n(e);
34
- if (typeof i != "string" || i.length === 0) throw new t(`getKey must return a non-empty string for every ${r} element.`);
32
+ function s(t, n, r) {
33
+ let i = n(t);
34
+ if (typeof i != "string" || i.length === 0) throw new e(`getKey must return a non-empty string for every ${r} element.`);
35
35
  return i;
36
36
  }
37
- function c(e, n, r) {
37
+ function c(t, n, r) {
38
38
  let i = /* @__PURE__ */ new Map();
39
- for (let a of e) {
40
- let e = s(a.element, n, r);
41
- if (i.has(e)) throw new t(`getKey returned duplicate keys for the ${r} elements.`);
42
- i.set(e, a);
39
+ for (let a of t) {
40
+ let t = s(a.element, n, r);
41
+ if (i.has(t)) throw new e(`getKey returned duplicate keys for the ${r} elements.`);
42
+ i.set(t, a);
43
43
  }
44
44
  return i;
45
45
  }
@@ -61,22 +61,22 @@ function u(e, t) {
61
61
  return t ? [l(e, t.rect)].filter((e) => !!e) : [];
62
62
  });
63
63
  }
64
- function d(e, n, r) {
64
+ function d(t, n, r) {
65
65
  let i = /* @__PURE__ */ new Set();
66
66
  return n.flatMap((n) => {
67
- let a = s(n, r, "committed"), o = e.get(a);
68
- if (i.has(a)) throw new t("getKey returned duplicate keys for the committed elements.");
67
+ let a = s(n, r, "committed"), o = t.get(a);
68
+ if (i.has(a)) throw new e("getKey returned duplicate keys for the committed elements.");
69
69
  return i.add(a), o ? [l(n, o.rect)].filter((e) => !!e) : [];
70
70
  });
71
71
  }
72
72
  function f(i, o = {}) {
73
- let s = e(i).map((e) => ({
73
+ let s = t(i).map((e) => ({
74
74
  element: e,
75
75
  rect: a(e)
76
76
  })), l = o.getKey, f = l ? c(s, l, "captured") : void 0, p = !1;
77
77
  return { animate(i = {}) {
78
- if (p) throw new t("This layout transition has already been animated.");
79
- let { elements: a, ...o } = i, c = e(a ?? s.map(({ element: e }) => e)), m = l && f ? d(f, c, l) : u(s, c);
78
+ if (p) throw new e("This layout transition has already been animated.");
79
+ let { elements: a, ...o } = i, c = t(a ?? s.map(({ element: e }) => e)), m = l && f ? d(f, c, l) : u(s, c);
80
80
  return p = !0, n(m.map(({ element: e }) => e), (e, t) => {
81
81
  let n = m[t], i = `${r(n.x, 2)}px ${r(n.y, 2)}px`;
82
82
  return [{
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":";;;;AAaA,SAAS,EAAM,GAAe,GAA2B;CACvD,IAAM,IAAS,MAAM;CAErB,OAAO,KAAK,MAAM,IAAQ,CAAM,IAAI;AACtC;AAMA,SAAS,EAAY,GAA+D;CAClF,IAAI,iBAAiB,KAAW,kBAAkB,GAChD,OAAO;EAAE,QAAS,EAAwB;EAAc,OAAQ,EAAwB;CAAY;CAGtG,IAAM,IAAO,EAAQ,sBAAsB;CAE3C,OAAO;EAAE,QAAQ,EAAK;EAAQ,OAAO,EAAK;CAAM;AAClD;AAEA,SAAS,EAAc,GAA8B;CACnD,IAAM,IAAO,EAAQ,sBAAsB,GACrC,IAAO,EAAY,CAAO;CAEhC,OAAO;EAAE,QAAQ,EAAK;EAAQ,OAAO,EAAK;EAAO,GAAG,EAAK;EAAG,GAAG,EAAK;CAAE;AACxE;AAIA,SAAS,EAAa,GAAkB,GAAsB;CAC5D,OAAO,MAAS,IAAI,IAAI,IAAW;AACrC;AAEA,SAAS,EAAQ,GAAkB,GAAsC,GAAuB;CAC9F,IAAM,IAAM,EAAO,CAAO;CAE1B,IAAI,OAAO,KAAQ,YAAY,EAAI,WAAW,GAC5C,MAAM,IAAI,EAAuB,mDAAmD,EAAM,UAAU;CAGtG,OAAO;AACT;AAEA,SAAS,EAAa,GAAiC,GAAsC,GAAe;CAC1G,IAAM,oBAAO,IAAI,IAAyB;CAE1C,KAAK,IAAM,KAAS,GAAS;EAC3B,IAAM,IAAM,EAAQ,EAAM,SAAS,GAAQ,CAAK;EAEhD,IAAI,EAAK,IAAI,CAAG,GACd,MAAM,IAAI,EAAuB,0CAA0C,EAAM,WAAW;EAG9F,EAAK,IAAI,GAAK,CAAK;CACrB;CAEA,OAAO;AACT;AAEA,SAAS,EAAe,GAAkB,GAA4C;CACpF,IAAI,CAAC,EAAQ,aAAa;CAE1B,IAAM,IAAO,EAAc,CAAO,GAC5B,IAAI,EAAK,IAAI,EAAK,GAClB,IAAI,EAAK,IAAI,EAAK,GAClB,IAAS,EAAa,EAAK,OAAO,EAAK,KAAK,GAC5C,IAAS,EAAa,EAAK,QAAQ,EAAK,MAAM;CAEpD,OAAO,MAAM,KAAK,MAAM,KAAK,MAAW,KAAK,MAAW,IAAI,KAAA,IAAY;EAAE;EAAS;EAAQ;EAAQ;EAAG;CAAE;AAC1G;AAEA,SAAS,EAAgB,GAAiC,GAA8C;CACtG,IAAM,IAAW,IAAI,IAAI,EAAQ,KAAK,MAAU,CAAC,EAAM,SAAS,CAAK,CAAC,CAAC;CAEvE,OAAO,EAAS,SAAS,MAAY;EACnC,IAAM,IAAQ,EAAS,IAAI,CAAO;EAElC,OAAO,IACH,CAAC,EAAe,GAAS,EAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAmC,EAAQ,CAAO,IAChG,CAAC;CACP,CAAC;AACH;AAEA,SAAS,EACP,GACA,GACA,GACgB;CAChB,IAAM,oBAAY,IAAI,IAAY;CAElC,OAAO,EAAS,SAAS,MAAY;EACnC,IAAM,IAAM,EAAQ,GAAS,GAAQ,WAAW,GAC1C,IAAQ,EAAS,IAAI,CAAG;EAE9B,IAAI,EAAU,IAAI,CAAG,GACnB,MAAM,IAAI,EAAuB,4DAA4D;EAK/F,OAFA,EAAU,IAAI,CAAG,GAEV,IACH,CAAC,EAAe,GAAS,EAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAmC,EAAQ,CAAO,IAChG,CAAC;CACP,CAAC;AACH;AAWA,SAAgB,EAAc,GAA6B,IAAgC,CAAC,GAAqB;CAC/G,IAAM,IAAyB,EAAe,CAAQ,CAAC,CAAC,KAAK,OAAa;EACxE;EACA,MAAM,EAAc,CAAO;CAC7B,EAAE,GACI,IAAS,EAAQ,QACjB,IAAW,IAAS,EAAa,GAAS,GAAQ,UAAU,IAAI,KAAA,GAElE,IAAW;CAEf,OAAO,EACL,QAAQ,IAAkC,CAAC,GAAG;EAC5C,IAAI,GAAU,MAAM,IAAI,EAAuB,mDAAmD;EAElG,IAAM,EAAE,UAAU,GAAmB,GAAG,MAAqB,GACvD,IAAW,EAAe,KAAqB,EAAQ,KAAK,EAAE,iBAAc,CAAO,CAAC,GACpF,IAAU,KAAU,IAAW,EAAW,GAAU,GAAU,CAAM,IAAI,EAAgB,GAAS,CAAQ;EAI/G,OAFA,IAAW,IAEJ,EACL,EAAQ,KAAK,EAAE,iBAAc,CAAO,IACnC,GAAG,MAAU;GAEZ,IAAM,IAAS,EAAQ,IACjB,IAAY,GAAG,EAAM,EAAO,GAAG,CAAC,EAAE,KAAK,EAAM,EAAO,GAAG,CAAC,EAAE;GAGhE,OAAO,CACL;IAAE,WAAW;IAAO,OAAA,GAHL,EAAM,EAAO,QAAQ,CAAC,EAAE,GAAG,EAAM,EAAO,QAAQ,CAAC;IAGrC;GAAU,GACrC;IAAE,WAAW;IAAO,OAAO;IAAO,WAAW;GAAU,CACzD;EACF,GACA,CACF;CACF,EACF;AACF"}
1
+ {"version":3,"file":"layout.js","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return[...new Set(e)]}function t(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function n(e,n,r=!1){let i=!1,a,o=!1,s,c=t(e,()=>{s?.(),s=void 0},r).then(e=>i&&o&&e.status===`cancelled`?{reason:a,status:`cancelled`}:e),l=t=>{i||(i=!0,a=t,o=t!==void 0,s?.(),s=void 0,e.cancel())};if(n){let e=()=>l(n.reason);n.addEventListener(`abort`,e,{once:!0}),s=()=>n.removeEventListener(`abort`,e)}return{animation:e,dispose:l,get disposed(){return i},result:c,[Symbol.dispose](){this.dispose()}}}function r(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),a=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};if(t){let e=()=>a(t.reason);t.addEventListener(`abort`,e,{once:!0}),r=()=>t.removeEventListener(`abort`,e)}return{dispose:a,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}var i=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},a=class extends i{},o=class extends i{},s=180;function c(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function l(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,c=n===void 0?{...o,duration:s}:{...o,duration:n};return t?{...c,delay:0,duration:0,endDelay:0,iterations:1}:c}function u(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new a(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}var d=new WeakMap;function f(e){for(let t of d.get(e)??[])t.dispose()}function p(e,t){let n=d.get(e);n||(n=new Set,d.set(e,n)),n.add(t),t.result.then(()=>{n?.delete(t),n?.size===0&&d.delete(e)})}function m(e,t,r={}){if(r.signal?.aborted)throw r.signal.reason;if(typeof e.animate!=`function`)throw new o(`The Web Animations API is not available in this environment.`);r.interrupt===`cancel`&&f(e);let i=c(r.motion??`system`),a=n(e.animate(t,l(r,i)),r.signal,i);return p(e,a),a}function h(e){if(!Number.isFinite(e)||e<0)throw new a(`stagger must be a finite, non-negative number.`)}function g(t,n,i={}){let{signal:a,stagger:o=0,...s}=i;if(h(o),a?.aborted)throw a.reason;let c=e(t),l=c.map((e,t)=>typeof n==`function`?n(e,t,c.length):n),d=[];try{for(let[e,t]of c.entries()){let n=l[e];d.push(m(t,n,u(s,e*o)))}}catch(e){for(let t of d)t.dispose(e);throw e}return r(d,a)}function _(e,t){let n=10**t;return Math.round(e*n)/n}function v(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function y(e){let t=e.getBoundingClientRect(),n=v(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function b(e,t){return t===0?1:e/t}function x(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new a(`getKey must return a non-empty string for every ${n} element.`);return r}function S(e,t,n){let r=new Map;for(let i of e){let e=x(i.element,t,n);if(r.has(e))throw new a(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function C(e,t){if(!e.isConnected)return;let n=y(e),r=t.x-n.x,i=t.y-n.y,a=b(t.width,n.width),o=b(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function w(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[C(e,t.rect)].filter(e=>!!e):[]})}function T(e,t,n){let r=new Set;return t.flatMap(t=>{let i=x(t,n,`committed`),o=e.get(i);if(r.has(i))throw new a(`getKey returned duplicate keys for the committed elements.`);return r.add(i),o?[C(t,o.rect)].filter(e=>!!e):[]})}function E(t,n={}){let r=e(t).map(e=>({element:e,rect:y(e)})),i=n.getKey,o=i?S(r,i,`captured`):void 0,s=!1;return{animate(t={}){if(s)throw new a(`This layout transition has already been animated.`);let{elements:n,...c}=t,l=e(n??r.map(({element:e})=>e)),u=i&&o?T(o,l,i):w(r,l);return s=!0,g(u.map(({element:e})=>e),(e,t)=>{let n=u[t],r=`${_(n.x,2)}px ${_(n.y,2)}px`;return[{composite:`add`,scale:`${_(n.scaleX,4)} ${_(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},c)}}}exports.NecromancerConfigError=a,exports.NecromancerError=i,exports.NecromancerUnsupportedError=o,exports.animate=m,exports.animateEach=g,exports.captureLayout=E;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=new WeakMap;function t(t){for(let n of e.get(t)??[])n.dispose()}function n(t,n){let r=e.get(t);r||(r=new Set,e.set(t,r)),r.add(n),n.result.then(()=>{r?.delete(n),r?.size===0&&e.delete(t)})}function r(e,t){let n=()=>t(e.reason);return e.addEventListener(`abort`,n,{once:!0}),()=>e.removeEventListener(`abort`,n)}function i(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function a(e,t,n=!1){let a=!1,o,s=!1,c,l=i(e,()=>{c?.(),c=void 0},n).then(e=>a&&s&&e.status===`cancelled`?{reason:o,status:`cancelled`}:e),u=t=>{a||(a=!0,o=t,s=t!==void 0,c?.(),c=void 0,e.cancel())};return t&&(c=r(t,u)),{animation:e,dispose:u,get disposed(){return a},result:l,[Symbol.dispose](){this.dispose()}}}function o(e,t){let n=!1,i,a=Promise.all(e.map(e=>e.result)).then(e=>(i?.(),i=void 0,e)),o=t=>{if(!n){n=!0,i?.(),i=void 0;for(let n of e)n.dispose(t)}};return t&&(i=r(t,o)),{dispose:o,get disposed(){return n},handles:e,results:a,[Symbol.dispose](){this.dispose()}}}var s=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},c=class extends s{},l=class extends s{},u=180;function d(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function f(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,s=n===void 0?{...o,duration:u}:{...o,duration:n};return t?{...s,delay:0,duration:0,endDelay:0,iterations:1}:s}function p(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new c(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}function m(e,r,i={}){if(i.signal?.aborted)throw i.signal.reason;if(typeof e.animate!=`function`)throw new l(`The Web Animations API is not available in this environment.`);i.interrupt===`cancel`&&t(e);let o=d(i.motion??`system`),s=a(e.animate(r,f(i,o)),i.signal,o);return n(e,s),s}function h(e){return[...new Set(e)]}function g(e){if(!Number.isFinite(e)||e<0)throw new c(`stagger must be a finite, non-negative number.`)}function _(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;if(g(i),r?.aborted)throw r.reason;let s=h(e),c=s.map((e,n)=>typeof t==`function`?t(e,n,s.length):t),l=[];try{for(let[e,t]of s.entries()){let n=c[e];l.push(m(t,n,p(a,e*i)))}}catch(e){for(let t of l)t.dispose(e);throw e}return o(l,r)}function v(e,t){let n=10**t;return Math.round(e*n)/n}function y(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function b(e){let t=e.getBoundingClientRect(),n=y(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function x(e,t){return t===0?1:e/t}function S(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new c(`getKey must return a non-empty string for every ${n} element.`);return r}function C(e,t,n){let r=new Map;for(let i of e){let e=S(i.element,t,n);if(r.has(e))throw new c(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function w(e,t){if(!e.isConnected)return;let n=b(e),r=t.x-n.x,i=t.y-n.y,a=x(t.width,n.width),o=x(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function T(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[w(e,t.rect)].filter(e=>!!e):[]})}function E(e,t,n){let r=new Set;return t.flatMap(t=>{let i=S(t,n,`committed`),a=e.get(i);if(r.has(i))throw new c(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[w(t,a.rect)].filter(e=>!!e):[]})}function D(e,t={}){let n=h(e).map(e=>({element:e,rect:b(e)})),r=t.getKey,i=r?C(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new c(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=h(t??n.map(({element:e})=>e)),l=r&&i?E(i,s,r):T(n,s);return a=!0,_(l.map(({element:e})=>e),(e,t)=>{let n=l[t],r=`${v(n.x,2)}px ${v(n.y,2)}px`;return[{composite:`add`,scale:`${v(n.scaleX,4)} ${v(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.NecromancerConfigError=c,exports.NecromancerError=s,exports.NecromancerUnsupportedError=l,exports.animate=m,exports.animateEach=_,exports.captureLayout=D;
2
2
  //# sourceMappingURL=necromancer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.cjs","names":[],"sources":["../src/_elements.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/_active.ts","../src/animate.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import type { AnimateOptions, MotionMode } from './types';\n\nimport { NecromancerConfigError } from './errors';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\nimport { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","import type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"mEACA,SAAgB,EAAe,EAAwC,CACrE,MAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,CAC9B,CCDA,SAAS,EAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,EAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,EACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,EAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAEA,GAAI,EAAQ,CACV,IAAM,MAAc,EAAQ,EAAO,MAAM,EAEzC,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACtD,MAA4B,EAAO,oBAAoB,QAAS,CAAK,CACvE,CAEA,MAAO,CACL,YACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,EAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,EAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAEA,GAAI,EAAQ,CACV,IAAM,MAAc,EAAQ,EAAO,MAAM,EAEzC,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACtD,MAA4B,EAAO,oBAAoB,QAAS,CAAK,CACvE,CAEA,MAAO,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CC9GA,IAAa,EAAb,MAAa,UAAyB,KAAM,CAC1C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAGA,OAAO,GAAG,EAA2C,CACnD,OAAO,aAAiB,CAC1B,CACF,EAGa,EAAb,cAA4C,CAAiB,CAAC,EAGjD,EAAb,cAAiD,CAAiB,CAAC,ECd7D,EAAmB,IAGzB,SAAgB,EAAmB,EAA2B,CAK5D,OAJI,IAAS,OAAe,GAExB,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,CAGA,SAAgB,EAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,WAAU,UAAW,EAAY,OAAQ,EAAU,SAAU,OAAQ,EAAS,GAAG,GAAkB,EACrG,EACJ,IAAa,IAAA,GAAY,CAAE,GAAG,EAAe,SAAU,CAAiB,EAAI,CAAE,GAAG,EAAe,UAAS,EAE3G,OAAO,EAAU,CAAE,GAAG,EAAQ,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAI,CACtF,CAGA,SAAgB,EAAmB,EAAyB,EAAgC,CAC1F,GAAI,IAAW,EAAG,OAAO,EAEzB,IAAM,EAAQ,EAAQ,OAAS,EAE/B,GAAI,OAAO,GAAU,SACnB,MAAM,IAAI,EAAuB,yDAAyD,OAAO,EAAM,EAAE,EAG3G,MAAO,CAAE,GAAG,EAAS,MAAO,EAAQ,CAAO,CAC7C,CC3BA,IAAM,EAAmB,IAAI,QAG7B,SAAgB,EAAoB,EAAwB,CAC1D,IAAK,IAAM,KAAU,EAAiB,IAAI,CAAO,GAAK,CAAC,EAAG,EAAO,QAAQ,CAC3E,CAGA,SAAgB,EAAe,EAAkB,EAA+B,CAC9E,IAAI,EAAU,EAAiB,IAAI,CAAO,EAErC,IACH,EAAU,IAAI,IACd,EAAiB,IAAI,EAAS,CAAO,GAGvC,EAAQ,IAAI,CAAM,EAElB,EAAY,OAAO,SAAW,CAC5B,GAAS,OAAO,CAAM,EAElB,GAAS,OAAS,GAAG,EAAiB,OAAO,CAAO,CAC1D,CAAC,CACH,CClBA,SAAgB,EAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAA4B,8DAA8D,EAGlG,EAAQ,YAAc,UAAU,EAAoB,CAAO,EAE/D,IAAM,EAAU,EAAmB,EAAQ,QAAU,QAAQ,EAKvD,EAAS,EAJS,EAAQ,QAC9B,EACA,EAAwB,EAAS,CAAO,CAEL,EAAiB,EAAQ,OAAQ,CAAO,EAI7E,OAFA,EAAe,EAAS,CAAM,EAEvB,CACT,CCxBA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAuB,gDAAgD,CAErF,CAQA,SAAgB,EACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,EAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EACM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAQ,EAAS,EAAe,EAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAqB,EAAS,CAAM,CAC7C,CCtCA,SAAS,EAAM,EAAe,EAA2B,CACvD,IAAM,EAAS,IAAM,EAErB,OAAO,KAAK,MAAM,EAAQ,CAAM,EAAI,CACtC,CAMA,SAAS,EAAY,EAA+D,CAClF,GAAI,gBAAiB,GAAW,iBAAkB,EAChD,MAAO,CAAE,OAAS,EAAwB,aAAc,MAAQ,EAAwB,WAAY,EAGtG,IAAM,EAAO,EAAQ,sBAAsB,EAE3C,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,KAAM,CAClD,CAEA,SAAS,EAAc,EAA8B,CACnD,IAAM,EAAO,EAAQ,sBAAsB,EACrC,EAAO,EAAY,CAAO,EAEhC,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,MAAO,EAAG,EAAK,EAAG,EAAG,EAAK,CAAE,CACxE,CAIA,SAAS,EAAa,EAAkB,EAAsB,CAC5D,OAAO,IAAS,EAAI,EAAI,EAAW,CACrC,CAEA,SAAS,EAAQ,EAAkB,EAAsC,EAAuB,CAC9F,IAAM,EAAM,EAAO,CAAO,EAE1B,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAC5C,MAAM,IAAI,EAAuB,mDAAmD,EAAM,UAAU,EAGtG,OAAO,CACT,CAEA,SAAS,EAAa,EAAiC,EAAsC,EAAe,CAC1G,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAM,EAAQ,EAAM,QAAS,EAAQ,CAAK,EAEhD,GAAI,EAAK,IAAI,CAAG,EACd,MAAM,IAAI,EAAuB,0CAA0C,EAAM,WAAW,EAG9F,EAAK,IAAI,EAAK,CAAK,CACrB,CAEA,OAAO,CACT,CAEA,SAAS,EAAe,EAAkB,EAA4C,CACpF,GAAI,CAAC,EAAQ,YAAa,OAE1B,IAAM,EAAO,EAAc,CAAO,EAC5B,EAAI,EAAK,EAAI,EAAK,EAClB,EAAI,EAAK,EAAI,EAAK,EAClB,EAAS,EAAa,EAAK,MAAO,EAAK,KAAK,EAC5C,EAAS,EAAa,EAAK,OAAQ,EAAK,MAAM,EAEpD,OAAO,IAAM,GAAK,IAAM,GAAK,IAAW,GAAK,IAAW,EAAI,IAAA,GAAY,CAAE,UAAS,SAAQ,SAAQ,IAAG,GAAE,CAC1G,CAEA,SAAS,EAAgB,EAAiC,EAA8C,CACtG,IAAM,EAAW,IAAI,IAAI,EAAQ,IAAK,GAAU,CAAC,EAAM,QAAS,CAAK,CAAC,CAAC,EAEvE,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAQ,EAAS,IAAI,CAAO,EAElC,OAAO,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAEA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAY,IAAI,IAEtB,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAM,EAAQ,EAAS,EAAQ,WAAW,EAC1C,EAAQ,EAAS,IAAI,CAAG,EAE9B,GAAI,EAAU,IAAI,CAAG,EACnB,MAAM,IAAI,EAAuB,4DAA4D,EAK/F,OAFA,EAAU,IAAI,CAAG,EAEV,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAWA,SAAgB,EAAc,EAA6B,EAAgC,CAAC,EAAqB,CAC/G,IAAM,EAAyB,EAAe,CAAQ,CAAC,CAAC,IAAK,IAAa,CACxE,UACA,KAAM,EAAc,CAAO,CAC7B,EAAE,EACI,EAAS,EAAQ,OACjB,EAAW,EAAS,EAAa,EAAS,EAAQ,UAAU,EAAI,IAAA,GAElE,EAAW,GAEf,MAAO,CACL,QAAQ,EAAkC,CAAC,EAAG,CAC5C,GAAI,EAAU,MAAM,IAAI,EAAuB,mDAAmD,EAElG,GAAM,CAAE,SAAU,EAAmB,GAAG,GAAqB,EACvD,EAAW,EAAe,GAAqB,EAAQ,KAAK,CAAE,aAAc,CAAO,CAAC,EACpF,EAAU,GAAU,EAAW,EAAW,EAAU,EAAU,CAAM,EAAI,EAAgB,EAAS,CAAQ,EAI/G,MAFA,GAAW,GAEJ,EACL,EAAQ,KAAK,CAAE,aAAc,CAAO,GACnC,EAAG,IAAU,CAEZ,IAAM,EAAS,EAAQ,GACjB,EAAY,GAAG,EAAM,EAAO,EAAG,CAAC,EAAE,KAAK,EAAM,EAAO,EAAG,CAAC,EAAE,IAGhE,MAAO,CACL,CAAE,UAAW,MAAO,MAAA,GAHL,EAAM,EAAO,OAAQ,CAAC,EAAE,GAAG,EAAM,EAAO,OAAQ,CAAC,IAGrC,WAAU,EACrC,CAAE,UAAW,MAAO,MAAO,MAAO,UAAW,SAAU,CACzD,CACF,EACA,CACF,CACF,CACF,CACF"}
1
+ {"version":3,"file":"necromancer.cjs","names":[],"sources":["../src/_active.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"mEAQA,IAAM,EAAmB,IAAI,QAG7B,SAAgB,EAAoB,EAAwB,CAC1D,IAAK,IAAM,KAAU,EAAiB,IAAI,CAAO,GAAK,CAAC,EAAG,EAAO,QAAQ,CAC3E,CAGA,SAAgB,EAAe,EAAkB,EAA+B,CAC9E,IAAI,EAAU,EAAiB,IAAI,CAAO,EAErC,IACH,EAAU,IAAI,IACd,EAAiB,IAAI,EAAS,CAAO,GAGvC,EAAQ,IAAI,CAAM,EAElB,EAAY,OAAO,SAAW,CAC5B,GAAS,OAAO,CAAM,EAElB,GAAS,OAAS,GAAG,EAAiB,OAAO,CAAO,CAC1D,CAAC,CACH,CCzBA,SAAS,EAA0B,EAAqB,EAAiD,CACvG,IAAM,MAAc,EAAQ,EAAO,MAAM,EAIzC,OAFA,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,MAEzC,EAAO,oBAAoB,QAAS,CAAK,CACxD,CAEA,SAAS,EAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,EAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,EACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,EAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,YACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,EAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,EAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CCpHA,IAAa,EAAb,MAAa,UAAyB,KAAM,CAC1C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAGA,OAAO,GAAG,EAA2C,CACnD,OAAO,aAAiB,CAC1B,CACF,EAGa,EAAb,cAA4C,CAAiB,CAAC,EAGjD,EAAb,cAAiD,CAAiB,CAAC,ECf7D,EAAmB,IAGzB,SAAgB,EAAmB,EAA2B,CAK5D,OAJI,IAAS,OAAe,GAExB,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,CAGA,SAAgB,EAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,WAAU,UAAW,EAAY,OAAQ,EAAU,SAAU,OAAQ,EAAS,GAAG,GAAkB,EACrG,EACJ,IAAa,IAAA,GAAY,CAAE,GAAG,EAAe,SAAU,CAAiB,EAAI,CAAE,GAAG,EAAe,UAAS,EAE3G,OAAO,EAAU,CAAE,GAAG,EAAQ,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAI,CACtF,CAGA,SAAgB,EAAmB,EAAyB,EAAgC,CAC1F,GAAI,IAAW,EAAG,OAAO,EAEzB,IAAM,EAAQ,EAAQ,OAAS,EAE/B,GAAI,OAAO,GAAU,SACnB,MAAM,IAAI,EAAuB,yDAAyD,OAAO,EAAM,EAAE,EAG3G,MAAO,CAAE,GAAG,EAAS,MAAO,EAAQ,CAAO,CAC7C,CCtBA,SAAgB,EAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAA4B,8DAA8D,EAGlG,EAAQ,YAAc,UAAU,EAAoB,CAAO,EAE/D,IAAM,EAAU,EAAmB,EAAQ,QAAU,QAAQ,EAKvD,EAAS,EAJS,EAAQ,QAC9B,EACA,EAAwB,EAAS,CAAO,CAEL,EAAiB,EAAQ,OAAQ,CAAO,EAI7E,OAFA,EAAe,EAAS,CAAM,EAEvB,CACT,CC9BA,SAAgB,EAAe,EAAwC,CACrE,MAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,CAC9B,CCIA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAuB,gDAAgD,CAErF,CAQA,SAAgB,EACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,EAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EACM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAQ,EAAS,EAAe,EAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAqB,EAAS,CAAM,CAC7C,CCtCA,SAAS,EAAM,EAAe,EAA2B,CACvD,IAAM,EAAS,IAAM,EAErB,OAAO,KAAK,MAAM,EAAQ,CAAM,EAAI,CACtC,CAMA,SAAS,EAAY,EAA+D,CAClF,GAAI,gBAAiB,GAAW,iBAAkB,EAChD,MAAO,CAAE,OAAS,EAAwB,aAAc,MAAQ,EAAwB,WAAY,EAGtG,IAAM,EAAO,EAAQ,sBAAsB,EAE3C,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,KAAM,CAClD,CAEA,SAAS,EAAc,EAA8B,CACnD,IAAM,EAAO,EAAQ,sBAAsB,EACrC,EAAO,EAAY,CAAO,EAEhC,MAAO,CAAE,OAAQ,EAAK,OAAQ,MAAO,EAAK,MAAO,EAAG,EAAK,EAAG,EAAG,EAAK,CAAE,CACxE,CAIA,SAAS,EAAa,EAAkB,EAAsB,CAC5D,OAAO,IAAS,EAAI,EAAI,EAAW,CACrC,CAEA,SAAS,EAAQ,EAAkB,EAAsC,EAAuB,CAC9F,IAAM,EAAM,EAAO,CAAO,EAE1B,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAC5C,MAAM,IAAI,EAAuB,mDAAmD,EAAM,UAAU,EAGtG,OAAO,CACT,CAEA,SAAS,EAAa,EAAiC,EAAsC,EAAe,CAC1G,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAM,EAAQ,EAAM,QAAS,EAAQ,CAAK,EAEhD,GAAI,EAAK,IAAI,CAAG,EACd,MAAM,IAAI,EAAuB,0CAA0C,EAAM,WAAW,EAG9F,EAAK,IAAI,EAAK,CAAK,CACrB,CAEA,OAAO,CACT,CAEA,SAAS,EAAe,EAAkB,EAA4C,CACpF,GAAI,CAAC,EAAQ,YAAa,OAE1B,IAAM,EAAO,EAAc,CAAO,EAC5B,EAAI,EAAK,EAAI,EAAK,EAClB,EAAI,EAAK,EAAI,EAAK,EAClB,EAAS,EAAa,EAAK,MAAO,EAAK,KAAK,EAC5C,EAAS,EAAa,EAAK,OAAQ,EAAK,MAAM,EAEpD,OAAO,IAAM,GAAK,IAAM,GAAK,IAAW,GAAK,IAAW,EAAI,IAAA,GAAY,CAAE,UAAS,SAAQ,SAAQ,IAAG,GAAE,CAC1G,CAEA,SAAS,EAAgB,EAAiC,EAA8C,CACtG,IAAM,EAAW,IAAI,IAAI,EAAQ,IAAK,GAAU,CAAC,EAAM,QAAS,CAAK,CAAC,CAAC,EAEvE,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAQ,EAAS,IAAI,CAAO,EAElC,OAAO,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAEA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAY,IAAI,IAEtB,OAAO,EAAS,QAAS,GAAY,CACnC,IAAM,EAAM,EAAQ,EAAS,EAAQ,WAAW,EAC1C,EAAQ,EAAS,IAAI,CAAG,EAE9B,GAAI,EAAU,IAAI,CAAG,EACnB,MAAM,IAAI,EAAuB,4DAA4D,EAK/F,OAFA,EAAU,IAAI,CAAG,EAEV,EACH,CAAC,EAAe,EAAS,EAAM,IAAI,CAAC,CAAC,CAAC,OAAQ,GAAmC,EAAQ,CAAO,EAChG,CAAC,CACP,CAAC,CACH,CAWA,SAAgB,EAAc,EAA6B,EAAgC,CAAC,EAAqB,CAC/G,IAAM,EAAyB,EAAe,CAAQ,CAAC,CAAC,IAAK,IAAa,CACxE,UACA,KAAM,EAAc,CAAO,CAC7B,EAAE,EACI,EAAS,EAAQ,OACjB,EAAW,EAAS,EAAa,EAAS,EAAQ,UAAU,EAAI,IAAA,GAElE,EAAW,GAEf,MAAO,CACL,QAAQ,EAAkC,CAAC,EAAG,CAC5C,GAAI,EAAU,MAAM,IAAI,EAAuB,mDAAmD,EAElG,GAAM,CAAE,SAAU,EAAmB,GAAG,GAAqB,EACvD,EAAW,EAAe,GAAqB,EAAQ,KAAK,CAAE,aAAc,CAAO,CAAC,EACpF,EAAU,GAAU,EAAW,EAAW,EAAU,EAAU,CAAM,EAAI,EAAgB,EAAS,CAAQ,EAI/G,MAFA,GAAW,GAEJ,EACL,EAAQ,KAAK,CAAE,aAAc,CAAO,GACnC,EAAG,IAAU,CAEZ,IAAM,EAAS,EAAQ,GACjB,EAAY,GAAG,EAAM,EAAO,EAAG,CAAC,EAAE,KAAK,EAAM,EAAO,EAAG,CAAC,EAAE,IAGhE,MAAO,CACL,CAAE,UAAW,MAAO,MAAA,GAHL,EAAM,EAAO,OAAQ,CAAC,EAAE,GAAG,EAAM,EAAO,OAAQ,CAAC,IAGrC,WAAU,EACrC,CAAE,UAAW,MAAO,MAAO,MAAO,UAAW,SAAU,CACzD,CACF,EACA,CACF,CACF,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- var Necromancer=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return[...new Set(e)]}function n(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function r(e,t,r=!1){let i=!1,a,o=!1,s,c=n(e,()=>{s?.(),s=void 0},r).then(e=>i&&o&&e.status===`cancelled`?{reason:a,status:`cancelled`}:e),l=t=>{i||(i=!0,a=t,o=t!==void 0,s?.(),s=void 0,e.cancel())};if(t){let e=()=>l(t.reason);t.addEventListener(`abort`,e,{once:!0}),s=()=>t.removeEventListener(`abort`,e)}return{animation:e,dispose:l,get disposed(){return i},result:c,[Symbol.dispose](){this.dispose()}}}function i(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),a=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};if(t){let e=()=>a(t.reason);t.addEventListener(`abort`,e,{once:!0}),r=()=>t.removeEventListener(`abort`,e)}return{dispose:a,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}var a=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},o=class extends a{},s=class extends a{},c=180;function l(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function u(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,s=n===void 0?{...o,duration:c}:{...o,duration:n};return t?{...s,delay:0,duration:0,endDelay:0,iterations:1}:s}function d(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new o(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}var f=new WeakMap;function p(e){for(let t of f.get(e)??[])t.dispose()}function m(e,t){let n=f.get(e);n||(n=new Set,f.set(e,n)),n.add(t),t.result.then(()=>{n?.delete(t),n?.size===0&&f.delete(e)})}function h(e,t,n={}){if(n.signal?.aborted)throw n.signal.reason;if(typeof e.animate!=`function`)throw new s(`The Web Animations API is not available in this environment.`);n.interrupt===`cancel`&&p(e);let i=l(n.motion??`system`),a=r(e.animate(t,u(n,i)),n.signal,i);return m(e,a),a}function g(e){if(!Number.isFinite(e)||e<0)throw new o(`stagger must be a finite, non-negative number.`)}function _(e,n,r={}){let{signal:a,stagger:o=0,...s}=r;if(g(o),a?.aborted)throw a.reason;let c=t(e),l=c.map((e,t)=>typeof n==`function`?n(e,t,c.length):n),u=[];try{for(let[e,t]of c.entries()){let n=l[e];u.push(h(t,n,d(s,e*o)))}}catch(e){for(let t of u)t.dispose(e);throw e}return i(u,a)}function v(e,t){let n=10**t;return Math.round(e*n)/n}function y(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function b(e){let t=e.getBoundingClientRect(),n=y(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function x(e,t){return t===0?1:e/t}function S(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new o(`getKey must return a non-empty string for every ${n} element.`);return r}function C(e,t,n){let r=new Map;for(let i of e){let e=S(i.element,t,n);if(r.has(e))throw new o(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function w(e,t){if(!e.isConnected)return;let n=b(e),r=t.x-n.x,i=t.y-n.y,a=x(t.width,n.width),o=x(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function T(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[w(e,t.rect)].filter(e=>!!e):[]})}function E(e,t,n){let r=new Set;return t.flatMap(t=>{let i=S(t,n,`committed`),a=e.get(i);if(r.has(i))throw new o(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[w(t,a.rect)].filter(e=>!!e):[]})}function D(e,n={}){let r=t(e).map(e=>({element:e,rect:b(e)})),i=n.getKey,a=i?C(r,i,`captured`):void 0,s=!1;return{animate(e={}){if(s)throw new o(`This layout transition has already been animated.`);let{elements:n,...c}=e,l=t(n??r.map(({element:e})=>e)),u=i&&a?E(a,l,i):T(r,l);return s=!0,_(u.map(({element:e})=>e),(e,t)=>{let n=u[t],r=`${v(n.x,2)}px ${v(n.y,2)}px`;return[{composite:`add`,scale:`${v(n.scaleX,4)} ${v(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},c)}}}return e.NecromancerConfigError=o,e.NecromancerError=a,e.NecromancerUnsupportedError=s,e.animate=h,e.animateEach=_,e.captureLayout=D,e})({});
1
+ var Necromancer=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=new WeakMap;function n(e){for(let n of t.get(e)??[])n.dispose()}function r(e,n){let r=t.get(e);r||(r=new Set,t.set(e,r)),r.add(n),n.result.then(()=>{r?.delete(n),r?.size===0&&t.delete(e)})}function i(e,t){let n=()=>t(e.reason);return e.addEventListener(`abort`,n,{once:!0}),()=>e.removeEventListener(`abort`,n)}function a(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function o(e,t,n=!1){let r=!1,o,s=!1,c,l=a(e,()=>{c?.(),c=void 0},n).then(e=>r&&s&&e.status===`cancelled`?{reason:o,status:`cancelled`}:e),u=t=>{r||(r=!0,o=t,s=t!==void 0,c?.(),c=void 0,e.cancel())};return t&&(c=i(t,u)),{animation:e,dispose:u,get disposed(){return r},result:l,[Symbol.dispose](){this.dispose()}}}function s(e,t){let n=!1,r,a=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),o=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=i(t,o)),{dispose:o,get disposed(){return n},handles:e,results:a,[Symbol.dispose](){this.dispose()}}}var c=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},l=class extends c{},u=class extends c{},d=180;function f(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function p(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,s=n===void 0?{...o,duration:d}:{...o,duration:n};return t?{...s,delay:0,duration:0,endDelay:0,iterations:1}:s}function m(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new l(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}function h(e,t,i={}){if(i.signal?.aborted)throw i.signal.reason;if(typeof e.animate!=`function`)throw new u(`The Web Animations API is not available in this environment.`);i.interrupt===`cancel`&&n(e);let a=f(i.motion??`system`),s=o(e.animate(t,p(i,a)),i.signal,a);return r(e,s),s}function g(e){return[...new Set(e)]}function _(e){if(!Number.isFinite(e)||e<0)throw new l(`stagger must be a finite, non-negative number.`)}function v(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;if(_(i),r?.aborted)throw r.reason;let o=g(e),c=o.map((e,n)=>typeof t==`function`?t(e,n,o.length):t),l=[];try{for(let[e,t]of o.entries()){let n=c[e];l.push(h(t,n,m(a,e*i)))}}catch(e){for(let t of l)t.dispose(e);throw e}return s(l,r)}function y(e,t){let n=10**t;return Math.round(e*n)/n}function b(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function x(e){let t=e.getBoundingClientRect(),n=b(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function S(e,t){return t===0?1:e/t}function C(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new l(`getKey must return a non-empty string for every ${n} element.`);return r}function w(e,t,n){let r=new Map;for(let i of e){let e=C(i.element,t,n);if(r.has(e))throw new l(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function T(e,t){if(!e.isConnected)return;let n=x(e),r=t.x-n.x,i=t.y-n.y,a=S(t.width,n.width),o=S(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function E(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[T(e,t.rect)].filter(e=>!!e):[]})}function D(e,t,n){let r=new Set;return t.flatMap(t=>{let i=C(t,n,`committed`),a=e.get(i);if(r.has(i))throw new l(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[T(t,a.rect)].filter(e=>!!e):[]})}function O(e,t={}){let n=g(e).map(e=>({element:e,rect:x(e)})),r=t.getKey,i=r?w(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new l(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=g(t??n.map(({element:e})=>e)),c=r&&i?D(i,s,r):E(n,s);return a=!0,v(c.map(({element:e})=>e),(e,t)=>{let n=c[t],r=`${y(n.x,2)}px ${y(n.y,2)}px`;return[{composite:`add`,scale:`${y(n.scaleX,4)} ${y(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}return e.NecromancerConfigError=l,e.NecromancerError=c,e.NecromancerUnsupportedError=u,e.animate=h,e.animateEach=v,e.captureLayout=O,e})({});
2
2
  //# sourceMappingURL=necromancer.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.iife.js","names":[],"sources":["../src/_elements.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/_active.ts","../src/animate.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import type { AnimateOptions, MotionMode } from './types';\n\nimport { NecromancerConfigError } from './errors';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\nimport { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","import type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"necromancer.iife.js","names":[],"sources":["../src/_active.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function e(e){return[...new Set(e)]}function t(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function n(e,n,r=!1){let i=!1,a,o=!1,s,c=t(e,()=>{s?.(),s=void 0},r).then(e=>i&&o&&e.status===`cancelled`?{reason:a,status:`cancelled`}:e),l=t=>{i||(i=!0,a=t,o=t!==void 0,s?.(),s=void 0,e.cancel())};if(n){let e=()=>l(n.reason);n.addEventListener(`abort`,e,{once:!0}),s=()=>n.removeEventListener(`abort`,e)}return{animation:e,dispose:l,get disposed(){return i},result:c,[Symbol.dispose](){this.dispose()}}}function r(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),a=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};if(t){let e=()=>a(t.reason);t.addEventListener(`abort`,e,{once:!0}),r=()=>t.removeEventListener(`abort`,e)}return{dispose:a,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}var i=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},a=class extends i{},o=class extends i{},s=180;function c(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function l(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,c=n===void 0?{...o,duration:s}:{...o,duration:n};return t?{...c,delay:0,duration:0,endDelay:0,iterations:1}:c}function u(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new a(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}var d=new WeakMap;function f(e){for(let t of d.get(e)??[])t.dispose()}function p(e,t){let n=d.get(e);n||(n=new Set,d.set(e,n)),n.add(t),t.result.then(()=>{n?.delete(t),n?.size===0&&d.delete(e)})}function m(e,t,r={}){if(r.signal?.aborted)throw r.signal.reason;if(typeof e.animate!=`function`)throw new o(`The Web Animations API is not available in this environment.`);r.interrupt===`cancel`&&f(e);let i=c(r.motion??`system`),a=n(e.animate(t,l(r,i)),r.signal,i);return p(e,a),a}function h(e){if(!Number.isFinite(e)||e<0)throw new a(`stagger must be a finite, non-negative number.`)}function g(t,n,i={}){let{signal:a,stagger:o=0,...s}=i;if(h(o),a?.aborted)throw a.reason;let c=e(t),l=c.map((e,t)=>typeof n==`function`?n(e,t,c.length):n),d=[];try{for(let[e,t]of c.entries()){let n=l[e];d.push(m(t,n,u(s,e*o)))}}catch(e){for(let t of d)t.dispose(e);throw e}return r(d,a)}function _(e,t){let n=10**t;return Math.round(e*n)/n}function v(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function y(e){let t=e.getBoundingClientRect(),n=v(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function b(e,t){return t===0?1:e/t}function x(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new a(`getKey must return a non-empty string for every ${n} element.`);return r}function S(e,t,n){let r=new Map;for(let i of e){let e=x(i.element,t,n);if(r.has(e))throw new a(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function C(e,t){if(!e.isConnected)return;let n=y(e),r=t.x-n.x,i=t.y-n.y,a=b(t.width,n.width),o=b(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function w(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[C(e,t.rect)].filter(e=>!!e):[]})}function T(e,t,n){let r=new Set;return t.flatMap(t=>{let i=x(t,n,`committed`),o=e.get(i);if(r.has(i))throw new a(`getKey returned duplicate keys for the committed elements.`);return r.add(i),o?[C(t,o.rect)].filter(e=>!!e):[]})}function E(t,n={}){let r=e(t).map(e=>({element:e,rect:y(e)})),i=n.getKey,o=i?S(r,i,`captured`):void 0,s=!1;return{animate(t={}){if(s)throw new a(`This layout transition has already been animated.`);let{elements:n,...c}=t,l=e(n??r.map(({element:e})=>e)),u=i&&o?T(o,l,i):w(r,l);return s=!0,g(u.map(({element:e})=>e),(e,t)=>{let n=u[t],r=`${_(n.x,2)}px ${_(n.y,2)}px`;return[{composite:`add`,scale:`${_(n.scaleX,4)} ${_(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},c)}}}export{a as NecromancerConfigError,i as NecromancerError,o as NecromancerUnsupportedError,m as animate,g as animateEach,E as captureLayout};
1
+ var e=new WeakMap;function t(t){for(let n of e.get(t)??[])n.dispose()}function n(t,n){let r=e.get(t);r||(r=new Set,e.set(t,r)),r.add(n),n.result.then(()=>{r?.delete(n),r?.size===0&&e.delete(t)})}function r(e,t){let n=()=>t(e.reason);return e.addEventListener(`abort`,n,{once:!0}),()=>e.removeEventListener(`abort`,n)}function i(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function a(e,t,n=!1){let a=!1,o,s=!1,c,l=i(e,()=>{c?.(),c=void 0},n).then(e=>a&&s&&e.status===`cancelled`?{reason:o,status:`cancelled`}:e),u=t=>{a||(a=!0,o=t,s=t!==void 0,c?.(),c=void 0,e.cancel())};return t&&(c=r(t,u)),{animation:e,dispose:u,get disposed(){return a},result:l,[Symbol.dispose](){this.dispose()}}}function o(e,t){let n=!1,i,a=Promise.all(e.map(e=>e.result)).then(e=>(i?.(),i=void 0,e)),o=t=>{if(!n){n=!0,i?.(),i=void 0;for(let n of e)n.dispose(t)}};return t&&(i=r(t,o)),{dispose:o,get disposed(){return n},handles:e,results:a,[Symbol.dispose](){this.dispose()}}}var s=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},c=class extends s{},l=class extends s{},u=180;function d(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function f(e={},t=!1){let{duration:n,interrupt:r,motion:i=`system`,signal:a,...o}=e,s=n===void 0?{...o,duration:u}:{...o,duration:n};return t?{...s,delay:0,duration:0,endDelay:0,iterations:1}:s}function p(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new c(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}function m(e,r,i={}){if(i.signal?.aborted)throw i.signal.reason;if(typeof e.animate!=`function`)throw new l(`The Web Animations API is not available in this environment.`);i.interrupt===`cancel`&&t(e);let o=d(i.motion??`system`),s=a(e.animate(r,f(i,o)),i.signal,o);return n(e,s),s}function h(e){return[...new Set(e)]}function g(e){if(!Number.isFinite(e)||e<0)throw new c(`stagger must be a finite, non-negative number.`)}function _(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;if(g(i),r?.aborted)throw r.reason;let s=h(e),c=s.map((e,n)=>typeof t==`function`?t(e,n,s.length):t),l=[];try{for(let[e,t]of s.entries()){let n=c[e];l.push(m(t,n,p(a,e*i)))}}catch(e){for(let t of l)t.dispose(e);throw e}return o(l,r)}function v(e,t){let n=10**t;return Math.round(e*n)/n}function y(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function b(e){let t=e.getBoundingClientRect(),n=y(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function x(e,t){return t===0?1:e/t}function S(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new c(`getKey must return a non-empty string for every ${n} element.`);return r}function C(e,t,n){let r=new Map;for(let i of e){let e=S(i.element,t,n);if(r.has(e))throw new c(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function w(e,t){if(!e.isConnected)return;let n=b(e),r=t.x-n.x,i=t.y-n.y,a=x(t.width,n.width),o=x(t.height,n.height);return r===0&&i===0&&a===1&&o===1?void 0:{element:e,scaleX:a,scaleY:o,x:r,y:i}}function T(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[w(e,t.rect)].filter(e=>!!e):[]})}function E(e,t,n){let r=new Set;return t.flatMap(t=>{let i=S(t,n,`committed`),a=e.get(i);if(r.has(i))throw new c(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[w(t,a.rect)].filter(e=>!!e):[]})}function D(e,t={}){let n=h(e).map(e=>({element:e,rect:b(e)})),r=t.getKey,i=r?C(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new c(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=h(t??n.map(({element:e})=>e)),l=r&&i?E(i,s,r):T(n,s);return a=!0,_(l.map(({element:e})=>e),(e,t)=>{let n=l[t],r=`${v(n.x,2)}px ${v(n.y,2)}px`;return[{composite:`add`,scale:`${v(n.scaleX,4)} ${v(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}export{c as NecromancerConfigError,s as NecromancerError,l as NecromancerUnsupportedError,m as animate,_ as animateEach,D as captureLayout};
2
2
  //# sourceMappingURL=necromancer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.js","names":[],"sources":["../src/_elements.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/_active.ts","../src/animate.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', abort);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import type { AnimateOptions, MotionMode } from './types';\n\nimport { NecromancerConfigError } from './errors';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\nimport { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","import type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\nimport { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"necromancer.js","names":[],"sources":["../src/_active.ts","../src/_handle.ts","../src/errors.ts","../src/_motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.ts"],"sourcesContent":["import type { AnimationHandle } from './types';\n\n// Scoped to one DOM element by design: `interrupt: 'cancel'` answers \"replace whatever\n// Necromancer is doing to *this* element\", not \"coordinate across element categories\".\n// A caller that needs to cancel an animation on a different element (or coordinate several\n// elements as one unit) already has that element's own handle, or should use `animateEach()`\n// and a shared `signal` instead. Keeping the WeakMap element-scoped means it only ever needs\n// to answer one question and never grows into a general-purpose animation registry.\nconst handlesByElement = new WeakMap<Element, Set<AnimationHandle>>();\n\n/** Cancels active Necromancer-owned animations without touching native animations owned by callers. */\nexport function interruptAnimations(element: Element): void {\n for (const handle of handlesByElement.get(element) ?? []) handle.dispose();\n}\n\n/** Tracks a handle until its terminal result releases the element association. */\nexport function trackAnimation(element: Element, handle: AnimationHandle): void {\n let handles = handlesByElement.get(element);\n\n if (!handles) {\n handles = new Set();\n handlesByElement.set(element, handles);\n }\n\n handles.add(handle);\n\n void handle.result.then(() => {\n handles?.delete(handle);\n\n if (handles?.size === 0) handlesByElement.delete(element);\n });\n}\n","import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n","/** Base class for errors raised by `@vielzeug/necromancer`. */\nexport class NecromancerError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Narrows an unknown error to any Necromancer-originated error. */\n static is(error: unknown): error is NecromancerError {\n return error instanceof NecromancerError;\n }\n}\n\n/** Thrown when a Necromancer-owned option is invalid. */\nexport class NecromancerConfigError extends NecromancerError {}\n\n/** Thrown when the runtime does not implement the Web Animations API. */\nexport class NecromancerUnsupportedError extends NecromancerError {}\n","import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\n\nconst DEFAULT_DURATION = 180;\n\n/** Reports whether the requested motion preference reduces visible movement. */\nexport function shouldReduceMotion(mode: MotionMode): boolean {\n if (mode === 'full') return false;\n\n if (mode === 'reduced') return true;\n\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */\nexport function resolveAnimationOptions(options: AnimateOptions = {}, reduced = false): KeyframeAnimationOptions {\n const { duration, interrupt: _interrupt, motion: _motion = 'system', signal: _signal, ...nativeOptions } = options;\n const timing =\n duration === undefined ? { ...nativeOptions, duration: DEFAULT_DURATION } : { ...nativeOptions, duration };\n\n return reduced ? { ...timing, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : timing;\n}\n\n/** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n const delay = options.delay ?? 0;\n\n if (typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\n }\n\n return { ...options, delay: delay + offset };\n}\n","import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n","import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -62,25 +62,25 @@ export interface LayoutCaptureOptions {
62
62
  export interface AnimationHandle {
63
63
  /** The lifecycle-owned native Web Animation. */
64
64
  readonly animation: Animation;
65
- /** Resolves once the animation finishes, is reduced, or is cancelled. */
66
- readonly result: Promise<AnimationResult>;
67
- /** Whether this handle has been disposed. */
68
- readonly disposed: boolean;
69
65
  /** Cancels the native animation and releases owned listeners. */
70
66
  dispose(reason?: unknown): void;
67
+ /** Whether this handle has been disposed. */
68
+ readonly disposed: boolean;
69
+ /** Resolves once the animation finishes, is reduced, or is cancelled. */
70
+ readonly result: Promise<AnimationResult>;
71
71
  /** Calls {@link dispose}. */
72
72
  [Symbol.dispose](): void;
73
73
  }
74
74
  /** A lifecycle-owned collection of animations. */
75
75
  export interface AnimationGroup {
76
+ /** Disposes every child and releases group ownership. */
77
+ dispose(reason?: unknown): void;
78
+ /** Whether this group has been disposed. */
79
+ readonly disposed: boolean;
76
80
  /** Child handles in the order supplied to {@link animateEach}. */
77
81
  readonly handles: readonly AnimationHandle[];
78
82
  /** Resolves after all children have reached a terminal state. */
79
83
  readonly results: Promise<readonly AnimationResult[]>;
80
- /** Whether this group has been disposed. */
81
- readonly disposed: boolean;
82
- /** Disposes every child and releases group ownership. */
83
- dispose(reason?: unknown): void;
84
84
  /** Calls {@link dispose}. */
85
85
  [Symbol.dispose](): void;
86
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAEhE,oDAAoD;AACpD,MAAM,MAAM,SAAS,GAAG,SAAS,QAAQ,EAAE,GAAG,wBAAwB,CAAC;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,cAAc,GAAG,wBAAwB,GAAG;IACtD;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;AAE5F,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG;IAChD,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG;IACxD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CACvC,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;CAChD;AAED,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,6BAA6B;IAC7B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC;IACtD,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,6BAA6B;IAC7B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAAC;CAC3D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAEhE,oDAAoD;AACpD,MAAM,MAAM,SAAS,GAAG,SAAS,QAAQ,EAAE,GAAG,wBAAwB,CAAC;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,cAAc,GAAG,wBAAwB,GAAG;IACtD;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;AAE5F,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG;IAChD,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,yFAAyF;AACzF,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG;IACxD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CACvC,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;CAChD;AAED,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,iEAAiE;IACjE,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,6BAA6B;IAC7B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC;IACtD,6BAA6B;IAC7B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAAC;CAC3D"}
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@vielzeug/necromancer",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Dependency-free, lifecycle-owned animation primitives built on the Web Animations API",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/helmuthdu/vielzeug",
8
+ "directory": "packages/necromancer"
9
+ },
5
10
  "type": "module",
11
+ "engines": {
12
+ "node": ">=22"
13
+ },
6
14
  "files": [
7
15
  "dist"
8
16
  ],
@@ -11,46 +19,43 @@
11
19
  "types": "dist/index.d.ts",
12
20
  "exports": {
13
21
  ".": {
14
- "types": "./dist/index.d.ts",
15
22
  "import": "./dist/index.js",
16
- "require": "./dist/index.cjs"
23
+ "require": "./dist/index.cjs",
24
+ "types": "./dist/index.d.ts"
17
25
  },
18
26
  "./testing": {
19
- "types": "./dist/testing/index.d.ts",
20
27
  "import": "./dist/testing.js",
21
- "require": "./dist/testing.cjs"
28
+ "require": "./dist/testing.cjs",
29
+ "types": "./dist/testing/index.d.ts"
22
30
  }
23
31
  },
32
+ "typesVersions": {
33
+ "*": {
34
+ "testing": [
35
+ "dist/testing/index.d.ts"
36
+ ]
37
+ }
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "registry": "https://registry.npmjs.org/"
42
+ },
24
43
  "scripts": {
25
44
  "build": "vite build && pnpm run build:bundle && pnpm run build:types",
26
45
  "build:bundle": "vite build --config vite.bundle.config.ts",
27
46
  "build:types": "tsc -p tsconfig.declarations.json",
28
- "fix": "eslint --fix src",
29
- "lint": "eslint src",
47
+ "fix": "biome check --write src",
48
+ "lint": "biome ci src",
30
49
  "prepublishOnly": "pnpm run build",
31
50
  "test": "vitest",
32
51
  "test:e2e": "pnpm run build && playwright test"
33
52
  },
34
- "publishConfig": {
35
- "access": "public",
36
- "registry": "https://registry.npmjs.org/"
37
- },
38
- "engines": {
39
- "node": ">=22"
40
- },
41
- "typesVersions": {
42
- "*": {
43
- "testing": [
44
- "dist/testing/index.d.ts"
45
- ]
46
- }
47
- },
48
53
  "devDependencies": {
49
54
  "@playwright/test": "^1.62.1",
50
- "@types/node": "^26.1.2",
55
+ "@types/node": "^26.2.0",
51
56
  "jsdom": "^30.0.1",
52
57
  "typescript": "^6.0.3",
53
- "vite": "^8.2.0",
58
+ "vite": "^8.2.1",
54
59
  "vitest": "^4.1.10"
55
60
  }
56
61
  }