@ng-native/fabric 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -1
  3. package/dist/color-mix.d.ts +20 -0
  4. package/dist/color-mix.js +306 -0
  5. package/dist/color-mix.js.map +1 -0
  6. package/dist/css.d.ts +615 -0
  7. package/dist/css.js +1405 -0
  8. package/dist/css.js.map +1 -0
  9. package/dist/defer-triggers.d.ts +53 -0
  10. package/dist/defer-triggers.js +184 -0
  11. package/dist/defer-triggers.js.map +1 -0
  12. package/dist/engine.d.ts +1066 -0
  13. package/dist/engine.js +2764 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/fabric.d.ts +20 -0
  16. package/dist/fabric.js +44 -0
  17. package/dist/fabric.js.map +1 -0
  18. package/dist/forms-inputs.d.ts +15 -0
  19. package/dist/forms-inputs.js +54 -0
  20. package/dist/forms-inputs.js.map +1 -0
  21. package/dist/host.d.ts +133 -0
  22. package/dist/host.js +55 -0
  23. package/dist/host.js.map +1 -0
  24. package/dist/index.d.ts +20 -0
  25. package/dist/index.js +21 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/inline-token.d.ts +12 -0
  28. package/dist/inline-token.js +52 -0
  29. package/dist/inline-token.js.map +1 -0
  30. package/dist/inline-transform.d.ts +14 -0
  31. package/dist/inline-transform.js +41 -0
  32. package/dist/inline-transform.js.map +1 -0
  33. package/dist/native-drive.d.ts +100 -0
  34. package/dist/native-drive.js +318 -0
  35. package/dist/native-drive.js.map +1 -0
  36. package/dist/relative-colour.d.ts +17 -0
  37. package/dist/relative-colour.js +71 -0
  38. package/dist/relative-colour.js.map +1 -0
  39. package/dist/scroll-animation.d.ts +41 -0
  40. package/dist/scroll-animation.js +169 -0
  41. package/dist/scroll-animation.js.map +1 -0
  42. package/dist/transition.d.ts +139 -0
  43. package/dist/transition.js +569 -0
  44. package/dist/transition.js.map +1 -0
  45. package/package.json +43 -3
