@vielzeug/necromancer 2.1.0 → 26.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +9 -41
  2. package/dist/_elements.cjs +1 -1
  3. package/dist/_elements.cjs.map +1 -1
  4. package/dist/_elements.d.ts +1 -1
  5. package/dist/_elements.d.ts.map +1 -1
  6. package/dist/_elements.js +1 -7
  7. package/dist/_elements.js.map +1 -1
  8. package/dist/_handle.cjs +1 -1
  9. package/dist/_handle.cjs.map +1 -1
  10. package/dist/_handle.d.ts.map +1 -1
  11. package/dist/_handle.js +1 -54
  12. package/dist/_handle.js.map +1 -1
  13. package/dist/animate-each.cjs +1 -1
  14. package/dist/animate-each.cjs.map +1 -1
  15. package/dist/animate-each.d.ts +1 -1
  16. package/dist/animate-each.d.ts.map +1 -1
  17. package/dist/animate-each.js +1 -27
  18. package/dist/animate-each.js.map +1 -1
  19. package/dist/animate.cjs +1 -1
  20. package/dist/animate.cjs.map +1 -1
  21. package/dist/animate.d.ts.map +1 -1
  22. package/dist/animate.js +1 -15
  23. package/dist/animate.js.map +1 -1
  24. package/dist/errors.cjs +1 -1
  25. package/dist/errors.cjs.map +1 -1
  26. package/dist/errors.js +1 -9
  27. package/dist/errors.js.map +1 -1
  28. package/dist/index.cjs +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -5
  32. package/dist/layout.cjs +1 -1
  33. package/dist/layout.cjs.map +1 -1
  34. package/dist/layout.d.ts +1 -1
  35. package/dist/layout.d.ts.map +1 -1
  36. package/dist/layout.js +1 -96
  37. package/dist/layout.js.map +1 -1
  38. package/dist/motion.cjs +2 -0
  39. package/dist/motion.cjs.map +1 -0
  40. package/dist/{_motion.d.ts → motion.d.ts} +1 -1
  41. package/dist/motion.d.ts.map +1 -0
  42. package/dist/motion.js +2 -0
  43. package/dist/motion.js.map +1 -0
  44. package/dist/necromancer.cjs +1 -1
  45. package/dist/necromancer.cjs.map +1 -1
  46. package/dist/necromancer.iife.js +1 -1
  47. package/dist/necromancer.iife.js.map +1 -1
  48. package/dist/necromancer.js +1 -1
  49. package/dist/necromancer.js.map +1 -1
  50. package/dist/testing/index.d.ts +7 -7
  51. package/dist/testing/index.d.ts.map +1 -1
  52. package/dist/testing.cjs +1 -1
  53. package/dist/testing.cjs.map +1 -1
  54. package/dist/testing.js +1 -45
  55. package/dist/testing.js.map +1 -1
  56. package/dist/types.d.ts +12 -17
  57. package/dist/types.d.ts.map +1 -1
  58. package/package.json +11 -10
  59. package/dist/_active.cjs +0 -2
  60. package/dist/_active.cjs.map +0 -1
  61. package/dist/_active.d.ts +0 -6
  62. package/dist/_active.d.ts.map +0 -1
  63. package/dist/_active.js +0 -15
  64. package/dist/_active.js.map +0 -1
  65. package/dist/_motion.cjs +0 -2
  66. package/dist/_motion.cjs.map +0 -1
  67. package/dist/_motion.d.ts.map +0 -1
  68. package/dist/_motion.js +0 -35
  69. package/dist/_motion.js.map +0 -1
package/README.md CHANGED
@@ -1,24 +1,16 @@
1
1
  # @vielzeug/necromancer
2
2
 
3
- Lifecycle-owned DOM animation primitives built on the Web Animations API.
4
-
5
- ## Overview
6
-
7
- `@vielzeug/necromancer` owns native animations so UI lifecycles can cancel them predictably. It animates one element, coordinates staggered groups, and applies additive FLIP layout transitions.
8
-
9
- - `animate()` defaults to `180ms` and returns a lifecycle owner with direct access to its native `Animation`.
10
- - `animateEach()` animates unique elements with optional staggered delays.
11
- - `captureLayout()` returns a one-shot, additive FLIP transition covering both position and size.
3
+ > Lifecycle-owned Web Animations API primitives with native access, per-handle groups, and additive FLIP
12
4
 
13
5
  ## Installation
14
6
 
15
7
  ```sh
16
8
  pnpm add @vielzeug/necromancer
9
+ npm install @vielzeug/necromancer
10
+ yarn add @vielzeug/necromancer
17
11
  ```
18
12
 
19
- ## Basic Usage
20
-
21
- Create the element after mount, use the native animation when playback control is needed, and dispose its owner with the UI that created it.
13
+ ## Quick Start
22
14
 
23
15
  ```ts
24
16
  import { animate } from '@vielzeug/necromancer';
@@ -38,38 +30,14 @@ await handle.result;
38
30
  handle.dispose();
39
31
  ```
40
32
 
41
- ## API Summary
42
-
43
- | Export | Purpose |
44
- | --- | --- |
45
- | `animate()` | Animate one element and return an `AnimationHandle`. |
46
- | `animateEach()` | Animate unique elements in iteration order and return an `AnimationGroup`. |
47
- | `captureLayout()` | Capture element positions and sizes and return a one-shot layout transition, including keyed replacement nodes. |
48
- | `NecromancerError` | Base class for Necromancer-originated errors. |
49
-
50
- `AnimateOptions` accepts native `KeyframeAnimationOptions`, `interrupt`, `motion`, and `signal`. `duration` defaults to `180` milliseconds; use `interrupt: 'cancel'` to replace active Necromancer-owned animations on one element. `animateEach()` and layout transitions also accept `stagger`. Pass `captureLayout()` a `getKey` function and its transition the committed `elements` to animate framework-rendered replacement nodes from their captured positions.
51
-
52
- ## Testing
53
-
54
- `@vielzeug/necromancer/testing` provides `installFakeAnimations()`, `FakeAnimation`, and `createRect()` for testing code that calls Necromancer without a browser's `Element.animate()`. See the [Usage Guide](https://vielzeug.dev/necromancer/usage#testing).
55
-
56
- ## Environment
57
-
58
- Necromancer is for browser execution and requires the Web Animations API when an animation starts. Its root module is safe to import during SSR; call its functions only from a client mount lifecycle where DOM elements exist.
59
-
60
- `motion` defaults to `'system'`, which follows `prefers-reduced-motion`. When motion is reduced, Necromancer preserves the supplied keyframes but normalizes their timing to an instant transition and resolves with `{ status: 'reduced' }`. Set it to `'full'` to animate regardless of that preference or `'reduced'` to always remove movement.
61
-
62
- ## Peers and Dependencies
63
-
64
- Necromancer has no runtime or peer dependencies. It uses browser-provided `Element.animate()`, `AbortSignal`, and `Symbol.dispose`.
65
-
66
- ## Scope
67
-
68
- Necromancer owns explicit Web Animations API keyframes. It does not inject or observe CSS animations, watch DOM mutations, generate springs, interpolate SVG geometry, or provide a JavaScript tween fallback.
69
-
70
33
  ## Documentation
