@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 +4 -1
- package/dist/animate/js-animation.d.ts.map +1 -1
- package/dist/animate/js-animation.js +13 -11
- package/dist/animate/js-animation.js.map +2 -2
- package/dist/animate/style.d.ts +1 -1
- package/dist/animate/style.d.ts.map +1 -1
- package/dist/animate/style.js +18 -1
- package/dist/animate/style.js.map +2 -2
- package/dist/animate/types.d.ts +2 -2
- package/dist/animate/waapi.d.ts.map +1 -1
- package/dist/animate/waapi.js +5 -2
- package/dist/animate/waapi.js.map +2 -2
- package/dist/interpolate/color-parse.d.ts +1 -1
- package/dist/interpolate/color-parse.d.ts.map +1 -1
- package/dist/interpolate/color-parse.js +19 -1
- package/dist/interpolate/color-parse.js.map +2 -2
- package/dist/interpolate/mix.d.ts.map +1 -1
- package/dist/interpolate/mix.js.map +2 -2
- package/dist/sequence/sequence.d.ts.map +1 -1
- package/dist/sequence/sequence.js +32 -5
- package/dist/sequence/sequence.js.map +2 -2
- package/package.json +1 -1
- package/src/animate/js-animation.ts +15 -11
- package/src/animate/style.ts +28 -1
- package/src/animate/types.ts +2 -2
- package/src/animate/waapi.ts +7 -2
- package/src/interpolate/color-parse.ts +31 -2
- package/src/interpolate/mix.ts +3 -2
- package/src/sequence/sequence.ts +61 -15
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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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\
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe,eAAe,qBAAqB;AAM5D,SAAS,aAA6B,OAAO,WAAW;AACxD,SAAS,WAAW;AAuBb,SAAS,YAAe,QAA8C;AAC5E,QAAM,EAAE,WAAW,SAAS,OAAO,MAAM,IAAI;AAC7C,QAAM,EAAE,WAAW,WAAW,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACR;AACA,QAAM,WAAW,aACd,UAAU,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,UAAU,IAAI,UAAU,KAAK,GAAQ,EAAO,CAAC,IACzE,CAAC;AACJ,QAAM,SAAS,cAAc,UAAU,UAAU,OAAO;AACxD,QAAM,WAAW,QAAQ;AACzB,QAAM,eACL,QAAQ,eAAe,cAAc,QAAQ,UAAU,KAAK,MAAM;AACnE,QAAM,OAAO,UAAU,eAAe,IAAI,UAAU,SAAS,CAAC;AAE9D,MAAI,OAAO;AACX,MAAI,QAAQ;AACZ,MAAI,QAAwB;AAC5B,MAAI,WAAW,IAAI;AACnB,MAAI,UAAsB,MAAM;AAAA,EAAC;AACjC,QAAM,WAAW,IAAI,QAAc,CAAC,SAAS;AAC5C,cAAU;AAAA,EACX,CAAC;AAED,WAAS,QAAQ,UAAqB;AACrC,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,QAAQ,KAAK;AAAA,MAClB,KAAK,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AAAA,MAChC,SAAS,SAAS;AAAA,IACnB;AACA,WAAQ,SAAS,KAAK,EAA8B,WAAW,KAAK;AAAA,EACrE;AAEA,WAAS,SAAS;AACjB,UAAM,UAAU,KAAK,IAAI,GAAG,OAAO,KAAK;AACxC,WAAO,UAAU,GAAG,cAAc,SAAS,UAAU,UAAU,OAAO,CAAC;AAAA,EACxE;AAEA,WAAS,SAAS;AACjB,QAAI,OAAO,MAAO;AAClB,UAAM,QAAQ,WAAW,OAAO,QAAQ,OAAO,EAAE,KAAK,CAAC;AAAA,EACxD;AAEA,WAAS,OAAO,MAAsB;AACrC,YAAQ;AACR,gBAAY,IAAI;AAChB,YAAQ;AAAA,EACT;AAEA,WAAS,KAAK,MAAiB;AAC9B,WAAO,KAAK,IAAI,GAAG,QAAS,KAAK,YAAY,YAAY,MAAQ,KAAK;
|
|
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
|
}
|
package/dist/animate/style.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/animate/style.js
CHANGED
|
@@ -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": "
|
|
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
|
}
|
package/dist/animate/types.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/animate/waapi.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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": "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",
|
|
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()`,
|
|
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;
|
|
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
|
|
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()`,
|
|
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;
|
|
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
|
|
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;
|
|
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":"
|
|
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
|
|
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
|
-
|
|
66
|
+
ends = Math.max(start, controls.duration);
|
|
48
67
|
} else {
|
|
49
|
-
finished2 = Promise.all(before
|
|
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<
|
|
5
|
-
"mappings": "AAAA,SAAS,eAAe,gBAAgB;
|
|
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
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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() {
|
package/src/animate/style.ts
CHANGED
|
@@ -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) {
|
package/src/animate/types.ts
CHANGED
|
@@ -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
|
|
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
|
*/
|
package/src/animate/waapi.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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);
|
package/src/interpolate/mix.ts
CHANGED
|
@@ -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
|
|
22
|
-
*
|
|
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 =
|
package/src/sequence/sequence.ts
CHANGED
|
@@ -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<
|
|
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
|
|
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
|
-
|
|
149
|
+
ends = Math.max(start, controls.duration);
|
|
113
150
|
} else {
|
|
114
|
-
finished = Promise.all(before
|
|
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
|
}
|