@voila.dev/motion 6.0.122 → 6.0.123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,10 @@ React 19 is an optional peer, used only by `/react`. The core never imports it.
46
46
  with `composite: "add"`. They run on the compositor, add up, and stop
47
47
  independently. When they finish, their values are written into the
48
48
  element's inline `translate`, `scale` and `rotate`: those three styles
49
- belong to motion on an element it animates.
49
+ belong to motion on an element it animates. `rotateX`, `rotateY`, `skew`,
50
+ `skewX` and `skewY` have no property of their own: they animate
51
+ `transform` the same way, and once one has run on an element, its inline
52
+ `transform` belongs to motion too.
50
53
  - **Interruption.** A new animation on a property starts from what is
51
54
  painted, at the speed the last one had. For numbers, motion keeps the
52
55
  generator behind each browser animation and reads both from it; for
@@ -1 +1 @@
1
- {"version":3,"file":"js-animation.d.ts","sourceRoot":"","sources":["../../src/animate/js-animation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,iBAAiB,EACjB,gBAAgB,EAEhB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,iBAAiB,CAAC,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,eAAe;IACf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,IAAI,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,CAAE,SAAQ,iBAAiB;IACxD,qFAAqF;IACrF,OAAO,IAAI;QAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAuH3E"}
1
+ {"version":3,"file":"js-animation.d.ts","sourceRoot":"","sources":["../../src/animate/js-animation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,iBAAiB,EACjB,gBAAgB,EAEhB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,iBAAiB,CAAC,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,eAAe;IACf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,IAAI,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,CAAE,SAAQ,iBAAiB;IACxD,qFAAqF;IACrF,OAAO,IAAI;QAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CA2H3E"}
@@ -44,17 +44,23 @@ function jsAnimation(config) {
44
44
  cancelFrame(tick);
45
45
  resolve();
46
46
  }
47
+ function finish() {
48
+ time = speed < 0 ? 0 : duration;
49
+ apply(speed < 0 ? keyframes[0] : last);
50
+ settle("finished");
51
+ config.onComplete?.();
52
+ }
47
53
  function tick(data) {
48
- time = Math.max(0, time + (data.timestamp - previous) / 1e3 * speed);
54
+ time = Math.min(
55
+ duration,
56
+ Math.max(0, time + (data.timestamp - previous) / 1e3 * speed)
57
+ );
49
58
  previous = data.timestamp;
50
- if (time < duration) {
59
+ if (speed < 0 ? time > 0 : time < duration) {
51
60
  render();
52
61
  return;
53
62
  }
54
- time = duration;
55
- render();
56
- settle("finished");
57
- config.onComplete?.();
63
+ finish();
58
64
  }
59
65
  function play() {
60
66
  if (state === "running") return;
@@ -95,11 +101,7 @@ function jsAnimation(config) {
95
101
  settle("finished");
96
102
  },
97
103
  complete() {
98
- if (state === "finished") return;
99
- time = duration;
100
- render();
101
- settle("finished");
102
- config.onComplete?.();
104
+ if (state !== "finished") finish();
103
105
  },
104
106
  attachTimeline: () => false,
105
107
  current() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/animate/js-animation.ts"],
4
- "sourcesContent": ["import { thenable } from \"#/animate/controls.ts\";\nimport { iterationTime, planGenerator, totalDuration } from \"#/animate/plan.ts\";\nimport type {\n\tAnimationControls,\n\tAnimationOptions,\n\tAnimationState,\n} from \"#/animate/types.ts\";\nimport { cancelFrame, type FrameData, frame, now } from \"#/frame/frame.ts\";\nimport { mix } from \"#/interpolate/mix.ts\";\n\nexport interface JsAnimationConfig<T> {\n\t/** At least two: the caller resolves a lone target against the current value. */\n\treadonly keyframes: readonly T[];\n\treadonly options: AnimationOptions;\n\t/** Seconds. */\n\treadonly delay: number;\n\t/** The velocity the value has now, carried into a spring. */\n\treadonly velocity: number;\n\tapply(value: T): void;\n\tonComplete?(): void;\n}\n\nexport interface JsAnimation<T> extends AnimationControls {\n\t/** The value and velocity at the current time: where an interruption starts from. */\n\tcurrent(): { readonly value: T; readonly velocity: number };\n}\n\n/**\n * An animation stepped by the frame loop: plain values, objects, SVG\n * attributes and CSS variables \u2014 whatever the Web Animations API can't play.\n */\nexport function jsAnimation<T>(config: JsAnimationConfig<T>): JsAnimation<T> {\n\tconst { keyframes, options, delay, apply } = config;\n\tconst { generator, positional } = planGenerator(\n\t\tkeyframes,\n\t\toptions,\n\t\tconfig.velocity,\n\t);\n\tconst segments = positional\n\t\t? keyframes.slice(1).map((to, index) => mix(keyframes[index] as T, to as T))\n\t\t: [];\n\tconst active = totalDuration(generator.duration, options);\n\tconst duration = delay + active;\n\tconst endsReversed =\n\t\toptions.repeatType === \"reverse\" && (options.repeat ?? 0) % 2 === 1;\n\tconst last = keyframes[endsReversed ? 0 : keyframes.length - 1] as T;\n\n\tlet time = 0;\n\tlet speed = 1;\n\tlet state: AnimationState = \"running\";\n\tlet previous = now();\n\tlet resolve: () => void = () => {};\n\tconst finished = new Promise<void>((done) => {\n\t\tresolve = done;\n\t});\n\n\tfunction valueAt(position: number): T {\n\t\tif (!positional) return position as T;\n\t\tconst index = Math.min(\n\t\t\tMath.max(Math.floor(position), 0),\n\t\t\tsegments.length - 1,\n\t\t);\n\t\treturn (segments[index] as (progress: number) => T)(position - index);\n\t}\n\n\tfunction sample() {\n\t\tconst elapsed = Math.max(0, time - delay);\n\t\treturn generator.at(iterationTime(elapsed, generator.duration, options));\n\t}\n\n\tfunction render() {\n\t\tif (time < delay) return;\n\t\tapply(time >= duration ? last : valueAt(sample().value));\n\t}\n\n\tfunction settle(next: AnimationState) {\n\t\tstate = next;\n\t\tcancelFrame(tick);\n\t\tresolve();\n\t}\n\n\tfunction tick(data: FrameData) {\n\t\ttime = Math.max(0, time + ((data.timestamp - previous) / 1000) * speed);\n\t\tprevious = data.timestamp;\n\t\tif (time < duration) {\n\t\t\trender();\n\t\t\treturn;\n\t\t}\n\t\ttime = duration;\n\t\trender();\n\t\tsettle(\"finished\");\n\t\tconfig.onComplete?.();\n\t}\n\n\tfunction play() {\n\t\tif (state === \"running\") return;\n\t\tprevious = now();\n\t\tstate = \"running\";\n\t\tframe.update(tick, true);\n\t}\n\n\tframe.update(tick, true);\n\trender();\n\n\treturn {\n\t\t...thenable(finished),\n\t\tget time() {\n\t\t\treturn time;\n\t\t},\n\t\tset time(seconds) {\n\t\t\ttime = Math.min(Math.max(seconds, 0), duration);\n\t\t\trender();\n\t\t},\n\t\tget speed() {\n\t\t\treturn speed;\n\t\t},\n\t\tset speed(rate) {\n\t\t\tspeed = rate;\n\t\t},\n\t\tduration,\n\t\tget state() {\n\t\t\treturn state;\n\t\t},\n\t\tplay,\n\t\tpause() {\n\t\t\tif (state !== \"running\") return;\n\t\t\tstate = \"paused\";\n\t\t\tcancelFrame(tick);\n\t\t},\n\t\tstop: () => settle(\"finished\"),\n\t\tcancel() {\n\t\t\tapply(keyframes[0] as T);\n\t\t\tsettle(\"finished\");\n\t\t},\n\t\tcomplete() {\n\t\t\tif (state === \"finished\") return;\n\t\t\ttime = duration;\n\t\t\trender();\n\t\t\tsettle(\"finished\");\n\t\t\tconfig.onComplete?.();\n\t\t},\n\t\tattachTimeline: () => false,\n\t\tcurrent() {\n\t\t\tconst { value, velocity } = sample();\n\t\t\treturn {\n\t\t\t\tvalue: time < delay ? (keyframes[0] as T) : valueAt(value),\n\t\t\t\tvelocity: positional || time < delay ? 0 : velocity,\n\t\t\t};\n\t\t},\n\t};\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { thenable } from \"#/animate/controls.ts\";\nimport { iterationTime, planGenerator, totalDuration } from \"#/animate/plan.ts\";\nimport type {\n\tAnimationControls,\n\tAnimationOptions,\n\tAnimationState,\n} from \"#/animate/types.ts\";\nimport { cancelFrame, type FrameData, frame, now } from \"#/frame/frame.ts\";\nimport { mix } from \"#/interpolate/mix.ts\";\n\nexport interface JsAnimationConfig<T> {\n\t/** At least two: the caller resolves a lone target against the current value. */\n\treadonly keyframes: readonly T[];\n\treadonly options: AnimationOptions;\n\t/** Seconds. */\n\treadonly delay: number;\n\t/** The velocity the value has now, carried into a spring. */\n\treadonly velocity: number;\n\tapply(value: T): void;\n\tonComplete?(): void;\n}\n\nexport interface JsAnimation<T> extends AnimationControls {\n\t/** The value and velocity at the current time: where an interruption starts from. */\n\tcurrent(): { readonly value: T; readonly velocity: number };\n}\n\n/**\n * An animation stepped by the frame loop: plain values, objects, SVG\n * attributes and CSS variables \u2014 whatever the Web Animations API can't play.\n */\nexport function jsAnimation<T>(config: JsAnimationConfig<T>): JsAnimation<T> {\n\tconst { keyframes, options, delay, apply } = config;\n\tconst { generator, positional } = planGenerator(\n\t\tkeyframes,\n\t\toptions,\n\t\tconfig.velocity,\n\t);\n\tconst segments = positional\n\t\t? keyframes.slice(1).map((to, index) => mix(keyframes[index] as T, to as T))\n\t\t: [];\n\tconst active = totalDuration(generator.duration, options);\n\tconst duration = delay + active;\n\tconst endsReversed =\n\t\toptions.repeatType === \"reverse\" && (options.repeat ?? 0) % 2 === 1;\n\tconst last = keyframes[endsReversed ? 0 : keyframes.length - 1] as T;\n\n\tlet time = 0;\n\tlet speed = 1;\n\tlet state: AnimationState = \"running\";\n\tlet previous = now();\n\tlet resolve: () => void = () => {};\n\tconst finished = new Promise<void>((done) => {\n\t\tresolve = done;\n\t});\n\n\tfunction valueAt(position: number): T {\n\t\tif (!positional) return position as T;\n\t\tconst index = Math.min(\n\t\t\tMath.max(Math.floor(position), 0),\n\t\t\tsegments.length - 1,\n\t\t);\n\t\treturn (segments[index] as (progress: number) => T)(position - index);\n\t}\n\n\tfunction sample() {\n\t\tconst elapsed = Math.max(0, time - delay);\n\t\treturn generator.at(iterationTime(elapsed, generator.duration, options));\n\t}\n\n\tfunction render() {\n\t\tif (time < delay) return;\n\t\tapply(time >= duration ? last : valueAt(sample().value));\n\t}\n\n\tfunction settle(next: AnimationState) {\n\t\tstate = next;\n\t\tcancelFrame(tick);\n\t\tresolve();\n\t}\n\n\t/** Played backwards, it ends where it started, the way WAAPI does. */\n\tfunction finish() {\n\t\ttime = speed < 0 ? 0 : duration;\n\t\tapply(speed < 0 ? (keyframes[0] as T) : last);\n\t\tsettle(\"finished\");\n\t\tconfig.onComplete?.();\n\t}\n\n\tfunction tick(data: FrameData) {\n\t\ttime = Math.min(\n\t\t\tduration,\n\t\t\tMath.max(0, time + ((data.timestamp - previous) / 1000) * speed),\n\t\t);\n\t\tprevious = data.timestamp;\n\t\tif (speed < 0 ? time > 0 : time < duration) {\n\t\t\trender();\n\t\t\treturn;\n\t\t}\n\t\tfinish();\n\t}\n\n\tfunction play() {\n\t\tif (state === \"running\") return;\n\t\tprevious = now();\n\t\tstate = \"running\";\n\t\tframe.update(tick, true);\n\t}\n\n\tframe.update(tick, true);\n\trender();\n\n\treturn {\n\t\t...thenable(finished),\n\t\tget time() {\n\t\t\treturn time;\n\t\t},\n\t\tset time(seconds) {\n\t\t\ttime = Math.min(Math.max(seconds, 0), duration);\n\t\t\trender();\n\t\t},\n\t\tget speed() {\n\t\t\treturn speed;\n\t\t},\n\t\tset speed(rate) {\n\t\t\tspeed = rate;\n\t\t},\n\t\tduration,\n\t\tget state() {\n\t\t\treturn state;\n\t\t},\n\t\tplay,\n\t\tpause() {\n\t\t\tif (state !== \"running\") return;\n\t\t\tstate = \"paused\";\n\t\t\tcancelFrame(tick);\n\t\t},\n\t\tstop: () => settle(\"finished\"),\n\t\tcancel() {\n\t\t\tapply(keyframes[0] as T);\n\t\t\tsettle(\"finished\");\n\t\t},\n\t\tcomplete() {\n\t\t\tif (state !== \"finished\") finish();\n\t\t},\n\t\tattachTimeline: () => false,\n\t\tcurrent() {\n\t\t\tconst { value, velocity } = sample();\n\t\t\treturn {\n\t\t\t\tvalue: time < delay ? (keyframes[0] as T) : valueAt(value),\n\t\t\t\tvelocity: positional || time < delay ? 0 : velocity,\n\t\t\t};\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import type { KeyframeValue } from "./types.js";
2
- type TransformProperty = "translate" | "scale" | "rotate";
2
+ type TransformProperty = "translate" | "scale" | "rotate" | "transform";
3
3
  interface TransformChannel {
4
4
  readonly property: TransformProperty;
5
5
  readonly unit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/animate/style.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,KAAK,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,UAAU,gBAAgB;IACzB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAuBjE,CAAC;AAiBF,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAMlE;AAkBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxE;AASD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,QAO/C;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,QAG3D;AAED,wBAAgB,eAAe,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,aAAa,QAKpB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI3C;AAED,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C"}
1
+ {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/animate/style.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,KAAK,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAExE,UAAU,gBAAgB;IACzB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AASD;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAkCjE,CAAC;AAiBF,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAMlE;AAkBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxE;AASD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,QAgB/C;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,QAG3D;AAED,wBAAgB,eAAe,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,aAAa,QAKpB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI3C;AAED,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C"}
@@ -1,3 +1,4 @@
1
+ const LISTED = ["rotateX", "rotateY", "skew", "skewX", "skewY"];
1
2
  const TRANSFORMS = {
2
3
  x: { property: "translate", unit: "px", identity: 0, alone: (v) => `${v}` },
3
4
  y: {
@@ -20,7 +21,18 @@ const TRANSFORMS = {
20
21
  unit: "deg",
21
22
  identity: 0,
22
23
  alone: (v) => `${v}`
23
- }
24
+ },
25
+ ...Object.fromEntries(
26
+ LISTED.map((key) => [
27
+ key,
28
+ {
29
+ property: "transform",
30
+ unit: "deg",
31
+ identity: 0,
32
+ alone: (v) => `${key}(${v})`
33
+ }
34
+ ])
35
+ )
24
36
  };
25
37
  const UNITLESS = /* @__PURE__ */ new Set([
26
38
  "opacity",
@@ -67,6 +79,11 @@ function writeTransforms(element) {
67
79
  const uniform = Number.parseFloat(at("scale"));
68
80
  style.scale = `${uniform * Number.parseFloat(at("scaleX"))} ${uniform * Number.parseFloat(at("scaleY"))}`;
69
81
  style.rotate = at("rotate");
82
+ if (LISTED.some(
83
+ (key) => committed.get(element)?.has(key) || animating.get(element)?.has(key)
84
+ )) {
85
+ style.transform = LISTED.map((key) => `${key}(${at(key)})`).join(" ");
86
+ }
70
87
  }
71
88
  function beginTransform(element, key) {
72
89
  entries(animating, element, () => /* @__PURE__ */ new Set()).add(key);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/animate/style.ts"],
4
- "sourcesContent": ["import type { KeyframeValue } from \"#/animate/types.ts\";\n\ntype TransformProperty = \"translate\" | \"scale\" | \"rotate\";\n\ninterface TransformChannel {\n\treadonly property: TransformProperty;\n\treadonly unit: string;\n\treadonly identity: number;\n\t/** The channel alone as a value of its property, the other axes at identity. */\n\treadonly alone: (value: string) => string;\n}\n\n/**\n * The independent transforms. Each animates its CSS property alone with\n * `composite: \"add\"`, so `x` and `y` run as two compositor animations that\n * sum, and one can be interrupted without touching the other.\n */\nexport const TRANSFORMS: Readonly<Record<string, TransformChannel>> = {\n\tx: { property: \"translate\", unit: \"px\", identity: 0, alone: (v) => `${v}` },\n\ty: {\n\t\tproperty: \"translate\",\n\t\tunit: \"px\",\n\t\tidentity: 0,\n\t\talone: (v) => `0px ${v}`,\n\t},\n\tz: {\n\t\tproperty: \"translate\",\n\t\tunit: \"px\",\n\t\tidentity: 0,\n\t\talone: (v) => `0px 0px ${v}`,\n\t},\n\tscale: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `${v}` },\n\tscaleX: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `${v} 1` },\n\tscaleY: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `1 ${v}` },\n\trotate: {\n\t\tproperty: \"rotate\",\n\t\tunit: \"deg\",\n\t\tidentity: 0,\n\t\talone: (v) => `${v}`,\n\t},\n};\n\nconst UNITLESS = new Set([\n\t\"opacity\",\n\t\"zIndex\",\n\t\"fontWeight\",\n\t\"lineHeight\",\n\t\"flexGrow\",\n\t\"flexShrink\",\n\t\"order\",\n\t\"fillOpacity\",\n\t\"strokeOpacity\",\n\t\"strokeMiterlimit\",\n\t\"aspectRatio\",\n\t\"pathLength\",\n]);\n\n/** A keyframe as CSS: numbers take the property's unit. */\nexport function withUnit(key: string, value: KeyframeValue): string {\n\tif (typeof value === \"string\") return value;\n\tconst unit =\n\t\tTRANSFORMS[key]?.unit ??\n\t\t(UNITLESS.has(key) || key.startsWith(\"--\") ? \"\" : \"px\");\n\treturn `${value}${unit}`;\n}\n\n/** Committed transform values per element, the ones no animation is driving. */\nconst committed = new WeakMap<Element, Map<string, KeyframeValue>>();\nconst animating = new WeakMap<Element, Set<string>>();\n\nfunction entries<T>(\n\tmap: WeakMap<Element, T>,\n\telement: Element,\n\tfresh: () => T,\n) {\n\tconst existing = map.get(element);\n\tif (existing) return existing;\n\tconst created = fresh();\n\tmap.set(element, created);\n\treturn created;\n}\n\nexport function committedTransform(element: Element, key: string): number {\n\tconst value = committed.get(element)?.get(key);\n\treturn typeof value === \"number\"\n\t\t? value\n\t\t: Number.parseFloat(value ?? \"\") || (TRANSFORMS[key]?.identity ?? 0);\n}\n\nfunction channel(element: Element, key: string): string {\n\tconst channelDef = TRANSFORMS[key] as TransformChannel;\n\tif (animating.get(element)?.has(key))\n\t\treturn `${channelDef.identity}${channelDef.unit}`;\n\treturn withUnit(key, committed.get(element)?.get(key) ?? channelDef.identity);\n}\n\n/** Writes the committed transforms inline, with every animating channel at its identity. */\nexport function writeTransforms(element: Element) {\n\tconst style = (element as HTMLElement).style;\n\tconst at = (key: string) => channel(element, key);\n\tstyle.translate = `${at(\"x\")} ${at(\"y\")} ${at(\"z\")}`;\n\tconst uniform = Number.parseFloat(at(\"scale\"));\n\tstyle.scale = `${uniform * Number.parseFloat(at(\"scaleX\"))} ${uniform * Number.parseFloat(at(\"scaleY\"))}`;\n\tstyle.rotate = at(\"rotate\");\n}\n\nexport function beginTransform(element: Element, key: string) {\n\tentries(animating, element, () => new Set()).add(key);\n\twriteTransforms(element);\n}\n\nexport function commitTransform(\n\telement: Element,\n\tkey: string,\n\tvalue: KeyframeValue,\n) {\n\tentries(committed, element, () => new Map()).set(key, value);\n\tanimating.get(element)?.delete(key);\n\twriteTransforms(element);\n}\n\nexport function cssName(key: string): string {\n\treturn key.startsWith(\"--\")\n\t\t? key\n\t\t: key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n}\n\n/** Moves things on screen, as opposed to fading or tinting them: what reduced motion turns off. */\nexport function isMovement(key: string): boolean {\n\treturn !/opacity|color|fill|stroke$|filter/i.test(key);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { KeyframeValue } from \"#/animate/types.ts\";\n\ntype TransformProperty = \"translate\" | \"scale\" | \"rotate\" | \"transform\";\n\ninterface TransformChannel {\n\treadonly property: TransformProperty;\n\treadonly unit: string;\n\treadonly identity: number;\n\t/** The channel alone as a value of its property, the other axes at identity. */\n\treadonly alone: (value: string) => string;\n}\n\n/**\n * The transforms with no CSS property of their own (`rotate` takes one axis,\n * and skew has none): they go through `transform`, in this order, after\n * `translate`, `rotate` and `scale`.\n */\nconst LISTED = [\"rotateX\", \"rotateY\", \"skew\", \"skewX\", \"skewY\"];\n\n/**\n * The independent transforms. Each animates its CSS property alone with\n * `composite: \"add\"`, so `x` and `y` run as two compositor animations that\n * sum, and one can be interrupted without touching the other.\n */\nexport const TRANSFORMS: Readonly<Record<string, TransformChannel>> = {\n\tx: { property: \"translate\", unit: \"px\", identity: 0, alone: (v) => `${v}` },\n\ty: {\n\t\tproperty: \"translate\",\n\t\tunit: \"px\",\n\t\tidentity: 0,\n\t\talone: (v) => `0px ${v}`,\n\t},\n\tz: {\n\t\tproperty: \"translate\",\n\t\tunit: \"px\",\n\t\tidentity: 0,\n\t\talone: (v) => `0px 0px ${v}`,\n\t},\n\tscale: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `${v}` },\n\tscaleX: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `${v} 1` },\n\tscaleY: { property: \"scale\", unit: \"\", identity: 1, alone: (v) => `1 ${v}` },\n\trotate: {\n\t\tproperty: \"rotate\",\n\t\tunit: \"deg\",\n\t\tidentity: 0,\n\t\talone: (v) => `${v}`,\n\t},\n\t...Object.fromEntries(\n\t\tLISTED.map((key) => [\n\t\t\tkey,\n\t\t\t{\n\t\t\t\tproperty: \"transform\",\n\t\t\t\tunit: \"deg\",\n\t\t\t\tidentity: 0,\n\t\t\t\talone: (v: string) => `${key}(${v})`,\n\t\t\t},\n\t\t]),\n\t),\n};\n\nconst UNITLESS = new Set([\n\t\"opacity\",\n\t\"zIndex\",\n\t\"fontWeight\",\n\t\"lineHeight\",\n\t\"flexGrow\",\n\t\"flexShrink\",\n\t\"order\",\n\t\"fillOpacity\",\n\t\"strokeOpacity\",\n\t\"strokeMiterlimit\",\n\t\"aspectRatio\",\n\t\"pathLength\",\n]);\n\n/** A keyframe as CSS: numbers take the property's unit. */\nexport function withUnit(key: string, value: KeyframeValue): string {\n\tif (typeof value === \"string\") return value;\n\tconst unit =\n\t\tTRANSFORMS[key]?.unit ??\n\t\t(UNITLESS.has(key) || key.startsWith(\"--\") ? \"\" : \"px\");\n\treturn `${value}${unit}`;\n}\n\n/** Committed transform values per element, the ones no animation is driving. */\nconst committed = new WeakMap<Element, Map<string, KeyframeValue>>();\nconst animating = new WeakMap<Element, Set<string>>();\n\nfunction entries<T>(\n\tmap: WeakMap<Element, T>,\n\telement: Element,\n\tfresh: () => T,\n) {\n\tconst existing = map.get(element);\n\tif (existing) return existing;\n\tconst created = fresh();\n\tmap.set(element, created);\n\treturn created;\n}\n\nexport function committedTransform(element: Element, key: string): number {\n\tconst value = committed.get(element)?.get(key);\n\treturn typeof value === \"number\"\n\t\t? value\n\t\t: Number.parseFloat(value ?? \"\") || (TRANSFORMS[key]?.identity ?? 0);\n}\n\nfunction channel(element: Element, key: string): string {\n\tconst channelDef = TRANSFORMS[key] as TransformChannel;\n\tif (animating.get(element)?.has(key))\n\t\treturn `${channelDef.identity}${channelDef.unit}`;\n\treturn withUnit(key, committed.get(element)?.get(key) ?? channelDef.identity);\n}\n\n/** Writes the committed transforms inline, with every animating channel at its identity. */\nexport function writeTransforms(element: Element) {\n\tconst style = (element as HTMLElement).style;\n\tconst at = (key: string) => channel(element, key);\n\tstyle.translate = `${at(\"x\")} ${at(\"y\")} ${at(\"z\")}`;\n\tconst uniform = Number.parseFloat(at(\"scale\"));\n\tstyle.scale = `${uniform * Number.parseFloat(at(\"scaleX\"))} ${uniform * Number.parseFloat(at(\"scaleY\"))}`;\n\tstyle.rotate = at(\"rotate\");\n\t// `transform` stays the page's on an element that never animates these.\n\tif (\n\t\tLISTED.some(\n\t\t\t(key) =>\n\t\t\t\tcommitted.get(element)?.has(key) || animating.get(element)?.has(key),\n\t\t)\n\t) {\n\t\tstyle.transform = LISTED.map((key) => `${key}(${at(key)})`).join(\" \");\n\t}\n}\n\nexport function beginTransform(element: Element, key: string) {\n\tentries(animating, element, () => new Set()).add(key);\n\twriteTransforms(element);\n}\n\nexport function commitTransform(\n\telement: Element,\n\tkey: string,\n\tvalue: KeyframeValue,\n) {\n\tentries(committed, element, () => new Map()).set(key, value);\n\tanimating.get(element)?.delete(key);\n\twriteTransforms(element);\n}\n\nexport function cssName(key: string): string {\n\treturn key.startsWith(\"--\")\n\t\t? key\n\t\t: key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n}\n\n/** Moves things on screen, as opposed to fading or tinting them: what reduced motion turns off. */\nexport function isMovement(key: string): boolean {\n\treturn !/opacity|color|fill|stroke$|filter/i.test(key);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -52,8 +52,8 @@ export type KeyframeValue = number | string;
52
52
  export type Keyframes<T = KeyframeValue> = T | readonly T[];
53
53
  /**
54
54
  * DOM keyframes: any CSS property (camelCase), a CSS variable (`--name`),
55
- * the independent transforms (`x`, `y`, `z`, `scale`, `scaleX`, `scaleY`,
56
- * `rotate`), or an SVG attribute. A single value animates from the current
55
+ * the transforms (`x`, `y`, `z`, `scale`, `scaleX`, `scaleY`,
56
+ * `rotate`, `rotateX`, `rotateY`, `skew`, `skewX`, `skewY`), or an SVG attribute. A single value animates from the current
57
57
  * one; an array lists every keyframe. Numbers take the property's unit (px,
58
58
  * deg) when it has one.
59
59
  */
@@ -1 +1 @@
1
- {"version":3,"file":"waapi.d.ts","sourceRoot":"","sources":["../../src/animate/waapi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,OAAO,KAAK,EACX,gBAAgB,EAEhB,aAAa,EACb,MAAM,oBAAoB,CAAC;AAM5B,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;IAC7C,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,8DAA8D;IAC9D,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,IAAI,IAAI,CAAC;CACpB;AA0ED,yEAAyE;AACzE,wBAAgB,cAAc,CAC7B,MAAM,EAAE,WAAW,GACjB,WAAW,CAAC,aAAa,CAAC,CA0G5B"}
1
+ {"version":3,"file":"waapi.d.ts","sourceRoot":"","sources":["../../src/animate/waapi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,OAAO,KAAK,EACX,gBAAgB,EAEhB,aAAa,EACb,MAAM,oBAAoB,CAAC;AAM5B,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;IAC7C,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,8DAA8D;IAC9D,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,IAAI,IAAI,CAAC;CACpB;AA0ED,yEAAyE;AACzE,wBAAgB,cAAc,CAC7B,MAAM,EAAE,WAAW,GACjB,WAAW,CAAC,aAAa,CAAC,CA+G5B"}
@@ -78,6 +78,9 @@ function waapiAnimation(config) {
78
78
  composite: config.composite
79
79
  });
80
80
  const last = keyframes[options.repeatType === "reverse" && iterations % 2 === 0 ? 0 : keyframes.length - 1];
81
+ function end() {
82
+ return animation.playbackRate < 0 ? keyframes[0] : last;
83
+ }
81
84
  let settled = false;
82
85
  let held = keyframes[0];
83
86
  let resolve = () => {
@@ -110,7 +113,7 @@ function waapiAnimation(config) {
110
113
  animation.finished.then(
111
114
  () => {
112
115
  if (settled) return;
113
- settle(last);
116
+ settle(end());
114
117
  config.onComplete?.();
115
118
  },
116
119
  () => settle(void 0)
@@ -138,7 +141,7 @@ function waapiAnimation(config) {
138
141
  stop: () => settle(current().value),
139
142
  cancel: () => settle(keyframes[0]),
140
143
  // The browser refuses to finish an endless animation: settle on its last frame instead.
141
- complete: () => iterations === Number.POSITIVE_INFINITY ? settle(last) : animation.finish(),
144
+ complete: () => iterations === Number.POSITIVE_INFINITY ? settle(end()) : animation.finish(),
142
145
  attachTimeline(timeline) {
143
146
  animation.timeline = timeline;
144
147
  return true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/animate/waapi.ts"],
4
- "sourcesContent": ["import { thenable } from \"#/animate/controls.ts\";\nimport type { JsAnimation } from \"#/animate/js-animation.ts\";\nimport { iterationTime, planGenerator } from \"#/animate/plan.ts\";\nimport { cssName } from \"#/animate/style.ts\";\nimport type {\n\tAnimationOptions,\n\tAnimationState,\n\tKeyframeValue,\n} from \"#/animate/types.ts\";\nimport { linearEasing, supportsLinearEasing } from \"#/easing/linear-css.ts\";\nimport { easingToCss, resolveEasing } from \"#/easing/named.ts\";\nimport type { EasingDefinition } from \"#/easing/types.ts\";\nimport type { Generator } from \"#/generators/types.ts\";\n\nexport interface WaapiConfig {\n\treadonly element: Element;\n\t/** The CSS property the browser animates. */\n\treadonly property: string;\n\treadonly keyframes: readonly KeyframeValue[];\n\t/** A keyframe as the property's CSS value. */\n\tformat(value: KeyframeValue): string;\n\treadonly options: AnimationOptions;\n\treadonly delay: number;\n\treadonly velocity: number;\n\treadonly composite: CompositeOperation;\n\t/** Writes the value that stays once the animation is gone. */\n\tcommit(value: KeyframeValue): void;\n\tonComplete?(): void;\n}\n\nconst SAMPLE_STEP = 1 / 60;\n\nfunction cssEasing(definition: EasingDefinition, duration: number): string {\n\treturn (\n\t\teasingToCss(definition) ?? linearEasing(resolveEasing(definition), duration)\n\t);\n}\n\n/**\n * The browser keyframes and easing. Two keyframes on a spring become one\n * `linear()` easing sampled from the spring, so the compositor plays the\n * exact curve. Without `linear()`, or when the spring starts and ends at the\n * same value (a flick of velocity), the spring is sampled into keyframes.\n */\nfunction browserKeyframes(\n\tconfig: WaapiConfig,\n\tgenerator: Generator,\n\tpositional: boolean,\n): { keyframes: Keyframe[]; easing: string } {\n\tconst { keyframes, format, property, options } = config;\n\tconst duration = generator.duration;\n\tconst [from, to] = keyframes;\n\tconst springs =\n\t\toptions.type !== \"tween\" && keyframes.length === 2 && !options.ease;\n\tconst span =\n\t\ttypeof from === \"number\" && typeof to === \"number\" ? to - from : 1;\n\tif (springs && span !== 0 && supportsLinearEasing()) {\n\t\tconst progress = (t: number) => {\n\t\t\tconst { value } = generator.at(t * duration);\n\t\t\treturn positional ? value : (value - (from as number)) / span;\n\t\t};\n\t\treturn {\n\t\t\tkeyframes: keyframes.map((frame) => ({ [property]: format(frame) })),\n\t\t\teasing: linearEasing(progress, duration),\n\t\t};\n\t}\n\tif (springs && !positional) {\n\t\tconst count = Math.max(2, Math.ceil(duration / SAMPLE_STEP) + 1);\n\t\treturn {\n\t\t\tkeyframes: Array.from({ length: count }, (_unused, index) => {\n\t\t\t\tconst offset = index / (count - 1);\n\t\t\t\treturn {\n\t\t\t\t\t[property]: format(generator.at(offset * duration).value),\n\t\t\t\t\toffset,\n\t\t\t\t};\n\t\t\t}),\n\t\t\teasing: \"linear\",\n\t\t};\n\t}\n\tconst ease = options.ease ?? \"easeOut\";\n\treturn {\n\t\tkeyframes: keyframes.map((frame, index) => ({\n\t\t\t[property]: format(frame),\n\t\t\toffset: options.times?.[index] ?? index / (keyframes.length - 1),\n\t\t\teasing: cssEasing(\n\t\t\t\tArray.isArray(ease) && typeof ease[0] !== \"number\"\n\t\t\t\t\t? ((ease as readonly EasingDefinition[])[index] ?? \"linear\")\n\t\t\t\t\t: (ease as EasingDefinition),\n\t\t\t\tduration,\n\t\t\t),\n\t\t})),\n\t\teasing: \"linear\",\n\t};\n}\n\nconst STATES: Record<AnimationPlayState, AnimationState> = {\n\trunning: \"running\",\n\tpaused: \"paused\",\n\tfinished: \"finished\",\n\tidle: \"finished\",\n};\n\n/** One CSS property of one element, played by the Web Animations API. */\nexport function waapiAnimation(\n\tconfig: WaapiConfig,\n): JsAnimation<KeyframeValue> {\n\tconst { element, keyframes, options, delay, commit } = config;\n\tconst { generator, positional } = planGenerator(\n\t\tkeyframes,\n\t\toptions,\n\t\tconfig.velocity,\n\t);\n\tconst iterations = (options.repeat ?? 0) + 1;\n\tconst { keyframes: frames, easing } = browserKeyframes(\n\t\tconfig,\n\t\tgenerator,\n\t\tpositional,\n\t);\n\tconst animation = element.animate(frames, {\n\t\tduration: generator.duration * 1000,\n\t\tdelay: delay * 1000,\n\t\titerations,\n\t\tdirection: options.repeatType === \"reverse\" ? \"alternate\" : \"normal\",\n\t\teasing,\n\t\tfill: \"both\",\n\t\tcomposite: config.composite,\n\t});\n\tconst last = keyframes[\n\t\toptions.repeatType === \"reverse\" && iterations % 2 === 0\n\t\t\t? 0\n\t\t\t: keyframes.length - 1\n\t] as KeyframeValue;\n\n\tlet settled = false;\n\t/** What stayed on screen when it settled: where a following animation starts. */\n\tlet held: KeyframeValue = keyframes[0] as KeyframeValue;\n\tlet resolve: () => void = () => {};\n\tconst finished = new Promise<void>((done) => {\n\t\tresolve = done;\n\t});\n\n\tfunction current() {\n\t\tif (settled) return { value: held, velocity: 0 };\n\t\tconst elapsed = (Number(animation.currentTime) || 0) / 1000 - delay;\n\t\tif (elapsed <= 0)\n\t\t\treturn { value: keyframes[0] as KeyframeValue, velocity: 0 };\n\t\tconst state = generator.at(\n\t\t\titerationTime(elapsed, generator.duration, options),\n\t\t);\n\t\tif (!positional) return state;\n\t\t// The browser mixes colours and strings itself: ask it what it painted.\n\t\tconst painted =\n\t\t\tconfig.composite === \"replace\"\n\t\t\t\t? getComputedStyle(element).getPropertyValue(cssName(config.property))\n\t\t\t\t: \"\";\n\t\treturn { value: painted || last, velocity: 0 };\n\t}\n\n\tfunction settle(value: KeyframeValue | undefined) {\n\t\tif (settled) return;\n\t\tsettled = true;\n\t\tif (value !== undefined) {\n\t\t\theld = value;\n\t\t\tcommit(value);\n\t\t}\n\t\tanimation.cancel();\n\t\tresolve();\n\t}\n\n\tanimation.finished.then(\n\t\t() => {\n\t\t\tif (settled) return;\n\t\t\tsettle(last);\n\t\t\tconfig.onComplete?.();\n\t\t},\n\t\t() => settle(undefined),\n\t);\n\n\treturn {\n\t\t...thenable(finished),\n\t\tget time() {\n\t\t\treturn (Number(animation.currentTime) || 0) / 1000;\n\t\t},\n\t\tset time(seconds) {\n\t\t\tanimation.currentTime = seconds * 1000;\n\t\t},\n\t\tget speed() {\n\t\t\treturn animation.playbackRate;\n\t\t},\n\t\tset speed(rate) {\n\t\t\tanimation.playbackRate = rate;\n\t\t},\n\t\tduration: delay + generator.duration * iterations,\n\t\tget state() {\n\t\t\treturn settled ? \"finished\" : STATES[animation.playState];\n\t\t},\n\t\tplay: () => animation.play(),\n\t\tpause: () => animation.pause(),\n\t\tstop: () => settle(current().value),\n\t\tcancel: () => settle(keyframes[0]),\n\t\t// The browser refuses to finish an endless animation: settle on its last frame instead.\n\t\tcomplete: () =>\n\t\t\titerations === Number.POSITIVE_INFINITY\n\t\t\t\t? settle(last)\n\t\t\t\t: animation.finish(),\n\t\tattachTimeline(timeline) {\n\t\t\tanimation.timeline = timeline;\n\t\t\treturn true;\n\t\t},\n\t\tcurrent,\n\t};\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { thenable } from \"#/animate/controls.ts\";\nimport type { JsAnimation } from \"#/animate/js-animation.ts\";\nimport { iterationTime, planGenerator } from \"#/animate/plan.ts\";\nimport { cssName } from \"#/animate/style.ts\";\nimport type {\n\tAnimationOptions,\n\tAnimationState,\n\tKeyframeValue,\n} from \"#/animate/types.ts\";\nimport { linearEasing, supportsLinearEasing } from \"#/easing/linear-css.ts\";\nimport { easingToCss, resolveEasing } from \"#/easing/named.ts\";\nimport type { EasingDefinition } from \"#/easing/types.ts\";\nimport type { Generator } from \"#/generators/types.ts\";\n\nexport interface WaapiConfig {\n\treadonly element: Element;\n\t/** The CSS property the browser animates. */\n\treadonly property: string;\n\treadonly keyframes: readonly KeyframeValue[];\n\t/** A keyframe as the property's CSS value. */\n\tformat(value: KeyframeValue): string;\n\treadonly options: AnimationOptions;\n\treadonly delay: number;\n\treadonly velocity: number;\n\treadonly composite: CompositeOperation;\n\t/** Writes the value that stays once the animation is gone. */\n\tcommit(value: KeyframeValue): void;\n\tonComplete?(): void;\n}\n\nconst SAMPLE_STEP = 1 / 60;\n\nfunction cssEasing(definition: EasingDefinition, duration: number): string {\n\treturn (\n\t\teasingToCss(definition) ?? linearEasing(resolveEasing(definition), duration)\n\t);\n}\n\n/**\n * The browser keyframes and easing. Two keyframes on a spring become one\n * `linear()` easing sampled from the spring, so the compositor plays the\n * exact curve. Without `linear()`, or when the spring starts and ends at the\n * same value (a flick of velocity), the spring is sampled into keyframes.\n */\nfunction browserKeyframes(\n\tconfig: WaapiConfig,\n\tgenerator: Generator,\n\tpositional: boolean,\n): { keyframes: Keyframe[]; easing: string } {\n\tconst { keyframes, format, property, options } = config;\n\tconst duration = generator.duration;\n\tconst [from, to] = keyframes;\n\tconst springs =\n\t\toptions.type !== \"tween\" && keyframes.length === 2 && !options.ease;\n\tconst span =\n\t\ttypeof from === \"number\" && typeof to === \"number\" ? to - from : 1;\n\tif (springs && span !== 0 && supportsLinearEasing()) {\n\t\tconst progress = (t: number) => {\n\t\t\tconst { value } = generator.at(t * duration);\n\t\t\treturn positional ? value : (value - (from as number)) / span;\n\t\t};\n\t\treturn {\n\t\t\tkeyframes: keyframes.map((frame) => ({ [property]: format(frame) })),\n\t\t\teasing: linearEasing(progress, duration),\n\t\t};\n\t}\n\tif (springs && !positional) {\n\t\tconst count = Math.max(2, Math.ceil(duration / SAMPLE_STEP) + 1);\n\t\treturn {\n\t\t\tkeyframes: Array.from({ length: count }, (_unused, index) => {\n\t\t\t\tconst offset = index / (count - 1);\n\t\t\t\treturn {\n\t\t\t\t\t[property]: format(generator.at(offset * duration).value),\n\t\t\t\t\toffset,\n\t\t\t\t};\n\t\t\t}),\n\t\t\teasing: \"linear\",\n\t\t};\n\t}\n\tconst ease = options.ease ?? \"easeOut\";\n\treturn {\n\t\tkeyframes: keyframes.map((frame, index) => ({\n\t\t\t[property]: format(frame),\n\t\t\toffset: options.times?.[index] ?? index / (keyframes.length - 1),\n\t\t\teasing: cssEasing(\n\t\t\t\tArray.isArray(ease) && typeof ease[0] !== \"number\"\n\t\t\t\t\t? ((ease as readonly EasingDefinition[])[index] ?? \"linear\")\n\t\t\t\t\t: (ease as EasingDefinition),\n\t\t\t\tduration,\n\t\t\t),\n\t\t})),\n\t\teasing: \"linear\",\n\t};\n}\n\nconst STATES: Record<AnimationPlayState, AnimationState> = {\n\trunning: \"running\",\n\tpaused: \"paused\",\n\tfinished: \"finished\",\n\tidle: \"finished\",\n};\n\n/** One CSS property of one element, played by the Web Animations API. */\nexport function waapiAnimation(\n\tconfig: WaapiConfig,\n): JsAnimation<KeyframeValue> {\n\tconst { element, keyframes, options, delay, commit } = config;\n\tconst { generator, positional } = planGenerator(\n\t\tkeyframes,\n\t\toptions,\n\t\tconfig.velocity,\n\t);\n\tconst iterations = (options.repeat ?? 0) + 1;\n\tconst { keyframes: frames, easing } = browserKeyframes(\n\t\tconfig,\n\t\tgenerator,\n\t\tpositional,\n\t);\n\tconst animation = element.animate(frames, {\n\t\tduration: generator.duration * 1000,\n\t\tdelay: delay * 1000,\n\t\titerations,\n\t\tdirection: options.repeatType === \"reverse\" ? \"alternate\" : \"normal\",\n\t\teasing,\n\t\tfill: \"both\",\n\t\tcomposite: config.composite,\n\t});\n\tconst last = keyframes[\n\t\toptions.repeatType === \"reverse\" && iterations % 2 === 0\n\t\t\t? 0\n\t\t\t: keyframes.length - 1\n\t] as KeyframeValue;\n\n\t/** Played backwards, it ends on its first keyframe. */\n\tfunction end(): KeyframeValue {\n\t\treturn animation.playbackRate < 0 ? (keyframes[0] as KeyframeValue) : last;\n\t}\n\n\tlet settled = false;\n\t/** What stayed on screen when it settled: where a following animation starts. */\n\tlet held: KeyframeValue = keyframes[0] as KeyframeValue;\n\tlet resolve: () => void = () => {};\n\tconst finished = new Promise<void>((done) => {\n\t\tresolve = done;\n\t});\n\n\tfunction current() {\n\t\tif (settled) return { value: held, velocity: 0 };\n\t\tconst elapsed = (Number(animation.currentTime) || 0) / 1000 - delay;\n\t\tif (elapsed <= 0)\n\t\t\treturn { value: keyframes[0] as KeyframeValue, velocity: 0 };\n\t\tconst state = generator.at(\n\t\t\titerationTime(elapsed, generator.duration, options),\n\t\t);\n\t\tif (!positional) return state;\n\t\t// The browser mixes colours and strings itself: ask it what it painted.\n\t\tconst painted =\n\t\t\tconfig.composite === \"replace\"\n\t\t\t\t? getComputedStyle(element).getPropertyValue(cssName(config.property))\n\t\t\t\t: \"\";\n\t\treturn { value: painted || last, velocity: 0 };\n\t}\n\n\tfunction settle(value: KeyframeValue | undefined) {\n\t\tif (settled) return;\n\t\tsettled = true;\n\t\tif (value !== undefined) {\n\t\t\theld = value;\n\t\t\tcommit(value);\n\t\t}\n\t\tanimation.cancel();\n\t\tresolve();\n\t}\n\n\tanimation.finished.then(\n\t\t() => {\n\t\t\tif (settled) return;\n\t\t\tsettle(end());\n\t\t\tconfig.onComplete?.();\n\t\t},\n\t\t() => settle(undefined),\n\t);\n\n\treturn {\n\t\t...thenable(finished),\n\t\tget time() {\n\t\t\treturn (Number(animation.currentTime) || 0) / 1000;\n\t\t},\n\t\tset time(seconds) {\n\t\t\tanimation.currentTime = seconds * 1000;\n\t\t},\n\t\tget speed() {\n\t\t\treturn animation.playbackRate;\n\t\t},\n\t\tset speed(rate) {\n\t\t\tanimation.playbackRate = rate;\n\t\t},\n\t\tduration: delay + generator.duration * iterations,\n\t\tget state() {\n\t\t\treturn settled ? \"finished\" : STATES[animation.playState];\n\t\t},\n\t\tplay: () => animation.play(),\n\t\tpause: () => animation.pause(),\n\t\tstop: () => settle(current().value),\n\t\tcancel: () => settle(keyframes[0]),\n\t\t// The browser refuses to finish an endless animation: settle on its last frame instead.\n\t\tcomplete: () =>\n\t\t\titerations === Number.POSITIVE_INFINITY\n\t\t\t\t? settle(end())\n\t\t\t\t: animation.finish(),\n\t\tattachTimeline(timeline) {\n\t\t\tanimation.timeline = timeline;\n\t\t\treturn true;\n\t\t},\n\t\tcurrent,\n\t};\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AAEzB,SAAS,eAAe,qBAAqB;AAC7C,SAAS,eAAe;AAMxB,SAAS,cAAc,4BAA4B;AACnD,SAAS,aAAa,qBAAqB;AAoB3C,MAAM,cAAc,IAAI;AAExB,SAAS,UAAU,YAA8B,UAA0B;AAC1E,SACC,YAAY,UAAU,KAAK,aAAa,cAAc,UAAU,GAAG,QAAQ;AAE7E;AAQA,SAAS,iBACR,QACA,WACA,YAC4C;AAC5C,QAAM,EAAE,WAAW,QAAQ,UAAU,QAAQ,IAAI;AACjD,QAAM,WAAW,UAAU;AAC3B,QAAM,CAAC,MAAM,EAAE,IAAI;AACnB,QAAM,UACL,QAAQ,SAAS,WAAW,UAAU,WAAW,KAAK,CAAC,QAAQ;AAChE,QAAM,OACL,OAAO,SAAS,YAAY,OAAO,OAAO,WAAW,KAAK,OAAO;AAClE,MAAI,WAAW,SAAS,KAAK,qBAAqB,GAAG;AACpD,UAAM,WAAW,CAAC,MAAc;AAC/B,YAAM,EAAE,MAAM,IAAI,UAAU,GAAG,IAAI,QAAQ;AAC3C,aAAO,aAAa,SAAS,QAAS,QAAmB;AAAA,IAC1D;AACA,WAAO;AAAA,MACN,WAAW,UAAU,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,GAAG,OAAO,KAAK,EAAE,EAAE;AAAA,MACnE,QAAQ,aAAa,UAAU,QAAQ;AAAA,IACxC;AAAA,EACD;AACA,MAAI,WAAW,CAAC,YAAY;AAC3B,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,WAAW,WAAW,IAAI,CAAC;AAC/D,WAAO;AAAA,MACN,WAAW,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,SAAS,UAAU;AAC5D,cAAM,SAAS,SAAS,QAAQ;AAChC,eAAO;AAAA,UACN,CAAC,QAAQ,GAAG,OAAO,UAAU,GAAG,SAAS,QAAQ,EAAE,KAAK;AAAA,UACxD;AAAA,QACD;AAAA,MACD,CAAC;AAAA,MACD,QAAQ;AAAA,IACT;AAAA,EACD;AACA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,SAAO;AAAA,IACN,WAAW,UAAU,IAAI,CAAC,OAAO,WAAW;AAAA,MAC3C,CAAC,QAAQ,GAAG,OAAO,KAAK;AAAA,MACxB,QAAQ,QAAQ,QAAQ,KAAK,KAAK,SAAS,UAAU,SAAS;AAAA,MAC9D,QAAQ;AAAA,QACP,MAAM,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC,MAAM,WACrC,KAAqC,KAAK,KAAK,WAChD;AAAA,QACJ;AAAA,MACD;AAAA,IACD,EAAE;AAAA,IACF,QAAQ;AAAA,EACT;AACD;AAEA,MAAM,SAAqD;AAAA,EAC1D,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,MAAM;AACP;AAGO,SAAS,eACf,QAC6B;AAC7B,QAAM,EAAE,SAAS,WAAW,SAAS,OAAO,OAAO,IAAI;AACvD,QAAM,EAAE,WAAW,WAAW,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACR;AACA,QAAM,cAAc,QAAQ,UAAU,KAAK;AAC3C,QAAM,EAAE,WAAW,QAAQ,OAAO,IAAI;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACA,QAAM,YAAY,QAAQ,QAAQ,QAAQ;AAAA,IACzC,UAAU,UAAU,WAAW;AAAA,IAC/B,OAAO,QAAQ;AAAA,IACf;AAAA,IACA,WAAW,QAAQ,eAAe,YAAY,cAAc;AAAA,IAC5D;AAAA,IACA,MAAM;AAAA,IACN,WAAW,OAAO;AAAA,EACnB,CAAC;AACD,QAAM,OAAO,UACZ,QAAQ,eAAe,aAAa,aAAa,MAAM,IACpD,IACA,UAAU,SAAS,CACvB;AAGA,WAAS,MAAqB;AAC7B,WAAO,UAAU,eAAe,IAAK,UAAU,CAAC,IAAsB;AAAA,EACvE;AAEA,MAAI,UAAU;AAEd,MAAI,OAAsB,UAAU,CAAC;AACrC,MAAI,UAAsB,MAAM;AAAA,EAAC;AACjC,QAAM,WAAW,IAAI,QAAc,CAAC,SAAS;AAC5C,cAAU;AAAA,EACX,CAAC;AAED,WAAS,UAAU;AAClB,QAAI,QAAS,QAAO,EAAE,OAAO,MAAM,UAAU,EAAE;AAC/C,UAAM,WAAW,OAAO,UAAU,WAAW,KAAK,KAAK,MAAO;AAC9D,QAAI,WAAW;AACd,aAAO,EAAE,OAAO,UAAU,CAAC,GAAoB,UAAU,EAAE;AAC5D,UAAM,QAAQ,UAAU;AAAA,MACvB,cAAc,SAAS,UAAU,UAAU,OAAO;AAAA,IACnD;AACA,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,UACL,OAAO,cAAc,YAClB,iBAAiB,OAAO,EAAE,iBAAiB,QAAQ,OAAO,QAAQ,CAAC,IACnE;AACJ,WAAO,EAAE,OAAO,WAAW,MAAM,UAAU,EAAE;AAAA,EAC9C;AAEA,WAAS,OAAO,OAAkC;AACjD,QAAI,QAAS;AACb,cAAU;AACV,QAAI,UAAU,QAAW;AACxB,aAAO;AACP,aAAO,KAAK;AAAA,IACb;AACA,cAAU,OAAO;AACjB,YAAQ;AAAA,EACT;AAEA,YAAU,SAAS;AAAA,IAClB,MAAM;AACL,UAAI,QAAS;AACb,aAAO,IAAI,CAAC;AACZ,aAAO,aAAa;AAAA,IACrB;AAAA,IACA,MAAM,OAAO,MAAS;AAAA,EACvB;AAEA,SAAO;AAAA,IACN,GAAG,SAAS,QAAQ;AAAA,IACpB,IAAI,OAAO;AACV,cAAQ,OAAO,UAAU,WAAW,KAAK,KAAK;AAAA,IAC/C;AAAA,IACA,IAAI,KAAK,SAAS;AACjB,gBAAU,cAAc,UAAU;AAAA,IACnC;AAAA,IACA,IAAI,QAAQ;AACX,aAAO,UAAU;AAAA,IAClB;AAAA,IACA,IAAI,MAAM,MAAM;AACf,gBAAU,eAAe;AAAA,IAC1B;AAAA,IACA,UAAU,QAAQ,UAAU,WAAW;AAAA,IACvC,IAAI,QAAQ;AACX,aAAO,UAAU,aAAa,OAAO,UAAU,SAAS;AAAA,IACzD;AAAA,IACA,MAAM,MAAM,UAAU,KAAK;AAAA,IAC3B,OAAO,MAAM,UAAU,MAAM;AAAA,IAC7B,MAAM,MAAM,OAAO,QAAQ,EAAE,KAAK;AAAA,IAClC,QAAQ,MAAM,OAAO,UAAU,CAAC,CAAC;AAAA;AAAA,IAEjC,UAAU,MACT,eAAe,OAAO,oBACnB,OAAO,IAAI,CAAC,IACZ,UAAU,OAAO;AAAA,IACrB,eAAe,UAAU;AACxB,gBAAU,WAAW;AACrB,aAAO;AAAA,IACR;AAAA,IACA;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,7 @@ export interface Rgba {
8
8
  type Channels = readonly [number, number, number, number];
9
9
  /** Oklab is mixed in, and also a syntax a page can write colours in. */
10
10
  export declare function oklabToRgba([l, a, b, alpha]: Channels): Rgba;
11
- /** A colour written out in full; `undefined` for what only the page can resolve (`var()`, names). */
11
+ /** A colour written out in full, or a name the browser knows; `undefined` for what only the page can resolve (`var()`, `currentColor`). */
12
12
  export declare function parseColor(text: string): Rgba | undefined;
13
13
  export {};
14
14
  //# sourceMappingURL=color-parse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"color-parse.d.ts","sourceRoot":"","sources":["../../src/interpolate/color-parse.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAoC1D,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAa5D;AA6DD,qGAAqG;AACrG,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAezD"}
1
+ {"version":3,"file":"color-parse.d.ts","sourceRoot":"","sources":["../../src/interpolate/color-parse.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAoC1D,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAa5D;AA0FD,2IAA2I;AAC3I,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAezD"}
@@ -88,6 +88,24 @@ function parseHex(hex) {
88
88
  alpha: digits.length === 8 ? byte(6) : 1
89
89
  };
90
90
  }
91
+ const names = /* @__PURE__ */ new Map();
92
+ let probe;
93
+ const SENTINEL = "#010203";
94
+ function parseName(name) {
95
+ if (name === "currentcolor" || !/^[a-z]+$/.test(name)) return void 0;
96
+ if (!names.has(name)) {
97
+ probe ??= typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
98
+ let resolved;
99
+ if (probe) {
100
+ probe.fillStyle = SENTINEL;
101
+ probe.fillStyle = name;
102
+ const spelled = String(probe.fillStyle);
103
+ resolved = spelled === SENTINEL ? void 0 : parseColor(spelled);
104
+ }
105
+ names.set(name, resolved);
106
+ }
107
+ return names.get(name);
108
+ }
91
109
  function parseColor(text) {
92
110
  const trimmed = text.trim();
93
111
  if (trimmed.toLowerCase() === "transparent") {
@@ -98,7 +116,7 @@ function parseColor(text) {
98
116
  }
99
117
  const match = FUNCTION.exec(trimmed);
100
118
  if (match === null) {
101
- return void 0;
119
+ return parseName(trimmed.toLowerCase());
102
120
  }
103
121
  const name = match[1].toLowerCase().replace(/a$/, "");
104
122
  const args = match[2].split(/[\s,/]+/).filter(Boolean);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/interpolate/color-parse.ts"],
4
- "sourcesContent": ["/** A colour in sRGB, every channel 0\u20131. */\nexport interface Rgba {\n\treadonly r: number;\n\treadonly g: number;\n\treadonly b: number;\n\treadonly alpha: number;\n}\n\ntype Channels = readonly [number, number, number, number];\ntype Parser = (args: readonly string[]) => Rgba | undefined;\n\nconst FUNCTION = /^(rgba?|hsla?|oklch|oklab)\\(\\s*([^()]*)\\)$/i;\nconst HUE_UNITS: Readonly<Record<string, number>> = {\n\tdeg: 1,\n\tgrad: 0.9,\n\trad: 180 / Math.PI,\n\tturn: 360,\n};\n\n/** A number, a percentage of `percentScale`, or `none`. */\nfunction channel(text: string | undefined, percentScale: number): number {\n\tif (text === undefined || text === \"none\") {\n\t\treturn 0;\n\t}\n\treturn text.endsWith(\"%\")\n\t\t? (Number.parseFloat(text) / 100) * percentScale\n\t\t: Number.parseFloat(text);\n}\n\nfunction hue(text: string | undefined): number {\n\tconst unit = text?.match(/[a-z]+$/i)?.[0].toLowerCase() ?? \"deg\";\n\treturn channel(text, 1) * (HUE_UNITS[unit] ?? 1);\n}\n\nfunction alphaOf(args: readonly string[]): number {\n\treturn args[3] === undefined ? 1 : channel(args[3], 1);\n}\n\nfunction linearToSrgb(value: number): number {\n\treturn value <= 0.0031308\n\t\t? 12.92 * value\n\t\t: 1.055 * value ** (1 / 2.4) - 0.055;\n}\n\n/** Oklab is mixed in, and also a syntax a page can write colours in. */\nexport function oklabToRgba([l, a, b, alpha]: Channels): Rgba {\n\tconst lms = [\n\t\t(l + 0.3963377774 * a + 0.2158037573 * b) ** 3,\n\t\t(l - 0.1055613458 * a - 0.0638541728 * b) ** 3,\n\t\t(l - 0.0894841775 * a - 1.291485548 * b) ** 3,\n\t] as const;\n\tconst [x, y, z] = lms;\n\treturn {\n\t\tr: linearToSrgb(4.0767416621 * x - 3.3077115913 * y + 0.2309699292 * z),\n\t\tg: linearToSrgb(-1.2684380046 * x + 2.6097574011 * y - 0.3413193965 * z),\n\t\tb: linearToSrgb(-0.0041960863 * x - 0.7034186147 * y + 1.707614701 * z),\n\t\talpha,\n\t};\n}\n\nfunction hslToRgba(h: number, s: number, l: number, alpha: number): Rgba {\n\tconst amount = s * Math.min(l, 1 - l);\n\tfunction f(n: number) {\n\t\tconst k = (n + h / 30) % 12;\n\t\treturn l - amount * Math.max(-1, Math.min(k - 3, 9 - k, 1));\n\t}\n\treturn { r: f(0), g: f(8), b: f(4), alpha };\n}\n\nconst PARSERS: Readonly<Record<string, Parser>> = {\n\trgb: (args) => ({\n\t\tr: channel(args[0], 255) / 255,\n\t\tg: channel(args[1], 255) / 255,\n\t\tb: channel(args[2], 255) / 255,\n\t\talpha: alphaOf(args),\n\t}),\n\thsl: (args) =>\n\t\thslToRgba(\n\t\t\t((hue(args[0]) % 360) + 360) % 360,\n\t\t\tchannel(args[1], 100) / 100,\n\t\t\tchannel(args[2], 100) / 100,\n\t\t\talphaOf(args),\n\t\t),\n\toklab: (args) =>\n\t\toklabToRgba([\n\t\t\tchannel(args[0], 1),\n\t\t\tchannel(args[1], 0.4),\n\t\t\tchannel(args[2], 0.4),\n\t\t\talphaOf(args),\n\t\t]),\n\toklch: (args) => {\n\t\tconst chroma = channel(args[1], 0.4);\n\t\tconst angle = (hue(args[2]) * Math.PI) / 180;\n\t\treturn oklabToRgba([\n\t\t\tchannel(args[0], 1),\n\t\t\tchroma * Math.cos(angle),\n\t\t\tchroma * Math.sin(angle),\n\t\t\talphaOf(args),\n\t\t]);\n\t},\n};\n\nfunction parseHex(hex: string): Rgba | undefined {\n\tconst digits =\n\t\thex.length <= 4 ? [...hex].map((digit) => digit + digit).join(\"\") : hex;\n\tif (!/^[0-9a-f]{6}(?:[0-9a-f]{2})?$/i.test(digits)) {\n\t\treturn undefined;\n\t}\n\tfunction byte(index: number) {\n\t\treturn Number.parseInt(digits.slice(index, index + 2), 16) / 255;\n\t}\n\treturn {\n\t\tr: byte(0),\n\t\tg: byte(2),\n\t\tb: byte(4),\n\t\talpha: digits.length === 8 ? byte(6) : 1,\n\t};\n}\n\n/** A colour written out in full; `undefined` for what only the page can resolve (`var()`, names). */\nexport function parseColor(text: string): Rgba | undefined {\n\tconst trimmed = text.trim();\n\tif (trimmed.toLowerCase() === \"transparent\") {\n\t\treturn { r: 0, g: 0, b: 0, alpha: 0 };\n\t}\n\tif (trimmed.startsWith(\"#\")) {\n\t\treturn parseHex(trimmed.slice(1));\n\t}\n\tconst match = FUNCTION.exec(trimmed);\n\tif (match === null) {\n\t\treturn undefined;\n\t}\n\tconst name = (match[1] as string).toLowerCase().replace(/a$/, \"\");\n\tconst args = (match[2] as string).split(/[\\s,/]+/).filter(Boolean);\n\treturn PARSERS[name]?.(args);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/** A colour in sRGB, every channel 0\u20131. */\nexport interface Rgba {\n\treadonly r: number;\n\treadonly g: number;\n\treadonly b: number;\n\treadonly alpha: number;\n}\n\ntype Channels = readonly [number, number, number, number];\ntype Parser = (args: readonly string[]) => Rgba | undefined;\n\nconst FUNCTION = /^(rgba?|hsla?|oklch|oklab)\\(\\s*([^()]*)\\)$/i;\nconst HUE_UNITS: Readonly<Record<string, number>> = {\n\tdeg: 1,\n\tgrad: 0.9,\n\trad: 180 / Math.PI,\n\tturn: 360,\n};\n\n/** A number, a percentage of `percentScale`, or `none`. */\nfunction channel(text: string | undefined, percentScale: number): number {\n\tif (text === undefined || text === \"none\") {\n\t\treturn 0;\n\t}\n\treturn text.endsWith(\"%\")\n\t\t? (Number.parseFloat(text) / 100) * percentScale\n\t\t: Number.parseFloat(text);\n}\n\nfunction hue(text: string | undefined): number {\n\tconst unit = text?.match(/[a-z]+$/i)?.[0].toLowerCase() ?? \"deg\";\n\treturn channel(text, 1) * (HUE_UNITS[unit] ?? 1);\n}\n\nfunction alphaOf(args: readonly string[]): number {\n\treturn args[3] === undefined ? 1 : channel(args[3], 1);\n}\n\nfunction linearToSrgb(value: number): number {\n\treturn value <= 0.0031308\n\t\t? 12.92 * value\n\t\t: 1.055 * value ** (1 / 2.4) - 0.055;\n}\n\n/** Oklab is mixed in, and also a syntax a page can write colours in. */\nexport function oklabToRgba([l, a, b, alpha]: Channels): Rgba {\n\tconst lms = [\n\t\t(l + 0.3963377774 * a + 0.2158037573 * b) ** 3,\n\t\t(l - 0.1055613458 * a - 0.0638541728 * b) ** 3,\n\t\t(l - 0.0894841775 * a - 1.291485548 * b) ** 3,\n\t] as const;\n\tconst [x, y, z] = lms;\n\treturn {\n\t\tr: linearToSrgb(4.0767416621 * x - 3.3077115913 * y + 0.2309699292 * z),\n\t\tg: linearToSrgb(-1.2684380046 * x + 2.6097574011 * y - 0.3413193965 * z),\n\t\tb: linearToSrgb(-0.0041960863 * x - 0.7034186147 * y + 1.707614701 * z),\n\t\talpha,\n\t};\n}\n\nfunction hslToRgba(h: number, s: number, l: number, alpha: number): Rgba {\n\tconst amount = s * Math.min(l, 1 - l);\n\tfunction f(n: number) {\n\t\tconst k = (n + h / 30) % 12;\n\t\treturn l - amount * Math.max(-1, Math.min(k - 3, 9 - k, 1));\n\t}\n\treturn { r: f(0), g: f(8), b: f(4), alpha };\n}\n\nconst PARSERS: Readonly<Record<string, Parser>> = {\n\trgb: (args) => ({\n\t\tr: channel(args[0], 255) / 255,\n\t\tg: channel(args[1], 255) / 255,\n\t\tb: channel(args[2], 255) / 255,\n\t\talpha: alphaOf(args),\n\t}),\n\thsl: (args) =>\n\t\thslToRgba(\n\t\t\t((hue(args[0]) % 360) + 360) % 360,\n\t\t\tchannel(args[1], 100) / 100,\n\t\t\tchannel(args[2], 100) / 100,\n\t\t\talphaOf(args),\n\t\t),\n\toklab: (args) =>\n\t\toklabToRgba([\n\t\t\tchannel(args[0], 1),\n\t\t\tchannel(args[1], 0.4),\n\t\t\tchannel(args[2], 0.4),\n\t\t\talphaOf(args),\n\t\t]),\n\toklch: (args) => {\n\t\tconst chroma = channel(args[1], 0.4);\n\t\tconst angle = (hue(args[2]) * Math.PI) / 180;\n\t\treturn oklabToRgba([\n\t\t\tchannel(args[0], 1),\n\t\t\tchroma * Math.cos(angle),\n\t\t\tchroma * Math.sin(angle),\n\t\t\talphaOf(args),\n\t\t]);\n\t},\n};\n\nfunction parseHex(hex: string): Rgba | undefined {\n\tconst digits =\n\t\thex.length <= 4 ? [...hex].map((digit) => digit + digit).join(\"\") : hex;\n\tif (!/^[0-9a-f]{6}(?:[0-9a-f]{2})?$/i.test(digits)) {\n\t\treturn undefined;\n\t}\n\tfunction byte(index: number) {\n\t\treturn Number.parseInt(digits.slice(index, index + 2), 16) / 255;\n\t}\n\treturn {\n\t\tr: byte(0),\n\t\tg: byte(2),\n\t\tb: byte(4),\n\t\talpha: digits.length === 8 ? byte(6) : 1,\n\t};\n}\n\nconst names = new Map<string, Rgba | undefined>();\nlet probe: CanvasRenderingContext2D | null | undefined;\nconst SENTINEL = \"#010203\";\n\n/**\n * A colour name (`red`, `rebeccapurple`) as the browser spells it out: a\n * canvas drops what isn't a colour (`auto`, `none`) and hands back hex for\n * what is. The 148 names would weigh more than this; without a browser a\n * name stays unknown and the page mixes it.\n */\nfunction parseName(name: string): Rgba | undefined {\n\tif (name === \"currentcolor\" || !/^[a-z]+$/.test(name)) return undefined;\n\tif (!names.has(name)) {\n\t\tprobe ??=\n\t\t\ttypeof document === \"undefined\"\n\t\t\t\t? null\n\t\t\t\t: document.createElement(\"canvas\").getContext(\"2d\");\n\t\tlet resolved: Rgba | undefined;\n\t\tif (probe) {\n\t\t\tprobe.fillStyle = SENTINEL;\n\t\t\tprobe.fillStyle = name;\n\t\t\tconst spelled = String(probe.fillStyle);\n\t\t\tresolved = spelled === SENTINEL ? undefined : parseColor(spelled);\n\t\t}\n\t\tnames.set(name, resolved);\n\t}\n\treturn names.get(name);\n}\n\n/** A colour written out in full, or a name the browser knows; `undefined` for what only the page can resolve (`var()`, `currentColor`). */\nexport function parseColor(text: string): Rgba | undefined {\n\tconst trimmed = text.trim();\n\tif (trimmed.toLowerCase() === \"transparent\") {\n\t\treturn { r: 0, g: 0, b: 0, alpha: 0 };\n\t}\n\tif (trimmed.startsWith(\"#\")) {\n\t\treturn parseHex(trimmed.slice(1));\n\t}\n\tconst match = FUNCTION.exec(trimmed);\n\tif (match === null) {\n\t\treturn parseName(trimmed.toLowerCase());\n\t}\n\tconst name = (match[1] as string).toLowerCase().replace(/a$/, \"\");\n\tconst args = (match[2] as string).split(/[\\s,/]+/).filter(Boolean);\n\treturn PARSERS[name]?.(args);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mix.d.ts","sourceRoot":"","sources":["../../src/interpolate/mix.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAqD/C;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAQ/C"}
1
+ {"version":3,"file":"mix.d.ts","sourceRoot":"","sources":["../../src/interpolate/mix.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAsD/C;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAQ/C"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/interpolate/mix.ts"],
4
- "sourcesContent": ["import { mixArray } from \"#/interpolate/array.ts\";\nimport { isColorLike, mixColor, parseColor } from \"#/interpolate/color.ts\";\nimport { mixComplex } from \"#/interpolate/complex.ts\";\nimport { mixNumber } from \"#/interpolate/number.ts\";\nimport { mixObject } from \"#/interpolate/object.ts\";\nimport { mixPath } from \"#/interpolate/path/morph.ts\";\n\nexport type Mixer<T> = (progress: number) => T;\n\ntype Strategy = (from: unknown, to: unknown) => Mixer<unknown> | undefined;\n\nconst PATH = /^\\s*[Mm]/;\n\nfunction bothStrings(from: unknown, to: unknown): [string, string] | undefined {\n\treturn typeof from === \"string\" && typeof to === \"string\"\n\t\t? [from, to]\n\t\t: undefined;\n}\n\n/**\n * A colour pair needs one side written out, or both to be variables: two bare\n * words are as likely `auto` and `none` as `red` and `blue`.\n */\nfunction colors(from: string, to: string): boolean {\n\tconst resolved =\n\t\tparseColor(from) !== undefined || parseColor(to) !== undefined;\n\tconst variables = /^\\s*var\\(/i.test(from) && /^\\s*var\\(/i.test(to);\n\treturn isColorLike(from) && isColorLike(to) && (resolved || variables);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Tried in order; the first that knows the pair mixes it. */\nconst STRATEGIES: readonly Strategy[] = [\n\t(from, to) =>\n\t\ttypeof from === \"number\" && typeof to === \"number\"\n\t\t\t? (progress) => mixNumber(from, to, progress)\n\t\t\t: undefined,\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && PATH.test(pair[0]) && PATH.test(pair[1])\n\t\t\t? mixPath(...pair)\n\t\t\t: undefined;\n\t},\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && colors(...pair) ? mixColor(...pair) : undefined;\n\t},\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && mixComplex(...pair);\n\t},\n\t(from, to) =>\n\t\tArray.isArray(from) && Array.isArray(to) ? mixArray(from, to) : undefined,\n\t(from, to) =>\n\t\tisRecord(from) && isRecord(to) ? mixObject(from, to) : undefined,\n];\n\n/**\n * The value `progress` of the way from `from` to `to`, whatever they are:\n * numbers, colours (in oklab), paths (morphed), strings of numbers, arrays,\n * objects. What has no midpoint switches halfway.\n */\nexport function mix<T>(from: T, to: T): Mixer<T> {\n\tfor (const strategy of STRATEGIES) {\n\t\tconst mixer = strategy(from, to);\n\t\tif (mixer !== undefined) {\n\t\t\treturn mixer as Mixer<T>;\n\t\t}\n\t}\n\treturn (progress) => (progress < 0.5 ? from : to);\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,aAAa,UAAU,kBAAkB;AAClD,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAMxB,MAAM,OAAO;AAEb,SAAS,YAAY,MAAe,IAA2C;AAC9E,SAAO,OAAO,SAAS,YAAY,OAAO,OAAO,WAC9C,CAAC,MAAM,EAAE,IACT;AACJ;AAMA,SAAS,OAAO,MAAc,IAAqB;AAClD,QAAM,WACL,WAAW,IAAI,MAAM,UAAa,WAAW,EAAE,MAAM;AACtD,QAAM,YAAY,aAAa,KAAK,IAAI,KAAK,aAAa,KAAK,EAAE;AACjE,SAAO,YAAY,IAAI,KAAK,YAAY,EAAE,MAAM,YAAY;AAC7D;AAEA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAGA,MAAM,aAAkC;AAAA,EACvC,CAAC,MAAM,OACN,OAAO,SAAS,YAAY,OAAO,OAAO,WACvC,CAAC,aAAa,UAAU,MAAM,IAAI,QAAQ,IAC1C;AAAA,EACJ,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,KAAK,KAAK,KAAK,CAAC,CAAC,KAAK,KAAK,KAAK,KAAK,CAAC,CAAC,IACnD,QAAQ,GAAG,IAAI,IACf;AAAA,EACJ;AAAA,EACA,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,OAAO,GAAG,IAAI,IAAI,SAAS,GAAG,IAAI,IAAI;AAAA,EACtD;AAAA,EACA,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,WAAW,GAAG,IAAI;AAAA,EAClC;AAAA,EACA,CAAC,MAAM,OACN,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,EAAE,IAAI,SAAS,MAAM,EAAE,IAAI;AAAA,EACjE,CAAC,MAAM,OACN,SAAS,IAAI,KAAK,SAAS,EAAE,IAAI,UAAU,MAAM,EAAE,IAAI;AACzD;AAOO,SAAS,IAAO,MAAS,IAAiB;AAChD,aAAW,YAAY,YAAY;AAClC,UAAM,QAAQ,SAAS,MAAM,EAAE;AAC/B,QAAI,UAAU,QAAW;AACxB,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO,CAAC,aAAc,WAAW,MAAM,OAAO;AAC/C;",
4
+ "sourcesContent": ["import { mixArray } from \"#/interpolate/array.ts\";\nimport { isColorLike, mixColor, parseColor } from \"#/interpolate/color.ts\";\nimport { mixComplex } from \"#/interpolate/complex.ts\";\nimport { mixNumber } from \"#/interpolate/number.ts\";\nimport { mixObject } from \"#/interpolate/object.ts\";\nimport { mixPath } from \"#/interpolate/path/morph.ts\";\n\nexport type Mixer<T> = (progress: number) => T;\n\ntype Strategy = (from: unknown, to: unknown) => Mixer<unknown> | undefined;\n\nconst PATH = /^\\s*[Mm]/;\n\nfunction bothStrings(from: unknown, to: unknown): [string, string] | undefined {\n\treturn typeof from === \"string\" && typeof to === \"string\"\n\t\t? [from, to]\n\t\t: undefined;\n}\n\n/**\n * A colour pair needs one side spelled out (a name counts where a browser can\n * spell it), or both to be variables: two other bare words are as likely\n * `auto` and `none` as colours.\n */\nfunction colors(from: string, to: string): boolean {\n\tconst resolved =\n\t\tparseColor(from) !== undefined || parseColor(to) !== undefined;\n\tconst variables = /^\\s*var\\(/i.test(from) && /^\\s*var\\(/i.test(to);\n\treturn isColorLike(from) && isColorLike(to) && (resolved || variables);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Tried in order; the first that knows the pair mixes it. */\nconst STRATEGIES: readonly Strategy[] = [\n\t(from, to) =>\n\t\ttypeof from === \"number\" && typeof to === \"number\"\n\t\t\t? (progress) => mixNumber(from, to, progress)\n\t\t\t: undefined,\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && PATH.test(pair[0]) && PATH.test(pair[1])\n\t\t\t? mixPath(...pair)\n\t\t\t: undefined;\n\t},\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && colors(...pair) ? mixColor(...pair) : undefined;\n\t},\n\t(from, to) => {\n\t\tconst pair = bothStrings(from, to);\n\t\treturn pair && mixComplex(...pair);\n\t},\n\t(from, to) =>\n\t\tArray.isArray(from) && Array.isArray(to) ? mixArray(from, to) : undefined,\n\t(from, to) =>\n\t\tisRecord(from) && isRecord(to) ? mixObject(from, to) : undefined,\n];\n\n/**\n * The value `progress` of the way from `from` to `to`, whatever they are:\n * numbers, colours (in oklab), paths (morphed), strings of numbers, arrays,\n * objects. What has no midpoint switches halfway.\n */\nexport function mix<T>(from: T, to: T): Mixer<T> {\n\tfor (const strategy of STRATEGIES) {\n\t\tconst mixer = strategy(from, to);\n\t\tif (mixer !== undefined) {\n\t\t\treturn mixer as Mixer<T>;\n\t\t}\n\t}\n\treturn (progress) => (progress < 0.5 ? from : to);\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,aAAa,UAAU,kBAAkB;AAClD,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAMxB,MAAM,OAAO;AAEb,SAAS,YAAY,MAAe,IAA2C;AAC9E,SAAO,OAAO,SAAS,YAAY,OAAO,OAAO,WAC9C,CAAC,MAAM,EAAE,IACT;AACJ;AAOA,SAAS,OAAO,MAAc,IAAqB;AAClD,QAAM,WACL,WAAW,IAAI,MAAM,UAAa,WAAW,EAAE,MAAM;AACtD,QAAM,YAAY,aAAa,KAAK,IAAI,KAAK,aAAa,KAAK,EAAE;AACjE,SAAO,YAAY,IAAI,KAAK,YAAY,EAAE,MAAM,YAAY;AAC7D;AAEA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAGA,MAAM,aAAkC;AAAA,EACvC,CAAC,MAAM,OACN,OAAO,SAAS,YAAY,OAAO,OAAO,WACvC,CAAC,aAAa,UAAU,MAAM,IAAI,QAAQ,IAC1C;AAAA,EACJ,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,KAAK,KAAK,KAAK,CAAC,CAAC,KAAK,KAAK,KAAK,KAAK,CAAC,CAAC,IACnD,QAAQ,GAAG,IAAI,IACf;AAAA,EACJ;AAAA,EACA,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,OAAO,GAAG,IAAI,IAAI,SAAS,GAAG,IAAI,IAAI;AAAA,EACtD;AAAA,EACA,CAAC,MAAM,OAAO;AACb,UAAM,OAAO,YAAY,MAAM,EAAE;AACjC,WAAO,QAAQ,WAAW,GAAG,IAAI;AAAA,EAClC;AAAA,EACA,CAAC,MAAM,OACN,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,EAAE,IAAI,SAAS,MAAM,EAAE,IAAI;AAAA,EACjE,CAAC,MAAM,OACN,SAAS,IAAI,KAAK,SAAS,EAAE,IAAI,UAAU,MAAM,EAAE,IAAI;AACzD;AAOO,SAAS,IAAO,MAAS,IAAiB;AAChD,aAAW,YAAY,YAAY;AAClC,UAAM,QAAQ,SAAS,MAAM,EAAE;AAC/B,QAAI,UAAU,QAAW;AACxB,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO,CAAC,aAAc,WAAW,MAAM,OAAO;AAC/C;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../../src/sequence/sequence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,SAAS,EACT,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,GAAG,GAAG,MAAM,CAAC;AAE7E,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD,QAAQ,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GAChB,SAAS,CAAC,aAAa,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC,GAEvD,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC,GAC5D,MAAM,GACN;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,gBAAgB,KACrB,iBAAiB,CAAC;AAEvB,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,CAAC;AAepE;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,GAC5B,iBAAiB,CAmEnB"}
1
+ {"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../../src/sequence/sequence.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,SAAS,EACT,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,GAAG,GAAG,MAAM,CAAC;AAE7E,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD,QAAQ,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GAChB,SAAS,CAAC,aAAa,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC,GAEvD,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC,GAC5D,MAAM,GACN;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,gBAAgB,KACrB,iBAAiB,CAAC;AAEvB,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,CAAC;AA6CpE;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,GAC5B,iBAAiB,CAkFnB"}
@@ -1,4 +1,5 @@
1
1
  import { groupControls, thenable } from "../animate/controls.js";
2
+ import { planGenerator, totalDuration } from "../animate/plan.js";
2
3
  import { now } from "../frame/frame.js";
3
4
  function startOf(at, cursor, previousStart, labels) {
4
5
  if (at === void 0) return cursor;
@@ -7,12 +8,28 @@ function startOf(at, cursor, previousStart, labels) {
7
8
  if (/^[+-]/.test(at)) return Math.max(0, cursor + Number(at));
8
9
  return labels.get(at) ?? cursor;
9
10
  }
11
+ function plannedDuration(keyframes, options) {
12
+ const channels = typeof keyframes === "object" && keyframes !== null && !Array.isArray(keyframes) ? Object.values(keyframes) : [keyframes];
13
+ const { delay } = options;
14
+ return (typeof delay === "number" ? delay : 0) + Math.max(
15
+ 0,
16
+ ...channels.map((channel) => {
17
+ const frames = Array.isArray(channel) ? channel : [0, channel];
18
+ const { generator } = planGenerator(
19
+ frames.map((_frame, index) => index),
20
+ options
21
+ );
22
+ return totalDuration(generator.duration, options);
23
+ })
24
+ );
25
+ }
10
26
  function animateSequence(segments, options, animateOne, resolveTargets) {
11
27
  const begun = now();
12
28
  const labels = /* @__PURE__ */ new Map();
13
29
  const busy = /* @__PURE__ */ new Map();
14
30
  const children = [];
15
31
  const waits = [];
32
+ let end = 0;
16
33
  let cursor = 0;
17
34
  let previousStart = 0;
18
35
  for (const segment of segments) {
@@ -31,28 +48,35 @@ function animateSequence(segments, options, animateOne, resolveTargets) {
31
48
  continue;
32
49
  }
33
50
  const [target, keyframes, own = {}] = segment;
34
- const start = startOf(own.at, cursor, previousStart, labels);
35
- const { delay } = own;
51
+ const placed = startOf(own.at, cursor, previousStart, labels);
36
52
  const keys = typeof keyframes === "object" && keyframes !== null && !Array.isArray(keyframes) ? Object.keys(keyframes) : ["value"];
37
53
  const targets = resolveTargets(target);
38
54
  const before = targets.flatMap(
39
55
  (one) => keys.flatMap((key) => busy.get(one)?.get(key) ?? [])
40
56
  );
57
+ const start = Math.max(placed, ...before.map((earlier) => earlier.end));
58
+ const { delay } = own;
41
59
  const launch = () => animateOne(target, keyframes, optionsAt2((now() - begun) / 1e3));
42
60
  let finished2;
61
+ let ends;
43
62
  if (before.length === 0) {
44
63
  const controls = launch();
45
64
  children.push(controls);
46
65
  finished2 = controls.finished;
47
- cursor = Math.max(start, controls.duration);
66
+ ends = Math.max(start, controls.duration);
48
67
  } else {
49
- finished2 = Promise.all(before).then(() => launch().finished);
68
+ finished2 = Promise.all(before.map((earlier) => earlier.finished)).then(
69
+ () => launch().finished
70
+ );
50
71
  waits.push(finished2);
72
+ ends = start + plannedDuration(keyframes, { ...options, ...own });
51
73
  }
74
+ cursor = ends;
75
+ end = Math.max(end, ends);
52
76
  for (const one of targets) {
53
77
  const byKey = busy.get(one) ?? /* @__PURE__ */ new Map();
54
78
  busy.set(one, byKey);
55
- for (const key of keys) byKey.set(key, finished2);
79
+ for (const key of keys) byKey.set(key, { finished: finished2, end: ends });
56
80
  }
57
81
  previousStart = start;
58
82
  }
@@ -60,6 +84,9 @@ function animateSequence(segments, options, animateOne, resolveTargets) {
60
84
  const finished = Promise.all([group.finished, ...waits]).then(
61
85
  () => void 0
62
86
  );
87
+ Object.defineProperty(group, "duration", {
88
+ value: Math.max(end, group.duration)
89
+ });
63
90
  return Object.assign(group, thenable(finished));
64
91
  }
65
92
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/sequence/sequence.ts"],
4
- "sourcesContent": ["import { groupControls, thenable } from \"#/animate/controls.ts\";\nimport type {\n\tAnimationControls,\n\tAnimationOptions,\n\tDOMKeyframes,\n\tElementTarget,\n\tKeyframes,\n} from \"#/animate/types.ts\";\nimport { now } from \"#/frame/frame.ts\";\nimport type { MotionValue } from \"#/value/motion-value.ts\";\n\n/**\n * When a segment starts: seconds from the start of the sequence, `\"+0.1\"` or\n * `\"-0.2\"` from the end of the segment before, `\"<\"` with the segment before,\n * or the name of a label.\n */\nexport type SequenceAt = number | `+${number}` | `-${number}` | \"<\" | string;\n\nexport interface SegmentOptions extends AnimationOptions {\n\treadonly at?: SequenceAt;\n}\n\nexport type Segment =\n\t| readonly [ElementTarget, DOMKeyframes, SegmentOptions?]\n\t// biome-ignore lint/suspicious/noExplicitAny: a sequence mixes values of any type.\n\t| readonly [MotionValue<any>, Keyframes<any>, SegmentOptions?]\n\t| string\n\t| { readonly name: string; readonly at?: SequenceAt };\n\nexport type AnimateOne = (\n\ttarget: unknown,\n\tkeyframes: unknown,\n\toptions: AnimationOptions,\n) => AnimationControls;\n\nexport type ResolveTargets = (target: unknown) => readonly object[];\n\nfunction startOf(\n\tat: SequenceAt | undefined,\n\tcursor: number,\n\tpreviousStart: number,\n\tlabels: ReadonlyMap<string, number>,\n): number {\n\tif (at === undefined) return cursor;\n\tif (typeof at === \"number\") return at;\n\tif (at === \"<\") return previousStart;\n\tif (/^[+-]/.test(at)) return Math.max(0, cursor + Number(at));\n\treturn labels.get(at) ?? cursor;\n}\n\n/**\n * Launches every segment at once, each delayed to its place on the\n * timeline, so the whole sequence shares one clock and can be scrubbed. A\n * segment that animates a property an earlier segment already animates\n * waits for that one to finish, then starts from where it left off.\n */\nexport function animateSequence(\n\tsegments: readonly Segment[],\n\toptions: AnimationOptions,\n\tanimateOne: AnimateOne,\n\tresolveTargets: ResolveTargets,\n): AnimationControls {\n\tconst begun = now();\n\tconst labels = new Map<string, number>();\n\tconst busy = new Map<object, Map<string, Promise<void>>>();\n\tconst children: AnimationControls[] = [];\n\tconst waits: Promise<void>[] = [];\n\tlet cursor = 0;\n\tlet previousStart = 0;\n\tfor (const segment of segments) {\n\t\tif (typeof segment === \"string\" || \"name\" in segment) {\n\t\t\tconst label = typeof segment === \"string\" ? { name: segment } : segment;\n\t\t\tlabels.set(label.name, startOf(label.at, cursor, previousStart, labels));\n\t\t\tcontinue;\n\t\t}\n\t\tconst [target, keyframes, own = {}] = segment as readonly [\n\t\t\tunknown,\n\t\t\tunknown,\n\t\t\tSegmentOptions?,\n\t\t];\n\t\tconst start = startOf(own.at, cursor, previousStart, labels);\n\t\tconst { delay } = own;\n\t\t/** Launched late, a segment only waits for what is left of its delay. */\n\t\tfunction optionsAt(elapsed: number): AnimationOptions {\n\t\t\tconst offset = start - elapsed;\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\t...own,\n\t\t\t\tdelay:\n\t\t\t\t\ttypeof delay === \"function\"\n\t\t\t\t\t\t? (index, total) => Math.max(0, offset + delay(index, total))\n\t\t\t\t\t\t: Math.max(0, offset + (delay ?? 0)),\n\t\t\t};\n\t\t}\n\t\tconst keys =\n\t\t\ttypeof keyframes === \"object\" &&\n\t\t\tkeyframes !== null &&\n\t\t\t!Array.isArray(keyframes)\n\t\t\t\t? Object.keys(keyframes)\n\t\t\t\t: [\"value\"];\n\t\tconst targets = resolveTargets(target);\n\t\tconst before = targets.flatMap((one) =>\n\t\t\tkeys.flatMap((key) => busy.get(one)?.get(key) ?? []),\n\t\t);\n\t\tconst launch = () =>\n\t\t\tanimateOne(target, keyframes, optionsAt((now() - begun) / 1000));\n\t\tlet finished: Promise<void>;\n\t\tif (before.length === 0) {\n\t\t\tconst controls = launch();\n\t\t\tchildren.push(controls);\n\t\t\tfinished = controls.finished;\n\t\t\tcursor = Math.max(start, controls.duration);\n\t\t} else {\n\t\t\tfinished = Promise.all(before).then(() => launch().finished);\n\t\t\twaits.push(finished);\n\t\t}\n\t\tfor (const one of targets) {\n\t\t\tconst byKey = busy.get(one) ?? new Map();\n\t\t\tbusy.set(one, byKey);\n\t\t\tfor (const key of keys) byKey.set(key, finished);\n\t\t}\n\t\tpreviousStart = start;\n\t}\n\tconst group = groupControls(children);\n\tconst finished = Promise.all([group.finished, ...waits]).then(\n\t\t() => undefined,\n\t);\n\treturn Object.assign(group, thenable(finished));\n}\n"],
5
- "mappings": "AAAA,SAAS,eAAe,gBAAgB;AAQxC,SAAS,WAAW;AA6BpB,SAAS,QACR,IACA,QACA,eACA,QACS;AACT,MAAI,OAAO,OAAW,QAAO;AAC7B,MAAI,OAAO,OAAO,SAAU,QAAO;AACnC,MAAI,OAAO,IAAK,QAAO;AACvB,MAAI,QAAQ,KAAK,EAAE,EAAG,QAAO,KAAK,IAAI,GAAG,SAAS,OAAO,EAAE,CAAC;AAC5D,SAAO,OAAO,IAAI,EAAE,KAAK;AAC1B;AAQO,SAAS,gBACf,UACA,SACA,YACA,gBACoB;AACpB,QAAM,QAAQ,IAAI;AAClB,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,OAAO,oBAAI,IAAwC;AACzD,QAAM,WAAgC,CAAC;AACvC,QAAM,QAAyB,CAAC;AAChC,MAAI,SAAS;AACb,MAAI,gBAAgB;AACpB,aAAW,WAAW,UAAU;AAc/B,QAASA,aAAT,SAAmB,SAAmC;AACrD,YAAM,SAAS,QAAQ;AACvB,aAAO;AAAA,QACN,GAAG;AAAA,QACH,GAAG;AAAA,QACH,OACC,OAAO,UAAU,aACd,CAAC,OAAO,UAAU,KAAK,IAAI,GAAG,SAAS,MAAM,OAAO,KAAK,CAAC,IAC1D,KAAK,IAAI,GAAG,UAAU,SAAS,EAAE;AAAA,MACtC;AAAA,IACD;AAVS,oBAAAA;AAbT,QAAI,OAAO,YAAY,YAAY,UAAU,SAAS;AACrD,YAAM,QAAQ,OAAO,YAAY,WAAW,EAAE,MAAM,QAAQ,IAAI;AAChE,aAAO,IAAI,MAAM,MAAM,QAAQ,MAAM,IAAI,QAAQ,eAAe,MAAM,CAAC;AACvE;AAAA,IACD;AACA,UAAM,CAAC,QAAQ,WAAW,MAAM,CAAC,CAAC,IAAI;AAKtC,UAAM,QAAQ,QAAQ,IAAI,IAAI,QAAQ,eAAe,MAAM;AAC3D,UAAM,EAAE,MAAM,IAAI;AAalB,UAAM,OACL,OAAO,cAAc,YACrB,cAAc,QACd,CAAC,MAAM,QAAQ,SAAS,IACrB,OAAO,KAAK,SAAS,IACrB,CAAC,OAAO;AACZ,UAAM,UAAU,eAAe,MAAM;AACrC,UAAM,SAAS,QAAQ;AAAA,MAAQ,CAAC,QAC/B,KAAK,QAAQ,CAAC,QAAQ,KAAK,IAAI,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;AAAA,IACpD;AACA,UAAM,SAAS,MACd,WAAW,QAAQ,WAAWA,YAAW,IAAI,IAAI,SAAS,GAAI,CAAC;AAChE,QAAIC;AACJ,QAAI,OAAO,WAAW,GAAG;AACxB,YAAM,WAAW,OAAO;AACxB,eAAS,KAAK,QAAQ;AACtB,MAAAA,YAAW,SAAS;AACpB,eAAS,KAAK,IAAI,OAAO,SAAS,QAAQ;AAAA,IAC3C,OAAO;AACN,MAAAA,YAAW,QAAQ,IAAI,MAAM,EAAE,KAAK,MAAM,OAAO,EAAE,QAAQ;AAC3D,YAAM,KAAKA,SAAQ;AAAA,IACpB;AACA,eAAW,OAAO,SAAS;AAC1B,YAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,oBAAI,IAAI;AACvC,WAAK,IAAI,KAAK,KAAK;AACnB,iBAAW,OAAO,KAAM,OAAM,IAAI,KAAKA,SAAQ;AAAA,IAChD;AACA,oBAAgB;AAAA,EACjB;AACA,QAAM,QAAQ,cAAc,QAAQ;AACpC,QAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE;AAAA,IACxD,MAAM;AAAA,EACP;AACA,SAAO,OAAO,OAAO,OAAO,SAAS,QAAQ,CAAC;AAC/C;",
4
+ "sourcesContent": ["import { groupControls, thenable } from \"#/animate/controls.ts\";\nimport { planGenerator, totalDuration } from \"#/animate/plan.ts\";\nimport type {\n\tAnimationControls,\n\tAnimationOptions,\n\tDOMKeyframes,\n\tElementTarget,\n\tKeyframes,\n} from \"#/animate/types.ts\";\nimport { now } from \"#/frame/frame.ts\";\nimport type { MotionValue } from \"#/value/motion-value.ts\";\n\n/**\n * When a segment starts: seconds from the start of the sequence, `\"+0.1\"` or\n * `\"-0.2\"` from the end of the segment before, `\"<\"` with the segment before,\n * or the name of a label.\n */\nexport type SequenceAt = number | `+${number}` | `-${number}` | \"<\" | string;\n\nexport interface SegmentOptions extends AnimationOptions {\n\treadonly at?: SequenceAt;\n}\n\nexport type Segment =\n\t| readonly [ElementTarget, DOMKeyframes, SegmentOptions?]\n\t// biome-ignore lint/suspicious/noExplicitAny: a sequence mixes values of any type.\n\t| readonly [MotionValue<any>, Keyframes<any>, SegmentOptions?]\n\t| string\n\t| { readonly name: string; readonly at?: SequenceAt };\n\nexport type AnimateOne = (\n\ttarget: unknown,\n\tkeyframes: unknown,\n\toptions: AnimationOptions,\n) => AnimationControls;\n\nexport type ResolveTargets = (target: unknown) => readonly object[];\n\nfunction startOf(\n\tat: SequenceAt | undefined,\n\tcursor: number,\n\tpreviousStart: number,\n\tlabels: ReadonlyMap<string, number>,\n): number {\n\tif (at === undefined) return cursor;\n\tif (typeof at === \"number\") return at;\n\tif (at === \"<\") return previousStart;\n\tif (/^[+-]/.test(at)) return Math.max(0, cursor + Number(at));\n\treturn labels.get(at) ?? cursor;\n}\n\n/**\n * How long a segment that waits for an earlier one will run once launched.\n * Its first keyframe is only read then, but a tween's length doesn't depend\n * on it, and neither does a default spring's (its rest thresholds scale with\n * the distance).\n */\nfunction plannedDuration(keyframes: unknown, options: AnimationOptions) {\n\tconst channels =\n\t\ttypeof keyframes === \"object\" &&\n\t\tkeyframes !== null &&\n\t\t!Array.isArray(keyframes)\n\t\t\t? Object.values(keyframes)\n\t\t\t: [keyframes];\n\tconst { delay } = options;\n\treturn (\n\t\t(typeof delay === \"number\" ? delay : 0) +\n\t\tMath.max(\n\t\t\t0,\n\t\t\t...channels.map((channel) => {\n\t\t\t\tconst frames = Array.isArray(channel) ? channel : [0, channel];\n\t\t\t\tconst { generator } = planGenerator(\n\t\t\t\t\tframes.map((_frame, index) => index),\n\t\t\t\t\toptions,\n\t\t\t\t);\n\t\t\t\treturn totalDuration(generator.duration, options);\n\t\t\t}),\n\t\t)\n\t);\n}\n\n/**\n * Launches every segment at once, each delayed to its place on the\n * timeline, so the whole sequence shares one clock and can be scrubbed. A\n * segment that animates a property an earlier segment already animates\n * waits for that one to finish, then starts from where it left off.\n */\nexport function animateSequence(\n\tsegments: readonly Segment[],\n\toptions: AnimationOptions,\n\tanimateOne: AnimateOne,\n\tresolveTargets: ResolveTargets,\n): AnimationControls {\n\tconst begun = now();\n\tconst labels = new Map<string, number>();\n\tconst busy = new Map<\n\t\tobject,\n\t\tMap<string, { readonly finished: Promise<void>; readonly end: number }>\n\t>();\n\tconst children: AnimationControls[] = [];\n\tconst waits: Promise<void>[] = [];\n\tlet end = 0;\n\tlet cursor = 0;\n\tlet previousStart = 0;\n\tfor (const segment of segments) {\n\t\tif (typeof segment === \"string\" || \"name\" in segment) {\n\t\t\tconst label = typeof segment === \"string\" ? { name: segment } : segment;\n\t\t\tlabels.set(label.name, startOf(label.at, cursor, previousStart, labels));\n\t\t\tcontinue;\n\t\t}\n\t\tconst [target, keyframes, own = {}] = segment as readonly [\n\t\t\tunknown,\n\t\t\tunknown,\n\t\t\tSegmentOptions?,\n\t\t];\n\t\tconst placed = startOf(own.at, cursor, previousStart, labels);\n\t\tconst keys =\n\t\t\ttypeof keyframes === \"object\" &&\n\t\t\tkeyframes !== null &&\n\t\t\t!Array.isArray(keyframes)\n\t\t\t\t? Object.keys(keyframes)\n\t\t\t\t: [\"value\"];\n\t\tconst targets = resolveTargets(target);\n\t\tconst before = targets.flatMap((one) =>\n\t\t\tkeys.flatMap((key) => busy.get(one)?.get(key) ?? []),\n\t\t);\n\t\tconst start = Math.max(placed, ...before.map((earlier) => earlier.end));\n\t\tconst { delay } = own;\n\t\t/** Launched late, a segment only waits for what is left of its delay. */\n\t\tfunction optionsAt(elapsed: number): AnimationOptions {\n\t\t\tconst offset = start - elapsed;\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\t...own,\n\t\t\t\tdelay:\n\t\t\t\t\ttypeof delay === \"function\"\n\t\t\t\t\t\t? (index, total) => Math.max(0, offset + delay(index, total))\n\t\t\t\t\t\t: Math.max(0, offset + (delay ?? 0)),\n\t\t\t};\n\t\t}\n\t\tconst launch = () =>\n\t\t\tanimateOne(target, keyframes, optionsAt((now() - begun) / 1000));\n\t\tlet finished: Promise<void>;\n\t\tlet ends: number;\n\t\tif (before.length === 0) {\n\t\t\tconst controls = launch();\n\t\t\tchildren.push(controls);\n\t\t\tfinished = controls.finished;\n\t\t\tends = Math.max(start, controls.duration);\n\t\t} else {\n\t\t\tfinished = Promise.all(before.map((earlier) => earlier.finished)).then(\n\t\t\t\t() => launch().finished,\n\t\t\t);\n\t\t\twaits.push(finished);\n\t\t\tends = start + plannedDuration(keyframes, { ...options, ...own });\n\t\t}\n\t\tcursor = ends;\n\t\tend = Math.max(end, ends);\n\t\tfor (const one of targets) {\n\t\t\tconst byKey = busy.get(one) ?? new Map();\n\t\t\tbusy.set(one, byKey);\n\t\t\tfor (const key of keys) byKey.set(key, { finished, end: ends });\n\t\t}\n\t\tpreviousStart = start;\n\t}\n\tconst group = groupControls(children);\n\tconst finished = Promise.all([group.finished, ...waits]).then(\n\t\t() => undefined,\n\t);\n\t// The segments still waiting count too: they end after the ones launched.\n\tObject.defineProperty(group, \"duration\", {\n\t\tvalue: Math.max(end, group.duration),\n\t});\n\treturn Object.assign(group, thenable(finished));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["optionsAt", "finished"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voila.dev/motion",
3
- "version": "6.0.122",
3
+ "version": "6.0.123",
4
4
  "type": "module",
5
5
  "description": "Springs played by the browser, values on one frame loop, exits React waits for.",
6
6
  "license": "MIT",
@@ -79,17 +79,25 @@ export function jsAnimation<T>(config: JsAnimationConfig<T>): JsAnimation<T> {
79
79
  resolve();
80
80
  }
81
81
 
82
+ /** Played backwards, it ends where it started, the way WAAPI does. */
83
+ function finish() {
84
+ time = speed < 0 ? 0 : duration;
85
+ apply(speed < 0 ? (keyframes[0] as T) : last);
86
+ settle("finished");
87
+ config.onComplete?.();
88
+ }
89
+
82
90
  function tick(data: FrameData) {
83
- time = Math.max(0, time + ((data.timestamp - previous) / 1000) * speed);
91
+ time = Math.min(
92
+ duration,
93
+ Math.max(0, time + ((data.timestamp - previous) / 1000) * speed),
94
+ );
84
95
  previous = data.timestamp;
85
- if (time < duration) {
96
+ if (speed < 0 ? time > 0 : time < duration) {
86
97
  render();
87
98
  return;
88
99
  }
89
- time = duration;
90
- render();
91
- settle("finished");
92
- config.onComplete?.();
100
+ finish();
93
101
  }
94
102
 
95
103
  function play() {
@@ -133,11 +141,7 @@ export function jsAnimation<T>(config: JsAnimationConfig<T>): JsAnimation<T> {
133
141
  settle("finished");
134
142
  },
135
143
  complete() {
136
- if (state === "finished") return;
137
- time = duration;
138
- render();
139
- settle("finished");
140
- config.onComplete?.();
144
+ if (state !== "finished") finish();
141
145
  },
142
146
  attachTimeline: () => false,
143
147
  current() {
@@ -1,6 +1,6 @@
1
1
  import type { KeyframeValue } from "#/animate/types.ts";
2
2
 
3
- type TransformProperty = "translate" | "scale" | "rotate";
3
+ type TransformProperty = "translate" | "scale" | "rotate" | "transform";
4
4
 
5
5
  interface TransformChannel {
6
6
  readonly property: TransformProperty;
@@ -10,6 +10,13 @@ interface TransformChannel {
10
10
  readonly alone: (value: string) => string;
11
11
  }
12
12
 
13
+ /**
14
+ * The transforms with no CSS property of their own (`rotate` takes one axis,
15
+ * and skew has none): they go through `transform`, in this order, after
16
+ * `translate`, `rotate` and `scale`.
17
+ */
18
+ const LISTED = ["rotateX", "rotateY", "skew", "skewX", "skewY"];
19
+
13
20
  /**
14
21
  * The independent transforms. Each animates its CSS property alone with
15
22
  * `composite: "add"`, so `x` and `y` run as two compositor animations that
@@ -38,6 +45,17 @@ export const TRANSFORMS: Readonly<Record<string, TransformChannel>> = {
38
45
  identity: 0,
39
46
  alone: (v) => `${v}`,
40
47
  },
48
+ ...Object.fromEntries(
49
+ LISTED.map((key) => [
50
+ key,
51
+ {
52
+ property: "transform",
53
+ unit: "deg",
54
+ identity: 0,
55
+ alone: (v: string) => `${key}(${v})`,
56
+ },
57
+ ]),
58
+ ),
41
59
  };
42
60
 
43
61
  const UNITLESS = new Set([
@@ -102,6 +120,15 @@ export function writeTransforms(element: Element) {
102
120
  const uniform = Number.parseFloat(at("scale"));
103
121
  style.scale = `${uniform * Number.parseFloat(at("scaleX"))} ${uniform * Number.parseFloat(at("scaleY"))}`;
104
122
  style.rotate = at("rotate");
123
+ // `transform` stays the page's on an element that never animates these.
124
+ if (
125
+ LISTED.some(
126
+ (key) =>
127
+ committed.get(element)?.has(key) || animating.get(element)?.has(key),
128
+ )
129
+ ) {
130
+ style.transform = LISTED.map((key) => `${key}(${at(key)})`).join(" ");
131
+ }
105
132
  }
106
133
 
107
134
  export function beginTransform(element: Element, key: string) {
@@ -59,8 +59,8 @@ export type Keyframes<T = KeyframeValue> = T | readonly T[];
59
59
 
60
60
  /**
61
61
  * DOM keyframes: any CSS property (camelCase), a CSS variable (`--name`),
62
- * the independent transforms (`x`, `y`, `z`, `scale`, `scaleX`, `scaleY`,
63
- * `rotate`), or an SVG attribute. A single value animates from the current
62
+ * the transforms (`x`, `y`, `z`, `scale`, `scaleX`, `scaleY`,
63
+ * `rotate`, `rotateX`, `rotateY`, `skew`, `skewX`, `skewY`), or an SVG attribute. A single value animates from the current
64
64
  * one; an array lists every keyframe. Numbers take the property's unit (px,
65
65
  * deg) when it has one.
66
66
  */
@@ -131,6 +131,11 @@ export function waapiAnimation(
131
131
  : keyframes.length - 1
132
132
  ] as KeyframeValue;
133
133
 
134
+ /** Played backwards, it ends on its first keyframe. */
135
+ function end(): KeyframeValue {
136
+ return animation.playbackRate < 0 ? (keyframes[0] as KeyframeValue) : last;
137
+ }
138
+
134
139
  let settled = false;
135
140
  /** What stayed on screen when it settled: where a following animation starts. */
136
141
  let held: KeyframeValue = keyframes[0] as KeyframeValue;
@@ -170,7 +175,7 @@ export function waapiAnimation(
170
175
  animation.finished.then(
171
176
  () => {
172
177
  if (settled) return;
173
- settle(last);
178
+ settle(end());
174
179
  config.onComplete?.();
175
180
  },
176
181
  () => settle(undefined),
@@ -201,7 +206,7 @@ export function waapiAnimation(
201
206
  // The browser refuses to finish an endless animation: settle on its last frame instead.
202
207
  complete: () =>
203
208
  iterations === Number.POSITIVE_INFINITY
204
- ? settle(last)
209
+ ? settle(end())
205
210
  : animation.finish(),
206
211
  attachTimeline(timeline) {
207
212
  animation.timeline = timeline;
@@ -117,7 +117,36 @@ function parseHex(hex: string): Rgba | undefined {
117
117
  };
118
118
  }
119
119
 
120
- /** A colour written out in full; `undefined` for what only the page can resolve (`var()`, names). */
120
+ const names = new Map<string, Rgba | undefined>();
121
+ let probe: CanvasRenderingContext2D | null | undefined;
122
+ const SENTINEL = "#010203";
123
+
124
+ /**
125
+ * A colour name (`red`, `rebeccapurple`) as the browser spells it out: a
126
+ * canvas drops what isn't a colour (`auto`, `none`) and hands back hex for
127
+ * what is. The 148 names would weigh more than this; without a browser a
128
+ * name stays unknown and the page mixes it.
129
+ */
130
+ function parseName(name: string): Rgba | undefined {
131
+ if (name === "currentcolor" || !/^[a-z]+$/.test(name)) return undefined;
132
+ if (!names.has(name)) {
133
+ probe ??=
134
+ typeof document === "undefined"
135
+ ? null
136
+ : document.createElement("canvas").getContext("2d");
137
+ let resolved: Rgba | undefined;
138
+ if (probe) {
139
+ probe.fillStyle = SENTINEL;
140
+ probe.fillStyle = name;
141
+ const spelled = String(probe.fillStyle);
142
+ resolved = spelled === SENTINEL ? undefined : parseColor(spelled);
143
+ }
144
+ names.set(name, resolved);
145
+ }
146
+ return names.get(name);
147
+ }
148
+
149
+ /** A colour written out in full, or a name the browser knows; `undefined` for what only the page can resolve (`var()`, `currentColor`). */
121
150
  export function parseColor(text: string): Rgba | undefined {
122
151
  const trimmed = text.trim();
123
152
  if (trimmed.toLowerCase() === "transparent") {
@@ -128,7 +157,7 @@ export function parseColor(text: string): Rgba | undefined {
128
157
  }
129
158
  const match = FUNCTION.exec(trimmed);
130
159
  if (match === null) {
131
- return undefined;
160
+ return parseName(trimmed.toLowerCase());
132
161
  }
133
162
  const name = (match[1] as string).toLowerCase().replace(/a$/, "");
134
163
  const args = (match[2] as string).split(/[\s,/]+/).filter(Boolean);
@@ -18,8 +18,9 @@ function bothStrings(from: unknown, to: unknown): [string, string] | undefined {
18
18
  }
19
19
 
20
20
  /**
21
- * A colour pair needs one side written out, or both to be variables: two bare
22
- * words are as likely `auto` and `none` as `red` and `blue`.
21
+ * A colour pair needs one side spelled out (a name counts where a browser can
22
+ * spell it), or both to be variables: two other bare words are as likely
23
+ * `auto` and `none` as colours.
23
24
  */
24
25
  function colors(from: string, to: string): boolean {
25
26
  const resolved =
@@ -1,4 +1,5 @@
1
1
  import { groupControls, thenable } from "#/animate/controls.ts";
2
+ import { planGenerator, totalDuration } from "#/animate/plan.ts";
2
3
  import type {
3
4
  AnimationControls,
4
5
  AnimationOptions,
@@ -48,6 +49,36 @@ function startOf(
48
49
  return labels.get(at) ?? cursor;
49
50
  }
50
51
 
52
+ /**
53
+ * How long a segment that waits for an earlier one will run once launched.
54
+ * Its first keyframe is only read then, but a tween's length doesn't depend
55
+ * on it, and neither does a default spring's (its rest thresholds scale with
56
+ * the distance).
57
+ */
58
+ function plannedDuration(keyframes: unknown, options: AnimationOptions) {
59
+ const channels =
60
+ typeof keyframes === "object" &&
61
+ keyframes !== null &&
62
+ !Array.isArray(keyframes)
63
+ ? Object.values(keyframes)
64
+ : [keyframes];
65
+ const { delay } = options;
66
+ return (
67
+ (typeof delay === "number" ? delay : 0) +
68
+ Math.max(
69
+ 0,
70
+ ...channels.map((channel) => {
71
+ const frames = Array.isArray(channel) ? channel : [0, channel];
72
+ const { generator } = planGenerator(
73
+ frames.map((_frame, index) => index),
74
+ options,
75
+ );
76
+ return totalDuration(generator.duration, options);
77
+ }),
78
+ )
79
+ );
80
+ }
81
+
51
82
  /**
52
83
  * Launches every segment at once, each delayed to its place on the
53
84
  * timeline, so the whole sequence shares one clock and can be scrubbed. A
@@ -62,9 +93,13 @@ export function animateSequence(
62
93
  ): AnimationControls {
63
94
  const begun = now();
64
95
  const labels = new Map<string, number>();
65
- const busy = new Map<object, Map<string, Promise<void>>>();
96
+ const busy = new Map<
97
+ object,
98
+ Map<string, { readonly finished: Promise<void>; readonly end: number }>
99
+ >();
66
100
  const children: AnimationControls[] = [];
67
101
  const waits: Promise<void>[] = [];
102
+ let end = 0;
68
103
  let cursor = 0;
69
104
  let previousStart = 0;
70
105
  for (const segment of segments) {
@@ -78,7 +113,18 @@ export function animateSequence(
78
113
  unknown,
79
114
  SegmentOptions?,
80
115
  ];
81
- const start = startOf(own.at, cursor, previousStart, labels);
116
+ const placed = startOf(own.at, cursor, previousStart, labels);
117
+ const keys =
118
+ typeof keyframes === "object" &&
119
+ keyframes !== null &&
120
+ !Array.isArray(keyframes)
121
+ ? Object.keys(keyframes)
122
+ : ["value"];
123
+ const targets = resolveTargets(target);
124
+ const before = targets.flatMap((one) =>
125
+ keys.flatMap((key) => busy.get(one)?.get(key) ?? []),
126
+ );
127
+ const start = Math.max(placed, ...before.map((earlier) => earlier.end));
82
128
  const { delay } = own;
83
129
  /** Launched late, a segment only waits for what is left of its delay. */
84
130
  function optionsAt(elapsed: number): AnimationOptions {
@@ -92,32 +138,28 @@ export function animateSequence(
92
138
  : Math.max(0, offset + (delay ?? 0)),
93
139
  };
94
140
  }
95
- const keys =
96
- typeof keyframes === "object" &&
97
- keyframes !== null &&
98
- !Array.isArray(keyframes)
99
- ? Object.keys(keyframes)
100
- : ["value"];
101
- const targets = resolveTargets(target);
102
- const before = targets.flatMap((one) =>
103
- keys.flatMap((key) => busy.get(one)?.get(key) ?? []),
104
- );
105
141
  const launch = () =>
106
142
  animateOne(target, keyframes, optionsAt((now() - begun) / 1000));
107
143
  let finished: Promise<void>;
144
+ let ends: number;
108
145
  if (before.length === 0) {
109
146
  const controls = launch();
110
147
  children.push(controls);
111
148
  finished = controls.finished;
112
- cursor = Math.max(start, controls.duration);
149
+ ends = Math.max(start, controls.duration);
113
150
  } else {
114
- finished = Promise.all(before).then(() => launch().finished);
151
+ finished = Promise.all(before.map((earlier) => earlier.finished)).then(
152
+ () => launch().finished,
153
+ );
115
154
  waits.push(finished);
155
+ ends = start + plannedDuration(keyframes, { ...options, ...own });
116
156
  }
157
+ cursor = ends;
158
+ end = Math.max(end, ends);
117
159
  for (const one of targets) {
118
160
  const byKey = busy.get(one) ?? new Map();
119
161
  busy.set(one, byKey);
120
- for (const key of keys) byKey.set(key, finished);
162
+ for (const key of keys) byKey.set(key, { finished, end: ends });
121
163
  }
122
164
  previousStart = start;
123
165
  }
@@ -125,5 +167,9 @@ export function animateSequence(
125
167
  const finished = Promise.all([group.finished, ...waits]).then(
126
168
  () => undefined,
127
169
  );
170
+ // The segments still waiting count too: they end after the ones launched.
171
+ Object.defineProperty(group, "duration", {
172
+ value: Math.max(end, group.duration),
173
+ });
128
174
  return Object.assign(group, thenable(finished));
129
175
  }