@ng-native/fabric 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/color-mix.js +1 -16
- package/dist/color-mix.js.map +1 -1
- package/dist/css.d.ts +299 -28
- package/dist/css.js +1093 -122
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +267 -3
- package/dist/engine.js +1209 -76
- package/dist/engine.js.map +1 -1
- package/dist/inline-token.js +27 -15
- package/dist/inline-token.js.map +1 -1
- package/dist/inline-transform.js +21 -3
- package/dist/inline-transform.js.map +1 -1
- package/dist/native-drive.d.ts +21 -0
- package/dist/native-drive.js +65 -0
- package/dist/native-drive.js.map +1 -1
- package/dist/premultiplied-stops.d.ts +7 -0
- package/dist/premultiplied-stops.js +131 -0
- package/dist/premultiplied-stops.js.map +1 -0
- package/dist/scroll-animation.d.ts +24 -0
- package/dist/scroll-animation.js +82 -4
- package/dist/scroll-animation.js.map +1 -1
- package/dist/transition.d.ts +15 -0
- package/dist/transition.js +134 -11
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A gradient's stops, with each one at no opacity given the colour of the stop it fades to or from.
|
|
3
|
+
*
|
|
4
|
+
* CSS interpolates stops in premultiplied alpha, where a stop at no opacity adds no colour of its
|
|
5
|
+
* own: `transparent` to white is white throughout. iOS interpolates the channels as they stand,
|
|
6
|
+
* and halfway from `rgb(0 0 0 / 0)` to white is grey at half opacity. With its neighbour's
|
|
7
|
+
* channels the stop paints the same either way. One between two colours becomes two stops at one
|
|
8
|
+
* position, the first fading from the colour before and the second to the colour after.
|
|
9
|
+
*
|
|
10
|
+
* A stop at part opacity adds its colour in proportion, so between two stops of different colour
|
|
11
|
+
* and opacity the colour bends towards the more opaque one. Stops written along that curve stand
|
|
12
|
+
* in for it.
|
|
13
|
+
*/
|
|
14
|
+
import { parseColor } from "./transition.js";
|
|
15
|
+
const BLACK = '0, 0, 0';
|
|
16
|
+
export function premultipliedStops(stops) {
|
|
17
|
+
const colours = stops.map((stop) => {
|
|
18
|
+
const parsed = parseColor(stop.color);
|
|
19
|
+
return parsed && { channels: parsed.slice(0, 3).join(', '), clear: parsed[3] === 0 };
|
|
20
|
+
});
|
|
21
|
+
const visible = (i) => (colours[i]?.clear === false ? colours[i].channels : undefined);
|
|
22
|
+
const out = [];
|
|
23
|
+
// The colour React Native's iOS painter gives a transparent black stop: the last stop that was
|
|
24
|
+
// not one. Right when that is the colour wanted, and otherwise avoided by a stop a shade off
|
|
25
|
+
// black, which at no opacity paints the same.
|
|
26
|
+
let lent;
|
|
27
|
+
const fading = (stop, channels) => {
|
|
28
|
+
const written = channels === BLACK && lent && lent !== BLACK ? '0, 0, 1' : channels;
|
|
29
|
+
if (written !== BLACK)
|
|
30
|
+
lent = written;
|
|
31
|
+
out.push({ ...stop, color: `rgba(${written}, 0)` });
|
|
32
|
+
};
|
|
33
|
+
stops.forEach((stop, i) => {
|
|
34
|
+
const colour = colours[i];
|
|
35
|
+
const lenders = colour?.clear ? [...new Set([visible(i - 1), visible(i + 1)])] : [];
|
|
36
|
+
const [first, second] = lenders.filter((channels) => channels !== undefined);
|
|
37
|
+
if (!first) {
|
|
38
|
+
const own = colour?.channels ?? 'unknown';
|
|
39
|
+
if (!colour?.clear || own !== BLACK)
|
|
40
|
+
lent = own;
|
|
41
|
+
out.push(stop);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const position = second ? (stop.position ?? impliedPosition(stops, i)) : undefined;
|
|
45
|
+
if (position === undefined)
|
|
46
|
+
return fading(stop, first);
|
|
47
|
+
fading({ ...stop, position }, first);
|
|
48
|
+
fading({ ...stop, position }, second);
|
|
49
|
+
});
|
|
50
|
+
return curved(out);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* How many stops stand in for the curve between two. They sit where the opacity has changed by
|
|
54
|
+
* equal ratios, which is where the curve bends evenly, and at 8 native's straight lines between
|
|
55
|
+
* them stay within a level of it from 2% opacity to full.
|
|
56
|
+
*/
|
|
57
|
+
const STEPS = 8;
|
|
58
|
+
/** The stops, with those along the curve between two of different colour and opacity written in. */
|
|
59
|
+
function curved(stops) {
|
|
60
|
+
const spans = stops.map((_, i) => span(stops, i));
|
|
61
|
+
return stops.flatMap((stop, i) => {
|
|
62
|
+
const [before, after] = [spans[i - 1], spans[i]];
|
|
63
|
+
// A stop beside a curve is placed too: native would space an unplaced one between the stops
|
|
64
|
+
// written in around it.
|
|
65
|
+
const position = after?.start ?? before?.end ?? stop.position;
|
|
66
|
+
return [{ ...stop, position }, ...(after ? along(after) : [])];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** The run from stop `i` to the next, when its colour bends and both ends can be placed. */
|
|
70
|
+
function span(stops, i) {
|
|
71
|
+
const from = parseColor(stops[i].color);
|
|
72
|
+
const to = parseColor(stops[i + 1]?.color);
|
|
73
|
+
if (!from || !to || !bends(from, to))
|
|
74
|
+
return undefined;
|
|
75
|
+
const start = stops[i].position ?? impliedPosition(stops, i);
|
|
76
|
+
const end = stops[i + 1].position ?? impliedPosition(stops, i + 1);
|
|
77
|
+
// Two stops at one position are a hard edge, with no run between them to curve.
|
|
78
|
+
if (unit(start) === undefined || unit(start) !== unit(end) || start === end)
|
|
79
|
+
return undefined;
|
|
80
|
+
return { from, to, start: start, end: end };
|
|
81
|
+
}
|
|
82
|
+
/** Whether the colour between two bends: both have some opacity, and differ in it and in colour. */
|
|
83
|
+
function bends(from, to) {
|
|
84
|
+
if (!from[3] || !to[3] || from[3] === to[3])
|
|
85
|
+
return false;
|
|
86
|
+
return from.slice(0, 3).join() !== to.slice(0, 3).join();
|
|
87
|
+
}
|
|
88
|
+
/** The stops along a span's curve, each the colour CSS paints at its position. */
|
|
89
|
+
function along({ from, to, start, end }) {
|
|
90
|
+
const [a, b] = [parseFloat(String(start)), parseFloat(String(end))];
|
|
91
|
+
return Array.from({ length: STEPS - 1 }, (_, k) => {
|
|
92
|
+
const alpha = from[3] * (to[3] / from[3]) ** ((k + 1) / STEPS);
|
|
93
|
+
const t = (alpha - from[3]) / (to[3] - from[3]);
|
|
94
|
+
const channel = (c) => Math.round((from[c] * from[3] * (1 - t) + to[c] * to[3] * t) / alpha);
|
|
95
|
+
const at = Math.round((a + (b - a) * t) * 1e4) / 1e4;
|
|
96
|
+
return {
|
|
97
|
+
color: `rgba(${channel(0)}, ${channel(1)}, ${channel(2)}, ${Math.round(alpha * 1000) / 1000})`,
|
|
98
|
+
position: unit(start) ? `${at}%` : at,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Where CSS puts a stop written without a position: spaced evenly between the placed stops either
|
|
104
|
+
* side of it, the first stop at 0% and the last at 100%.
|
|
105
|
+
*/
|
|
106
|
+
function impliedPosition(stops, i) {
|
|
107
|
+
let from = i;
|
|
108
|
+
while (from > 0 && stops[from].position == null)
|
|
109
|
+
from--;
|
|
110
|
+
let to = i;
|
|
111
|
+
while (to < stops.length - 1 && stops[to].position == null)
|
|
112
|
+
to++;
|
|
113
|
+
const end = stops[to].position ?? '100%';
|
|
114
|
+
// The first stop is at the start of the line, in whichever unit the stop after it is placed.
|
|
115
|
+
const start = stops[from].position ?? (typeof end === 'number' ? 0 : '0%');
|
|
116
|
+
// ponytail: a percentage and a length either side need the box's size, which only native has, so
|
|
117
|
+
// the stop stays one stop in the colour before it. Positions written out of order are not
|
|
118
|
+
// clamped as CSS clamps them either. Resolve both in points if a design needs one.
|
|
119
|
+
if (unit(start) === undefined || unit(start) !== unit(end))
|
|
120
|
+
return undefined;
|
|
121
|
+
const [a, b] = [parseFloat(String(start)), parseFloat(String(end))];
|
|
122
|
+
const at = Math.round((a + ((b - a) * (i - from)) / (to - from)) * 1e4) / 1e4;
|
|
123
|
+
return unit(start) ? `${at}%` : at;
|
|
124
|
+
}
|
|
125
|
+
/** What a position is measured in: `%`, nothing for points, or undefined for neither. */
|
|
126
|
+
function unit(position) {
|
|
127
|
+
if (typeof position === 'number')
|
|
128
|
+
return '';
|
|
129
|
+
return typeof position === 'string' && position.endsWith('%') ? '%' : undefined;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=premultiplied-stops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"premultiplied-stops.js","sourceRoot":"","sources":["../src/premultiplied-stops.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A gradient's stops, with each one at no opacity given the colour of the stop it fades to or from.\n *\n * CSS interpolates stops in premultiplied alpha, where a stop at no opacity adds no colour of its\n * own: `transparent` to white is white throughout. iOS interpolates the channels as they stand,\n * and halfway from `rgb(0 0 0 / 0)` to white is grey at half opacity. With its neighbour's\n * channels the stop paints the same either way. One between two colours becomes two stops at one\n * position, the first fading from the colour before and the second to the colour after.\n *\n * A stop at part opacity adds its colour in proportion, so between two stops of different colour\n * and opacity the colour bends towards the more opaque one. Stops written along that curve stand\n * in for it.\n */\nimport { parseColor } from './transition.ts';\n\n/** One stop of a gradient as native takes it. */\ninterface GradientStop {\n readonly color?: unknown;\n readonly position?: unknown;\n}\n\nconst BLACK = '0, 0, 0';\n\nexport function premultipliedStops(stops: readonly GradientStop[]): GradientStop[] {\n const colours = stops.map((stop) => {\n const parsed = parseColor(stop.color);\n return parsed && { channels: parsed.slice(0, 3).join(', '), clear: parsed[3] === 0 };\n });\n const visible = (i: number) => (colours[i]?.clear === false ? colours[i].channels : undefined);\n\n const out: GradientStop[] = [];\n // The colour React Native's iOS painter gives a transparent black stop: the last stop that was\n // not one. Right when that is the colour wanted, and otherwise avoided by a stop a shade off\n // black, which at no opacity paints the same.\n let lent: string | undefined;\n const fading = (stop: GradientStop, channels: string) => {\n const written = channels === BLACK && lent && lent !== BLACK ? '0, 0, 1' : channels;\n if (written !== BLACK) lent = written;\n out.push({ ...stop, color: `rgba(${written}, 0)` });\n };\n\n stops.forEach((stop, i) => {\n const colour = colours[i];\n const lenders = colour?.clear ? [...new Set([visible(i - 1), visible(i + 1)])] : [];\n const [first, second] = lenders.filter((channels) => channels !== undefined);\n if (!first) {\n const own = colour?.channels ?? 'unknown';\n if (!colour?.clear || own !== BLACK) lent = own;\n out.push(stop);\n return;\n }\n const position = second ? (stop.position ?? impliedPosition(stops, i)) : undefined;\n if (position === undefined) return fading(stop, first);\n fading({ ...stop, position }, first);\n fading({ ...stop, position }, second!);\n });\n return curved(out);\n}\n\n/**\n * How many stops stand in for the curve between two. They sit where the opacity has changed by\n * equal ratios, which is where the curve bends evenly, and at 8 native's straight lines between\n * them stay within a level of it from 2% opacity to full.\n */\nconst STEPS = 8;\n\n/** The stops, with those along the curve between two of different colour and opacity written in. */\nfunction curved(stops: readonly GradientStop[]): GradientStop[] {\n const spans = stops.map((_, i) => span(stops, i));\n return stops.flatMap((stop, i) => {\n const [before, after] = [spans[i - 1], spans[i]];\n // A stop beside a curve is placed too: native would space an unplaced one between the stops\n // written in around it.\n const position = after?.start ?? before?.end ?? stop.position;\n return [{ ...stop, position }, ...(after ? along(after) : [])];\n });\n}\n\ninterface Span {\n readonly from: readonly number[];\n readonly to: readonly number[];\n readonly start: string | number;\n readonly end: string | number;\n}\n\n/** The run from stop `i` to the next, when its colour bends and both ends can be placed. */\nfunction span(stops: readonly GradientStop[], i: number): Span | undefined {\n const from = parseColor(stops[i]!.color);\n const to = parseColor(stops[i + 1]?.color);\n if (!from || !to || !bends(from, to)) return undefined;\n const start = stops[i]!.position ?? impliedPosition(stops, i);\n const end = stops[i + 1]!.position ?? impliedPosition(stops, i + 1);\n // Two stops at one position are a hard edge, with no run between them to curve.\n if (unit(start) === undefined || unit(start) !== unit(end) || start === end) return undefined;\n return { from, to, start: start as string | number, end: end as string | number };\n}\n\n/** Whether the colour between two bends: both have some opacity, and differ in it and in colour. */\nfunction bends(from: readonly number[], to: readonly number[]): boolean {\n if (!from[3] || !to[3] || from[3] === to[3]) return false;\n return from.slice(0, 3).join() !== to.slice(0, 3).join();\n}\n\n/** The stops along a span's curve, each the colour CSS paints at its position. */\nfunction along({ from, to, start, end }: Span): GradientStop[] {\n const [a, b] = [parseFloat(String(start)), parseFloat(String(end))];\n return Array.from({ length: STEPS - 1 }, (_, k) => {\n const alpha = from[3]! * (to[3]! / from[3]!) ** ((k + 1) / STEPS);\n const t = (alpha - from[3]!) / (to[3]! - from[3]!);\n const channel = (c: number) =>\n Math.round((from[c]! * from[3]! * (1 - t) + to[c]! * to[3]! * t) / alpha);\n const at = Math.round((a + (b - a) * t) * 1e4) / 1e4;\n return {\n color: `rgba(${channel(0)}, ${channel(1)}, ${channel(2)}, ${Math.round(alpha * 1000) / 1000})`,\n position: unit(start) ? `${at}%` : at,\n };\n });\n}\n\n/**\n * Where CSS puts a stop written without a position: spaced evenly between the placed stops either\n * side of it, the first stop at 0% and the last at 100%.\n */\nfunction impliedPosition(stops: readonly GradientStop[], i: number): string | number | undefined {\n let from = i;\n while (from > 0 && stops[from]!.position == null) from--;\n let to = i;\n while (to < stops.length - 1 && stops[to]!.position == null) to++;\n const end = stops[to]!.position ?? '100%';\n // The first stop is at the start of the line, in whichever unit the stop after it is placed.\n const start = stops[from]!.position ?? (typeof end === 'number' ? 0 : '0%');\n // ponytail: a percentage and a length either side need the box's size, which only native has, so\n // the stop stays one stop in the colour before it. Positions written out of order are not\n // clamped as CSS clamps them either. Resolve both in points if a design needs one.\n if (unit(start) === undefined || unit(start) !== unit(end)) return undefined;\n const [a, b] = [parseFloat(String(start)), parseFloat(String(end))];\n const at = Math.round((a + ((b - a) * (i - from)) / (to - from)) * 1e4) / 1e4;\n return unit(start) ? `${at}%` : at;\n}\n\n/** What a position is measured in: `%`, nothing for points, or undefined for neither. */\nfunction unit(position: unknown): string | undefined {\n if (typeof position === 'number') return '';\n return typeof position === 'string' && position.endsWith('%') ? '%' : undefined;\n}\n"]}
|
|
@@ -36,6 +36,30 @@ export declare function scrollChannels(tracks: ReadonlyMap<string, Track>, spec:
|
|
|
36
36
|
channels: DrivenChannels;
|
|
37
37
|
held: string[];
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* The channels for an animation played by the clock, along a value native runs from nothing to
|
|
41
|
+
* `span`: one iteration along 0 to 1, and for `alternate` the way back along 1 to 2, so that
|
|
42
|
+
* native repeats there and back as one animation. Both ends are the frames' own: what the
|
|
43
|
+
* element rests at is committed when the animation ends, and not played.
|
|
44
|
+
*/
|
|
45
|
+
export declare function clockChannels(tracks: ReadonlyMap<string, Track>, spec: AnimationSpec, resting: Readonly<Record<string, unknown>>): {
|
|
46
|
+
channels: DrivenChannels;
|
|
47
|
+
held: string[];
|
|
48
|
+
span: number;
|
|
49
|
+
};
|
|
50
|
+
/** Whether an animation moves a box by a share of its own size, in a frame or at rest. */
|
|
51
|
+
export declare function movesByShare(tracks: ReadonlyMap<string, Track>, resting: Readonly<Record<string, unknown>>): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* An animation's tracks and resting style with every move by a share of the box as points, which
|
|
54
|
+
* is what native interpolates: `translateX(200%)` of a box 300 wide is 600.
|
|
55
|
+
*/
|
|
56
|
+
export declare function sharesAsPoints<T extends Track>(tracks: ReadonlyMap<string, T>, resting: Readonly<Record<string, unknown>>, size: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
}): {
|
|
60
|
+
tracks: Map<string, T>;
|
|
61
|
+
resting: Record<string, unknown>;
|
|
62
|
+
};
|
|
39
63
|
/** The values a scroll-driven animation commits: its frame at the offset the view starts at. */
|
|
40
64
|
export declare function firstFrame(tracks: ReadonlyMap<string, Track>, spec: AnimationSpec, range: readonly [number, number]): Record<string, unknown>;
|
|
41
65
|
export {};
|
package/dist/scroll-animation.js
CHANGED
|
@@ -50,10 +50,45 @@ export function scrollChannels(tracks, spec, resting, range) {
|
|
|
50
50
|
held,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The channels for an animation played by the clock, along a value native runs from nothing to
|
|
55
|
+
* `span`: one iteration along 0 to 1, and for `alternate` the way back along 1 to 2, so that
|
|
56
|
+
* native repeats there and back as one animation. Both ends are the frames' own: what the
|
|
57
|
+
* element rests at is committed when the animation ends, and not played.
|
|
58
|
+
*/
|
|
59
|
+
export function clockChannels(tracks, spec, resting) {
|
|
60
|
+
const way = (direction, range) => scrollChannels(tracks, { ...spec, fill: 'both', direction }, resting, range);
|
|
61
|
+
if (spec.direction !== 'alternate' && spec.direction !== 'alternate-reverse') {
|
|
62
|
+
return { ...way(spec.direction, [0, 1]), span: 1 };
|
|
63
|
+
}
|
|
64
|
+
const reversed = spec.direction === 'alternate-reverse';
|
|
65
|
+
const out = way(reversed ? 'reverse' : undefined, [0, 1]);
|
|
66
|
+
const back = way(reversed ? undefined : 'reverse', [1, 2]);
|
|
67
|
+
const join = (a, b) => ({
|
|
68
|
+
input: [...a.input, ...b.input.slice(1)],
|
|
69
|
+
output: [...a.output, ...b.output.slice(1)],
|
|
70
|
+
});
|
|
71
|
+
const transform = out.channels.transform.map((channel, i) => {
|
|
72
|
+
const other = back.channels.transform[i];
|
|
73
|
+
return 'range' in channel && other && 'range' in other
|
|
74
|
+
? { property: channel.property, range: join(channel.range, other.range) }
|
|
75
|
+
: channel;
|
|
76
|
+
});
|
|
77
|
+
const opacity = out.channels.opacity && back.channels.opacity
|
|
78
|
+
? { opacity: join(out.channels.opacity, back.channels.opacity) }
|
|
79
|
+
: {};
|
|
80
|
+
return { channels: { ...opacity, transform }, held: out.held, span: 2 };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* One transform group's entries, each driven along the scroll; null if their shapes differ. A
|
|
84
|
+
* frame, or the element at rest, with fewer functions than the longest is that list with the
|
|
85
|
+
* rest at their identity, which is how CSS eases a list into a longer one that starts the same.
|
|
86
|
+
*/
|
|
54
87
|
function drivenGroup(track, resting, along) {
|
|
55
|
-
const shapes = track.map((point) => (Array.isArray(point.value) ? point.value :
|
|
56
|
-
|
|
88
|
+
const shapes = track.map((point) => (Array.isArray(point.value) ? point.value : []));
|
|
89
|
+
if (Array.isArray(resting))
|
|
90
|
+
shapes.push(resting);
|
|
91
|
+
const shape = shapes.reduce((longest, entries) => entries.length > longest.length ? entries : longest);
|
|
57
92
|
const keys = shape.map((entry) => Object.keys(entry)[0]);
|
|
58
93
|
const channels = [];
|
|
59
94
|
for (const [index, key] of keys.entries()) {
|
|
@@ -63,7 +98,10 @@ function drivenGroup(track, resting, along) {
|
|
|
63
98
|
if (!entries?.length)
|
|
64
99
|
return identity;
|
|
65
100
|
const entry = entries[index];
|
|
66
|
-
|
|
101
|
+
// Past the end of a shorter list. Where it differs before its end, an earlier entry says.
|
|
102
|
+
if (!entry)
|
|
103
|
+
return identity;
|
|
104
|
+
return key in entry ? amount(key, entry[key]) : null;
|
|
67
105
|
};
|
|
68
106
|
const range = along(track, pick, restingAmount(resting, index, key, identity));
|
|
69
107
|
if (!range)
|
|
@@ -97,6 +135,46 @@ const RADIANS = {
|
|
|
97
135
|
grad: Math.PI / 200,
|
|
98
136
|
turn: 2 * Math.PI,
|
|
99
137
|
};
|
|
138
|
+
/** The side of a box each move by a share is a share of. */
|
|
139
|
+
const SHARED = {
|
|
140
|
+
translateX: 'width',
|
|
141
|
+
translateY: 'height',
|
|
142
|
+
};
|
|
143
|
+
const isShare = (value) => typeof value === 'string' && value.endsWith('%');
|
|
144
|
+
const sharesIn = (value) => Array.isArray(value) &&
|
|
145
|
+
value.some((entry) => Object.entries(entry).some(([key, to]) => key in SHARED && isShare(to)));
|
|
146
|
+
/** Whether an animation moves a box by a share of its own size, in a frame or at rest. */
|
|
147
|
+
export function movesByShare(tracks, resting) {
|
|
148
|
+
return GROUPS.some((group) => sharesIn(resting[group]) ||
|
|
149
|
+
tracks.get(group)?.some((point) => sharesIn(point.value)) === true);
|
|
150
|
+
}
|
|
151
|
+
/** A transform list with each move by a share as the points it is of a box of `size`. */
|
|
152
|
+
function inPoints(value, size) {
|
|
153
|
+
if (!sharesIn(value))
|
|
154
|
+
return value;
|
|
155
|
+
return value.map((entry) => {
|
|
156
|
+
const [key, to] = Object.entries(entry)[0];
|
|
157
|
+
const side = SHARED[key];
|
|
158
|
+
return side && isShare(to) ? { [key]: (size[side] * Number.parseFloat(to)) / 100 } : entry;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* An animation's tracks and resting style with every move by a share of the box as points, which
|
|
163
|
+
* is what native interpolates: `translateX(200%)` of a box 300 wide is 600.
|
|
164
|
+
*/
|
|
165
|
+
export function sharesAsPoints(tracks, resting, size) {
|
|
166
|
+
const sized = new Map(tracks);
|
|
167
|
+
const rest = { ...resting };
|
|
168
|
+
for (const group of GROUPS) {
|
|
169
|
+
const track = tracks.get(group);
|
|
170
|
+
if (track) {
|
|
171
|
+
sized.set(group, track.map((point) => ({ ...point, value: inPoints(point.value, size) })));
|
|
172
|
+
}
|
|
173
|
+
if (group in rest)
|
|
174
|
+
rest[group] = inPoints(rest[group], size);
|
|
175
|
+
}
|
|
176
|
+
return { tracks: sized, resting: rest };
|
|
177
|
+
}
|
|
100
178
|
/**
|
|
101
179
|
* A transform entry's amount as native's transform node takes it: points, a factor, or an angle
|
|
102
180
|
* in radians. Null for what it cannot take, a percentage translate.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-animation.js","sourceRoot":"","sources":["../src/scroll-animation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A `@keyframes` animation played by a scroll view's offset: `animation-timeline: scroll()`.\n *\n * Native interpolates linearly between the points it is given and clamps past both ends, which is\n * all a scroll-driven animation needs from it once its keyframes are laid along the offset: each\n * keyframe sits at its share of the range, a segment's timing function is sampled into points\n * along it, and an unfilled end falls back to the resting value just outside the range. What\n * native can animate this way is opacity and the transforms; anything else holds its first frame.\n */\nimport type { ScrollRange } from './native-drive.ts';\nimport { at, type AnimationSpec, type TrackPoint } from './transition.ts';\n\ntype Track = readonly { offset: number; value: unknown }[];\n\n/** The channels a scroll-driven animation drives natively, in the order native applies them. */\nexport interface DrivenChannels {\n readonly opacity?: ScrollRange;\n /** Every transform entry the view paints: driven by the scroll, or held as it is. */\n readonly transform: readonly TransformChannel[];\n}\n\nexport type TransformChannel =\n | { readonly property: string; readonly range: ScrollRange }\n | { readonly property: string; readonly value: number };\n\n/** What a range with no end, or a percentage of one, stands in for until the scroll view says. */\nconst UNKNOWN_EXTENT = 1e6;\n\n/** Points per eased segment: enough that the curve between them reads as the curve. */\nconst SAMPLES = 12;\n\n/** The transform groups in the order CSS applies them, as the engine composes them. */\nconst GROUPS = ['__translate', '__rotate', '__scale', 'transform'] as const;\n\n/** Where the range starts and ends on the scroll view's offset. */\nexport function rangeOf(spec: AnimationSpec, extent: number | null): [number, number] {\n const end = (value: number | string | undefined, otherwise: number): number => {\n if (value === undefined) return otherwise;\n if (typeof value === 'number') return value;\n return (parseFloat(value) / 100) * (extent ?? UNKNOWN_EXTENT);\n };\n return [end(spec.range?.start, 0), end(spec.range?.end, extent ?? UNKNOWN_EXTENT)];\n}\n\n/**\n * The channels for an animation's tracks over a range, and the properties it cannot drive\n * natively, which the caller holds at their first frame.\n */\nexport function scrollChannels(\n tracks: ReadonlyMap<string, Track>,\n spec: AnimationSpec,\n resting: Readonly<Record<string, unknown>>,\n range: readonly [number, number],\n): { channels: DrivenChannels; held: string[] } {\n const along = (track: Track, pick: (value: unknown) => number | null, rest: number) =>\n laid(track, pick, rest, spec, range);\n const held: string[] = [];\n let opacity: ScrollRange | undefined;\n const transform: TransformChannel[] = [];\n for (const [property, track] of tracks) {\n if (property === 'opacity') {\n opacity =\n along(\n track,\n (value) => (typeof value === 'number' ? value : null),\n numberOr(resting['opacity'], 1),\n ) ?? undefined;\n if (!opacity) held.push(property);\n } else if (!(GROUPS as readonly string[]).includes(property)) {\n held.push(property);\n }\n }\n for (const group of GROUPS) {\n const track = tracks.get(group);\n const channels = track ? drivenGroup(track, resting[group], along) : heldGroup(resting[group]);\n if (channels === null) held.push(group);\n else transform.push(...channels);\n }\n const anyDriven = transform.some((channel) => 'range' in channel);\n return {\n channels: { ...(opacity ? { opacity } : {}), transform: anyDriven ? transform : [] },\n held,\n };\n}\n\n/** One transform group's entries, each driven along the scroll; null if their shapes differ. */\nfunction drivenGroup(\n track: Track,\n resting: unknown,\n along: (\n track: Track,\n pick: (value: unknown) => number | null,\n rest: number,\n ) => ScrollRange | null,\n): TransformChannel[] | null {\n const shapes = track.map((point) => (Array.isArray(point.value) ? point.value : null));\n const shape = shapes.find((entries) => entries?.length) ?? [];\n const keys = shape.map((entry) => Object.keys(entry as object)[0]!);\n const channels: TransformChannel[] = [];\n for (const [index, key] of keys.entries()) {\n const identity = key.startsWith('scale') ? 1 : 0;\n const pick = (value: unknown) => {\n const entries = Array.isArray(value) ? value : null;\n if (!entries?.length) return identity;\n const entry = entries[index] as Record<string, unknown> | undefined;\n return entry && key in entry ? amount(key, entry[key]) : null;\n };\n const range = along(track, pick, restingAmount(resting, index, key, identity));\n if (!range) return null;\n channels.push({ property: key, range });\n }\n return channels;\n}\n\n/** A transform group no keyframe touches, held as the element has it. */\nfunction heldGroup(resting: unknown): TransformChannel[] | null {\n if (!Array.isArray(resting)) return [];\n const channels: TransformChannel[] = [];\n for (const entry of resting as Record<string, unknown>[]) {\n const [key, value] = Object.entries(entry)[0] ?? [];\n const number = key === undefined ? null : amount(key, value);\n if (key === undefined || number === null) return null;\n channels.push({ property: key, value: number });\n }\n return channels;\n}\n\nfunction restingAmount(resting: unknown, index: number, key: string, identity: number): number {\n const entry = Array.isArray(resting) ? (resting[index] as Record<string, unknown>) : undefined;\n return (entry && key in entry ? amount(key, entry[key]) : null) ?? identity;\n}\n\nconst numberOr = (value: unknown, otherwise: number) =>\n typeof value === 'number' ? value : otherwise;\n\nconst RADIANS: Record<string, number> = {\n deg: Math.PI / 180,\n rad: 1,\n grad: Math.PI / 200,\n turn: 2 * Math.PI,\n};\n\n/**\n * A transform entry's amount as native's transform node takes it: points, a factor, or an angle\n * in radians. Null for what it cannot take, a percentage translate.\n */\nfunction amount(key: string, value: unknown): number | null {\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return null;\n const angle = /^(-?[\\d.]+)(deg|rad|grad|turn)$/.exec(value);\n if (angle && (key.startsWith('rotate') || key.startsWith('skew'))) {\n return Number(angle[1]) * RADIANS[angle[2]!]!;\n }\n return null;\n}\n\n/**\n * One property's frames laid along the scroll: each keyframe at its share of the range, eased\n * segments sampled, and the resting value just past an end the animation does not fill.\n */\nfunction laid(\n track: Track,\n pick: (value: unknown) => number | null,\n rest: number,\n spec: AnimationSpec,\n [start, end]: readonly [number, number],\n): ScrollRange | null {\n const values = track.map((point) => pick(point.value));\n if (values.some((value) => value === null)) return null;\n // Each point keeps its own timing function, which eases the stretch it starts.\n const numeric = track.map((point, i) => ({ ...point, value: values[i]! }));\n const points = sampled(numeric, spec, start, Math.max(end - start, 1e-3));\n const fills = spec.fill;\n const backwards = fills === 'backwards' || fills === 'both';\n const forwards = fills === 'forwards' || fills === 'both';\n if (!backwards) points.unshift([points[0]![0] - 1e-3, rest]);\n if (!forwards) points.push([points.at(-1)![0] + 1e-3, rest]);\n return { input: points.map(([x]) => x), output: points.map(([, y]) => round(y)) };\n}\n\n/** A numeric track's points along the scroll, each eased segment sampled, in offset order. */\nfunction sampled(\n numeric: readonly (TrackPoint & { value: number })[],\n spec: AnimationSpec,\n start: number,\n span: number,\n): [number, number][] {\n const reversed = spec.direction === 'reverse' || spec.direction === 'alternate-reverse';\n const points: [number, number][] = [];\n for (let i = 0; i < numeric.length - 1; i++) {\n const from = numeric[i]!;\n const to = numeric[i + 1]!;\n const steps = isLinear(from.easing ?? spec.easing) ? 1 : SAMPLES;\n for (let s = i === 0 ? 0 : 1; s <= steps; s++) {\n const offset = from.offset + ((to.offset - from.offset) * s) / steps;\n const value = at(numeric, offset, spec.easing) as number;\n points.push([start + (reversed ? 1 - offset : offset) * span, value]);\n }\n }\n if (points.length === 1) points.push([start + span, points[0]![1]]);\n return points.sort((a, b) => a[0] - b[0]);\n}\n\nconst isLinear = (easing: readonly number[]) =>\n easing[0] === 0 && easing[1] === 0 && easing[2] === 1 && easing[3] === 1;\n\nconst round = (value: number) => Math.round(value * 1e6) / 1e6;\n\n/** The values a scroll-driven animation commits: its frame at the offset the view starts at. */\nexport function firstFrame(\n tracks: ReadonlyMap<string, Track>,\n spec: AnimationSpec,\n range: readonly [number, number],\n): Record<string, unknown> {\n const [start, end] = range;\n const reversed = spec.direction === 'reverse' || spec.direction === 'alternate-reverse';\n const before = 0 < start;\n if (before && spec.fill !== 'backwards' && spec.fill !== 'both') return {};\n const through = Math.min(1, Math.max(0, (0 - start) / Math.max(end - start, 1e-3)));\n const local = reversed ? 1 - through : through;\n const values: Record<string, unknown> = {};\n for (const [property, track] of tracks) values[property] = at(track, local, spec.easing);\n return values;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scroll-animation.js","sourceRoot":"","sources":["../src/scroll-animation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A `@keyframes` animation played by a scroll view's offset: `animation-timeline: scroll()`.\n *\n * Native interpolates linearly between the points it is given and clamps past both ends, which is\n * all a scroll-driven animation needs from it once its keyframes are laid along the offset: each\n * keyframe sits at its share of the range, a segment's timing function is sampled into points\n * along it, and an unfilled end falls back to the resting value just outside the range. What\n * native can animate this way is opacity and the transforms; anything else holds its first frame.\n */\nimport type { ScrollRange } from './native-drive.ts';\nimport { at, type AnimationSpec, type TrackPoint } from './transition.ts';\n\ntype Track = readonly { offset: number; value: unknown }[];\n\n/** The channels a scroll-driven animation drives natively, in the order native applies them. */\nexport interface DrivenChannels {\n readonly opacity?: ScrollRange;\n /** Every transform entry the view paints: driven by the scroll, or held as it is. */\n readonly transform: readonly TransformChannel[];\n}\n\nexport type TransformChannel =\n | { readonly property: string; readonly range: ScrollRange }\n | { readonly property: string; readonly value: number };\n\n/** What a range with no end, or a percentage of one, stands in for until the scroll view says. */\nconst UNKNOWN_EXTENT = 1e6;\n\n/** Points per eased segment: enough that the curve between them reads as the curve. */\nconst SAMPLES = 12;\n\n/** The transform groups in the order CSS applies them, as the engine composes them. */\nconst GROUPS = ['__translate', '__rotate', '__scale', 'transform'] as const;\n\n/** Where the range starts and ends on the scroll view's offset. */\nexport function rangeOf(spec: AnimationSpec, extent: number | null): [number, number] {\n const end = (value: number | string | undefined, otherwise: number): number => {\n if (value === undefined) return otherwise;\n if (typeof value === 'number') return value;\n return (parseFloat(value) / 100) * (extent ?? UNKNOWN_EXTENT);\n };\n return [end(spec.range?.start, 0), end(spec.range?.end, extent ?? UNKNOWN_EXTENT)];\n}\n\n/**\n * The channels for an animation's tracks over a range, and the properties it cannot drive\n * natively, which the caller holds at their first frame.\n */\nexport function scrollChannels(\n tracks: ReadonlyMap<string, Track>,\n spec: AnimationSpec,\n resting: Readonly<Record<string, unknown>>,\n range: readonly [number, number],\n): { channels: DrivenChannels; held: string[] } {\n const along = (track: Track, pick: (value: unknown) => number | null, rest: number) =>\n laid(track, pick, rest, spec, range);\n const held: string[] = [];\n let opacity: ScrollRange | undefined;\n const transform: TransformChannel[] = [];\n for (const [property, track] of tracks) {\n if (property === 'opacity') {\n opacity =\n along(\n track,\n (value) => (typeof value === 'number' ? value : null),\n numberOr(resting['opacity'], 1),\n ) ?? undefined;\n if (!opacity) held.push(property);\n } else if (!(GROUPS as readonly string[]).includes(property)) {\n held.push(property);\n }\n }\n for (const group of GROUPS) {\n const track = tracks.get(group);\n const channels = track ? drivenGroup(track, resting[group], along) : heldGroup(resting[group]);\n if (channels === null) held.push(group);\n else transform.push(...channels);\n }\n const anyDriven = transform.some((channel) => 'range' in channel);\n return {\n channels: { ...(opacity ? { opacity } : {}), transform: anyDriven ? transform : [] },\n held,\n };\n}\n\n/**\n * The channels for an animation played by the clock, along a value native runs from nothing to\n * `span`: one iteration along 0 to 1, and for `alternate` the way back along 1 to 2, so that\n * native repeats there and back as one animation. Both ends are the frames' own: what the\n * element rests at is committed when the animation ends, and not played.\n */\nexport function clockChannels(\n tracks: ReadonlyMap<string, Track>,\n spec: AnimationSpec,\n resting: Readonly<Record<string, unknown>>,\n): { channels: DrivenChannels; held: string[]; span: number } {\n const way = (direction: AnimationSpec['direction'], range: readonly [number, number]) =>\n scrollChannels(tracks, { ...spec, fill: 'both', direction }, resting, range);\n if (spec.direction !== 'alternate' && spec.direction !== 'alternate-reverse') {\n return { ...way(spec.direction, [0, 1]), span: 1 };\n }\n const reversed = spec.direction === 'alternate-reverse';\n const out = way(reversed ? 'reverse' : undefined, [0, 1]);\n const back = way(reversed ? undefined : 'reverse', [1, 2]);\n const join = (a: ScrollRange, b: ScrollRange): ScrollRange => ({\n input: [...a.input, ...b.input.slice(1)],\n output: [...a.output, ...b.output.slice(1)],\n });\n const transform = out.channels.transform.map((channel, i) => {\n const other = back.channels.transform[i];\n return 'range' in channel && other && 'range' in other\n ? { property: channel.property, range: join(channel.range, other.range) }\n : channel;\n });\n const opacity =\n out.channels.opacity && back.channels.opacity\n ? { opacity: join(out.channels.opacity, back.channels.opacity) }\n : {};\n return { channels: { ...opacity, transform }, held: out.held, span: 2 };\n}\n\n/**\n * One transform group's entries, each driven along the scroll; null if their shapes differ. A\n * frame, or the element at rest, with fewer functions than the longest is that list with the\n * rest at their identity, which is how CSS eases a list into a longer one that starts the same.\n */\nfunction drivenGroup(\n track: Track,\n resting: unknown,\n along: (\n track: Track,\n pick: (value: unknown) => number | null,\n rest: number,\n ) => ScrollRange | null,\n): TransformChannel[] | null {\n const shapes = track.map((point) => (Array.isArray(point.value) ? point.value : []));\n if (Array.isArray(resting)) shapes.push(resting);\n const shape = shapes.reduce((longest, entries) =>\n entries.length > longest.length ? entries : longest,\n );\n const keys = shape.map((entry) => Object.keys(entry as object)[0]!);\n const channels: TransformChannel[] = [];\n for (const [index, key] of keys.entries()) {\n const identity = key.startsWith('scale') ? 1 : 0;\n const pick = (value: unknown) => {\n const entries = Array.isArray(value) ? value : null;\n if (!entries?.length) return identity;\n const entry = entries[index] as Record<string, unknown> | undefined;\n // Past the end of a shorter list. Where it differs before its end, an earlier entry says.\n if (!entry) return identity;\n return key in entry ? amount(key, entry[key]) : null;\n };\n const range = along(track, pick, restingAmount(resting, index, key, identity));\n if (!range) return null;\n channels.push({ property: key, range });\n }\n return channels;\n}\n\n/** A transform group no keyframe touches, held as the element has it. */\nfunction heldGroup(resting: unknown): TransformChannel[] | null {\n if (!Array.isArray(resting)) return [];\n const channels: TransformChannel[] = [];\n for (const entry of resting as Record<string, unknown>[]) {\n const [key, value] = Object.entries(entry)[0] ?? [];\n const number = key === undefined ? null : amount(key, value);\n if (key === undefined || number === null) return null;\n channels.push({ property: key, value: number });\n }\n return channels;\n}\n\nfunction restingAmount(resting: unknown, index: number, key: string, identity: number): number {\n const entry = Array.isArray(resting) ? (resting[index] as Record<string, unknown>) : undefined;\n return (entry && key in entry ? amount(key, entry[key]) : null) ?? identity;\n}\n\nconst numberOr = (value: unknown, otherwise: number) =>\n typeof value === 'number' ? value : otherwise;\n\nconst RADIANS: Record<string, number> = {\n deg: Math.PI / 180,\n rad: 1,\n grad: Math.PI / 200,\n turn: 2 * Math.PI,\n};\n\n/** The side of a box each move by a share is a share of. */\nconst SHARED: Readonly<Record<string, 'width' | 'height'>> = {\n translateX: 'width',\n translateY: 'height',\n};\n\nconst isShare = (value: unknown): value is string =>\n typeof value === 'string' && value.endsWith('%');\n\nconst sharesIn = (value: unknown): boolean =>\n Array.isArray(value) &&\n value.some((entry) =>\n Object.entries(entry as object).some(([key, to]) => key in SHARED && isShare(to)),\n );\n\n/** Whether an animation moves a box by a share of its own size, in a frame or at rest. */\nexport function movesByShare(\n tracks: ReadonlyMap<string, Track>,\n resting: Readonly<Record<string, unknown>>,\n): boolean {\n return GROUPS.some(\n (group) =>\n sharesIn(resting[group]) ||\n tracks.get(group)?.some((point) => sharesIn(point.value)) === true,\n );\n}\n\n/** A transform list with each move by a share as the points it is of a box of `size`. */\nfunction inPoints(value: unknown, size: { width: number; height: number }): unknown {\n if (!sharesIn(value)) return value;\n return (value as Record<string, unknown>[]).map((entry) => {\n const [key, to] = Object.entries(entry)[0]!;\n const side = SHARED[key];\n return side && isShare(to) ? { [key]: (size[side] * Number.parseFloat(to)) / 100 } : entry;\n });\n}\n\n/**\n * An animation's tracks and resting style with every move by a share of the box as points, which\n * is what native interpolates: `translateX(200%)` of a box 300 wide is 600.\n */\nexport function sharesAsPoints<T extends Track>(\n tracks: ReadonlyMap<string, T>,\n resting: Readonly<Record<string, unknown>>,\n size: { width: number; height: number },\n): { tracks: Map<string, T>; resting: Record<string, unknown> } {\n const sized = new Map(tracks);\n const rest = { ...resting };\n for (const group of GROUPS) {\n const track = tracks.get(group);\n if (track) {\n sized.set(\n group,\n track.map((point) => ({ ...point, value: inPoints(point.value, size) })) as never,\n );\n }\n if (group in rest) rest[group] = inPoints(rest[group], size);\n }\n return { tracks: sized, resting: rest };\n}\n\n/**\n * A transform entry's amount as native's transform node takes it: points, a factor, or an angle\n * in radians. Null for what it cannot take, a percentage translate.\n */\nfunction amount(key: string, value: unknown): number | null {\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return null;\n const angle = /^(-?[\\d.]+)(deg|rad|grad|turn)$/.exec(value);\n if (angle && (key.startsWith('rotate') || key.startsWith('skew'))) {\n return Number(angle[1]) * RADIANS[angle[2]!]!;\n }\n return null;\n}\n\n/**\n * One property's frames laid along the scroll: each keyframe at its share of the range, eased\n * segments sampled, and the resting value just past an end the animation does not fill.\n */\nfunction laid(\n track: Track,\n pick: (value: unknown) => number | null,\n rest: number,\n spec: AnimationSpec,\n [start, end]: readonly [number, number],\n): ScrollRange | null {\n const values = track.map((point) => pick(point.value));\n if (values.some((value) => value === null)) return null;\n // Each point keeps its own timing function, which eases the stretch it starts.\n const numeric = track.map((point, i) => ({ ...point, value: values[i]! }));\n const points = sampled(numeric, spec, start, Math.max(end - start, 1e-3));\n const fills = spec.fill;\n const backwards = fills === 'backwards' || fills === 'both';\n const forwards = fills === 'forwards' || fills === 'both';\n if (!backwards) points.unshift([points[0]![0] - 1e-3, rest]);\n if (!forwards) points.push([points.at(-1)![0] + 1e-3, rest]);\n return { input: points.map(([x]) => x), output: points.map(([, y]) => round(y)) };\n}\n\n/** A numeric track's points along the scroll, each eased segment sampled, in offset order. */\nfunction sampled(\n numeric: readonly (TrackPoint & { value: number })[],\n spec: AnimationSpec,\n start: number,\n span: number,\n): [number, number][] {\n const reversed = spec.direction === 'reverse' || spec.direction === 'alternate-reverse';\n const points: [number, number][] = [];\n for (let i = 0; i < numeric.length - 1; i++) {\n const from = numeric[i]!;\n const to = numeric[i + 1]!;\n const steps = isLinear(from.easing ?? spec.easing) ? 1 : SAMPLES;\n for (let s = i === 0 ? 0 : 1; s <= steps; s++) {\n const offset = from.offset + ((to.offset - from.offset) * s) / steps;\n const value = at(numeric, offset, spec.easing) as number;\n points.push([start + (reversed ? 1 - offset : offset) * span, value]);\n }\n }\n if (points.length === 1) points.push([start + span, points[0]![1]]);\n return points.sort((a, b) => a[0] - b[0]);\n}\n\nconst isLinear = (easing: readonly number[]) =>\n easing[0] === 0 && easing[1] === 0 && easing[2] === 1 && easing[3] === 1;\n\nconst round = (value: number) => Math.round(value * 1e6) / 1e6;\n\n/** The values a scroll-driven animation commits: its frame at the offset the view starts at. */\nexport function firstFrame(\n tracks: ReadonlyMap<string, Track>,\n spec: AnimationSpec,\n range: readonly [number, number],\n): Record<string, unknown> {\n const [start, end] = range;\n const reversed = spec.direction === 'reverse' || spec.direction === 'alternate-reverse';\n const before = 0 < start;\n if (before && spec.fill !== 'backwards' && spec.fill !== 'both') return {};\n const through = Math.min(1, Math.max(0, (0 - start) / Math.max(end - start, 1e-3)));\n const local = reversed ? 1 - through : through;\n const values: Record<string, unknown> = {};\n for (const [property, track] of tracks) values[property] = at(track, local, spec.easing);\n return values;\n}\n"]}
|
package/dist/transition.d.ts
CHANGED
|
@@ -29,6 +29,13 @@ export interface Transition {
|
|
|
29
29
|
done: boolean;
|
|
30
30
|
}
|
|
31
31
|
export declare function bezier(points: readonly number[], x: number): number;
|
|
32
|
+
/** How many degrees one of each hue unit is. */
|
|
33
|
+
export declare const HUE_DEGREES: Record<string, number>;
|
|
34
|
+
type Channels = [number, number, number];
|
|
35
|
+
/** `hsl()`'s hue in degrees and its saturation and lightness as fractions, as sRGB from 0 to 1. */
|
|
36
|
+
export declare function hslToSrgb([h, s, l]: Channels): Channels;
|
|
37
|
+
/** `hwb()`'s hue in degrees and its whiteness and blackness as fractions, as sRGB from 0 to 1. */
|
|
38
|
+
export declare function hwbToSrgb([h, w, b]: Channels): Channels;
|
|
32
39
|
/** A colour as `[r, g, b, a]`, or null for a form not worth parsing. */
|
|
33
40
|
export declare function parseColor(value: unknown): [number, number, number, number] | null;
|
|
34
41
|
/** Whether a word is a CSS colour name, such as `red` or `transparent`. */
|
|
@@ -104,6 +111,13 @@ export interface RunningAnimation {
|
|
|
104
111
|
/** What the properties read right now. Empty once a finished animation stops filling. */
|
|
105
112
|
values: Record<string, unknown>;
|
|
106
113
|
done: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Set while native plays it, with no frame of it in JavaScript: what stops it there. `values`
|
|
116
|
+
* is then the frame it started at, which is what the view is committed with.
|
|
117
|
+
*/
|
|
118
|
+
native?: {
|
|
119
|
+
stop(): void;
|
|
120
|
+
};
|
|
107
121
|
}
|
|
108
122
|
/**
|
|
109
123
|
* Build one track per property the frames touch.
|
|
@@ -148,3 +162,4 @@ export declare function at(track: readonly TrackPoint[], local: number, easing:
|
|
|
148
162
|
* reason: it must not be one.
|
|
149
163
|
*/
|
|
150
164
|
export declare function animationEvent(topLevelType: string, target: unknown, name: string): unknown;
|
|
165
|
+
export {};
|
package/dist/transition.js
CHANGED
|
@@ -45,6 +45,47 @@ export function bezier(points, x) {
|
|
|
45
45
|
}
|
|
46
46
|
const HEX = /^#([0-9a-f]{3,8})$/i;
|
|
47
47
|
const RGB = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+))?\s*\)$/i;
|
|
48
|
+
/** `hsl()` and `hwb()`, with commas or without: a hue, two percentages, and an alpha if any. */
|
|
49
|
+
const HUED = /^(hsla?|hwb)\(\s*(-?[\d.]+)(deg|grad|rad|turn)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%(?:[\s,/]+([\d.]+)(%)?)?\s*\)$/i;
|
|
50
|
+
/** How many degrees one of each hue unit is. */
|
|
51
|
+
export const HUE_DEGREES = {
|
|
52
|
+
deg: 1,
|
|
53
|
+
grad: 0.9,
|
|
54
|
+
rad: 180 / Math.PI,
|
|
55
|
+
turn: 360,
|
|
56
|
+
};
|
|
57
|
+
/** `hsl()`'s hue in degrees and its saturation and lightness as fractions, as sRGB from 0 to 1. */
|
|
58
|
+
export function hslToSrgb([h, s, l]) {
|
|
59
|
+
const hue = Number.isFinite(h) ? ((h % 360) + 360) % 360 : 0;
|
|
60
|
+
const f = (n) => {
|
|
61
|
+
const k = (n + hue / 30) % 12;
|
|
62
|
+
return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
63
|
+
};
|
|
64
|
+
return [f(0), f(8), f(4)];
|
|
65
|
+
}
|
|
66
|
+
/** `hwb()`'s hue in degrees and its whiteness and blackness as fractions, as sRGB from 0 to 1. */
|
|
67
|
+
export function hwbToSrgb([h, w, b]) {
|
|
68
|
+
if (w + b >= 1) {
|
|
69
|
+
const grey = w / (w + b);
|
|
70
|
+
return [grey, grey, grey];
|
|
71
|
+
}
|
|
72
|
+
return hslToSrgb([h, 1, 0.5]).map((c) => c * (1 - w - b) + w);
|
|
73
|
+
}
|
|
74
|
+
/** A colour written as `hsl()` or `hwb()`, as `[r, g, b, a]`. */
|
|
75
|
+
function parseHued(value) {
|
|
76
|
+
const parts = HUED.exec(value);
|
|
77
|
+
if (!parts)
|
|
78
|
+
return null;
|
|
79
|
+
const [, name, hue, unit = 'deg', first, second, alpha, percent] = parts;
|
|
80
|
+
const convert = name.toLowerCase() === 'hwb' ? hwbToSrgb : hslToSrgb;
|
|
81
|
+
const [r, g, b] = convert([
|
|
82
|
+
Number(hue) * HUE_DEGREES[unit.toLowerCase()],
|
|
83
|
+
Math.min(1, Number(first) / 100),
|
|
84
|
+
Math.min(1, Number(second) / 100),
|
|
85
|
+
]).map((channel) => Math.round(channel * 255));
|
|
86
|
+
const opacity = alpha === undefined ? 1 : Number(alpha) / (percent ? 100 : 1);
|
|
87
|
+
return [r, g, b, Math.min(1, opacity)];
|
|
88
|
+
}
|
|
48
89
|
/**
|
|
49
90
|
* Every CSS named colour, as the `[r, g, b, a]` the compiler's own `namedColor()` in
|
|
50
91
|
* `packages/metro/css/values.cjs` resolves it to - generated from that table rather than typed
|
|
@@ -229,7 +270,7 @@ export function parseColor(value) {
|
|
|
229
270
|
return [part(0), part(1), part(2), alpha];
|
|
230
271
|
}
|
|
231
272
|
const named = NAMED_COLORS[value.toLowerCase()];
|
|
232
|
-
return named ? [...named] :
|
|
273
|
+
return named ? [...named] : parseHued(value);
|
|
233
274
|
}
|
|
234
275
|
/** Whether a word is a CSS colour name, such as `red` or `transparent`. */
|
|
235
276
|
export function isNamedColor(value) {
|
|
@@ -267,8 +308,9 @@ export function tween(from, to, t) {
|
|
|
267
308
|
return null;
|
|
268
309
|
const a = UNIT.exec(from);
|
|
269
310
|
const b = UNIT.exec(to);
|
|
270
|
-
// Matching units only. Converting between them needs a table, and for
|
|
271
|
-
//
|
|
311
|
+
// Matching units only. Converting between them needs a table, and for a pair anybody actually
|
|
312
|
+
// writes, `0deg` to `1turn`, the answer would be a guess. A translate by a percentage and by a
|
|
313
|
+
// length is the pair with an answer, and `interpolateTransform` gives it: see `summed`.
|
|
272
314
|
if (!a || !b || a[2] !== b[2])
|
|
273
315
|
return null;
|
|
274
316
|
const value = Number(a[1]) + (Number(b[1]) - Number(a[1])) * t;
|
|
@@ -296,6 +338,78 @@ function operation(entry) {
|
|
|
296
338
|
const keys = Object.keys(entry);
|
|
297
339
|
return keys.length === 1 ? keys[0] : null;
|
|
298
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Two lists as lists of one length: the shorter with the operations the longer goes on to, each
|
|
343
|
+
* at rest, which is how CSS blends `translateY(4px)` into `translateY(8px) scale(0.5)`. Where the
|
|
344
|
+
* two start differently the caller finds nothing between them.
|
|
345
|
+
*/
|
|
346
|
+
function sameLength(a, b) {
|
|
347
|
+
if (a.length === b.length)
|
|
348
|
+
return [a, b];
|
|
349
|
+
const [short, long] = a.length < b.length ? [a, b] : [b, a];
|
|
350
|
+
const padded = [...short, ...atRest(long.slice(short.length))];
|
|
351
|
+
return a.length < b.length ? [padded, b] : [a, padded];
|
|
352
|
+
}
|
|
353
|
+
/** The translates: the operations whose amount may be a share of the box, a length, or both. */
|
|
354
|
+
const TRANSLATES = new Set(['translateX', 'translateY']);
|
|
355
|
+
function sumOf(value) {
|
|
356
|
+
if (Array.isArray(value))
|
|
357
|
+
return value;
|
|
358
|
+
if (typeof value === 'number')
|
|
359
|
+
return [0, value];
|
|
360
|
+
const percent = typeof value === 'string' ? PERCENTAGE.exec(value) : null;
|
|
361
|
+
return percent ? [Number(percent[1]), 0] : null;
|
|
362
|
+
}
|
|
363
|
+
/** Whether a translate by a percentage is followed at once by one by points along the same axis. */
|
|
364
|
+
function isSum(before, entry) {
|
|
365
|
+
const name = operation(entry);
|
|
366
|
+
if (name === null || !TRANSLATES.has(name) || operation(before) !== name)
|
|
367
|
+
return false;
|
|
368
|
+
const percent = before[name];
|
|
369
|
+
const points = entry[name];
|
|
370
|
+
return typeof points === 'number' && typeof percent === 'string' && PERCENTAGE.test(percent);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* A list with each such pair read as the one translate it comes to, its amount a `Sum`.
|
|
374
|
+
*
|
|
375
|
+
* `translateY(-25%) translateY(-17px)` is how a frame between `translateY(-50%)` and
|
|
376
|
+
* `translateY(-34px)` is written: CSS eases the two as `calc(-25% - 17px)`, and two translates
|
|
377
|
+
* along one axis add up to that whatever size the box is, which nothing here knows. A transition
|
|
378
|
+
* turned round part way starts from such a frame, so the pair has to pair with one translate.
|
|
379
|
+
*/
|
|
380
|
+
function summed(list) {
|
|
381
|
+
if (!list.some((entry, i) => i > 0 && isSum(list[i - 1], entry)))
|
|
382
|
+
return list;
|
|
383
|
+
const out = [];
|
|
384
|
+
for (const entry of list) {
|
|
385
|
+
const before = out.at(-1);
|
|
386
|
+
if (!isSum(before, entry)) {
|
|
387
|
+
out.push(entry);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const name = operation(entry);
|
|
391
|
+
const percent = before[name];
|
|
392
|
+
out[out.length - 1] = {
|
|
393
|
+
[name]: [parseFloat(percent), entry[name]],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
/** A translate part way between two amounts `tween` cannot blend, as its pair; null if not one. */
|
|
399
|
+
function tweenSum(name, from, to, t) {
|
|
400
|
+
const a = TRANSLATES.has(name) ? sumOf(from) : null;
|
|
401
|
+
const b = a && sumOf(to);
|
|
402
|
+
if (!a || !b)
|
|
403
|
+
return null;
|
|
404
|
+
const percent = Math.round((a[0] + (b[0] - a[0]) * t) * 1000) / 1000;
|
|
405
|
+
const points = a[1] + (b[1] - a[1]) * t;
|
|
406
|
+
// At an end one of the two is nothing, and the frame is the one translate that was written.
|
|
407
|
+
if (percent === 0)
|
|
408
|
+
return [{ [name]: points }];
|
|
409
|
+
return points === 0
|
|
410
|
+
? [{ [name]: `${percent}%` }]
|
|
411
|
+
: [{ [name]: `${percent}%` }, { [name]: points }];
|
|
412
|
+
}
|
|
299
413
|
/**
|
|
300
414
|
* Two transform lists blended operation by operation.
|
|
301
415
|
*
|
|
@@ -303,6 +417,9 @@ function operation(entry) {
|
|
|
303
417
|
* matrix, which is a great deal of arithmetic for a case an author can always write out. Anything
|
|
304
418
|
* that does not line up returns null and steps instead, which is what CSS falls back to.
|
|
305
419
|
*
|
|
420
|
+
* A translate by a share of the box and one by a length do line up, and the frame between them is
|
|
421
|
+
* two translates: see `summed`.
|
|
422
|
+
*
|
|
306
423
|
* A missing side is the ordinary case rather than the exception - `.slider` to
|
|
307
424
|
* `.slider-end { transform: translateX(180px) }`, and the same again when the class comes off -
|
|
308
425
|
* so whichever side is absent is read as each operation at rest, which is CSS's `none`.
|
|
@@ -311,19 +428,19 @@ function interpolateTransform(from, to, t) {
|
|
|
311
428
|
const shape = hasOperations(to) ? to : hasOperations(from) ? from : null;
|
|
312
429
|
if (!shape)
|
|
313
430
|
return null;
|
|
314
|
-
const start = hasOperations(from) ? from : atRest(shape);
|
|
315
|
-
const end = hasOperations(to) ? to : atRest(shape);
|
|
316
|
-
if (start.length !== end.length)
|
|
317
|
-
return null;
|
|
431
|
+
const [start, end] = sameLength(summed(hasOperations(from) ? from : atRest(shape)), summed(hasOperations(to) ? to : atRest(shape)));
|
|
318
432
|
const out = [];
|
|
319
433
|
for (let i = 0; i < end.length; i++) {
|
|
320
434
|
const name = operation(end[i]);
|
|
321
435
|
if (name === null || name !== operation(start[i]))
|
|
322
436
|
return null;
|
|
323
|
-
const
|
|
324
|
-
|
|
437
|
+
const a = start[i][name];
|
|
438
|
+
const b = end[i][name];
|
|
439
|
+
const value = tween(a, b, t);
|
|
440
|
+
const entries = value === null ? tweenSum(name, a, b, t) : [{ [name]: value }];
|
|
441
|
+
if (entries === null)
|
|
325
442
|
return null;
|
|
326
|
-
out.push(
|
|
443
|
+
out.push(...entries);
|
|
327
444
|
}
|
|
328
445
|
return out;
|
|
329
446
|
}
|
|
@@ -371,11 +488,17 @@ function interpolatePercentage(from, to, t) {
|
|
|
371
488
|
* value it has reached rather than the one it is heading for.
|
|
372
489
|
*/
|
|
373
490
|
export function step(state, key, props, rule, now) {
|
|
374
|
-
if (!rule
|
|
491
|
+
if (!rule) {
|
|
375
492
|
// No longer transitioning: forget it, so re-adding the rule later starts fresh.
|
|
376
493
|
state.delete(key);
|
|
377
494
|
return false;
|
|
378
495
|
}
|
|
496
|
+
if (rule.duration <= 0) {
|
|
497
|
+
// Named, with no time to take: it arrives at once, and where it is stays known. A change
|
|
498
|
+
// that comes with a duration, as a ripple's style sets both in one go, starts from here.
|
|
499
|
+
state.set(key, settled(props[key], rule));
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
379
502
|
const seen = state.get(key);
|
|
380
503
|
if (seen === undefined) {
|
|
381
504
|
// First sight of the property. CSS does not animate one of these either; without the rule
|