@vielzeug/necromancer 26.11.2 → 26.11.4

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 attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t,n=!1){let r=!1,i,a=!1,o,s=createResultPromise(e,()=>{o?.(),o=void 0},n).then(e=>r&&a&&e.status===`cancelled`?{reason:i,status:`cancelled`}:e),dispose=t=>{r||(r=!0,i=t,a=t!==void 0,o?.(),o=void 0,e.cancel())};return t&&(o=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return r},result:s,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}exports.createAnimationGroup=createAnimationGroup,exports.createAnimationHandle=createAnimationHandle;
1
+ function attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t){return e.finished.then(()=>(t(),{status:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t){let n=!1,r,i,a=createResultPromise(e,()=>{i?.(),i=void 0}).then(e=>n&&e.status===`cancelled`?r===void 0?{status:`cancelled`}:{reason:r,status:`cancelled`}:e),dispose=t=>{n||(n=!0,r=t,i?.(),i=void 0,e.cancel())};return t&&(i=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return n},result:a,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}exports.createAnimationGroup=createAnimationGroup,exports.createAnimationHandle=createAnimationHandle;
2
2
  //# sourceMappingURL=_handle.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.cjs","names":["dispose","signal","animation","onSettled","reduced","disposed","disposeReason","hasDisposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults"],"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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\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,0BAA0B,EAAqB,EAAiD,CACvG,IAAM,UAAcA,EAAQC,EAAO,MAAM,EAUzC,OARAA,EAAO,iBAAiB,QAAS,MAAO,CAAE,KAAM,EAAK,CAAC,EAElDA,EAAO,SACTA,EAAO,oBAAoB,QAAS,KAAK,EACzCD,EAAQC,EAAO,MAAM,MACR,IAAA,QAGFA,EAAO,oBAAoB,QAAS,KAAK,CACxD,CAEA,SAAS,oBAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAOC,EAAU,SAAS,UAEtBC,EAAU,EAEH,CAAE,OAAQC,EAAU,UAAY,UAAW,GAEnD,IACCD,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,sBAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAIE,EAAW,GACXC,EACAC,EAAmB,GACnBC,EAEEC,EAAmC,oBACvCP,MACM,CACJM,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACAJ,CACF,CAAC,CAAC,KAAM,GACFC,GAAYE,GAAoBG,EAAQ,SAAW,YAC9C,CAAE,OAAQJ,EAAe,OAAQ,WAAY,EAG/CI,CACR,EAEK,QAAW,GAA2B,CACtCL,IAEJ,EAAW,GACX,EAAgBM,EAChB,EAAmBA,IAAW,IAAA,GAC9BH,IAAsB,EACtB,EAAsB,IAAA,GACtBN,EAAU,OAAO,EACnB,EAMA,OAJID,IACF,EAAsB,0BAA0BA,EAAQ,OAAO,GAG1D,CACL,YACA,QACA,IAAI,UAAW,CACb,OAAOI,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,qBAAqB,EAAqC,EAAsC,CAC9G,IAAIA,EAAW,GACXG,EAEEI,EAA+C,QAAQ,IAAIC,EAAQ,IAAK,GAAWC,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACCN,IAAsB,EACtB,EAAsB,IAAA,GAEfO,EAEX,EAEM,QAAW,GAA2B,CAC1C,GAAI,GAEJ,GAAW,GACXP,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAMM,KAAUD,EAASC,EAAO,QAAQH,CAAM,CAHxC,CAIb,EAMA,OAJIV,IACF,EAAsB,0BAA0BA,EAAQ,OAAO,GAG1D,CACL,QACA,IAAI,UAAW,CACb,OAAOI,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,C"}
1
+ {"version":3,"file":"_handle.cjs","names":["dispose","signal","animation","onSettled","disposed","disposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults"],"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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: '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): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(animation, () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n }).then((outcome): AnimationResult => {\n // An owner-driven cancellation reports the owner's dispose reason (none for a\n // bare dispose()), never the native cancel's DOMException: only an external\n // cancellation of the native animation surfaces that reason here.\n if (disposed && outcome.status === 'cancelled') {\n return disposeReason === undefined ? { status: 'cancelled' } : { 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 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,0BAA0B,EAAqB,EAAiD,CACvG,IAAM,UAAcA,EAAQC,EAAO,MAAM,EAUzC,OARAA,EAAO,iBAAiB,QAAS,MAAO,CAAE,KAAM,EAAK,CAAC,EAElDA,EAAO,SACTA,EAAO,oBAAoB,QAAS,KAAK,EACzCD,EAAQC,EAAO,MAAM,MACR,IAAA,QAGFA,EAAO,oBAAoB,QAAS,KAAK,CACxD,CAEA,SAAS,oBAAoB,EAAsB,EAAiD,CAClG,OAAOC,EAAU,SAAS,UAEtBC,EAAU,EAEH,CAAE,OAAQ,UAAW,GAE7B,IACCA,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,sBAAsB,EAAsB,EAAuC,CACjG,IAAIC,EAAW,GACXC,EACAC,EAEEC,EAAmC,oBAAoBL,MAAiB,CAC5EI,IAAsB,EACtB,EAAsB,IAAA,EACxB,CAAC,CAAC,CAAC,KAAM,GAIHF,GAAYI,EAAQ,SAAW,YAC1BH,IAAkB,IAAA,GAAY,CAAE,OAAQ,WAAY,EAAI,CAAE,OAAQA,EAAe,OAAQ,WAAY,EAGvGG,CACR,EAEK,QAAW,GAA2B,CACtCJ,IAEJ,EAAW,GACX,EAAgBK,EAChBH,IAAsB,EACtB,EAAsB,IAAA,GACtBJ,EAAU,OAAO,EACnB,EAMA,OAJID,IACF,EAAsB,0BAA0BA,EAAQ,OAAO,GAG1D,CACL,YACA,QACA,IAAI,UAAW,CACb,OAAOG,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,qBAAqB,EAAqC,EAAsC,CAC9G,IAAIA,EAAW,GACXE,EAEEI,EAA+C,QAAQ,IAAIC,EAAQ,IAAK,GAAWC,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACCN,IAAsB,EACtB,EAAsB,IAAA,GAEfO,EAEX,EAEM,QAAW,GAA2B,CAC1C,GAAI,GAEJ,GAAW,GACXP,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAMM,KAAUD,EAASC,EAAO,QAAQH,CAAM,CAHxC,CAIb,EAMA,OAJIR,IACF,EAAsB,0BAA0BA,EAAQ,OAAO,GAG1D,CACL,QACA,IAAI,UAAW,CACb,OAAOG,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,C"}
package/dist/_handle.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AnimationGroup, AnimationHandle } from './types';
2
2
  /** Wraps a native Animation with Necromancer's lifecycle contract. */
3
- export declare function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced?: boolean): AnimationHandle;
3
+ export declare function createAnimationHandle(animation: Animation, signal?: AbortSignal): AnimationHandle;
4
4
  /** Combines child animation handles under one disposal owner. */
5
5
  export declare function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup;
6
6
  //# sourceMappingURL=_handle.d.ts.map
@@ -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;AAmChF,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"}
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;AAmChF,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,eAAe,CA4CjG;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAqC9G"}
package/dist/_handle.js CHANGED
@@ -3,28 +3,28 @@ function attachAbortSignalListener(e, t) {
3
3
  let abort = () => t(e.reason);
4
4
  return e.addEventListener("abort", abort, { once: !0 }), e.aborted ? (e.removeEventListener("abort", abort), t(e.reason), () => void 0) : () => e.removeEventListener("abort", abort);
5
5
  }
6
- function createResultPromise(e, t, n) {
7
- return e.finished.then(() => (t(), { status: n ? "reduced" : "finished" }), (e) => (t(), {
6
+ function createResultPromise(e, t) {
7
+ return e.finished.then(() => (t(), { status: "finished" }), (e) => (t(), {
8
8
  reason: e,
9
9
  status: "cancelled"
10
10
  }));
11
11
  }
12
- function createAnimationHandle(e, t, n = !1) {
13
- let r = !1, i, a = !1, o, s = createResultPromise(e, () => {
14
- o?.(), o = void 0;
15
- }, n).then((e) => r && a && e.status === "cancelled" ? {
16
- reason: i,
12
+ function createAnimationHandle(e, t) {
13
+ let n = !1, r, i, a = createResultPromise(e, () => {
14
+ i?.(), i = void 0;
15
+ }).then((e) => n && e.status === "cancelled" ? r === void 0 ? { status: "cancelled" } : {
16
+ reason: r,
17
17
  status: "cancelled"
18
18
  } : e), dispose = (t) => {
19
- r || (r = !0, i = t, a = t !== void 0, o?.(), o = void 0, e.cancel());
19
+ n || (n = !0, r = t, i?.(), i = void 0, e.cancel());
20
20
  };
21
- return t && (o = attachAbortSignalListener(t, dispose)), {
21
+ return t && (i = attachAbortSignalListener(t, dispose)), {
22
22
  animation: e,
23
23
  dispose,
24
24
  get disposed() {
25
- return r;
25
+ return n;
26
26
  },
27
- result: s,
27
+ result: a,
28
28
  [Symbol.dispose]() {
29
29
  this.dispose();
30
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.js","names":["dispose","signal","animation","onSettled","reduced","disposed","disposeReason","hasDisposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults"],"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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\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,0BAA0B,GAAqB,GAAiD;CACvG,IAAM,cAAcA,EAAQC,EAAO,MAAM;CAUzC,OARAA,EAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,GAAK,CAAC,GAElDA,EAAO,WACTA,EAAO,oBAAoB,SAAS,KAAK,GACzCD,EAAQC,EAAO,MAAM,SACR,KAAA,WAGFA,EAAO,oBAAoB,SAAS,KAAK;AACxD;AAEA,SAAS,oBAAoB,GAAsB,GAAuB,GAA4C;CACpH,OAAOC,EAAU,SAAS,YAEtBC,EAAU,GAEH,EAAE,QAAQC,IAAU,YAAY,WAAW,KAEnD,OACCD,EAAU,GAEH;EAAE;EAAQ,QAAQ;CAAY,EAEzC;AACF;AAGA,SAAgB,sBAAsB,GAAsB,GAAsB,IAAU,IAAwB;CAClH,IAAIE,IAAW,IACXC,GACAC,IAAmB,IACnBC,GAEEC,IAAmC,oBACvCP,SACM;EAEJ,AADAM,IAAsB,GACtB,IAAsB,KAAA;CACxB,GACAJ,CACF,CAAC,CAAC,MAAM,MACFC,KAAYE,KAAoBG,EAAQ,WAAW,cAC9C;EAAE,QAAQJ;EAAe,QAAQ;CAAY,IAG/CI,CACR,GAEK,WAAW,MAA2B;EAC1C,AAAIL,MAEJ,IAAW,IACX,IAAgBM,GAChB,IAAmBA,MAAW,KAAA,GAC9BH,IAAsB,GACtB,IAAsB,KAAA,GACtBN,EAAU,OAAO;CACnB;CAMA,OAJID,MACF,IAAsB,0BAA0BA,GAAQ,OAAO,IAG1D;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAOI;EACT;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF;AAGA,SAAgB,qBAAqB,GAAqC,GAAsC;CAC9G,IAAIA,IAAW,IACXG,GAEEI,IAA+C,QAAQ,IAAIC,EAAQ,KAAK,MAAWC,EAAO,MAAM,CAAC,CAAC,CAAC,MACtG,OACCN,IAAsB,GACtB,IAAsB,KAAA,GAEfO,EAEX,GAEM,WAAW,MAA2B;EAC1C,IAAI,IAEJ;GAEA,AAFA,IAAW,IACXP,IAAsB,GACtB,IAAsB,KAAA;GACtB,KAAK,IAAMM,KAAUD,GAAS,EAAO,QAAQF,CAAM;EAHxC;CAIb;CAMA,OAJIV,MACF,IAAsB,0BAA0BA,GAAQ,OAAO,IAG1D;EACL;EACA,IAAI,WAAW;GACb,OAAOI;EACT;EACA;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF"}
1
+ {"version":3,"file":"_handle.js","names":["dispose","signal","animation","onSettled","disposed","disposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults"],"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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: '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): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(animation, () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n }).then((outcome): AnimationResult => {\n // An owner-driven cancellation reports the owner's dispose reason (none for a\n // bare dispose()), never the native cancel's DOMException: only an external\n // cancellation of the native animation surfaces that reason here.\n if (disposed && outcome.status === 'cancelled') {\n return disposeReason === undefined ? { status: 'cancelled' } : { 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 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,0BAA0B,GAAqB,GAAiD;CACvG,IAAM,cAAcA,EAAQC,EAAO,MAAM;CAUzC,OARAA,EAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,GAAK,CAAC,GAElDA,EAAO,WACTA,EAAO,oBAAoB,SAAS,KAAK,GACzCD,EAAQC,EAAO,MAAM,SACR,KAAA,WAGFA,EAAO,oBAAoB,SAAS,KAAK;AACxD;AAEA,SAAS,oBAAoB,GAAsB,GAAiD;CAClG,OAAOC,EAAU,SAAS,YAEtBC,EAAU,GAEH,EAAE,QAAQ,WAAW,KAE7B,OACCA,EAAU,GAEH;EAAE;EAAQ,QAAQ;CAAY,EAEzC;AACF;AAGA,SAAgB,sBAAsB,GAAsB,GAAuC;CACjG,IAAIC,IAAW,IACXC,GACAC,GAEEC,IAAmC,oBAAoBL,SAAiB;EAE5E,AADAI,IAAsB,GACtB,IAAsB,KAAA;CACxB,CAAC,CAAC,CAAC,MAAM,MAIHF,KAAYI,EAAQ,WAAW,cAC1BH,MAAkB,KAAA,IAAY,EAAE,QAAQ,YAAY,IAAI;EAAE,QAAQA;EAAe,QAAQ;CAAY,IAGvGG,CACR,GAEK,WAAW,MAA2B;EAC1C,AAAIJ,MAEJ,IAAW,IACX,IAAgBK,GAChBH,IAAsB,GACtB,IAAsB,KAAA,GACtBJ,EAAU,OAAO;CACnB;CAMA,OAJID,MACF,IAAsB,0BAA0BA,GAAQ,OAAO,IAG1D;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAOG;EACT;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF;AAGA,SAAgB,qBAAqB,GAAqC,GAAsC;CAC9G,IAAIA,IAAW,IACXE,GAEEI,IAA+C,QAAQ,IAAIC,EAAQ,KAAK,MAAWC,EAAO,MAAM,CAAC,CAAC,CAAC,MACtG,OACCN,IAAsB,GACtB,IAAsB,KAAA,GAEfO,EAEX,GAEM,WAAW,MAA2B;EAC1C,IAAI,IAEJ;GAEA,AAFA,IAAW,IACXP,IAAsB,GACtB,IAAsB,KAAA;GACtB,KAAK,IAAMM,KAAUD,GAAS,EAAO,QAAQF,CAAM;EAHxC;CAIb;CAMA,OAJIR,MACF,IAAsB,0BAA0BA,GAAQ,OAAO,IAG1D;EACL;EACA,IAAI,WAAW;GACb,OAAOG;EACT;EACA;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF"}
@@ -0,0 +1,2 @@
1
+ function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(e,t){if(t===0)return e;let n=e.delay??0;return{...e,delay:n+t}}exports.resolveAnimationOptions=resolveAnimationOptions,exports.withStaggeredDelay=withStaggeredDelay;
2
+ //# sourceMappingURL=_options.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_options.cjs","names":["_motion","_signal","nativeOptions","options","reduced","offset","delay"],"sources":["../src/_options.ts"],"sourcesContent":["import type { AnimateOptions } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\n}\n\n/** Adds an element's stagger offset to a validated numeric delay. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n // animateEach() validates up front that a non-zero stagger carries a numeric\n // (or absent) delay, so a non-zero offset here can only ever meet a number.\n const delay = options.delay ?? 0;\n\n return { ...options, delay: (delay as number) + offset };\n}\n"],"mappings":"AAGA,SAAgB,wBAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,OAAQA,EAAS,OAAQC,EAAS,GAAGC,GAAkBC,EAE/D,OAAOC,EAAU,CAAE,GAAGF,EAAe,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAIA,CAC7F,CAGA,SAAgB,mBAAmB,EAAyB,EAAgC,CAC1F,GAAIG,IAAW,EAAG,OAAOF,EAIzB,IAAMG,EAAQH,EAAQ,OAAS,EAE/B,MAAO,CAAE,GAAGA,EAAS,MAAQG,EAAmBD,CAAO,CACzD,C"}
@@ -0,0 +1,6 @@
1
+ import type { AnimateOptions } from './types';
2
+ /** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */
3
+ export declare function resolveAnimationOptions(options?: AnimateOptions, reduced?: boolean): KeyframeAnimationOptions;
4
+ /** Adds an element's stagger offset to a validated numeric delay. */
5
+ export declare function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions;
6
+ //# sourceMappingURL=_options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_options.d.ts","sourceRoot":"","sources":["../src/_options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,EAAE,OAAO,UAAQ,GAAG,wBAAwB,CAI/G;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAQ1F"}
@@ -0,0 +1,23 @@
1
+ //#region src/_options.ts
2
+ function resolveAnimationOptions(e = {}, t = !1) {
3
+ let { motion: n, signal: r, ...i } = e;
4
+ return t ? {
5
+ ...i,
6
+ delay: 0,
7
+ duration: 0,
8
+ endDelay: 0,
9
+ iterations: 1
10
+ } : i;
11
+ }
12
+ function withStaggeredDelay(e, t) {
13
+ if (t === 0) return e;
14
+ let n = e.delay ?? 0;
15
+ return {
16
+ ...e,
17
+ delay: n + t
18
+ };
19
+ }
20
+ //#endregion
21
+ export { resolveAnimationOptions, withStaggeredDelay };
22
+
23
+ //# sourceMappingURL=_options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_options.js","names":["_motion","_signal","nativeOptions","options","reduced","offset","delay"],"sources":["../src/_options.ts"],"sourcesContent":["import type { AnimateOptions } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\n}\n\n/** Adds an element's stagger offset to a validated numeric delay. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n // animateEach() validates up front that a non-zero stagger carries a numeric\n // (or absent) delay, so a non-zero offset here can only ever meet a number.\n const delay = options.delay ?? 0;\n\n return { ...options, delay: (delay as number) + offset };\n}\n"],"mappings":";AAGA,SAAgB,wBAAwB,IAA0B,CAAC,GAAG,IAAU,IAAiC;CAC/G,IAAM,EAAE,QAAQA,GAAS,QAAQC,GAAS,GAAGC,MAAkBC;CAE/D,OAAOC,IAAU;EAAE,GAAGF;EAAe,OAAO;EAAG,UAAU;EAAG,UAAU;EAAG,YAAY;CAAE,IAAIA;AAC7F;AAGA,SAAgB,mBAAmB,GAAyB,GAAgC;CAC1F,IAAIG,MAAW,GAAG,OAAOF;CAIzB,IAAMG,IAAQH,EAAQ,SAAS;CAE/B,OAAO;EAAE,GAAGA;EAAS,OAAQG,IAAmBD;CAAO;AACzD"}
@@ -1,2 +1,2 @@
1
- const e=require("./_handle.cjs"),t=require("./errors.cjs"),n=require("./motion.cjs"),r=require("./animate.cjs"),i=require("./_elements.cjs");function validateStagger(e){if(!Number.isFinite(e)||e<0)throw new t.NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function animateEach(t,a,o={}){let{signal:s,stagger:c=0,...l}=o;if(validateStagger(c),s?.aborted)throw s.reason;let u=i.uniqueElements(t),d=u.map((e,t)=>typeof a==`function`?a(e,t,u.length):a);if(s?.aborted)throw s.reason;let f=[];try{for(let[e,t]of u.entries()){let i=d[e];f.push(r.animate(t,i,n.withStaggeredDelay(l,e*c)))}}catch(e){for(let t of f)t.dispose(e);throw e}return e.createAnimationGroup(f,s)}exports.animateEach=animateEach;
1
+ const e=require("./_handle.cjs"),t=require("./_options.cjs"),n=require("./errors.cjs"),r=require("./animate.cjs"),i=require("./_elements.cjs");function validateStagger(e,t){if(!Number.isFinite(e)||e<0)throw new n.NecromancerConfigError(`stagger must be a finite, non-negative number.`);if(e>0&&t!==void 0&&typeof t!=`number`)throw new n.NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof t}.`)}function animateEach(n,a,o={}){let{signal:s,stagger:c=0,...l}=o;validateStagger(c,l.delay),s?.throwIfAborted();let u=i.uniqueElements(n),d=u.map((e,t)=>typeof a==`function`?a(e,t,u.length):a);s?.throwIfAborted();let f=[];try{for(let[e,n]of u.entries()){let i=d[e];f.push(r.animate(n,i,t.withStaggeredDelay(l,e*c)))}}catch(e){for(let t of f)t.dispose(e);throw e}return e.createAnimationGroup(f,s)}exports.animateEach=animateEach;
2
2
  //# sourceMappingURL=animate-each.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.cjs","names":["NecromancerConfigError","uniqueElements","animate","withStaggeredDelay","createAnimationGroup","require__handle","require_errors","require_motion","require_animate","require__elements","stagger","animationOptions","options","signal","unique","elements","frames","keyframes","element","index","handles","currentFrames","error","handle"],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport { withStaggeredDelay } from './motion';\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\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\n if (signal?.aborted) throw signal.reason;\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":"A,6IAOA,SAAS,gBAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAASU,CAAO,GAAKA,EAAU,EACzC,MAAM,IAAIV,EAAAA,uBAAuB,gDAAgD,CAErF,CAQA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAGW,GAAqBC,EAIrD,GAFA,gBAAgBF,CAAO,EAEnBG,GAAQ,QAAS,MAAMA,EAAO,OAElC,IAAMC,EAASb,EAAAA,eAAec,CAAQ,EAChCC,EAASF,EAAO,KAAK,EAAS,IAClC,OAAOG,GAAc,WAAaA,EAAUC,EAASC,EAAOL,EAAO,MAAM,EAAIG,CAC/E,EAEA,GAAIJ,GAAQ,QAAS,MAAMA,EAAO,OAElC,IAAMO,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAACD,EAAOD,KAAYJ,EAAO,QAAQ,EAAG,CAE/C,IAAMO,EAAgBL,EAAOG,GAE7BC,EAAQ,KAAKlB,EAAAA,QAAQgB,EAASG,EAAelB,EAAAA,mBAAmBQ,EAAkBQ,EAAQT,CAAO,CAAC,CAAC,CACrG,CACF,OAASY,EAAO,CACd,IAAK,IAAMC,KAAUH,EAASG,EAAO,QAAQD,CAAK,EAElD,MAAMA,CACR,CAEA,OAAOlB,EAAAA,qBAAqBgB,EAASP,CAAM,CAC7C,C"}
1
+ {"version":3,"file":"animate-each.cjs","names":["NecromancerConfigError","uniqueElements","animate","withStaggeredDelay","createAnimationGroup","require__handle","require__options","require_errors","require_animate","require__elements","stagger","delay","animationOptions","options","signal","unique","elements","frames","keyframes","element","index","handles","currentFrames","error","handle"],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_options';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number, delay: number | string | null | undefined): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n\n // A non-zero stagger adds to the delay, so it needs a numeric one. Checking here,\n // before any animation starts, keeps a pure config error free of side effects.\n if (stagger > 0 && delay !== undefined && typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger, animationOptions.delay);\n\n signal?.throwIfAborted();\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\n signal?.throwIfAborted();\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":"A,+IAOA,SAAS,gBAAgB,EAAiB,EAAiD,CACzF,GAAI,CAAC,OAAO,SAASU,CAAO,GAAKA,EAAU,EACzC,MAAM,IAAIV,EAAAA,uBAAuB,gDAAgD,EAKnF,GAAIU,EAAU,GAAKC,IAAU,IAAA,IAAa,OAAOA,GAAU,SACzD,MAAM,IAAIX,EAAAA,uBAAuB,yDAAyD,OAAOW,EAAM,EAAE,CAE7G,CAQA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAGC,GAAqBC,EAErD,gBAAgBH,EAASE,EAAiB,KAAK,EAE/CE,GAAQ,eAAe,EAEvB,IAAMC,EAASd,EAAAA,eAAee,CAAQ,EAChCC,EAASF,EAAO,KAAK,EAAS,IAClC,OAAOG,GAAc,WAAaA,EAAUC,EAASC,EAAOL,EAAO,MAAM,EAAIG,CAC/E,EAEAJ,GAAQ,eAAe,EAEvB,IAAMO,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAACD,EAAOD,KAAYJ,EAAO,QAAQ,EAAG,CAE/C,IAAMO,EAAgBL,EAAOG,GAE7BC,EAAQ,KAAKnB,EAAAA,QAAQiB,EAASG,EAAenB,EAAAA,mBAAmBS,EAAkBQ,EAAQV,CAAO,CAAC,CAAC,CACrG,CACF,OAASa,EAAO,CACd,IAAK,IAAMC,KAAUH,EAASG,EAAO,QAAQD,CAAK,EAElD,MAAMA,CACR,CAEA,OAAOnB,EAAAA,qBAAqBiB,EAASP,CAAM,CAC7C,C"}
@@ -1 +1 @@
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,CAAC,WAAW,SAAS,OAAO,EACrD,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC/B,SAAS,EAAE,SAAS,GAAG,eAAe,CAAC,WAAW,CAAC,EACnD,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CA8BhB"}
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;AAc/G;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,WAAW,SAAS,OAAO,EACrD,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC/B,SAAS,EAAE,SAAS,GAAG,eAAe,CAAC,WAAW,CAAC,EACnD,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CA8BhB"}
@@ -1,22 +1,23 @@
1
1
  import { createAnimationGroup as e } from "./_handle.js";
2
- import { NecromancerConfigError as t } from "./errors.js";
3
- import { withStaggeredDelay as n } from "./motion.js";
2
+ import { withStaggeredDelay as t } from "./_options.js";
3
+ import { NecromancerConfigError as n } from "./errors.js";
4
4
  import { animate as r } from "./animate.js";
5
5
  import { uniqueElements as i } from "./_elements.js";
6
6
  //#region src/animate-each.ts
7
- function validateStagger(e) {
8
- if (!Number.isFinite(e) || e < 0) throw new t("stagger must be a finite, non-negative number.");
7
+ function validateStagger(e, t) {
8
+ if (!Number.isFinite(e) || e < 0) throw new n("stagger must be a finite, non-negative number.");
9
+ if (e > 0 && t !== void 0 && typeof t != "number") throw new n(`A non-zero stagger requires a numeric delay; received ${typeof t}.`);
9
10
  }
10
- function animateEach(t, a, o = {}) {
11
+ function animateEach(n, a, o = {}) {
11
12
  let { signal: s, stagger: c = 0, ...l } = o;
12
- if (validateStagger(c), s?.aborted) throw s.reason;
13
- let u = i(t), d = u.map((e, t) => typeof a == "function" ? a(e, t, u.length) : a);
14
- if (s?.aborted) throw s.reason;
13
+ validateStagger(c, l.delay), s?.throwIfAborted();
14
+ let u = i(n), d = u.map((e, t) => typeof a == "function" ? a(e, t, u.length) : a);
15
+ s?.throwIfAborted();
15
16
  let f = [];
16
17
  try {
17
- for (let [e, t] of u.entries()) {
18
+ for (let [e, n] of u.entries()) {
18
19
  let i = d[e];
19
- f.push(r(t, i, n(l, e * c)));
20
+ f.push(r(n, i, t(l, e * c)));
20
21
  }
21
22
  } catch (e) {
22
23
  for (let t of f) t.dispose(e);
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.js","names":["stagger","NecromancerConfigError","animationOptions","options","signal","unique","uniqueElements","elements","frames","keyframes","element","index","handles","currentFrames","animate","withStaggeredDelay","error","handle","createAnimationGroup"],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport { withStaggeredDelay } from './motion';\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\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\n if (signal?.aborted) throw signal.reason;\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,gBAAgB,GAAuB;CAC9C,IAAI,CAAC,OAAO,SAASA,CAAO,KAAKA,IAAU,GACzC,MAAM,IAAIC,EAAuB,gDAAgD;AAErF;AAQA,SAAgB,YACd,GACA,GACA,IAA8B,CAAC,GACf;CAChB,IAAM,EAAE,WAAQ,aAAU,GAAG,GAAGC,MAAqBC;CAIrD,IAFA,gBAAgBH,CAAO,GAEnBI,GAAQ,SAAS,MAAMA,EAAO;CAElC,IAAMC,IAASC,EAAeC,CAAQ,GAChCC,IAASH,EAAO,KAAK,GAAS,MAClC,OAAOI,KAAc,aAAaA,EAAUC,GAASC,GAAON,EAAO,MAAM,IAAII,CAC/E;CAEA,IAAIL,GAAQ,SAAS,MAAMA,EAAO;CAElC,IAAMQ,IAA6B,CAAC;CAEpC,IAAI;EACF,KAAK,IAAM,CAACD,GAAOD,MAAYL,EAAO,QAAQ,GAAG;GAE/C,IAAMQ,IAAgBL,EAAOG;GAE7B,EAAQ,KAAKG,EAAQJ,GAASG,GAAeE,EAAmBb,GAAkBS,IAAQX,CAAO,CAAC,CAAC;EACrG;CACF,SAASgB,GAAO;EACd,KAAK,IAAMC,KAAUL,GAAS,EAAO,QAAQI,CAAK;EAElD,MAAMA;CACR;CAEA,OAAOE,EAAqBN,GAASR,CAAM;AAC7C"}
1
+ {"version":3,"file":"animate-each.js","names":["stagger","NecromancerConfigError","delay","animationOptions","options","signal","unique","uniqueElements","elements","frames","keyframes","element","index","handles","currentFrames","animate","withStaggeredDelay","error","handle","createAnimationGroup"],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_options';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number, delay: number | string | null | undefined): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n\n // A non-zero stagger adds to the delay, so it needs a numeric one. Checking here,\n // before any animation starts, keeps a pure config error free of side effects.\n if (stagger > 0 && delay !== undefined && typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger, animationOptions.delay);\n\n signal?.throwIfAborted();\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\n signal?.throwIfAborted();\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,gBAAgB,GAAiB,GAAiD;CACzF,IAAI,CAAC,OAAO,SAASA,CAAO,KAAKA,IAAU,GACzC,MAAM,IAAIC,EAAuB,gDAAgD;CAKnF,IAAID,IAAU,KAAKE,MAAU,KAAA,KAAa,OAAOA,KAAU,UACzD,MAAM,IAAID,EAAuB,yDAAyD,OAAOC,EAAM,EAAE;AAE7G;AAQA,SAAgB,YACd,GACA,GACA,IAA8B,CAAC,GACf;CAChB,IAAM,EAAE,WAAQ,aAAU,GAAG,GAAGC,MAAqBC;CAIrD,AAFA,gBAAgBJ,GAASG,EAAiB,KAAK,GAE/CE,GAAQ,eAAe;CAEvB,IAAMC,IAASC,EAAeC,CAAQ,GAChCC,IAASH,EAAO,KAAK,GAAS,MAClC,OAAOI,KAAc,aAAaA,EAAUC,GAASC,GAAON,EAAO,MAAM,IAAII,CAC/E;CAEA,GAAQ,eAAe;CAEvB,IAAMG,IAA6B,CAAC;CAEpC,IAAI;EACF,KAAK,IAAM,CAACD,GAAOD,MAAYL,EAAO,QAAQ,GAAG;GAE/C,IAAMQ,IAAgBL,EAAOG;GAE7B,EAAQ,KAAKG,EAAQJ,GAASG,GAAeE,EAAmBb,GAAkBS,IAAQZ,CAAO,CAAC,CAAC;EACrG;CACF,SAASiB,GAAO;EACd,KAAK,IAAMC,KAAUL,GAAS,EAAO,QAAQI,CAAK;EAElD,MAAMA;CACR;CAEA,OAAOE,EAAqBN,GAASR,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");function animate(r,i,a={}){if(a.signal?.aborted)throw a.signal.reason;if(typeof r.animate!=`function`)throw new t.NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);let o=n.shouldReduceMotion(a.motion??`system`),s=r.animate(i,n.resolveAnimationOptions(a,o));return e.createAnimationHandle(s,a.signal,o)}exports.animate=animate;
1
+ const e=require("./_handle.cjs"),t=require("./_options.cjs"),n=require("./errors.cjs"),r=require("./motion.cjs");function animate(i,a,o={}){if(o.signal?.throwIfAborted(),typeof i.animate!=`function`)throw new n.NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);let s=i.animate(a,t.resolveAnimationOptions(o,r.shouldReduceMotion(o.motion??`system`)));return e.createAnimationHandle(s,o.signal)}exports.animate=animate;
2
2
  //# sourceMappingURL=animate.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate.cjs","names":["NecromancerUnsupportedError","shouldReduceMotion","resolveAnimationOptions","createAnimationHandle","require__handle","require_errors","require_motion","options","element","reduced","nativeAnimation","keyframes"],"sources":["../src/animate.ts"],"sourcesContent":["import { createAnimationHandle } from './_handle';\nimport { NecromancerUnsupportedError } from './errors';\nimport { resolveAnimationOptions, shouldReduceMotion } from './motion';\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 const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n return createAnimationHandle(nativeAnimation, options.signal, reduced);\n}\n"],"mappings":"A,qFAWA,SAAgB,QAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAIO,EAAQ,QAAQ,QAAS,MAAMA,EAAQ,OAAO,OAElD,GAAI,OAAOC,EAAQ,SAAY,WAC7B,MAAM,IAAIR,EAAAA,4BAA4B,8DAA8D,EAGtG,IAAMS,EAAUR,EAAAA,mBAAmBM,EAAQ,QAAU,QAAQ,EACvDG,EAAkBF,EAAQ,QAC9BG,EACAT,EAAAA,wBAAwBK,EAASE,CAAO,CAC1C,EACA,OAAON,EAAAA,sBAAsBO,EAAiBH,EAAQ,OAAQE,CAAO,CACvE,C"}
1
+ {"version":3,"file":"animate.cjs","names":["NecromancerUnsupportedError","resolveAnimationOptions","shouldReduceMotion","createAnimationHandle","require__handle","require__options","require_errors","require_motion","options","element","nativeAnimation","keyframes"],"sources":["../src/animate.ts"],"sourcesContent":["import { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions } from './_options';\nimport { NecromancerUnsupportedError } from './errors';\nimport { shouldReduceMotion } from './motion';\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 options.signal?.throwIfAborted();\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, shouldReduceMotion(options.motion ?? 'system')),\n );\n return createAnimationHandle(nativeAnimation, options.signal);\n}\n"],"mappings":"A,iHAYA,SAAgB,QAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAG7G,GAFAQ,EAAQ,QAAQ,eAAe,EAE3B,OAAOC,EAAQ,SAAY,WAC7B,MAAM,IAAIT,EAAAA,4BAA4B,8DAA8D,EAGtG,IAAMU,EAAkBD,EAAQ,QAC9BE,EACAV,EAAAA,wBAAwBO,EAASN,EAAAA,mBAAmBM,EAAQ,QAAU,QAAQ,CAAC,CACjF,EACA,OAAOL,EAAAA,sBAAsBO,EAAiBF,EAAQ,MAAM,CAC9D,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAGA,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,CAa7G"}
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,CAY7G"}
package/dist/animate.js CHANGED
@@ -1,12 +1,12 @@
1
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";
2
+ import { resolveAnimationOptions as t } from "./_options.js";
3
+ import { NecromancerUnsupportedError as n } from "./errors.js";
4
+ import { shouldReduceMotion as r } from "./motion.js";
4
5
  //#region src/animate.ts
5
6
  function animate(i, a, o = {}) {
6
- if (o.signal?.aborted) throw o.signal.reason;
7
- if (typeof i.animate != "function") throw new t("The Web Animations API is not available in this environment.");
8
- let s = r(o.motion ?? "system"), c = i.animate(a, n(o, s));
9
- return e(c, o.signal, s);
7
+ if (o.signal?.throwIfAborted(), typeof i.animate != "function") throw new n("The Web Animations API is not available in this environment.");
8
+ let s = i.animate(a, t(o, r(o.motion ?? "system")));
9
+ return e(s, o.signal);
10
10
  }
11
11
  //#endregion
12
12
  export { animate };
@@ -1 +1 @@
1
- {"version":3,"file":"animate.js","names":["options","element","NecromancerUnsupportedError","reduced","shouldReduceMotion","nativeAnimation","keyframes","resolveAnimationOptions","createAnimationHandle"],"sources":["../src/animate.ts"],"sourcesContent":["import { createAnimationHandle } from './_handle';\nimport { NecromancerUnsupportedError } from './errors';\nimport { resolveAnimationOptions, shouldReduceMotion } from './motion';\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 const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n return createAnimationHandle(nativeAnimation, options.signal, reduced);\n}\n"],"mappings":";;;;AAWA,SAAgB,QAAQ,GAAkB,GAAsB,IAA0B,CAAC,GAAoB;CAC7G,IAAIA,EAAQ,QAAQ,SAAS,MAAMA,EAAQ,OAAO;CAElD,IAAI,OAAOC,EAAQ,WAAY,YAC7B,MAAM,IAAIC,EAA4B,8DAA8D;CAGtG,IAAMC,IAAUC,EAAmBJ,EAAQ,UAAU,QAAQ,GACvDK,IAAkBJ,EAAQ,QAC9BK,GACAC,EAAwBP,GAASG,CAAO,CAC1C;CACA,OAAOK,EAAsBH,GAAiBL,EAAQ,QAAQG,CAAO;AACvE"}
1
+ {"version":3,"file":"animate.js","names":["options","element","NecromancerUnsupportedError","nativeAnimation","keyframes","resolveAnimationOptions","shouldReduceMotion","createAnimationHandle"],"sources":["../src/animate.ts"],"sourcesContent":["import { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions } from './_options';\nimport { NecromancerUnsupportedError } from './errors';\nimport { shouldReduceMotion } from './motion';\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 options.signal?.throwIfAborted();\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, shouldReduceMotion(options.motion ?? 'system')),\n );\n return createAnimationHandle(nativeAnimation, options.signal);\n}\n"],"mappings":";;;;;AAYA,SAAgB,QAAQ,GAAkB,GAAsB,IAA0B,CAAC,GAAoB;CAG7G,IAFAA,EAAQ,QAAQ,eAAe,GAE3B,OAAOC,EAAQ,WAAY,YAC7B,MAAM,IAAIC,EAA4B,8DAA8D;CAGtG,IAAMC,IAAkBF,EAAQ,QAC9BG,GACAC,EAAwBL,GAASM,EAAmBN,EAAQ,UAAU,QAAQ,CAAC,CACjF;CACA,OAAOO,EAAsBJ,GAAiBH,EAAQ,MAAM;AAC9D"}
package/dist/motion.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(t,n){if(n===0)return t;let r=t.delay??0;if(typeof r!=`number`)throw new e.NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof r}.`);return{...t,delay:r+n}}exports.resolveAnimationOptions=resolveAnimationOptions,exports.shouldReduceMotion=shouldReduceMotion,exports.withStaggeredDelay=withStaggeredDelay;
1
+ function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}exports.shouldReduceMotion=shouldReduceMotion;
2
2
  //# sourceMappingURL=motion.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"motion.cjs","names":["NecromancerConfigError","require_errors","mode","_motion","_signal","nativeOptions","options","reduced","offset","delay"],"sources":["../src/motion.ts"],"sourcesContent":["import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\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":"A,gCAIA,SAAgB,mBAAmB,EAA2B,CAK5D,OAJIE,IAAS,OAAe,GAExBA,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,CAGA,SAAgB,wBAAwB,EAA0B,CAAC,EAAG,EAAU,GAAiC,CAC/G,GAAM,CAAE,OAAQC,EAAS,OAAQC,EAAS,GAAGC,GAAkBC,EAE/D,OAAOC,EAAU,CAAE,GAAGF,EAAe,MAAO,EAAG,SAAU,EAAG,SAAU,EAAG,WAAY,CAAE,EAAIA,CAC7F,CAGA,SAAgB,mBAAmB,EAAyB,EAAgC,CAC1F,GAAIG,IAAW,EAAG,OAAOF,EAEzB,IAAMG,EAAQH,EAAQ,OAAS,EAE/B,GAAI,OAAOG,GAAU,SACnB,MAAM,IAAIT,EAAAA,uBAAuB,yDAAyD,OAAOS,EAAM,EAAE,EAG3G,MAAO,CAAE,GAAGH,EAAS,MAAOG,EAAQD,CAAO,CAC7C,C"}
1
+ {"version":3,"file":"motion.cjs","names":["mode"],"sources":["../src/motion.ts"],"sourcesContent":["import type { MotionMode } from './types';\n\n/**\n * Reports whether the requested motion preference reduces visible movement.\n *\n * This is the same check `animate()` applies internally, exposed so hand-rolled\n * effects (scroll behavior, tweens) stay consistent with it.\n */\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"],"mappings":"AAQA,SAAgB,mBAAmB,EAA2B,CAK5D,OAJIA,IAAS,OAAe,GAExBA,IAAS,WAEN,OAAO,YAAe,YAAc,WAAW,kCAAkC,CAAC,CAAC,OAC5F,C"}
package/dist/motion.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { AnimateOptions, MotionMode } from './types';
2
- /** Reports whether the requested motion preference reduces visible movement. */
1
+ import type { MotionMode } from './types';
2
+ /**
3
+ * Reports whether the requested motion preference reduces visible movement.
4
+ *
5
+ * This is the same check `animate()` applies internally, exposed so hand-rolled
6
+ * effects (scroll behavior, tweens) stay consistent with it.
7
+ */
3
8
  export declare function shouldReduceMotion(mode: MotionMode): boolean;
4
- /** Converts ownership options to native timing, collapsing reduced motion to an instant transition. */
5
- export declare function resolveAnimationOptions(options?: AnimateOptions, reduced?: boolean): KeyframeAnimationOptions;
6
- /** Adds an element's stagger offset without changing native nonnumeric delays when no offset is needed. */
7
- export declare function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions;
8
9
  //# sourceMappingURL=motion.d.ts.map
@@ -1 +1 @@
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;AAE1D,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,CAI/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":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAM5D"}
package/dist/motion.js CHANGED
@@ -1,28 +1,8 @@
1
- import { NecromancerConfigError as e } from "./errors.js";
2
1
  //#region src/motion.ts
3
2
  function shouldReduceMotion(e) {
4
3
  return e === "full" ? !1 : e === "reduced" || typeof matchMedia == "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
5
4
  }
6
- function resolveAnimationOptions(e = {}, t = !1) {
7
- let { motion: n, signal: r, ...i } = e;
8
- return t ? {
9
- ...i,
10
- delay: 0,
11
- duration: 0,
12
- endDelay: 0,
13
- iterations: 1
14
- } : i;
15
- }
16
- function withStaggeredDelay(t, n) {
17
- if (n === 0) return t;
18
- let r = t.delay ?? 0;
19
- if (typeof r != "number") throw new e(`A non-zero stagger requires a numeric delay; received ${typeof r}.`);
20
- return {
21
- ...t,
22
- delay: r + n
23
- };
24
- }
25
5
  //#endregion
26
- export { resolveAnimationOptions, shouldReduceMotion, withStaggeredDelay };
6
+ export { shouldReduceMotion };
27
7
 
28
8
  //# sourceMappingURL=motion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"motion.js","names":["mode","_motion","_signal","nativeOptions","options","reduced","offset","delay","NecromancerConfigError"],"sources":["../src/motion.ts"],"sourcesContent":["import { NecromancerConfigError } from './errors';\nimport type { AnimateOptions, MotionMode } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\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,SAAgB,mBAAmB,GAA2B;CAK5D,OAJIA,MAAS,SAAe,KAExBA,MAAS,aAEN,OAAO,cAAe,cAAc,WAAW,kCAAkC,CAAC,CAAC;AAC5F;AAGA,SAAgB,wBAAwB,IAA0B,CAAC,GAAG,IAAU,IAAiC;CAC/G,IAAM,EAAE,QAAQC,GAAS,QAAQC,GAAS,GAAGC,MAAkBC;CAE/D,OAAOC,IAAU;EAAE,GAAGF;EAAe,OAAO;EAAG,UAAU;EAAG,UAAU;EAAG,YAAY;CAAE,IAAIA;AAC7F;AAGA,SAAgB,mBAAmB,GAAyB,GAAgC;CAC1F,IAAIG,MAAW,GAAG,OAAOF;CAEzB,IAAMG,IAAQH,EAAQ,SAAS;CAE/B,IAAI,OAAOG,KAAU,UACnB,MAAM,IAAIC,EAAuB,yDAAyD,OAAOD,EAAM,EAAE;CAG3G,OAAO;EAAE,GAAGH;EAAS,OAAOG,IAAQD;CAAO;AAC7C"}
1
+ {"version":3,"file":"motion.js","names":["mode"],"sources":["../src/motion.ts"],"sourcesContent":["import type { MotionMode } from './types';\n\n/**\n * Reports whether the requested motion preference reduces visible movement.\n *\n * This is the same check `animate()` applies internally, exposed so hand-rolled\n * effects (scroll behavior, tweens) stay consistent with it.\n */\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"],"mappings":";AAQA,SAAgB,mBAAmB,GAA2B;CAK5D,OAJIA,MAAS,SAAe,KAExBA,MAAS,aAEN,OAAO,cAAe,cAAc,WAAW,kCAAkC,CAAC,CAAC;AAC5F"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t,n=!1){let r=!1,i,a=!1,o,s=createResultPromise(e,()=>{o?.(),o=void 0},n).then(e=>r&&a&&e.status===`cancelled`?{reason:i,status:`cancelled`}:e),dispose=t=>{r||(r=!0,i=t,a=t!==void 0,o?.(),o=void 0,e.cancel())};return t&&(o=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return r},result:s,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}var NecromancerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},NecromancerConfigError=class extends NecromancerError{},NecromancerUnsupportedError=class extends NecromancerError{};function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}function animate(e,t,n={}){if(n.signal?.aborted)throw n.signal.reason;if(typeof e.animate!=`function`)throw new NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);let r=shouldReduceMotion(n.motion??`system`);return createAnimationHandle(e.animate(t,resolveAnimationOptions(n,r)),n.signal,r)}function uniqueElements(e){return[...new Set(e)]}function validateStagger(e){if(!Number.isFinite(e)||e<0)throw new NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function animateEach(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;if(validateStagger(i),r?.aborted)throw r.reason;let o=uniqueElements(e),s=o.map((e,n)=>typeof t==`function`?t(e,n,o.length):t);if(r?.aborted)throw r.reason;let c=[];try{for(let[e,t]of o.entries()){let n=s[e];c.push(animate(t,n,withStaggeredDelay(a,e*i)))}}catch(e){for(let t of c)t.dispose(e);throw e}return createAnimationGroup(c,r)}function round(e,t){let n=10**t;return Math.round(e*n)/n}function measureSize(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 measureLayout(e){let t=e.getBoundingClientRect(),n=measureSize(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function computeScale(e,t){return t===0?1:e/t}function readKey(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new NecromancerConfigError(`getKey must return a non-empty string for every ${n} element.`);return r}function createKeyMap(e,t,n){let r=new Map;for(let i of e){let e=readKey(i.element,t,n);if(r.has(e))throw new NecromancerConfigError(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function changedElement(e,t){if(!e.isConnected)return;let n=measureLayout(e),r=t.x-n.x,i=t.y-n.y,a=computeScale(t.width,n.width),o=computeScale(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 matchByIdentity(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[changedElement(e,t.rect)].filter(e=>!!e):[]})}function matchByKey(e,t,n){let r=new Set;return t.flatMap(t=>{let i=readKey(t,n,`committed`),a=e.get(i);if(r.has(i))throw new NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[changedElement(t,a.rect)].filter(e=>!!e):[]})}function captureLayout(e,t={}){let n=uniqueElements(e).map(e=>({element:e,rect:measureLayout(e)})),r=t.getKey,i=r?createKeyMap(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new NecromancerConfigError(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=uniqueElements(t??n.map(({element:e})=>e)),c=r&&i?matchByKey(i,s,r):matchByIdentity(n,s);return a=!0,animateEach(c.map(({element:e})=>e),(e,t)=>{let n=c[t],r=`${round(n.x,2)}px ${round(n.y,2)}px`;return[{composite:`add`,scale:`${round(n.scaleX,4)} ${round(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.NecromancerConfigError=NecromancerConfigError,exports.NecromancerError=NecromancerError,exports.NecromancerUnsupportedError=NecromancerUnsupportedError,exports.animate=animate,exports.animateEach=animateEach,exports.captureLayout=captureLayout,exports.shouldReduceMotion=shouldReduceMotion;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t){return e.finished.then(()=>(t(),{status:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t){let n=!1,r,i,a=createResultPromise(e,()=>{i?.(),i=void 0}).then(e=>n&&e.status===`cancelled`?r===void 0?{status:`cancelled`}:{reason:r,status:`cancelled`}:e),dispose=t=>{n||(n=!0,r=t,i?.(),i=void 0,e.cancel())};return t&&(i=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return n},result:a,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(e,t){if(t===0)return e;let n=e.delay??0;return{...e,delay:n+t}}var NecromancerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},NecromancerConfigError=class extends NecromancerError{},NecromancerUnsupportedError=class extends NecromancerError{};function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function animate(e,t,n={}){if(n.signal?.throwIfAborted(),typeof e.animate!=`function`)throw new NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);return createAnimationHandle(e.animate(t,resolveAnimationOptions(n,shouldReduceMotion(n.motion??`system`))),n.signal)}function uniqueElements(e){return[...new Set(e)]}function validateStagger(e,t){if(!Number.isFinite(e)||e<0)throw new NecromancerConfigError(`stagger must be a finite, non-negative number.`);if(e>0&&t!==void 0&&typeof t!=`number`)throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof t}.`)}function animateEach(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;validateStagger(i,a.delay),r?.throwIfAborted();let o=uniqueElements(e),s=o.map((e,n)=>typeof t==`function`?t(e,n,o.length):t);r?.throwIfAborted();let c=[];try{for(let[e,t]of o.entries()){let n=s[e];c.push(animate(t,n,withStaggeredDelay(a,e*i)))}}catch(e){for(let t of c)t.dispose(e);throw e}return createAnimationGroup(c,r)}function round(e,t){let n=10**t;return Math.round(e*n)/n}function measureSize(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 measureLayout(e){let t=e.getBoundingClientRect(),n=measureSize(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function computeScale(e,t){return t===0?1:e/t}function readKey(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new NecromancerConfigError(`getKey must return a non-empty string for every ${n} element.`);return r}function createKeyMap(e,t,n){let r=new Map;for(let i of e){let e=readKey(i.element,t,n);if(r.has(e))throw new NecromancerConfigError(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function changedElement(e,t){if(!e.isConnected)return;let n=measureLayout(e),r=t.x-n.x,i=t.y-n.y,a=computeScale(t.width,n.width),o=computeScale(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 matchByIdentity(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[changedElement(e,t.rect)].filter(e=>!!e):[]})}function matchByKey(e,t,n){let r=new Set;return t.flatMap(t=>{let i=readKey(t,n,`committed`),a=e.get(i);if(r.has(i))throw new NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[changedElement(t,a.rect)].filter(e=>!!e):[]})}function captureLayout(e,t={}){let n=uniqueElements(e).map(e=>({element:e,rect:measureLayout(e)})),r=t.getKey,i=r?createKeyMap(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new NecromancerConfigError(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=uniqueElements(t??n.map(({element:e})=>e)),c=r&&i?matchByKey(i,s,r):matchByIdentity(n,s);return a=!0,animateEach(c.map(({element:e})=>e),(e,t)=>{let n=c[t],r=`${round(n.x,2)}px ${round(n.y,2)}px`;return[{composite:`add`,scale:`${round(n.scaleX,4)} ${round(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.NecromancerConfigError=NecromancerConfigError,exports.NecromancerError=NecromancerError,exports.NecromancerUnsupportedError=NecromancerUnsupportedError,exports.animate=animate,exports.animateEach=animateEach,exports.captureLayout=captureLayout,exports.shouldReduceMotion=shouldReduceMotion;
2
2
  //# sourceMappingURL=necromancer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.cjs","names":["dispose","signal","animation","onSettled","reduced","disposed","disposeReason","hasDisposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","message","opts","mode","_motion","_signal","nativeOptions","options","offset","delay","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../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 { 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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\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\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\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\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 { createAnimationHandle } from './_handle';\nimport { NecromancerUnsupportedError } from './errors';\nimport { resolveAnimationOptions, shouldReduceMotion } from './motion';\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 const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n return createAnimationHandle(nativeAnimation, options.signal, reduced);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport { withStaggeredDelay } from './motion';\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\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\n if (signal?.aborted) throw signal.reason;\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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.cjs","names":["dispose","signal","animation","onSettled","disposed","disposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","_motion","_signal","nativeOptions","options","reduced","offset","delay","message","opts","mode","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../src/_handle.ts","../src/_options.ts","../src/errors.ts","../src/motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: '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): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(animation, () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n }).then((outcome): AnimationResult => {\n // An owner-driven cancellation reports the owner's dispose reason (none for a\n // bare dispose()), never the native cancel's DOMException: only an external\n // cancellation of the native animation surfaces that reason here.\n if (disposed && outcome.status === 'cancelled') {\n return disposeReason === undefined ? { status: 'cancelled' } : { 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 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","import type { AnimateOptions } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\n}\n\n/** Adds an element's stagger offset to a validated numeric delay. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n // animateEach() validates up front that a non-zero stagger carries a numeric\n // (or absent) delay, so a non-zero offset here can only ever meet a number.\n const delay = options.delay ?? 0;\n\n return { ...options, delay: (delay as number) + offset };\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\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 { MotionMode } from './types';\n\n/**\n * Reports whether the requested motion preference reduces visible movement.\n *\n * This is the same check `animate()` applies internally, exposed so hand-rolled\n * effects (scroll behavior, tweens) stay consistent with it.\n */\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","import { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions } from './_options';\nimport { NecromancerUnsupportedError } from './errors';\nimport { shouldReduceMotion } from './motion';\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 options.signal?.throwIfAborted();\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, shouldReduceMotion(options.motion ?? 'system')),\n );\n return createAnimationHandle(nativeAnimation, options.signal);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_options';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number, delay: number | string | null | undefined): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n\n // A non-zero stagger adds to the delay, so it needs a numeric one. Checking here,\n // before any animation starts, keeps a pure config error free of side effects.\n if (stagger > 0 && delay !== undefined && typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger, animationOptions.delay);\n\n signal?.throwIfAborted();\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\n signal?.throwIfAborted();\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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
- var Necromancer=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t,n=!1){let r=!1,i,a=!1,o,s=createResultPromise(e,()=>{o?.(),o=void 0},n).then(e=>r&&a&&e.status===`cancelled`?{reason:i,status:`cancelled`}:e),dispose=t=>{r||(r=!0,i=t,a=t!==void 0,o?.(),o=void 0,e.cancel())};return t&&(o=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return r},result:s,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}var NecromancerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},NecromancerConfigError=class extends NecromancerError{},NecromancerUnsupportedError=class extends NecromancerError{};function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(e,t){if(t===0)return e;let n=e.delay??0;if(typeof n!=`number`)throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);return{...e,delay:n+t}}function animate(e,t,n={}){if(n.signal?.aborted)throw n.signal.reason;if(typeof e.animate!=`function`)throw new NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);let r=shouldReduceMotion(n.motion??`system`);return createAnimationHandle(e.animate(t,resolveAnimationOptions(n,r)),n.signal,r)}function uniqueElements(e){return[...new Set(e)]}function validateStagger(e){if(!Number.isFinite(e)||e<0)throw new NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function animateEach(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;if(validateStagger(i),r?.aborted)throw r.reason;let o=uniqueElements(e),s=o.map((e,n)=>typeof t==`function`?t(e,n,o.length):t);if(r?.aborted)throw r.reason;let c=[];try{for(let[e,t]of o.entries()){let n=s[e];c.push(animate(t,n,withStaggeredDelay(a,e*i)))}}catch(e){for(let t of c)t.dispose(e);throw e}return createAnimationGroup(c,r)}function round(e,t){let n=10**t;return Math.round(e*n)/n}function measureSize(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 measureLayout(e){let t=e.getBoundingClientRect(),n=measureSize(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function computeScale(e,t){return t===0?1:e/t}function readKey(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new NecromancerConfigError(`getKey must return a non-empty string for every ${n} element.`);return r}function createKeyMap(e,t,n){let r=new Map;for(let i of e){let e=readKey(i.element,t,n);if(r.has(e))throw new NecromancerConfigError(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function changedElement(e,t){if(!e.isConnected)return;let n=measureLayout(e),r=t.x-n.x,i=t.y-n.y,a=computeScale(t.width,n.width),o=computeScale(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 matchByIdentity(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[changedElement(e,t.rect)].filter(e=>!!e):[]})}function matchByKey(e,t,n){let r=new Set;return t.flatMap(t=>{let i=readKey(t,n,`committed`),a=e.get(i);if(r.has(i))throw new NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[changedElement(t,a.rect)].filter(e=>!!e):[]})}function captureLayout(e,t={}){let n=uniqueElements(e).map(e=>({element:e,rect:measureLayout(e)})),r=t.getKey,i=r?createKeyMap(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new NecromancerConfigError(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=uniqueElements(t??n.map(({element:e})=>e)),c=r&&i?matchByKey(i,s,r):matchByIdentity(n,s);return a=!0,animateEach(c.map(({element:e})=>e),(e,t)=>{let n=c[t],r=`${round(n.x,2)}px ${round(n.y,2)}px`;return[{composite:`add`,scale:`${round(n.scaleX,4)} ${round(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}return e.NecromancerConfigError=NecromancerConfigError,e.NecromancerError=NecromancerError,e.NecromancerUnsupportedError=NecromancerUnsupportedError,e.animate=animate,e.animateEach=animateEach,e.captureLayout=captureLayout,e.shouldReduceMotion=shouldReduceMotion,e})({});
1
+ var Necromancer=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function attachAbortSignalListener(e,t){let abort=()=>t(e.reason);return e.addEventListener(`abort`,abort,{once:!0}),e.aborted?(e.removeEventListener(`abort`,abort),t(e.reason),()=>void 0):()=>e.removeEventListener(`abort`,abort)}function createResultPromise(e,t){return e.finished.then(()=>(t(),{status:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function createAnimationHandle(e,t){let n=!1,r,i,a=createResultPromise(e,()=>{i?.(),i=void 0}).then(e=>n&&e.status===`cancelled`?r===void 0?{status:`cancelled`}:{reason:r,status:`cancelled`}:e),dispose=t=>{n||(n=!0,r=t,i?.(),i=void 0,e.cancel())};return t&&(i=attachAbortSignalListener(t,dispose)),{animation:e,dispose,get disposed(){return n},result:a,[Symbol.dispose](){this.dispose()}}}function createAnimationGroup(e,t){let n=!1,r,i=Promise.all(e.map(e=>e.result)).then(e=>(r?.(),r=void 0,e)),dispose=t=>{if(!n){n=!0,r?.(),r=void 0;for(let n of e)n.dispose(t)}};return t&&(r=attachAbortSignalListener(t,dispose)),{dispose,get disposed(){return n},handles:e,results:i,[Symbol.dispose](){this.dispose()}}}function resolveAnimationOptions(e={},t=!1){let{motion:n,signal:r,...i}=e;return t?{...i,delay:0,duration:0,endDelay:0,iterations:1}:i}function withStaggeredDelay(e,t){if(t===0)return e;let n=e.delay??0;return{...e,delay:n+t}}var NecromancerError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},NecromancerConfigError=class extends NecromancerError{},NecromancerUnsupportedError=class extends NecromancerError{};function shouldReduceMotion(e){return e===`full`?!1:e===`reduced`||typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches}function animate(e,t,n={}){if(n.signal?.throwIfAborted(),typeof e.animate!=`function`)throw new NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);return createAnimationHandle(e.animate(t,resolveAnimationOptions(n,shouldReduceMotion(n.motion??`system`))),n.signal)}function uniqueElements(e){return[...new Set(e)]}function validateStagger(e,t){if(!Number.isFinite(e)||e<0)throw new NecromancerConfigError(`stagger must be a finite, non-negative number.`);if(e>0&&t!==void 0&&typeof t!=`number`)throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof t}.`)}function animateEach(e,t,n={}){let{signal:r,stagger:i=0,...a}=n;validateStagger(i,a.delay),r?.throwIfAborted();let o=uniqueElements(e),s=o.map((e,n)=>typeof t==`function`?t(e,n,o.length):t);r?.throwIfAborted();let c=[];try{for(let[e,t]of o.entries()){let n=s[e];c.push(animate(t,n,withStaggeredDelay(a,e*i)))}}catch(e){for(let t of c)t.dispose(e);throw e}return createAnimationGroup(c,r)}function round(e,t){let n=10**t;return Math.round(e*n)/n}function measureSize(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 measureLayout(e){let t=e.getBoundingClientRect(),n=measureSize(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function computeScale(e,t){return t===0?1:e/t}function readKey(e,t,n){let r=t(e);if(typeof r!=`string`||r.length===0)throw new NecromancerConfigError(`getKey must return a non-empty string for every ${n} element.`);return r}function createKeyMap(e,t,n){let r=new Map;for(let i of e){let e=readKey(i.element,t,n);if(r.has(e))throw new NecromancerConfigError(`getKey returned duplicate keys for the ${n} elements.`);r.set(e,i)}return r}function changedElement(e,t){if(!e.isConnected)return;let n=measureLayout(e),r=t.x-n.x,i=t.y-n.y,a=computeScale(t.width,n.width),o=computeScale(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 matchByIdentity(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[changedElement(e,t.rect)].filter(e=>!!e):[]})}function matchByKey(e,t,n){let r=new Set;return t.flatMap(t=>{let i=readKey(t,n,`committed`),a=e.get(i);if(r.has(i))throw new NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return r.add(i),a?[changedElement(t,a.rect)].filter(e=>!!e):[]})}function captureLayout(e,t={}){let n=uniqueElements(e).map(e=>({element:e,rect:measureLayout(e)})),r=t.getKey,i=r?createKeyMap(n,r,`captured`):void 0,a=!1;return{animate(e={}){if(a)throw new NecromancerConfigError(`This layout transition has already been animated.`);let{elements:t,...o}=e,s=uniqueElements(t??n.map(({element:e})=>e)),c=r&&i?matchByKey(i,s,r):matchByIdentity(n,s);return a=!0,animateEach(c.map(({element:e})=>e),(e,t)=>{let n=c[t],r=`${round(n.x,2)}px ${round(n.y,2)}px`;return[{composite:`add`,scale:`${round(n.scaleX,4)} ${round(n.scaleY,4)}`,translate:r},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}return e.NecromancerConfigError=NecromancerConfigError,e.NecromancerError=NecromancerError,e.NecromancerUnsupportedError=NecromancerUnsupportedError,e.animate=animate,e.animateEach=animateEach,e.captureLayout=captureLayout,e.shouldReduceMotion=shouldReduceMotion,e})({});
2
2
  //# sourceMappingURL=necromancer.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.iife.js","names":["exports","dispose","signal","animation","onSettled","reduced","disposed","disposeReason","hasDisposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","message","opts","mode","_motion","_signal","nativeOptions","options","offset","delay","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../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 { 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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\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\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\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\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 { createAnimationHandle } from './_handle';\nimport { NecromancerUnsupportedError } from './errors';\nimport { resolveAnimationOptions, shouldReduceMotion } from './motion';\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 const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n return createAnimationHandle(nativeAnimation, options.signal, reduced);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport { withStaggeredDelay } from './motion';\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\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\n if (signal?.aborted) throw signal.reason;\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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":["exports","dispose","signal","animation","onSettled","disposed","disposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","_motion","_signal","nativeOptions","options","reduced","offset","delay","message","opts","mode","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../src/_handle.ts","../src/_options.ts","../src/errors.ts","../src/motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: '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): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(animation, () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n }).then((outcome): AnimationResult => {\n // An owner-driven cancellation reports the owner's dispose reason (none for a\n // bare dispose()), never the native cancel's DOMException: only an external\n // cancellation of the native animation surfaces that reason here.\n if (disposed && outcome.status === 'cancelled') {\n return disposeReason === undefined ? { status: 'cancelled' } : { 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 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","import type { AnimateOptions } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\n}\n\n/** Adds an element's stagger offset to a validated numeric delay. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n // animateEach() validates up front that a non-zero stagger carries a numeric\n // (or absent) delay, so a non-zero offset here can only ever meet a number.\n const delay = options.delay ?? 0;\n\n return { ...options, delay: (delay as number) + offset };\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\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 { MotionMode } from './types';\n\n/**\n * Reports whether the requested motion preference reduces visible movement.\n *\n * This is the same check `animate()` applies internally, exposed so hand-rolled\n * effects (scroll behavior, tweens) stay consistent with it.\n */\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","import { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions } from './_options';\nimport { NecromancerUnsupportedError } from './errors';\nimport { shouldReduceMotion } from './motion';\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 options.signal?.throwIfAborted();\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, shouldReduceMotion(options.motion ?? 'system')),\n );\n return createAnimationHandle(nativeAnimation, options.signal);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_options';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number, delay: number | string | null | undefined): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n\n // A non-zero stagger adds to the delay, so it needs a numeric one. Checking here,\n // before any animation starts, keeps a pure config error free of side effects.\n if (stagger > 0 && delay !== undefined && typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger, animationOptions.delay);\n\n signal?.throwIfAborted();\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\n signal?.throwIfAborted();\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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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"}
@@ -3,28 +3,28 @@ function attachAbortSignalListener(e, t) {
3
3
  let abort = () => t(e.reason);
4
4
  return e.addEventListener("abort", abort, { once: !0 }), e.aborted ? (e.removeEventListener("abort", abort), t(e.reason), () => void 0) : () => e.removeEventListener("abort", abort);
5
5
  }
6
- function createResultPromise(e, t, n) {
7
- return e.finished.then(() => (t(), { status: n ? "reduced" : "finished" }), (e) => (t(), {
6
+ function createResultPromise(e, t) {
7
+ return e.finished.then(() => (t(), { status: "finished" }), (e) => (t(), {
8
8
  reason: e,
9
9
  status: "cancelled"
10
10
  }));
11
11
  }
12
- function createAnimationHandle(e, t, n = !1) {
13
- let r = !1, i, a = !1, o, s = createResultPromise(e, () => {
14
- o?.(), o = void 0;
15
- }, n).then((e) => r && a && e.status === "cancelled" ? {
16
- reason: i,
12
+ function createAnimationHandle(e, t) {
13
+ let n = !1, r, i, a = createResultPromise(e, () => {
14
+ i?.(), i = void 0;
15
+ }).then((e) => n && e.status === "cancelled" ? r === void 0 ? { status: "cancelled" } : {
16
+ reason: r,
17
17
  status: "cancelled"
18
18
  } : e), dispose = (t) => {
19
- r || (r = !0, i = t, a = t !== void 0, o?.(), o = void 0, e.cancel());
19
+ n || (n = !0, r = t, i?.(), i = void 0, e.cancel());
20
20
  };
21
- return t && (o = attachAbortSignalListener(t, dispose)), {
21
+ return t && (i = attachAbortSignalListener(t, dispose)), {
22
22
  animation: e,
23
23
  dispose,
24
24
  get disposed() {
25
- return r;
25
+ return n;
26
26
  },
27
- result: s,
27
+ result: a,
28
28
  [Symbol.dispose]() {
29
29
  this.dispose();
30
30
  }
@@ -50,17 +50,7 @@ function createAnimationGroup(e, t) {
50
50
  };
51
51
  }
52
52
  //#endregion
53
- //#region src/errors.ts
54
- var NecromancerError = class extends Error {
55
- constructor(e, t) {
56
- super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
57
- }
58
- }, NecromancerConfigError = class extends NecromancerError {}, NecromancerUnsupportedError = class extends NecromancerError {};
59
- //#endregion
60
- //#region src/motion.ts
61
- function shouldReduceMotion(e) {
62
- return e === "full" ? !1 : e === "reduced" || typeof matchMedia == "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
63
- }
53
+ //#region src/_options.ts
64
54
  function resolveAnimationOptions(e = {}, t = !1) {
65
55
  let { motion: n, signal: r, ...i } = e;
66
56
  return t ? {
@@ -74,19 +64,28 @@ function resolveAnimationOptions(e = {}, t = !1) {
74
64
  function withStaggeredDelay(e, t) {
75
65
  if (t === 0) return e;
76
66
  let n = e.delay ?? 0;
77
- if (typeof n != "number") throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof n}.`);
78
67
  return {
79
68
  ...e,
80
69
  delay: n + t
81
70
  };
82
71
  }
83
72
  //#endregion
73
+ //#region src/errors.ts
74
+ var NecromancerError = class extends Error {
75
+ constructor(e, t) {
76
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
77
+ }
78
+ }, NecromancerConfigError = class extends NecromancerError {}, NecromancerUnsupportedError = class extends NecromancerError {};
79
+ //#endregion
80
+ //#region src/motion.ts
81
+ function shouldReduceMotion(e) {
82
+ return e === "full" ? !1 : e === "reduced" || typeof matchMedia == "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
83
+ }
84
+ //#endregion
84
85
  //#region src/animate.ts
85
86
  function animate(e, t, n = {}) {
86
- if (n.signal?.aborted) throw n.signal.reason;
87
- if (typeof e.animate != "function") throw new NecromancerUnsupportedError("The Web Animations API is not available in this environment.");
88
- let r = shouldReduceMotion(n.motion ?? "system");
89
- return createAnimationHandle(e.animate(t, resolveAnimationOptions(n, r)), n.signal, r);
87
+ if (n.signal?.throwIfAborted(), typeof e.animate != "function") throw new NecromancerUnsupportedError("The Web Animations API is not available in this environment.");
88
+ return createAnimationHandle(e.animate(t, resolveAnimationOptions(n, shouldReduceMotion(n.motion ?? "system"))), n.signal);
90
89
  }
91
90
  //#endregion
92
91
  //#region src/_elements.ts
@@ -95,14 +94,15 @@ function uniqueElements(e) {
95
94
  }
96
95
  //#endregion
97
96
  //#region src/animate-each.ts
98
- function validateStagger(e) {
97
+ function validateStagger(e, t) {
99
98
  if (!Number.isFinite(e) || e < 0) throw new NecromancerConfigError("stagger must be a finite, non-negative number.");
99
+ if (e > 0 && t !== void 0 && typeof t != "number") throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof t}.`);
100
100
  }
101
101
  function animateEach(e, t, n = {}) {
102
102
  let { signal: r, stagger: i = 0, ...a } = n;
103
- if (validateStagger(i), r?.aborted) throw r.reason;
103
+ validateStagger(i, a.delay), r?.throwIfAborted();
104
104
  let o = uniqueElements(e), s = o.map((e, n) => typeof t == "function" ? t(e, n, o.length) : t);
105
- if (r?.aborted) throw r.reason;
105
+ r?.throwIfAborted();
106
106
  let c = [];
107
107
  try {
108
108
  for (let [e, t] of o.entries()) {
@@ -1 +1 @@
1
- {"version":3,"file":"necromancer.js","names":["dispose","signal","animation","onSettled","reduced","disposed","disposeReason","hasDisposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","message","opts","mode","_motion","_signal","nativeOptions","options","offset","delay","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../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 { 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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\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\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\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\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 { createAnimationHandle } from './_handle';\nimport { NecromancerUnsupportedError } from './errors';\nimport { resolveAnimationOptions, shouldReduceMotion } from './motion';\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 const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n return createAnimationHandle(nativeAnimation, options.signal, reduced);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport { withStaggeredDelay } from './motion';\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\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\n if (signal?.aborted) throw signal.reason;\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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":["dispose","signal","animation","onSettled","disposed","disposeReason","removeAbortListener","result","outcome","reason","results","handles","handle","childResults","_motion","_signal","nativeOptions","options","reduced","offset","delay","message","opts","mode","element","keyframes","elements","stagger","animationOptions","unique","frames","index","currentFrames","error","factor","precision","value","rect","size","next","captured","key","getKey","phase","keys","entry","entries","x","y","scaleX","scaleY","change","committed","consumed","committedElements","changed","translate"],"sources":["../src/_handle.ts","../src/_options.ts","../src/errors.ts","../src/motion.ts","../src/animate.ts","../src/_elements.ts","../src/animate-each.ts","../src/layout.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 if (signal.aborted) {\n signal.removeEventListener('abort', abort);\n dispose(signal.reason);\n return () => undefined;\n }\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: '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): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(animation, () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n }).then((outcome): AnimationResult => {\n // An owner-driven cancellation reports the owner's dispose reason (none for a\n // bare dispose()), never the native cancel's DOMException: only an external\n // cancellation of the native animation surfaces that reason here.\n if (disposed && outcome.status === 'cancelled') {\n return disposeReason === undefined ? { status: 'cancelled' } : { 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 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","import type { AnimateOptions } from './types';\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 { motion: _motion, signal: _signal, ...nativeOptions } = options;\n\n return reduced ? { ...nativeOptions, delay: 0, duration: 0, endDelay: 0, iterations: 1 } : nativeOptions;\n}\n\n/** Adds an element's stagger offset to a validated numeric delay. */\nexport function withStaggeredDelay(options: AnimateOptions, offset: number): AnimateOptions {\n if (offset === 0) return options;\n\n // animateEach() validates up front that a non-zero stagger carries a numeric\n // (or absent) delay, so a non-zero offset here can only ever meet a number.\n const delay = options.delay ?? 0;\n\n return { ...options, delay: (delay as number) + offset };\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\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 { MotionMode } from './types';\n\n/**\n * Reports whether the requested motion preference reduces visible movement.\n *\n * This is the same check `animate()` applies internally, exposed so hand-rolled\n * effects (scroll behavior, tweens) stay consistent with it.\n */\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","import { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions } from './_options';\nimport { NecromancerUnsupportedError } from './errors';\nimport { shouldReduceMotion } from './motion';\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 options.signal?.throwIfAborted();\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, shouldReduceMotion(options.motion ?? 'system')),\n );\n return createAnimationHandle(nativeAnimation, options.signal);\n}\n","/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[] {\n return [...new Set(elements)];\n}\n","import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_options';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number, delay: number | string | null | undefined): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n\n // A non-zero stagger adds to the delay, so it needs a numeric one. Checking here,\n // before any animation starts, keeps a pure config error free of side effects.\n if (stagger > 0 && delay !== undefined && typeof delay !== 'number') {\n throw new NecromancerConfigError(`A non-zero stagger requires a numeric delay; received ${typeof delay}.`);\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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n keyframes: Keyframes | KeyframeFactory<ElementType>,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger, animationOptions.delay);\n\n signal?.throwIfAborted();\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\n signal?.throwIfAborted();\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<ElementType extends Element> = Readonly<{ element: ElementType; rect: LayoutRect }>;\ntype LayoutChange<ElementType extends Element> = Readonly<{\n element: ElementType;\n scaleX: number;\n scaleY: number;\n x: number;\n y: number;\n}>;\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<ElementType extends Element>(\n element: ElementType,\n getKey: (element: ElementType) => string,\n phase: string,\n): 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n getKey: (element: ElementType) => string,\n phase: string,\n) {\n const keys = new Map<string, LayoutEntry<ElementType>>();\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<ElementType extends Element>(\n element: ElementType,\n rect: LayoutRect,\n): LayoutChange<ElementType> | 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<ElementType extends Element>(\n entries: readonly LayoutEntry<ElementType>[],\n elements: readonly ElementType[],\n): LayoutChange<ElementType>[] {\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<ElementType> => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey<ElementType extends Element>(\n captured: ReadonlyMap<string, LayoutEntry<ElementType>>,\n elements: readonly ElementType[],\n getKey: (element: ElementType) => string,\n): LayoutChange<ElementType>[] {\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<ElementType> => 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<ElementType extends Element>(\n elements: Iterable<ElementType>,\n options: LayoutCaptureOptions<ElementType> = {},\n): LayoutTransition<ElementType> {\n const entries: LayoutEntry<ElementType>[] = 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<ElementType> = {}) {\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
@@ -1,10 +1,13 @@
1
1
  /** Chooses whether an animation uses full or reduced motion. */
2
2
  export type MotionMode = 'full' | 'reduced' | 'system';
3
- /** The terminal result of a lifecycle-owned animation. */
3
+ /**
4
+ * The terminal result of a lifecycle-owned animation.
5
+ *
6
+ * Reduced motion is not a separate outcome: a reduced animation finishes, and
7
+ * `shouldReduceMotion()` answers whether a requested mode reduces it.
8
+ */
4
9
  export type AnimationResult = {
5
10
  readonly status: 'finished';
6
- } | {
7
- readonly status: 'reduced';
8
11
  } | {
9
12
  readonly reason?: unknown;
10
13
  readonly status: 'cancelled';
@@ -26,7 +29,7 @@ export type Keyframes = readonly Keyframe[] | PropertyIndexedKeyframes;
26
29
  *
27
30
  * Option chain: `AnimateOptions` -> `AnimateEachOptions` (+`stagger`) -> `LayoutAnimationOptions`
28
31
  * (+`elements`). Each layer's implementation module strips only the field it added before
29
- * forwarding the rest down (see `animate-each.ts`, `layout.ts`, and `motion.ts`'s
32
+ * forwarding the rest down (see `animate-each.ts`, `layout.ts`, and `_options.ts`'s
30
33
  * `resolveAnimationOptions()`, which is the last stop and strips `motion`/`signal`).
31
34
  */
32
35
  export type AnimateOptions = KeyframeAnimationOptions & {
@@ -61,7 +64,7 @@ export interface AnimationHandle {
61
64
  dispose(reason?: unknown): void;
62
65
  /** Whether this handle has been disposed. */
63
66
  readonly disposed: boolean;
64
- /** Resolves once the animation finishes, is reduced, or is cancelled. */
67
+ /** Resolves once the animation finishes or is cancelled. */
65
68
  readonly result: Promise<AnimationResult>;
66
69
  /** Calls {@link dispose}. */
67
70
  [Symbol.dispose](): void;
@@ -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,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,eAAe,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO,IAAI,CACnE,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,KACV,SAAS,CAAC;AAEf,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,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO,IAAI,kBAAkB,GAAG;IAC/F;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,oBAAoB,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO;IACzE,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC;CACpD;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,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO;IACrE;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC;CACxE"}
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;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC/B;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,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,eAAe,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO,IAAI,CACnE,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,KACV,SAAS,CAAC;AAEf,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,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO,IAAI,kBAAkB,GAAG;IAC/F;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,oBAAoB,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO;IACzE,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC;CACpD;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,4DAA4D;IAC5D,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,CAAC,WAAW,SAAS,OAAO,GAAG,OAAO;IACrE;;;;;OAKG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC;CACxE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/necromancer",
3
- "version": "26.11.2",
3
+ "version": "26.11.4",
4
4
  "description": "Lifecycle-owned Web Animations API primitives with native access, per-handle groups, and additive FLIP",
5
5
  "repository": {
6
6
  "type": "git",