71
34
 
72
35
  - [Overview](https://vielzeug.dev/necromancer/)
73
36
  - [Usage Guide](https://vielzeug.dev/necromancer/usage)
74
37
  - [API Reference](https://vielzeug.dev/necromancer/api)
75
38
  - [Examples](https://vielzeug.dev/necromancer/examples)
39
+ - [Migration Guide](https://vielzeug.dev/necromancer/migration)
40
+
41
+ ## License
42
+
43
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
@@ -1,2 +1,2 @@
1
- function e(e){return[...new Set(e)]}exports.uniqueElements=e;
1
+ function uniqueElements(e){return[...new Set(e)]}exports.uniqueElements=uniqueElements;
2
2
  //# sourceMappingURL=_elements.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_elements.cjs","names":[],"sources":["../src/_elements.ts"],"sourcesContent":["/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n"],"mappings":"AACA,SAAgB,EAAe,EAAwC,CACrE,MAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,CAC9B"}
1
+ {"version":3,"file":"_elements.cjs","names":[],"sources":["../src/_elements.ts"],"sourcesContent":["/** 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"],"mappings":"AACA,SAAgB,eAA4C,EAAgD,CAC1G,MAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,CAC9B"}
@@ -1,3 +1,3 @@
1
1
  /** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */
2
- export declare function uniqueElements(elements: Iterable<Element>): Element[];
2
+ export declare function uniqueElements<ElementType extends Element>(elements: Iterable<ElementType>): ElementType[];
3
3
  //# sourceMappingURL=_elements.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_elements.d.ts","sourceRoot":"","sources":["../src/_elements.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,EAAE,CAErE"}
1
+ {"version":3,"file":"_elements.d.ts","sourceRoot":"","sources":["../src/_elements.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,wBAAgB,cAAc,CAAC,WAAW,SAAS,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,CAE1G"}
package/dist/_elements.js CHANGED
@@ -1,8 +1,2 @@
1
- //#region src/_elements.ts
2
- function e(e) {
3
- return [...new Set(e)];
4
- }
5
- //#endregion
6
- export { e as uniqueElements };
7
-
1
+ function uniqueElements(e){return[...new Set(e)]}export{uniqueElements};
8
2
  //# sourceMappingURL=_elements.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_elements.js","names":[],"sources":["../src/_elements.ts"],"sourcesContent":["/** Deduplicates elements while preserving first-seen order. Shared by `animateEach()` and `captureLayout()`. */\nexport function uniqueElements(elements: Iterable<Element>): Element[] {\n return [...new Set(elements)];\n}\n"],"mappings":";AACA,SAAgB,EAAe,GAAwC;CACrE,OAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC;AAC9B"}
1
+ {"version":3,"file":"_elements.js","names":[],"sources":["../src/_elements.ts"],"sourcesContent":["/** 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"],"mappings":"AACA,SAAgB,eAA4C,EAAgD,CAC1G,MAAO,CAAC,GAAG,IAAI,IAAI,CAAQ,CAAC,CAC9B"}
package/dist/_handle.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t){let n=()=>t(e.reason);return e.addEventListener(`abort`,n,{once:!0}),()=>e.removeEventListener(`abort`,n)}function t(e,t,n){return e.finished.then(()=>(t(),{status:n?`reduced`:`finished`}),e=>(t(),{reason:e,status:`cancelled`}))}function n(n,r,i=!1){let a=!1,o,s=!1,c,l=t(n,()=>{c?.(),c=void 0},i).then(e=>a&&s&&e.status===`cancelled`?{reason:o,status:`cancelled`}:e),u=e=>{a||(a=!0,o=e,s=e!==void 0,c?.(),c=void 0,n.cancel())};return r&&(c=e(r,u)),{animation:n,dispose:u,get disposed(){return a},result:l,[Symbol.dispose](){this.dispose()}}}function r(t,n){let r=!1,i,a=Promise.all(t.map(e=>e.result)).then(e=>(i?.(),i=void 0,e)),o=e=>{if(!r){r=!0,i?.(),i=void 0;for(let n of t)n.dispose(e)}};return n&&(i=e(n,o)),{dispose:o,get disposed(){return r},handles:t,results:a,[Symbol.dispose](){this.dispose()}}}exports.createAnimationGroup=r,exports.createAnimationHandle=n;
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;
2
2
  //# sourceMappingURL=_handle.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.cjs","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":"AAMA,SAAS,EAA0B,EAAqB,EAAiD,CACvG,IAAM,MAAc,EAAQ,EAAO,MAAM,EAIzC,OAFA,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,MAEzC,EAAO,oBAAoB,QAAS,CAAK,CACxD,CAEA,SAAS,EAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,EAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,EACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,EAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,YACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,EAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,EAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAMA,OAJI,IACF,EAAsB,EAA0B,EAAQ,CAAO,GAG1D,CACL,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF"}
1
+ {"version":3,"file":"_handle.cjs","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n 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,UAAc,EAAQ,EAAO,MAAM,EAUzC,OARA,EAAO,iBAAiB,QAAS,MAAO,CAAE,KAAM,EAAK,CAAC,EAElD,EAAO,SACT,EAAO,oBAAoB,QAAS,KAAK,EACzC,EAAQ,EAAO,MAAM,MACR,IAAA,QAGF,EAAO,oBAAoB,QAAS,KAAK,CACxD,CAEA,SAAS,oBAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,sBAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,oBACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,QAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAMA,OAJI,IACF,EAAsB,0BAA0B,EAAQ,OAAO,GAG1D,CACL,YACA,QACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,qBAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,QAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAMA,OAJI,IACF,EAAsB,0BAA0B,EAAQ,OAAO,GAG1D,CACL,QACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.d.ts","sourceRoot":"","sources":["../src/_handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,MAAM,SAAS,CAAC;AA6BhF,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,UAAQ,GAAG,eAAe,CA+ClH;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAqC9G"}
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"}
package/dist/_handle.js CHANGED
@@ -1,55 +1,2 @@
1
- //#region src/_handle.ts
2
- function e(e, t) {
3
- let n = () => t(e.reason);
4
- return e.addEventListener("abort", n, { once: !0 }), () => e.removeEventListener("abort", n);
5
- }
6
- function t(e, t, n) {
7
- return e.finished.then(() => (t(), { status: n ? "reduced" : "finished" }), (e) => (t(), {
8
- reason: e,
9
- status: "cancelled"
10
- }));
11
- }
12
- function n(n, r, i = !1) {
13
- let a = !1, o, s = !1, c, l = t(n, () => {
14
- c?.(), c = void 0;
15
- }, i).then((e) => a && s && e.status === "cancelled" ? {
16
- reason: o,
17
- status: "cancelled"
18
- } : e), u = (e) => {
19
- a || (a = !0, o = e, s = e !== void 0, c?.(), c = void 0, n.cancel());
20
- };
21
- return r && (c = e(r, u)), {
22
- animation: n,
23
- dispose: u,
24
- get disposed() {
25
- return a;
26
- },
27
- result: l,
28
- [Symbol.dispose]() {
29
- this.dispose();
30
- }
31
- };
32
- }
33
- function r(t, n) {
34
- let r = !1, i, a = Promise.all(t.map((e) => e.result)).then((e) => (i?.(), i = void 0, e)), o = (e) => {
35
- if (!r) {
36
- r = !0, i?.(), i = void 0;
37
- for (let n of t) n.dispose(e);
38
- }
39
- };
40
- return n && (i = e(n, o)), {
41
- dispose: o,
42
- get disposed() {
43
- return r;
44
- },
45
- handles: t,
46
- results: a,
47
- [Symbol.dispose]() {
48
- this.dispose();
49
- }
50
- };
51
- }
52
- //#endregion
53
- export { r as createAnimationGroup, n as createAnimationHandle };
54
-
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()}}}export{createAnimationGroup,createAnimationHandle};
55
2
  //# sourceMappingURL=_handle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_handle.js","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n return () => signal.removeEventListener('abort', abort);\n}\n\nfunction createResultPromise(animation: Animation, onSettled: () => void, reduced: boolean): Promise<AnimationResult> {\n return animation.finished.then(\n () => {\n onSettled();\n\n return { status: reduced ? 'reduced' : 'finished' };\n },\n (reason: unknown) => {\n onSettled();\n\n return { reason, status: 'cancelled' };\n },\n );\n}\n\n/** Wraps a native Animation with Necromancer's lifecycle contract. */\nexport function createAnimationHandle(animation: Animation, signal?: AbortSignal, reduced = false): AnimationHandle {\n let disposed = false;\n let disposeReason: unknown;\n let hasDisposeReason = false;\n let removeAbortListener: (() => void) | undefined;\n\n const result: Promise<AnimationResult> = createResultPromise(\n animation,\n () => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n },\n reduced,\n ).then((outcome): AnimationResult => {\n if (disposed && hasDisposeReason && outcome.status === 'cancelled') {\n return { reason: disposeReason, status: 'cancelled' };\n }\n\n return outcome;\n });\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n disposeReason = reason;\n hasDisposeReason = reason !== undefined;\n removeAbortListener?.();\n removeAbortListener = undefined;\n animation.cancel();\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n animation,\n dispose,\n get disposed() {\n return disposed;\n },\n result,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n\n/** Combines child animation handles under one disposal owner. */\nexport function createAnimationGroup(handles: readonly AnimationHandle[], signal?: AbortSignal): AnimationGroup {\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n\n const results: Promise<readonly AnimationResult[]> = Promise.all(handles.map((handle) => handle.result)).then(\n (childResults): readonly AnimationResult[] => {\n removeAbortListener?.();\n removeAbortListener = undefined;\n\n return childResults;\n },\n );\n\n const dispose = (reason?: unknown): void => {\n if (disposed) return;\n\n disposed = true;\n removeAbortListener?.();\n removeAbortListener = undefined;\n for (const handle of handles) handle.dispose(reason);\n };\n\n if (signal) {\n removeAbortListener = attachAbortSignalListener(signal, dispose);\n }\n\n return {\n dispose,\n get disposed() {\n return disposed;\n },\n handles,\n results,\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n}\n"],"mappings":";AAMA,SAAS,EAA0B,GAAqB,GAAiD;CACvG,IAAM,UAAc,EAAQ,EAAO,MAAM;CAIzC,OAFA,EAAO,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC,SAEzC,EAAO,oBAAoB,SAAS,CAAK;AACxD;AAEA,SAAS,EAAoB,GAAsB,GAAuB,GAA4C;CACpH,OAAO,EAAU,SAAS,YAEtB,EAAU,GAEH,EAAE,QAAQ,IAAU,YAAY,WAAW,KAEnD,OACC,EAAU,GAEH;EAAE;EAAQ,QAAQ;CAAY,EAEzC;AACF;AAGA,SAAgB,EAAsB,GAAsB,GAAsB,IAAU,IAAwB;CAClH,IAAI,IAAW,IACX,GACA,IAAmB,IACnB,GAEE,IAAmC,EACvC,SACM;EAEJ,AADA,IAAsB,GACtB,IAAsB,KAAA;CACxB,GACA,CACF,CAAC,CAAC,MAAM,MACF,KAAY,KAAoB,EAAQ,WAAW,cAC9C;EAAE,QAAQ;EAAe,QAAQ;CAAY,IAG/C,CACR,GAEK,KAAW,MAA2B;EACtC,MAEJ,IAAW,IACX,IAAgB,GAChB,IAAmB,MAAW,KAAA,GAC9B,IAAsB,GACtB,IAAsB,KAAA,GACtB,EAAU,OAAO;CACnB;CAMA,OAJI,MACF,IAAsB,EAA0B,GAAQ,CAAO,IAG1D;EACL;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF;AAGA,SAAgB,EAAqB,GAAqC,GAAsC;CAC9G,IAAI,IAAW,IACX,GAEE,IAA+C,QAAQ,IAAI,EAAQ,KAAK,MAAW,EAAO,MAAM,CAAC,CAAC,CAAC,MACtG,OACC,IAAsB,GACtB,IAAsB,KAAA,GAEf,EAEX,GAEM,KAAW,MAA2B;EACtC,QAIJ;GAFA,IAAW,IACX,IAAsB,GACtB,IAAsB,KAAA;GACtB,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAM;EAD7B;CAExB;CAMA,OAJI,MACF,IAAsB,EAA0B,GAAQ,CAAO,IAG1D;EACL;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA;EACA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;CACF;AACF"}
1
+ {"version":3,"file":"_handle.js","names":[],"sources":["../src/_handle.ts"],"sourcesContent":["import type { AnimationGroup, AnimationHandle, AnimationResult } from './types';\n\n/**\n * Attaches an abort listener to a signal and returns a function that removes it.\n * When the signal aborts, calls the dispose function with the signal's reason.\n */\nfunction attachAbortSignalListener(signal: AbortSignal, dispose: (reason?: unknown) => void): () => void {\n const abort = () => dispose(signal.reason);\n\n signal.addEventListener('abort', abort, { once: true });\n\n 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,UAAc,EAAQ,EAAO,MAAM,EAUzC,OARA,EAAO,iBAAiB,QAAS,MAAO,CAAE,KAAM,EAAK,CAAC,EAElD,EAAO,SACT,EAAO,oBAAoB,QAAS,KAAK,EACzC,EAAQ,EAAO,MAAM,MACR,IAAA,QAGF,EAAO,oBAAoB,QAAS,KAAK,CACxD,CAEA,SAAS,oBAAoB,EAAsB,EAAuB,EAA4C,CACpH,OAAO,EAAU,SAAS,UAEtB,EAAU,EAEH,CAAE,OAAQ,EAAU,UAAY,UAAW,GAEnD,IACC,EAAU,EAEH,CAAE,SAAQ,OAAQ,WAAY,EAEzC,CACF,CAGA,SAAgB,sBAAsB,EAAsB,EAAsB,EAAU,GAAwB,CAClH,IAAI,EAAW,GACX,EACA,EAAmB,GACnB,EAEE,EAAmC,oBACvC,MACM,CACJ,IAAsB,EACtB,EAAsB,IAAA,EACxB,EACA,CACF,CAAC,CAAC,KAAM,GACF,GAAY,GAAoB,EAAQ,SAAW,YAC9C,CAAE,OAAQ,EAAe,OAAQ,WAAY,EAG/C,CACR,EAEK,QAAW,GAA2B,CACtC,IAEJ,EAAW,GACX,EAAgB,EAChB,EAAmB,IAAW,IAAA,GAC9B,IAAsB,EACtB,EAAsB,IAAA,GACtB,EAAU,OAAO,EACnB,EAMA,OAJI,IACF,EAAsB,0BAA0B,EAAQ,OAAO,GAG1D,CACL,YACA,QACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,SACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF,CAGA,SAAgB,qBAAqB,EAAqC,EAAsC,CAC9G,IAAI,EAAW,GACX,EAEE,EAA+C,QAAQ,IAAI,EAAQ,IAAK,GAAW,EAAO,MAAM,CAAC,CAAC,CAAC,KACtG,IACC,IAAsB,EACtB,EAAsB,IAAA,GAEf,EAEX,EAEM,QAAW,GAA2B,CACtC,MAIJ,CAFA,EAAW,GACX,IAAsB,EACtB,EAAsB,IAAA,GACtB,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAM,CAD7B,CAExB,EAMA,OAJI,IACF,EAAsB,0BAA0B,EAAQ,OAAO,GAG1D,CACL,QACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,UACA,UACA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,CACF,CACF"}
@@ -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 a(e){if(!Number.isFinite(e)||e<0)throw new t.NecromancerConfigError(`stagger must be a finite, non-negative number.`)}function o(t,o,s={}){let{signal:c,stagger:l=0,...u}=s;if(a(l),c?.aborted)throw c.reason;let d=i.uniqueElements(t),f=d.map((e,t)=>typeof o==`function`?o(e,t,d.length):o),p=[];try{for(let[e,t]of d.entries()){let i=f[e];p.push(r.animate(t,i,n.withStaggeredDelay(u,e*l)))}}catch(e){for(let t of p)t.dispose(e);throw e}return e.createAnimationGroup(p,c)}exports.animateEach=o;
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;
2
2
  //# sourceMappingURL=animate-each.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.cjs","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":"8IAOA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAA,uBAAuB,gDAAgD,CAErF,CAQA,SAAgB,EACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,EAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAA,eAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EACM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAA,QAAQ,EAAS,EAAe,EAAA,mBAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAA,qBAAqB,EAAS,CAAM,CAC7C"}
1
+ {"version":3,"file":"animate-each.cjs","names":[],"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":"6IAOA,SAAS,gBAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAA,uBAAuB,gDAAgD,CAErF,CAQA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,gBAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAA,eAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EAEA,GAAI,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAA,QAAQ,EAAS,EAAe,EAAA,mBAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAA,qBAAqB,EAAS,CAAM,CAC7C"}
@@ -5,5 +5,5 @@ import type { AnimateEachOptions, AnimationGroup, KeyframeFactory, Keyframes } f
5
5
  * Each subsequent element starts after `stagger` milliseconds. A keyframe factory
6
6
  * receives the stable unique-element index and total before any animation starts.
7
7
  */
8
- export declare function animateEach(elements: Iterable<Element>, keyframes: Keyframes | KeyframeFactory, options?: AnimateEachOptions): AnimationGroup;
8
+ export declare function animateEach<ElementType extends Element>(elements: Iterable<ElementType>, keyframes: Keyframes | KeyframeFactory<ElementType>, options?: AnimateEachOptions): AnimationGroup;
9
9
  //# sourceMappingURL=animate-each.d.ts.map
@@ -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,CACzB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,EACtC,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CA2BhB"}
1
+ {"version":3,"file":"animate-each.d.ts","sourceRoot":"","sources":["../src/animate-each.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAmB,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAQ/G;;;;;GAKG;AACH,wBAAgB,WAAW,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,28 +1,2 @@
1
- import { createAnimationGroup as e } from "./_handle.js";
2
- import { NecromancerConfigError as t } from "./errors.js";
3
- import { withStaggeredDelay as n } from "./_motion.js";
4
- import { animate as r } from "./animate.js";
5
- import { uniqueElements as i } from "./_elements.js";
6
- //#region src/animate-each.ts
7
- function a(e) {
8
- if (!Number.isFinite(e) || e < 0) throw new t("stagger must be a finite, non-negative number.");
9
- }
10
- function o(t, o, s = {}) {
11
- let { signal: c, stagger: l = 0, ...u } = s;
12
- if (a(l), c?.aborted) throw c.reason;
13
- let d = i(t), f = d.map((e, t) => typeof o == "function" ? o(e, t, d.length) : o), p = [];
14
- try {
15
- for (let [e, t] of d.entries()) {
16
- let i = f[e];
17
- p.push(r(t, i, n(u, e * l)));
18
- }
19
- } catch (e) {
20
- for (let t of p) t.dispose(e);
21
- throw e;
22
- }
23
- return e(p, c);
24
- }
25
- //#endregion
26
- export { o as animateEach };
27
-
1
+ import{createAnimationGroup as e}from"./_handle.js";import{NecromancerConfigError as t}from"./errors.js";import{withStaggeredDelay as n}from"./motion.js";import{animate as r}from"./animate.js";import{uniqueElements as i}from"./_elements.js";function validateStagger(e){if(!Number.isFinite(e)||e<0)throw new t(`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(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(t,i,n(l,e*c)))}}catch(e){for(let t of f)t.dispose(e);throw e}return e(f,s)}export{animateEach};
28
2
  //# sourceMappingURL=animate-each.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate-each.js","names":[],"sources":["../src/animate-each.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { createAnimationGroup } from './_handle';\nimport { withStaggeredDelay } from './_motion';\nimport { animate } from './animate';\nimport { NecromancerConfigError } from './errors';\nimport type { AnimateEachOptions, AnimationGroup, AnimationHandle, KeyframeFactory, Keyframes } from './types';\n\nfunction validateStagger(stagger: number): void {\n if (!Number.isFinite(stagger) || stagger < 0) {\n throw new NecromancerConfigError('stagger must be a finite, non-negative number.');\n }\n}\n\n/**\n * Starts lifecycle-owned animations for unique elements in iteration order.\n *\n * Each subsequent element starts after `stagger` milliseconds. A keyframe factory\n * receives the stable unique-element index and total before any animation starts.\n */\nexport function animateEach(\n elements: Iterable<Element>,\n keyframes: Keyframes | KeyframeFactory,\n options: AnimateEachOptions = {},\n): AnimationGroup {\n const { signal, stagger = 0, ...animationOptions } = options;\n\n validateStagger(stagger);\n\n if (signal?.aborted) throw signal.reason;\n\n const unique = uniqueElements(elements);\n const frames = unique.map((element, index) =>\n typeof keyframes === 'function' ? keyframes(element, index, unique.length) : keyframes,\n );\n const handles: AnimationHandle[] = [];\n\n try {\n for (const [index, element] of unique.entries()) {\n // `frames` was built above from `unique`, one entry per element, so this index is always in range.\n const currentFrames = frames[index]!;\n\n handles.push(animate(element, currentFrames, withStaggeredDelay(animationOptions, index * stagger)));\n }\n } catch (error) {\n for (const handle of handles) handle.dispose(error);\n\n throw error;\n }\n\n return createAnimationGroup(handles, signal);\n}\n"],"mappings":";;;;;;AAOA,SAAS,EAAgB,GAAuB;CAC9C,IAAI,CAAC,OAAO,SAAS,CAAO,KAAK,IAAU,GACzC,MAAM,IAAI,EAAuB,gDAAgD;AAErF;AAQA,SAAgB,EACd,GACA,GACA,IAA8B,CAAC,GACf;CAChB,IAAM,EAAE,WAAQ,aAAU,GAAG,GAAG,MAAqB;CAIrD,IAFA,EAAgB,CAAO,GAEnB,GAAQ,SAAS,MAAM,EAAO;CAElC,IAAM,IAAS,EAAe,CAAQ,GAChC,IAAS,EAAO,KAAK,GAAS,MAClC,OAAO,KAAc,aAAa,EAAU,GAAS,GAAO,EAAO,MAAM,IAAI,CAC/E,GACM,IAA6B,CAAC;CAEpC,IAAI;EACF,KAAK,IAAM,CAAC,GAAO,MAAY,EAAO,QAAQ,GAAG;GAE/C,IAAM,IAAgB,EAAO;GAE7B,EAAQ,KAAK,EAAQ,GAAS,GAAe,EAAmB,GAAkB,IAAQ,CAAO,CAAC,CAAC;EACrG;CACF,SAAS,GAAO;EACd,KAAK,IAAM,KAAU,GAAS,EAAO,QAAQ,CAAK;EAElD,MAAM;CACR;CAEA,OAAO,EAAqB,GAAS,CAAM;AAC7C"}
1
+ {"version":3,"file":"animate-each.js","names":[],"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":"iPAOA,SAAS,gBAAgB,EAAuB,CAC9C,GAAI,CAAC,OAAO,SAAS,CAAO,GAAK,EAAU,EACzC,MAAM,IAAI,EAAuB,gDAAgD,CAErF,CAQA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EACf,CAChB,GAAM,CAAE,SAAQ,UAAU,EAAG,GAAG,GAAqB,EAIrD,GAFA,gBAAgB,CAAO,EAEnB,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAAS,EAAe,CAAQ,EAChC,EAAS,EAAO,KAAK,EAAS,IAClC,OAAO,GAAc,WAAa,EAAU,EAAS,EAAO,EAAO,MAAM,EAAI,CAC/E,EAEA,GAAI,GAAQ,QAAS,MAAM,EAAO,OAElC,IAAM,EAA6B,CAAC,EAEpC,GAAI,CACF,IAAK,GAAM,CAAC,EAAO,KAAY,EAAO,QAAQ,EAAG,CAE/C,IAAM,EAAgB,EAAO,GAE7B,EAAQ,KAAK,EAAQ,EAAS,EAAe,EAAmB,EAAkB,EAAQ,CAAO,CAAC,CAAC,CACrG,CACF,OAAS,EAAO,CACd,IAAK,IAAM,KAAU,EAAS,EAAO,QAAQ,CAAK,EAElD,MAAM,CACR,CAEA,OAAO,EAAqB,EAAS,CAAM,CAC7C"}
package/dist/animate.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_active.cjs"),t=require("./_handle.cjs"),n=require("./errors.cjs"),r=require("./_motion.cjs");function i(i,a,o={}){if(o.signal?.aborted)throw o.signal.reason;if(typeof i.animate!=`function`)throw new n.NecromancerUnsupportedError(`The Web Animations API is not available in this environment.`);o.interrupt===`cancel`&&e.interruptAnimations(i);let s=r.shouldReduceMotion(o.motion??`system`),c=i.animate(a,r.resolveAnimationOptions(o,s)),l=t.createAnimationHandle(c,o.signal,s);return e.trackAnimation(i,l),l}exports.animate=i;
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;
2
2
  //# sourceMappingURL=animate.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate.cjs","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":"iHAYA,SAAgB,EAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAAA,4BAA4B,8DAA8D,EAGlG,EAAQ,YAAc,UAAU,EAAA,oBAAoB,CAAO,EAE/D,IAAM,EAAU,EAAA,mBAAmB,EAAQ,QAAU,QAAQ,EACvD,EAAkB,EAAQ,QAC9B,EACA,EAAA,wBAAwB,EAAS,CAAO,CAC1C,EACM,EAAS,EAAA,sBAAsB,EAAiB,EAAQ,OAAQ,CAAO,EAI7E,OAFA,EAAA,eAAe,EAAS,CAAM,EAEvB,CACT"}
1
+ {"version":3,"file":"animate.cjs","names":[],"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":"qFAWA,SAAgB,QAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAAA,4BAA4B,8DAA8D,EAGtG,IAAM,EAAU,EAAA,mBAAmB,EAAQ,QAAU,QAAQ,EACvD,EAAkB,EAAQ,QAC9B,EACA,EAAA,wBAAwB,EAAS,CAAO,CAC1C,EACA,OAAO,EAAA,sBAAsB,EAAiB,EAAQ,OAAQ,CAAO,CACvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,eAAe,CAmB7G"}
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"}
package/dist/animate.js CHANGED
@@ -1,16 +1,2 @@
1
- import { interruptAnimations as e, trackAnimation as t } from "./_active.js";
2
- import { createAnimationHandle as n } from "./_handle.js";
3
- import { NecromancerUnsupportedError as r } from "./errors.js";
4
- import { resolveAnimationOptions as i, shouldReduceMotion as a } from "./_motion.js";
5
- //#region src/animate.ts
6
- function o(o, s, c = {}) {
7
- if (c.signal?.aborted) throw c.signal.reason;
8
- if (typeof o.animate != "function") throw new r("The Web Animations API is not available in this environment.");
9
- c.interrupt === "cancel" && e(o);
10
- let l = a(c.motion ?? "system"), u = o.animate(s, i(c, l)), d = n(u, c.signal, l);
11
- return t(o, d), d;
12
- }
13
- //#endregion
14
- export { o as animate };
15
-
1
+ import{createAnimationHandle as e}from"./_handle.js";import{NecromancerUnsupportedError as t}from"./errors.js";import{resolveAnimationOptions as n,shouldReduceMotion as r}from"./motion.js";function animate(i,a,o={}){if(o.signal?.aborted)throw o.signal.reason;if(typeof i.animate!=`function`)throw new t(`The Web Animations API is not available in this environment.`);let s=r(o.motion??`system`),c=i.animate(a,n(o,s));return e(c,o.signal,s)}export{animate};
16
2
  //# sourceMappingURL=animate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"animate.js","names":[],"sources":["../src/animate.ts"],"sourcesContent":["import { interruptAnimations, trackAnimation } from './_active';\nimport { createAnimationHandle } from './_handle';\nimport { resolveAnimationOptions, shouldReduceMotion } from './_motion';\nimport { NecromancerUnsupportedError } from './errors';\nimport type { AnimateOptions, AnimationHandle, Keyframes } from './types';\n\n/**\n * Starts a lifecycle-owned native Web Animation for one element.\n *\n * The returned handle cancels its native animation when disposed. By default, the\n * operating system's reduced-motion preference suppresses movement.\n */\nexport function animate(element: Element, keyframes: Keyframes, options: AnimateOptions = {}): AnimationHandle {\n if (options.signal?.aborted) throw options.signal.reason;\n\n if (typeof element.animate !== 'function') {\n throw new NecromancerUnsupportedError('The Web Animations API is not available in this environment.');\n }\n\n if (options.interrupt === 'cancel') interruptAnimations(element);\n\n const reduced = shouldReduceMotion(options.motion ?? 'system');\n const nativeAnimation = element.animate(\n keyframes as Keyframe[] | PropertyIndexedKeyframes,\n resolveAnimationOptions(options, reduced),\n );\n const handle = createAnimationHandle(nativeAnimation, options.signal, reduced);\n\n trackAnimation(element, handle);\n\n return handle;\n}\n"],"mappings":";;;;;AAYA,SAAgB,EAAQ,GAAkB,GAAsB,IAA0B,CAAC,GAAoB;CAC7G,IAAI,EAAQ,QAAQ,SAAS,MAAM,EAAQ,OAAO;CAElD,IAAI,OAAO,EAAQ,WAAY,YAC7B,MAAM,IAAI,EAA4B,8DAA8D;CAGtG,AAAI,EAAQ,cAAc,YAAU,EAAoB,CAAO;CAE/D,IAAM,IAAU,EAAmB,EAAQ,UAAU,QAAQ,GACvD,IAAkB,EAAQ,QAC9B,GACA,EAAwB,GAAS,CAAO,CAC1C,GACM,IAAS,EAAsB,GAAiB,EAAQ,QAAQ,CAAO;CAI7E,OAFA,EAAe,GAAS,CAAM,GAEvB;AACT"}
1
+ {"version":3,"file":"animate.js","names":[],"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":"6LAWA,SAAgB,QAAQ,EAAkB,EAAsB,EAA0B,CAAC,EAAoB,CAC7G,GAAI,EAAQ,QAAQ,QAAS,MAAM,EAAQ,OAAO,OAElD,GAAI,OAAO,EAAQ,SAAY,WAC7B,MAAM,IAAI,EAA4B,8DAA8D,EAGtG,IAAM,EAAU,EAAmB,EAAQ,QAAU,QAAQ,EACvD,EAAkB,EAAQ,QAC9B,EACA,EAAwB,EAAS,CAAO,CAC1C,EACA,OAAO,EAAsB,EAAiB,EAAQ,OAAQ,CAAO,CACvE"}
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};exports.NecromancerConfigError=t,exports.NecromancerError=e,exports.NecromancerUnsupportedError=n;
1
+ 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{};exports.NecromancerConfigError=NecromancerConfigError,exports.NecromancerError=NecromancerError,exports.NecromancerUnsupportedError=NecromancerUnsupportedError;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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"],"mappings":"AACA,IAAa,EAAb,cAAsC,KAAM,CAC1C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAA4C,CAAiB,CAAC,EAGjD,EAAb,cAAiD,CAAiB,CAAC"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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"],"mappings":"AACA,IAAa,iBAAb,cAAsC,KAAM,CAC1C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,uBAAb,cAA4C,gBAAiB,CAAC,EAGjD,4BAAb,cAAiD,gBAAiB,CAAC"}
package/dist/errors.js CHANGED
@@ -1,10 +1,2 @@
1
- //#region src/errors.ts
2
- var e = class extends Error {
3
- constructor(e, t) {
4
- super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
- }
6
- }, t = class extends e {}, n = class extends e {};
7
- //#endregion
8
- export { t as NecromancerConfigError, e as NecromancerError, n as NecromancerUnsupportedError };
9
-
1
+ 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{};export{NecromancerConfigError,NecromancerError,NecromancerUnsupportedError};
10
2
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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"],"mappings":";AACA,IAAa,IAAb,cAAsC,MAAM;CAC1C,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAA4C,EAAiB,CAAC,GAGjD,IAAb,cAAiD,EAAiB,CAAC"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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"],"mappings":"AACA,IAAa,iBAAb,cAAsC,KAAM,CAC1C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,uBAAb,cAA4C,gBAAiB,CAAC,EAGjD,4BAAb,cAAiD,gBAAiB,CAAC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./animate.cjs"),n=require("./animate-each.cjs"),r=require("./layout.cjs");exports.NecromancerConfigError=e.NecromancerConfigError,exports.NecromancerError=e.NecromancerError,exports.NecromancerUnsupportedError=e.NecromancerUnsupportedError,exports.animate=t.animate,exports.animateEach=n.animateEach,exports.captureLayout=r.captureLayout;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./motion.cjs"),n=require("./animate.cjs"),r=require("./animate-each.cjs"),i=require("./layout.cjs");exports.NecromancerConfigError=e.NecromancerConfigError,exports.NecromancerError=e.NecromancerError,exports.NecromancerUnsupportedError=e.NecromancerUnsupportedError,exports.animate=n.animate,exports.animateEach=r.animateEach,exports.captureLayout=i.captureLayout,exports.shouldReduceMotion=t.shouldReduceMotion;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ export { animate } from './animate';
2
2
  export { animateEach } from './animate-each';
3
3
  export { NecromancerConfigError, NecromancerError, NecromancerUnsupportedError } from './errors';
4
4
  export { captureLayout } from './layout';
5
+ export { shouldReduceMotion } from './motion';
5
6
  export type { AnimateEachOptions, AnimateOptions, AnimationGroup, AnimationHandle, AnimationResult, KeyframeFactory, Keyframes, LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition, MotionMode, } from './types';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,eAAe,EACf,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- import { NecromancerConfigError as e, NecromancerError as t, NecromancerUnsupportedError as n } from "./errors.js";
2
- import { animate as r } from "./animate.js";
3
- import { animateEach as i } from "./animate-each.js";
4
- import { captureLayout as a } from "./layout.js";
5
- export { e as NecromancerConfigError, t as NecromancerError, n as NecromancerUnsupportedError, r as animate, i as animateEach, a as captureLayout };
1
+ import{NecromancerConfigError as e,NecromancerError as t,NecromancerUnsupportedError as n}from"./errors.js";import{shouldReduceMotion as r}from"./motion.js";import{animate as i}from"./animate.js";import{animateEach as a}from"./animate-each.js";import{captureLayout as o}from"./layout.js";export{e as NecromancerConfigError,t as NecromancerError,n as NecromancerUnsupportedError,i as animate,a as animateEach,o as captureLayout,r as shouldReduceMotion};
package/dist/layout.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_elements.cjs"),n=require("./animate-each.cjs");function r(e,t){let n=10**t;return Math.round(e*n)/n}function i(e){if(`offsetWidth`in e&&`offsetHeight`in e)return{height:e.offsetHeight,width:e.offsetWidth};let t=e.getBoundingClientRect();return{height:t.height,width:t.width}}function a(e){let t=e.getBoundingClientRect(),n=i(e);return{height:n.height,width:n.width,x:t.x,y:t.y}}function o(e,t){return t===0?1:e/t}function s(t,n,r){let i=n(t);if(typeof i!=`string`||i.length===0)throw new e.NecromancerConfigError(`getKey must return a non-empty string for every ${r} element.`);return i}function c(t,n,r){let i=new Map;for(let a of t){let t=s(a.element,n,r);if(i.has(t))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the ${r} elements.`);i.set(t,a)}return i}function l(e,t){if(!e.isConnected)return;let n=a(e),r=t.x-n.x,i=t.y-n.y,s=o(t.width,n.width),c=o(t.height,n.height);return r===0&&i===0&&s===1&&c===1?void 0:{element:e,scaleX:s,scaleY:c,x:r,y:i}}function u(e,t){let n=new Map(e.map(e=>[e.element,e]));return t.flatMap(e=>{let t=n.get(e);return t?[l(e,t.rect)].filter(e=>!!e):[]})}function d(t,n,r){let i=new Set;return n.flatMap(n=>{let a=s(n,r,`committed`),o=t.get(a);if(i.has(a))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return i.add(a),o?[l(n,o.rect)].filter(e=>!!e):[]})}function f(i,o={}){let s=t.uniqueElements(i).map(e=>({element:e,rect:a(e)})),l=o.getKey,f=l?c(s,l,`captured`):void 0,p=!1;return{animate(i={}){if(p)throw new e.NecromancerConfigError(`This layout transition has already been animated.`);let{elements:a,...o}=i,c=t.uniqueElements(a??s.map(({element:e})=>e)),m=l&&f?d(f,c,l):u(s,c);return p=!0,n.animateEach(m.map(({element:e})=>e),(e,t)=>{let n=m[t],i=`${r(n.x,2)}px ${r(n.y,2)}px`;return[{composite:`add`,scale:`${r(n.scaleX,4)} ${r(n.scaleY,4)}`,translate:i},{composite:`add`,scale:`1 1`,translate:`0px 0px`}]},o)}}}exports.captureLayout=f;
1
+ const e=require("./errors.cjs"),t=require("./_elements.cjs"),n=require("./animate-each.cjs");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(t,n,r){let i=n(t);if(typeof i!=`string`||i.length===0)throw new e.NecromancerConfigError(`getKey must return a non-empty string for every ${r} element.`);return i}function createKeyMap(t,n,r){let i=new Map;for(let a of t){let t=readKey(a.element,n,r);if(i.has(t))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the ${r} elements.`);i.set(t,a)}return i}function 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(t,n,r){let i=new Set;return n.flatMap(n=>{let a=readKey(n,r,`committed`),o=t.get(a);if(i.has(a))throw new e.NecromancerConfigError(`getKey returned duplicate keys for the committed elements.`);return i.add(a),o?[changedElement(n,o.rect)].filter(e=>!!e):[]})}function captureLayout(r,i={}){let a=t.uniqueElements(r).map(e=>({element:e,rect:measureLayout(e)})),o=i.getKey,s=o?createKeyMap(a,o,`captured`):void 0,c=!1;return{animate(r={}){if(c)throw new e.NecromancerConfigError(`This layout transition has already been animated.`);let{elements:i,...l}=r,u=t.uniqueElements(i??a.map(({element:e})=>e)),d=o&&s?matchByKey(s,u,o):matchByIdentity(a,u);return c=!0,n.animateEach(d.map(({element:e})=>e),(e,t)=>{let n=d[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`}]},l)}}}exports.captureLayout=captureLayout;
2
2
  //# sourceMappingURL=layout.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.cjs","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry = Readonly<{ element: Element; rect: LayoutRect }>;\ntype LayoutChange = Readonly<{ element: Element; scaleX: number; scaleY: number; x: number; y: number }>;\n\n// Rounds before templating into a keyframe string purely for DevTools readability — a `getBoundingClientRect()`\n// subtraction routinely produces values like `12.340000000000002`, which is noise once rendered as CSS.\n// Pixels only need whole-number precision; scale factors (close to 1) need finer precision to stay visually exact.\nfunction round(value: number, precision: number): number {\n const factor = 10 ** precision;\n\n return Math.round(value * factor) / factor;\n}\n\n// `getBoundingClientRect()`'s width/height is the rotated, axis-aligned bounding box — using it\n// for size would distort the scale ratio under any rotation. `offsetWidth`/`offsetHeight` report\n// the box's own untransformed layout size instead. Elements without an offset box (e.g. SVG)\n// fall back to the bounding rect, accepting that same distortion as the only option available.\nfunction measureSize(element: Element): Readonly<{ height: number; width: number }> {\n if ('offsetWidth' in element && 'offsetHeight' in element) {\n return { height: (element as HTMLElement).offsetHeight, width: (element as HTMLElement).offsetWidth };\n }\n\n const rect = element.getBoundingClientRect();\n\n return { height: rect.height, width: rect.width };\n}\n\nfunction measureLayout(element: Element): LayoutRect {\n const rect = element.getBoundingClientRect();\n const size = measureSize(element);\n\n return { height: size.height, width: size.width, x: rect.x, y: rect.y };\n}\n\n// `next` is 0 exactly when the element has collapsed (e.g. `display: none` mid-transition);\n// there's no meaningful ratio to compute then, so treat it as unscaled rather than producing Infinity/NaN.\nfunction computeScale(captured: number, next: number): number {\n return next === 0 ? 1 : captured / next;\n}\n\nfunction readKey(element: Element, getKey: (element: Element) => string, phase: string): string {\n const key = getKey(element);\n\n if (typeof key !== 'string' || key.length === 0) {\n throw new NecromancerConfigError(`getKey must return a non-empty string for every ${phase} element.`);\n }\n\n return key;\n}\n\nfunction createKeyMap(entries: readonly LayoutEntry[], getKey: (element: Element) => string, phase: string) {\n const keys = new Map<string, LayoutEntry>();\n\n for (const entry of entries) {\n const key = readKey(entry.element, getKey, phase);\n\n if (keys.has(key)) {\n throw new NecromancerConfigError(`getKey returned duplicate keys for the ${phase} elements.`);\n }\n\n keys.set(key, entry);\n }\n\n return keys;\n}\n\nfunction changedElement(element: Element, rect: LayoutRect): LayoutChange | undefined {\n if (!element.isConnected) return undefined;\n\n const next = measureLayout(element);\n const x = rect.x - next.x;\n const y = rect.y - next.y;\n const scaleX = computeScale(rect.width, next.width);\n const scaleY = computeScale(rect.height, next.height);\n\n return x === 0 && y === 0 && scaleX === 1 && scaleY === 1 ? undefined : { element, scaleX, scaleY, x, y };\n}\n\nfunction matchByIdentity(entries: readonly LayoutEntry[], elements: readonly Element[]): LayoutChange[] {\n const captured = new Map(entries.map((entry) => [entry.element, entry]));\n\n return elements.flatMap((element) => {\n const entry = captured.get(element);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\nfunction matchByKey(\n captured: ReadonlyMap<string, LayoutEntry>,\n elements: readonly Element[],\n getKey: (element: Element) => string,\n): LayoutChange[] {\n const committed = new Set<string>();\n\n return elements.flatMap((element) => {\n const key = readKey(element, getKey, 'committed');\n const entry = captured.get(key);\n\n if (committed.has(key)) {\n throw new NecromancerConfigError('getKey returned duplicate keys for the committed elements.');\n }\n\n committed.add(key);\n\n return entry\n ? [changedElement(element, entry.rect)].filter((change): change is LayoutChange => Boolean(change))\n : [];\n });\n}\n\n/**\n * Captures the current position and size of unique elements for a later FLIP animation.\n *\n * Each element's `x`/`y` position and `width`/`height` are captured. Rotation and other\n * transforms are not captured or compensated. Capture before mutating the DOM, then call\n * the returned transition's `animate()` method after the browser has applied the resulting\n * layout — position changes animate via translate, and size changes via scale, both\n * additively composed on top of any authored transform.\n */\nexport function captureLayout(elements: Iterable<Element>, options: LayoutCaptureOptions = {}): LayoutTransition {\n const entries: LayoutEntry[] = uniqueElements(elements).map((element) => ({\n element,\n rect: measureLayout(element),\n }));\n const getKey = options.getKey;\n const captured = getKey ? createKeyMap(entries, getKey, 'captured') : undefined;\n\n let consumed = false;\n\n return {\n animate(options: LayoutAnimationOptions = {}) {\n if (consumed) throw new NecromancerConfigError('This layout transition has already been animated.');\n\n const { elements: committedElements, ...animationOptions } = options;\n const elements = uniqueElements(committedElements ?? entries.map(({ element }) => element));\n const changed = getKey && captured ? matchByKey(captured, elements, getKey) : matchByIdentity(entries, elements);\n\n consumed = true;\n\n return animateEach(\n changed.map(({ element }) => element),\n (_, index) => {\n // `changed` was mapped directly into the elements array above, so this index is always in range.\n const change = changed[index]!;\n const translate = `${round(change.x, 2)}px ${round(change.y, 2)}px`;\n const scale = `${round(change.scaleX, 4)} ${round(change.scaleY, 4)}`;\n\n return [\n { composite: 'add', scale, translate },\n { composite: 'add', scale: '1 1', translate: '0px 0px' },\n ];\n },\n animationOptions,\n );\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"layout.cjs","names":[],"sources":["../src/layout.ts"],"sourcesContent":["import { uniqueElements } from './_elements';\nimport { animateEach } from './animate-each';\nimport { NecromancerConfigError } from './errors';\nimport type { LayoutAnimationOptions, LayoutCaptureOptions, LayoutTransition } from './types';\n\ntype LayoutRect = Readonly<Pick<DOMRectReadOnly, 'height' | 'width' | 'x' | 'y'>>;\ntype LayoutEntry<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/layout.d.ts CHANGED
@@ -8,5 +8,5 @@ import type { LayoutCaptureOptions, LayoutTransition } from './types';
8
8
  * layout — position changes animate via translate, and size changes via scale, both
9
9
  * additively composed on top of any authored transform.
10
10
  */
11
- export declare function captureLayout(elements: Iterable<Element>, options?: LayoutCaptureOptions): LayoutTransition;
11
+ export declare function captureLayout<ElementType extends Element>(elements: Iterable<ElementType>, options?: LayoutCaptureOptions<ElementType>): LayoutTransition<ElementType>;
12
12
  //# sourceMappingURL=layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA0B,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAmH9F;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CAqC/G"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA0B,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAuI9F;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,WAAW,SAAS,OAAO,EACvD,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC/B,OAAO,GAAE,oBAAoB,CAAC,WAAW,CAAM,GAC9C,gBAAgB,CAAC,WAAW,CAAC,CAqC/B"}