@@ -0,0 +1,569 @@
1
+ /**
2
+ * CSS transitions, the runtime half.
3
+ *
4
+ * React Native has no CSS animation of any kind, so a `transition` in a stylesheet is compiled to
5
+ * a spec and driven here: the engine notices a transitioning property change between commits,
6
+ * holds the old value, and interpolates towards the new one as the clock advances.
7
+ *
8
+ * This runs in JavaScript, unlike `AnimatedStyle`, which can hand a whole animation to the native
9
+ * driver. It has to: a transition is a reaction to the cascade recomputing, and only this side
10
+ * knows a value changed at all. The cost is a commit per frame, which is the same cost a
11
+ * JavaScript-driven `Animated` value has.
12
+ */
13
+ /**
14
+ * A cubic bezier's y at a given x, by bisection.
15
+ *
16
+ * Newton-Raphson converges faster and is what browsers use, but it needs a derivative and a
17
+ * fallback for the flat regions of a curve like `ease-in`. Twenty bisection steps land within
18
+ * 1e-6 on any curve, which is finer than a pixel, and the whole thing is eight lines.
19
+ */
20
+ const curve = (t, a, b) => {
21
+ const u = 1 - t;
22
+ return 3 * u * u * t * a + 3 * u * t * t * b + t * t * t;
23
+ };
24
+ /** A linear curve is both the default and by far the most common, so it skips the search. */
25
+ const isLinear = (p) => p[0] === 0 && p[1] === 0 && p[2] === 1 && p[3] === 1;
26
+ export function bezier(points, x) {
27
+ if (x <= 0)
28
+ return 0;
29
+ if (x >= 1)
30
+ return 1;
31
+ if (isLinear(points))
32
+ return x;
33
+ const [x1 = 0, y1 = 0, x2 = 1, y2 = 1] = points;
34
+ let low = 0;
35
+ let high = 1;
36
+ let t = x;
37
+ for (let i = 0; i < 20; i++) {
38
+ t = (low + high) / 2;
39
+ if (curve(t, x1, x2) < x)
40
+ low = t;
41
+ else
42
+ high = t;
43
+ }
44
+ return curve(t, y1, y2);
45
+ }
46
+ const HEX = /^#([0-9a-f]{3,8})$/i;
47
+ const RGB = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+))?\s*\)$/i;
48
+ /**
49
+ * Every CSS named colour, as the `[r, g, b, a]` the compiler's own `namedColor()` in
50
+ * `packages/metro/css/values.cjs` resolves it to - generated from that table rather than typed
51
+ * out by hand, so there is one authority for what a colour name means and this cannot drift from
52
+ * it. The compiler folds a named colour in a stylesheet into `rgb()` at build time, which is why
53
+ * this table matters only for the rarer case: a value that reaches here without ever having been
54
+ * through the compiler, such as a template binding a keyword straight into a style.
55
+ */
56
+ const NAMED_COLORS = {
57
+ aliceblue: [240, 248, 255, 1],
58
+ antiquewhite: [250, 235, 215, 1],
59
+ aqua: [0, 255, 255, 1],
60
+ aquamarine: [127, 255, 212, 1],
61
+ azure: [240, 255, 255, 1],
62
+ beige: [245, 245, 220, 1],
63
+ bisque: [255, 228, 196, 1],
64
+ black: [0, 0, 0, 1],
65
+ blanchedalmond: [255, 235, 205, 1],
66
+ blue: [0, 0, 255, 1],
67
+ blueviolet: [138, 43, 226, 1],
68
+ brown: [165, 42, 42, 1],
69
+ burlywood: [222, 184, 135, 1],
70
+ cadetblue: [95, 158, 160, 1],
71
+ chartreuse: [127, 255, 0, 1],
72
+ chocolate: [210, 105, 30, 1],
73
+ coral: [255, 127, 80, 1],
74
+ cornflowerblue: [100, 149, 237, 1],
75
+ cornsilk: [255, 248, 220, 1],
76
+ crimson: [220, 20, 60, 1],
77
+ cyan: [0, 255, 255, 1],
78
+ darkblue: [0, 0, 139, 1],
79
+ darkcyan: [0, 139, 139, 1],
80
+ darkgoldenrod: [184, 134, 11, 1],
81
+ darkgray: [169, 169, 169, 1],
82
+ darkgreen: [0, 100, 0, 1],
83
+ darkgrey: [169, 169, 169, 1],
84
+ darkkhaki: [189, 183, 107, 1],
85
+ darkmagenta: [139, 0, 139, 1],
86
+ darkolivegreen: [85, 107, 47, 1],
87
+ darkorange: [255, 140, 0, 1],
88
+ darkorchid: [153, 50, 204, 1],
89
+ darkred: [139, 0, 0, 1],
90
+ darksalmon: [233, 150, 122, 1],
91
+ darkseagreen: [143, 188, 143, 1],
92
+ darkslateblue: [72, 61, 139, 1],
93
+ darkslategray: [47, 79, 79, 1],
94
+ darkslategrey: [47, 79, 79, 1],
95
+ darkturquoise: [0, 206, 209, 1],
96
+ darkviolet: [148, 0, 211, 1],
97
+ deeppink: [255, 20, 147, 1],
98
+ deepskyblue: [0, 191, 255, 1],
99
+ dimgray: [105, 105, 105, 1],
100
+ dimgrey: [105, 105, 105, 1],
101
+ dodgerblue: [30, 144, 255, 1],
102
+ firebrick: [178, 34, 34, 1],
103
+ floralwhite: [255, 250, 240, 1],
104
+ forestgreen: [34, 139, 34, 1],
105
+ fuchsia: [255, 0, 255, 1],
106
+ gainsboro: [220, 220, 220, 1],
107
+ ghostwhite: [248, 248, 255, 1],
108
+ gold: [255, 215, 0, 1],
109
+ goldenrod: [218, 165, 32, 1],
110
+ gray: [128, 128, 128, 1],
111
+ green: [0, 128, 0, 1],
112
+ greenyellow: [173, 255, 47, 1],
113
+ grey: [128, 128, 128, 1],
114
+ honeydew: [240, 255, 240, 1],
115
+ hotpink: [255, 105, 180, 1],
116
+ indianred: [205, 92, 92, 1],
117
+ indigo: [75, 0, 130, 1],
118
+ ivory: [255, 255, 240, 1],
119
+ khaki: [240, 230, 140, 1],
120
+ lavender: [230, 230, 250, 1],
121
+ lavenderblush: [255, 240, 245, 1],
122
+ lawngreen: [124, 252, 0, 1],
123
+ lemonchiffon: [255, 250, 205, 1],
124
+ lightblue: [173, 216, 230, 1],
125
+ lightcoral: [240, 128, 128, 1],
126
+ lightcyan: [224, 255, 255, 1],
127
+ lightgoldenrodyellow: [250, 250, 210, 1],
128
+ lightgray: [211, 211, 211, 1],
129
+ lightgreen: [144, 238, 144, 1],
130
+ lightgrey: [211, 211, 211, 1],
131
+ lightpink: [255, 182, 193, 1],
132
+ lightsalmon: [255, 160, 122, 1],
133
+ lightseagreen: [32, 178, 170, 1],
134
+ lightskyblue: [135, 206, 250, 1],
135
+ lightslategray: [119, 136, 153, 1],
136
+ lightslategrey: [119, 136, 153, 1],
137
+ lightsteelblue: [176, 196, 222, 1],
138
+ lightyellow: [255, 255, 224, 1],
139
+ lime: [0, 255, 0, 1],
140
+ limegreen: [50, 205, 50, 1],
141
+ linen: [250, 240, 230, 1],
142
+ magenta: [255, 0, 255, 1],
143
+ maroon: [128, 0, 0, 1],
144
+ mediumaquamarine: [102, 205, 170, 1],
145
+ mediumblue: [0, 0, 205, 1],
146
+ mediumorchid: [186, 85, 211, 1],
147
+ mediumpurple: [147, 112, 219, 1],
148
+ mediumseagreen: [60, 179, 113, 1],
149
+ mediumslateblue: [123, 104, 238, 1],
150
+ mediumspringgreen: [0, 250, 154, 1],
151
+ mediumturquoise: [72, 209, 204, 1],
152
+ mediumvioletred: [199, 21, 133, 1],
153
+ midnightblue: [25, 25, 112, 1],
154
+ mintcream: [245, 255, 250, 1],
155
+ mistyrose: [255, 228, 225, 1],
156
+ moccasin: [255, 228, 181, 1],
157
+ navajowhite: [255, 222, 173, 1],
158
+ navy: [0, 0, 128, 1],
159
+ oldlace: [253, 245, 230, 1],
160
+ olive: [128, 128, 0, 1],
161
+ olivedrab: [107, 142, 35, 1],
162
+ orange: [255, 165, 0, 1],
163
+ orangered: [255, 69, 0, 1],
164
+ orchid: [218, 112, 214, 1],
165
+ palegoldenrod: [238, 232, 170, 1],
166
+ palegreen: [152, 251, 152, 1],
167
+ paleturquoise: [175, 238, 238, 1],
168
+ palevioletred: [219, 112, 147, 1],
169
+ papayawhip: [255, 239, 213, 1],
170
+ peachpuff: [255, 218, 185, 1],
171
+ peru: [205, 133, 63, 1],
172
+ pink: [255, 192, 203, 1],
173
+ plum: [221, 160, 221, 1],
174
+ powderblue: [176, 224, 230, 1],
175
+ purple: [128, 0, 128, 1],
176
+ rebeccapurple: [102, 51, 153, 1],
177
+ red: [255, 0, 0, 1],
178
+ rosybrown: [188, 143, 143, 1],
179
+ royalblue: [65, 105, 225, 1],
180
+ saddlebrown: [139, 69, 19, 1],
181
+ salmon: [250, 128, 114, 1],
182
+ sandybrown: [244, 164, 96, 1],
183
+ seagreen: [46, 139, 87, 1],
184
+ seashell: [255, 245, 238, 1],
185
+ sienna: [160, 82, 45, 1],
186
+ silver: [192, 192, 192, 1],
187
+ skyblue: [135, 206, 235, 1],
188
+ slateblue: [106, 90, 205, 1],
189
+ slategray: [112, 128, 144, 1],
190
+ slategrey: [112, 128, 144, 1],
191
+ snow: [255, 250, 250, 1],
192
+ springgreen: [0, 255, 127, 1],
193
+ steelblue: [70, 130, 180, 1],
194
+ tan: [210, 180, 140, 1],
195
+ teal: [0, 128, 128, 1],
196
+ thistle: [216, 191, 216, 1],
197
+ tomato: [255, 99, 71, 1],
198
+ transparent: [0, 0, 0, 0],
199
+ turquoise: [64, 224, 208, 1],
200
+ violet: [238, 130, 238, 1],
201
+ wheat: [245, 222, 179, 1],
202
+ white: [255, 255, 255, 1],
203
+ whitesmoke: [245, 245, 245, 1],
204
+ yellow: [255, 255, 0, 1],
205
+ yellowgreen: [154, 205, 50, 1],
206
+ };
207
+ /** A colour as `[r, g, b, a]`, or null for a form not worth parsing. */
208
+ export function parseColor(value) {
209
+ if (typeof value !== 'string')
210
+ return null;
211
+ const rgb = RGB.exec(value);
212
+ if (rgb) {
213
+ return [
214
+ Number(rgb[1]),
215
+ Number(rgb[2]),
216
+ Number(rgb[3]),
217
+ rgb[4] === undefined ? 1 : Number(rgb[4]),
218
+ ];
219
+ }
220
+ const hex = HEX.exec(value);
221
+ if (hex) {
222
+ const digits = hex[1];
223
+ const short = digits.length <= 4;
224
+ const part = (i) => {
225
+ const at = short ? digits.slice(i, i + 1).repeat(2) : digits.slice(i * 2, i * 2 + 2);
226
+ return parseInt(at, 16);
227
+ };
228
+ const alpha = digits.length === 4 || digits.length === 8 ? part(3) / 255 : 1;
229
+ return [part(0), part(1), part(2), alpha];
230
+ }
231
+ const named = NAMED_COLORS[value.toLowerCase()];
232
+ return named ? [...named] : null;
233
+ }
234
+ /** Whether a word is a CSS colour name, such as `red` or `transparent`. */
235
+ export function isNamedColor(value) {
236
+ return Object.hasOwn(NAMED_COLORS, value.toLowerCase());
237
+ }
238
+ /** A transform operation at rest, so a missing transform can be animated from rather than jumped. */
239
+ const IDENTITY = {
240
+ translateX: 0,
241
+ translateY: 0,
242
+ scale: 1,
243
+ scaleX: 1,
244
+ scaleY: 1,
245
+ rotate: '0deg',
246
+ rotateX: '0deg',
247
+ rotateY: '0deg',
248
+ rotateZ: '0deg',
249
+ skewX: '0deg',
250
+ skewY: '0deg',
251
+ };
252
+ /**
253
+ * A number with a unit on it, inside a transform operation.
254
+ *
255
+ * Angles were the only unit this matched, which quietly made every other one step instead of
256
+ * move: a sheet sliding in with `translateY(100%)` sat at 100% for its whole duration and then
257
+ * arrived, which on a phone reads as an animation that does not run at all. Percentages are the
258
+ * ones that matter - they are how a transform says "my own size" without knowing what that is -
259
+ * and lengths cost nothing extra now the shape is the same.
260
+ */
261
+ const UNIT = /^(-?\d*\.?\d+)(deg|rad|grad|turn|%|px|pt|em|rem)$/;
262
+ /** One operation's value: a number, or a number keeping whatever unit it was written in. */
263
+ export function tween(from, to, t) {
264
+ if (typeof from === 'number' && typeof to === 'number')
265
+ return from + (to - from) * t;
266
+ if (typeof from !== 'string' || typeof to !== 'string')
267
+ return null;
268
+ const a = UNIT.exec(from);
269
+ const b = UNIT.exec(to);
270
+ // Matching units only. Converting between them needs a table, and for the pairs anybody
271
+ // actually writes - `0deg` to `1turn`, `0%` to `10px` - the answer would be a guess.
272
+ if (!a || !b || a[2] !== b[2])
273
+ return null;
274
+ const value = Number(a[1]) + (Number(b[1]) - Number(a[1])) * t;
275
+ return `${Math.round(value * 1000) / 1000}${a[2]}`;
276
+ }
277
+ /** The same operations, each at rest: what CSS's `none` means beside a list that has values. */
278
+ function atRest(shape) {
279
+ return shape.map((entry) => {
280
+ const name = operation(entry);
281
+ if (name === null || !(name in IDENTITY))
282
+ return entry;
283
+ // At rest in the unit it is written in, so it can be eased toward: `-25%` from `0%`, and
284
+ // `1turn` from `0turn`. A bare 0 beside a percentage is a pair `tween` cannot blend.
285
+ const written = entry[name];
286
+ const unit = typeof written === 'string' ? UNIT.exec(written)?.[2] : undefined;
287
+ return { [name]: unit ? `0${unit}` : IDENTITY[name] };
288
+ });
289
+ }
290
+ /** A transform list with something in it; `none` is an empty one, and means the same as none. */
291
+ const hasOperations = (value) => Array.isArray(value) && value.length > 0;
292
+ /** The name of a single-key transform operation, or null for anything else. */
293
+ function operation(entry) {
294
+ if (typeof entry !== 'object' || entry === null)
295
+ return null;
296
+ const keys = Object.keys(entry);
297
+ return keys.length === 1 ? keys[0] : null;
298
+ }
299
+ /**
300
+ * Two transform lists blended operation by operation.
301
+ *
302
+ * Matched by position *and* name: blending a translate into a scale would mean decomposing a
303
+ * matrix, which is a great deal of arithmetic for a case an author can always write out. Anything
304
+ * that does not line up returns null and steps instead, which is what CSS falls back to.
305
+ *
306
+ * A missing side is the ordinary case rather than the exception - `.slider` to
307
+ * `.slider-end { transform: translateX(180px) }`, and the same again when the class comes off -
308
+ * so whichever side is absent is read as each operation at rest, which is CSS's `none`.
309
+ */
310
+ function interpolateTransform(from, to, t) {
311
+ const shape = hasOperations(to) ? to : hasOperations(from) ? from : null;
312
+ if (!shape)
313
+ 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;
318
+ const out = [];
319
+ for (let i = 0; i < end.length; i++) {
320
+ const name = operation(end[i]);
321
+ if (name === null || name !== operation(start[i]))
322
+ return null;
323
+ const value = tween(start[i][name], end[i][name], t);
324
+ if (value === null)
325
+ return null;
326
+ out.push({ [name]: value });
327
+ }
328
+ return out;
329
+ }
330
+ /**
331
+ * One step between two values.
332
+ *
333
+ * Numbers, colours and transforms interpolate. Everything else steps to the new value at the end,
334
+ * which is what CSS does for a property it calls discrete, and is right for the keywords and
335
+ * strings that make up most of the rest.
336
+ */
337
+ export function interpolate(from, to, t) {
338
+ if (typeof from === 'number' && typeof to === 'number')
339
+ return from + (to - from) * t;
340
+ const a = parseColor(from);
341
+ const b = parseColor(to);
342
+ if (a && b) {
343
+ const mix = (i) => Math.round(a[i] + (b[i] - a[i]) * t);
344
+ const alpha = Math.round((a[3] + (b[3] - a[3]) * t) * 1000) / 1000;
345
+ return `rgba(${mix(0)}, ${mix(1)}, ${mix(2)}, ${alpha})`;
346
+ }
347
+ const percent = interpolatePercentage(from, to, t);
348
+ if (percent !== null)
349
+ return percent;
350
+ const transform = interpolateTransform(from, to, t);
351
+ if (transform)
352
+ return transform;
353
+ return t >= 1 ? to : from;
354
+ }
355
+ /** `50%` -> `75%`. Both sides have to be percentages; a percentage and a length are not a scale. */
356
+ const PERCENTAGE = /^(-?[\d.]+)%$/;
357
+ function interpolatePercentage(from, to, t) {
358
+ if (typeof from !== 'string' || typeof to !== 'string')
359
+ return null;
360
+ const a = PERCENTAGE.exec(from);
361
+ const b = PERCENTAGE.exec(to);
362
+ if (!a || !b)
363
+ return null;
364
+ const value = Number(a[1]) + (Number(b[1]) - Number(a[1])) * t;
365
+ return `${Math.round(value * 1000) / 1000}%`;
366
+ }
367
+ /**
368
+ * Bring one property up to date, and say whether that started a transition.
369
+ *
370
+ * Mutates `props[key]` to what should be painted now, which for a transition in flight is the
371
+ * value it has reached rather than the one it is heading for.
372
+ */
373
+ export function step(state, key, props, rule, now) {
374
+ if (!rule || rule.duration <= 0) {
375
+ // No longer transitioning: forget it, so re-adding the rule later starts fresh.
376
+ state.delete(key);
377
+ return false;
378
+ }
379
+ const seen = state.get(key);
380
+ if (seen === undefined) {
381
+ // First sight of the property. CSS does not animate one of these either; without the rule
382
+ // every element would animate in from whatever the previous value happened to be.
383
+ state.set(key, settled(props[key], rule));
384
+ return false;
385
+ }
386
+ // Nothing to interpolate when one end is missing: `null` here is `auto`, or a property the
387
+ // element no longer sets at all, and there is no value halfway between a number and "whatever
388
+ // this works out to be". CSS refuses the same transition. Interpolating anyway held the old
389
+ // value for the whole duration, which is how a section that opened to a height nothing had
390
+ // measured stayed shut - and clearing a height is also what stops the layout that would have
391
+ // corrected it, so it stayed shut rather than catching up a frame later.
392
+ //
393
+ // The same goes for a pair with nothing between them at all, a keyword such as `display` or
394
+ // `flex-direction`: CSS changes it at once rather than transitioning it, and `transition: all`
395
+ // covers it. Holding the old one for the duration kept a `display: none` on screen until the
396
+ // end and then made it vanish.
397
+ const started = !Object.is(seen.to, props[key]);
398
+ if (started && !interpolable(seen.current, props[key])) {
399
+ state.set(key, settled(props[key], rule));
400
+ return false;
401
+ }
402
+ if (started)
403
+ redirect(seen, props[key], rule, now);
404
+ if (!seen.done)
405
+ props[key] = seen.current;
406
+ return started;
407
+ }
408
+ /**
409
+ * Whether there is a pair here to interpolate at all: two numbers, two colours, two percentages,
410
+ * or two transform lists of the same shape.
411
+ *
412
+ * A transform is the one thing that animates from nothing, because "no transform" has a value -
413
+ * the identity - and `interpolateTransform` starts there. Everything else needs both ends.
414
+ */
415
+ function interpolable(from, to) {
416
+ if (typeof from === 'number' && typeof to === 'number')
417
+ return true;
418
+ if (parseColor(from) !== null && parseColor(to) !== null)
419
+ return true;
420
+ if (interpolatePercentage(from, to, 0) !== null)
421
+ return true;
422
+ return interpolateTransform(from, to, 0) !== null;
423
+ }
424
+ /** A property seen for the first time: recorded so the next change has something to aim from. */
425
+ export function settled(value, rule) {
426
+ return {
427
+ from: value,
428
+ to: value,
429
+ start: 0,
430
+ duration: 0,
431
+ easing: rule.easing,
432
+ current: value,
433
+ done: true,
434
+ };
435
+ }
436
+ /** Aim an existing transition at a new value, starting from wherever it had got to. */
437
+ export function redirect(transition, target, rule, now) {
438
+ transition.from = transition.current;
439
+ transition.to = target;
440
+ transition.start = now + rule.delay;
441
+ transition.duration = rule.duration;
442
+ transition.easing = rule.easing;
443
+ transition.done = false;
444
+ }
445
+ /**
446
+ * Build one track per property the frames touch.
447
+ *
448
+ * A property that no frame sets at 0% or 100% is anchored to the value the element would
449
+ * otherwise have, which is what CSS means by an implicit keyframe: `to { opacity: 1 }` alone
450
+ * animates from wherever the element already was.
451
+ */
452
+ export function tracksOf(frames, resting) {
453
+ const tracks = new Map();
454
+ for (const frame of frames) {
455
+ for (const property of Object.keys(frame.declarations)) {
456
+ const track = tracks.get(property) ?? [];
457
+ const point = { offset: frame.offset, value: frame.declarations[property] };
458
+ track.push(frame.easing ? { ...point, easing: frame.easing } : point);
459
+ tracks.set(property, track);
460
+ }
461
+ }
462
+ for (const [property, track] of tracks) {
463
+ const anchor = resting[property] ?? INITIAL[property];
464
+ if (track[0].offset > 0)
465
+ track.unshift({ offset: 0, value: anchor });
466
+ if (track[track.length - 1].offset < 1) {
467
+ track.push({ offset: 1, value: anchor });
468
+ }
469
+ }
470
+ return tracks;
471
+ }
472
+ /**
473
+ * What a property is worth when the element has not said.
474
+ *
475
+ * CSS anchors an implicit keyframe to the element's *computed* value, and a property nobody wrote
476
+ * still has one - `opacity` is 1, not nothing. Anchoring to nothing instead leaves a frame with no
477
+ * value to interpolate towards, so the animation holds its first value and jumps at the end.
478
+ *
479
+ * That is what `animate-pulse` was doing. It is a single frame, `50% { opacity: 0.5 }`, on a
480
+ * skeleton that declares no opacity of its own - so both of its implicit frames were empty and the
481
+ * pulse did not fade, in every skeleton in the library.
482
+ *
483
+ * Only the properties that appear in an animation and have a meaningful resting value. A length
484
+ * defaulting to zero is the same as absent for anything here, and a colour has no initial worth
485
+ * animating from.
486
+ */
487
+ const INITIAL = {
488
+ opacity: 1,
489
+ scale: 1,
490
+ rotate: '0deg',
491
+ };
492
+ /**
493
+ * Where an animation has got to, as the properties to paint.
494
+ *
495
+ * The easing runs per segment rather than across the whole pass, which is what CSS does with a
496
+ * single timing function: each pair of frames is eased on its own.
497
+ */
498
+ export function sample(running, now) {
499
+ const { spec } = running;
500
+ const elapsed = now - running.start - spec.delay;
501
+ // A null count is `infinite`. Multiplying by it would give 0, which reads as an animation that
502
+ // finished before its first frame - so an infinite animation would paint its last frame once
503
+ // and stop, which is exactly what it did.
504
+ const total = spec.duration * (spec.iterations ?? Infinity);
505
+ const finished = spec.duration <= 0 || (elapsed >= total && total !== Infinity);
506
+ // Before the delay is up, an animation shows its first frame only if it fills backwards.
507
+ const waiting = !finished && elapsed < 0;
508
+ if (waiting && spec.fill !== 'backwards' && spec.fill !== 'both')
509
+ return { values: {}, finished };
510
+ const local = directedProgress(spec, finished, elapsed);
511
+ const values = {};
512
+ for (const [property, track] of running.tracks) {
513
+ values[property] = at(track, local, spec.easing);
514
+ }
515
+ return { values, finished };
516
+ }
517
+ /**
518
+ * How far through its frames the animation is, 0 to 1, in the direction its current iteration
519
+ * runs. A finished animation is at the end of its last iteration, not the start of one after it,
520
+ * and a fractional count ends part of the way through that iteration.
521
+ */
522
+ function directedProgress(spec, finished, elapsed) {
523
+ const position = finished ? (spec.iterations ?? 1) : Math.max(0, elapsed) / spec.duration;
524
+ const iteration = finished ? Math.max(0, Math.ceil(position) - 1) : Math.floor(position);
525
+ const through = Math.min(1, Math.max(0, position - iteration));
526
+ return backwards(spec.direction, iteration) ? 1 - through : through;
527
+ }
528
+ /** Whether this iteration runs from the last frame to the first. */
529
+ function backwards(direction, iteration) {
530
+ switch (direction) {
531
+ case 'reverse':
532
+ return true;
533
+ case 'alternate':
534
+ return iteration % 2 === 1;
535
+ case 'alternate-reverse':
536
+ return iteration % 2 === 0;
537
+ default:
538
+ return false;
539
+ }
540
+ }
541
+ export function at(track, local, easing) {
542
+ let index = 0;
543
+ while (index < track.length - 2 && track[index + 1].offset <= local)
544
+ index++;
545
+ const from = track[index];
546
+ const to = track[index + 1] ?? from;
547
+ const span = to.offset - from.offset;
548
+ const t = span <= 0 ? 1 : (local - from.offset) / span;
549
+ // A keyframe's own timing function eases the stretch it starts, over the animation's.
550
+ return interpolate(from.value, to.value, bezier(from.easing ?? easing, Math.min(1, Math.max(0, t))));
551
+ }
552
+ /**
553
+ * The event object Angular reads.
554
+ *
555
+ * A keyframe animation is built with the global `AnimationEvent` when the host has defined one,
556
+ * because Angular tells an animation from a transition with `event instanceof AnimationEvent` and
557
+ * picks which end event to wait for from the answer. A transition is a plain object for the same
558
+ * reason: it must not be one.
559
+ */
560
+ export function animationEvent(topLevelType, target, name) {
561
+ const kind = topLevelType.slice(3).toLowerCase();
562
+ const base = { type: kind, target, stopPropagation: () => { } };
563
+ if (!kind.startsWith('animation'))
564
+ return { ...base, propertyName: name };
565
+ const Ctor = globalThis.AnimationEvent;
566
+ const event = Ctor ? new Ctor(kind) : {};
567
+ return Object.assign(event, base, { animationName: name });
568
+ }
569
+ //# sourceMappingURL=transition.js.map