@freestylejs/ani-core 1.1.0 → 1.2.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/index.js CHANGED
@@ -1,4 +1,39 @@
1
- // src/ani/nodes/base.ts
1
+ // src/utils/time/is_end.ts
2
+ function isEndOfAnimation(currentT, duration, tolerance = 1e-3) {
3
+ return currentT === duration || currentT - duration >= tolerance;
4
+ }
5
+
6
+ // src/ani/core/engine.ts
7
+ function calculateSegmentState(localTime, segmentDef, dt = 0) {
8
+ const t = Math.max(0, Math.min(localTime, segmentDef.duration));
9
+ const animeValues = [];
10
+ let allComplete = true;
11
+ const isMultipleTiming = Array.isArray(segmentDef.timing);
12
+ if (isMultipleTiming && segmentDef.timing.length !== segmentDef.from.length) {
13
+ throw new TypeError(
14
+ `[calculateSegmentState] timing does not correctly set. It requires multiple timing for ${segmentDef.from}, but received ${segmentDef.timing}`
15
+ );
16
+ }
17
+ for (let i = 0; i < segmentDef.from.length; i++) {
18
+ const timingFunction = isMultipleTiming ? segmentDef.timing[i] : segmentDef.timing;
19
+ const animeResponse = timingFunction.step(t, {
20
+ dt,
21
+ from: segmentDef.from[i],
22
+ to: segmentDef.to[i],
23
+ duration: segmentDef.duration
24
+ });
25
+ animeValues.push(animeResponse.value);
26
+ if (!animeResponse.endOfAnimation) {
27
+ allComplete = false;
28
+ }
29
+ }
30
+ return {
31
+ values: animeValues,
32
+ isComplete: allComplete || isEndOfAnimation(t, segmentDef.duration)
33
+ };
34
+ }
35
+
36
+ // src/nodes/base.ts
2
37
  var AnimationNode = class {
3
38
  constructor(id) {
4
39
  if (id) {
@@ -28,42 +63,122 @@ var TimingFunction = class _TimingFunction {
28
63
  }
29
64
  };
30
65
 
31
- // src/timing/linear.ts
32
- var LinearTimingFunction = class extends TimingFunction {
33
- step(time, context) {
34
- const progress = context.duration === 0 ? 1 : Math.max(0, Math.min(time / context.duration, 1));
35
- const value = context.from + (context.to - context.from) * progress;
36
- return { value, endOfAnimation: time >= context.duration };
37
- }
38
- };
39
-
40
66
  // src/timing/bezier.ts
67
+ var NEWTON_ITERATIONS = 4;
68
+ var NEWTON_MIN_SLOPE = 1e-3;
69
+ var SUBDIVISION_PRECISION = 1e-7;
70
+ var SUBDIVISION_MAX_ITERATIONS = 10;
71
+ var SAMPLE_TABLE_SIZE = 11;
72
+ var SAMPLE_STEP_SIZE = 1 / (SAMPLE_TABLE_SIZE - 1);
41
73
  var BezierTimingFunction = class extends TimingFunction {
42
74
  constructor(opt) {
43
75
  super();
44
76
  this.opt = opt;
45
- this.p1 = {
46
- x: 0,
47
- y: 0
48
- };
49
- this.p4 = {
50
- x: 1,
51
- y: 1
52
- };
77
+ this.sampleValues = null;
78
+ if (this.opt.p2.x !== this.opt.p2.y || this.opt.p3.x !== this.opt.p3.y) {
79
+ this.sampleValues = new Float32Array(SAMPLE_TABLE_SIZE);
80
+ for (let i = 0; i < SAMPLE_TABLE_SIZE; ++i) {
81
+ this.sampleValues[i] = this.calcBezier(
82
+ i * SAMPLE_STEP_SIZE,
83
+ this.opt.p2.x,
84
+ this.opt.p3.x
85
+ );
86
+ }
87
+ }
53
88
  }
54
- _bezierFunction(t, duration) {
55
- const end = duration || this.p4.y;
56
- return (1 - t) ** 3 * this.p1.y + 3 * (1 - t) ** 2 * t * this.opt.p2.y + 3 * (1 - t) * t ** 2 * this.opt.p3.y + t ** 3 * end;
89
+ calcBezier(t, a1, a2) {
90
+ return ((1 - 3 * a2 + 3 * a1) * t + (3 * a2 - 6 * a1)) * t * t + 3 * a1 * t;
91
+ }
92
+ getSlope(t, a1, a2) {
93
+ return 3 * (1 - 3 * a2 + 3 * a1) * t * t + 2 * (3 * a2 - 6 * a1) * t + 3 * a1;
94
+ }
95
+ getTForX(x) {
96
+ const mX1 = this.opt.p2.x;
97
+ const mX2 = this.opt.p3.x;
98
+ let intervalStart = 0;
99
+ let currentSample = 1;
100
+ const lastSample = SAMPLE_TABLE_SIZE - 1;
101
+ for (; currentSample !== lastSample && this.sampleValues[currentSample] <= x; ++currentSample) {
102
+ intervalStart += SAMPLE_STEP_SIZE;
103
+ }
104
+ --currentSample;
105
+ const dist = (x - this.sampleValues[currentSample]) / (this.sampleValues[currentSample + 1] - this.sampleValues[currentSample]);
106
+ const guessForT = intervalStart + dist * SAMPLE_STEP_SIZE;
107
+ const initialSlope = this.getSlope(guessForT, mX1, mX2);
108
+ if (initialSlope >= NEWTON_MIN_SLOPE) {
109
+ return this.newtonRaphsonIterate(x, guessForT, mX1, mX2);
110
+ }
111
+ if (initialSlope === 0) {
112
+ return guessForT;
113
+ }
114
+ return this.binarySubdivide(
115
+ x,
116
+ intervalStart,
117
+ intervalStart + SAMPLE_STEP_SIZE,
118
+ mX1,
119
+ mX2
120
+ );
121
+ }
122
+ binarySubdivide(aX, aA, aB, mX1, mX2) {
123
+ let currentX;
124
+ let currentT;
125
+ let i = 0;
126
+ let currentA = aA;
127
+ let currentB = aB;
128
+ do {
129
+ currentT = currentA + (currentB - currentA) / 2;
130
+ currentX = this.calcBezier(currentT, mX1, mX2) - aX;
131
+ if (currentX > 0) {
132
+ currentB = currentT;
133
+ } else {
134
+ currentA = currentT;
135
+ }
136
+ } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
137
+ return currentT;
138
+ }
139
+ newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
140
+ let guessT = aGuessT;
141
+ for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
142
+ const currentSlope = this.getSlope(guessT, mX1, mX2);
143
+ if (currentSlope === 0) {
144
+ return guessT;
145
+ }
146
+ const currentX = this.calcBezier(guessT, mX1, mX2) - aX;
147
+ guessT -= currentX / currentSlope;
148
+ }
149
+ return guessT;
57
150
  }
58
151
  step(time, context) {
59
- const f = this._bezierFunction(time, context.duration);
152
+ const { duration, from, to } = context;
153
+ if (duration === 0) {
154
+ return { value: to, endOfAnimation: true };
155
+ }
156
+ const x = Math.max(0, Math.min(time / duration, 1));
157
+ let easedT = x;
158
+ if (this.opt.p2.x !== this.opt.p2.y || this.opt.p3.x !== this.opt.p3.y) {
159
+ if (!this.sampleValues) {
160
+ }
161
+ const t = this.getTForX(x);
162
+ easedT = this.calcBezier(t, this.opt.p2.y, this.opt.p3.y);
163
+ }
164
+ const value = from + (to - from) * easedT;
165
+ const endOfAnimation = time >= duration;
60
166
  return {
61
- value: f,
62
- endOfAnimation: (context.duration ? time >= context.duration : time >= this.p4.x) && f >= context.to
167
+ value,
168
+ endOfAnimation
63
169
  };
64
170
  }
65
171
  };
66
172
 
173
+ // src/timing/linear.ts
174
+ var LinearTimingFunction = class extends TimingFunction {
175
+ step(time, context) {
176
+ const progress = context.duration === 0 ? 1 : Math.max(0, Math.min(time / context.duration, 1));
177
+ const value = context.from + (context.to - context.from) * progress;
178
+ return { value, endOfAnimation: time >= context.duration };
179
+ }
180
+ };
181
+
67
182
  // src/timing/dynamic_spring.ts
68
183
  var DynamicSpringTimingFunction = class extends TimingFunction {
69
184
  constructor(opt) {
@@ -221,10 +336,38 @@ var T = {
221
336
  /**
222
337
  * Creates linear timing function instance.
223
338
  */
224
- linear: () => new LinearTimingFunction()
339
+ linear: () => new LinearTimingFunction(),
340
+ /**
341
+ * Standard CSS 'ease' timing function (0.25, 0.1, 0.25, 1.0).
342
+ */
343
+ ease: () => new BezierTimingFunction({
344
+ p2: { x: 0.25, y: 0.1 },
345
+ p3: { x: 0.25, y: 1 }
346
+ }),
347
+ /**
348
+ * Standard CSS 'ease-in' timing function (0.42, 0, 1.0, 1.0).
349
+ */
350
+ easeIn: () => new BezierTimingFunction({
351
+ p2: { x: 0.42, y: 0 },
352
+ p3: { x: 1, y: 1 }
353
+ }),
354
+ /**
355
+ * Standard CSS 'ease-out' timing function (0, 0, 0.58, 1.0).
356
+ */
357
+ easeOut: () => new BezierTimingFunction({
358
+ p2: { x: 0, y: 0 },
359
+ p3: { x: 0.58, y: 1 }
360
+ }),
361
+ /**
362
+ * Standard CSS 'ease-in-out' timing function (0.42, 0, 0.58, 1.0).
363
+ */
364
+ easeInOut: () => new BezierTimingFunction({
365
+ p2: { x: 0.42, y: 0 },
366
+ p3: { x: 0.58, y: 1 }
367
+ })
225
368
  };
226
369
 
227
- // src/ani/nodes/segment.ts
370
+ // src/nodes/segment.ts
228
371
  var SegmentNode = class extends AnimationNode {
229
372
  constructor(props, id) {
230
373
  super(id);
@@ -251,7 +394,7 @@ function ani(props, id) {
251
394
  return new SegmentNode(props, id);
252
395
  }
253
396
 
254
- // src/ani/nodes/composition.ts
397
+ // src/nodes/composition.ts
255
398
  var CompositionNode = class _CompositionNode extends AnimationNode {
256
399
  constructor(children, timing, id) {
257
400
  super(id);
@@ -280,12 +423,12 @@ var CompositionNode = class _CompositionNode extends AnimationNode {
280
423
  }
281
424
  };
282
425
 
283
- // src/ani/nodes/delay.ts
426
+ // src/nodes/delay.ts
284
427
  function delay(duration, id) {
285
428
  return new SegmentNode({ to: {}, duration }, id);
286
429
  }
287
430
 
288
- // src/ani/nodes/loop.ts
431
+ // src/nodes/loop.ts
289
432
  var LoopNode = class extends CompositionNode {
290
433
  constructor(child, loopCount, timing, id) {
291
434
  super([child], timing, id);
@@ -306,7 +449,7 @@ function loop(child, loopCount, timing, id) {
306
449
  return new LoopNode(child, loopCount, timing, id);
307
450
  }
308
451
 
309
- // src/ani/nodes/parallel.ts
452
+ // src/nodes/parallel.ts
310
453
  var ParallelNode = class extends CompositionNode {
311
454
  constructor(children, timing, id) {
312
455
  const seenProperty = /* @__PURE__ */ new Set();
@@ -356,7 +499,7 @@ function parallel(children, timing, id) {
356
499
  return new ParallelNode(children, timing, id);
357
500
  }
358
501
 
359
- // src/ani/nodes/sequence.ts
502
+ // src/nodes/sequence.ts
360
503
  var SequenceNode = class extends CompositionNode {
361
504
  constructor(children, timing, id) {
362
505
  super(children, timing, id);
@@ -375,12 +518,12 @@ function sequence(children, timing, id) {
375
518
  return new SequenceNode(children, timing, id);
376
519
  }
377
520
 
378
- // src/ani/nodes/stagger.ts
521
+ // src/nodes/stagger.ts
379
522
  var StaggerNode = class extends CompositionNode {
380
- constructor(children, props, id) {
381
- super(children, props?.timing, id);
523
+ constructor(children, offset, timing, id) {
524
+ super(children, timing, id);
382
525
  this.type = "STAGGER";
383
- this.offset = props.offset;
526
+ this.offset = offset;
384
527
  if (children.length === 0) {
385
528
  this.duration = 0;
386
529
  } else {
@@ -396,10 +539,81 @@ var StaggerNode = class extends CompositionNode {
396
539
  }
397
540
  }
398
541
  };
399
- function stagger(children, props, id) {
400
- return new StaggerNode(children, props, id);
542
+ function stagger(children, offset, timing, id) {
543
+ return new StaggerNode(children, offset, timing, id);
401
544
  }
402
545
 
546
+ // src/ani/core/interface/timeline_interface.ts
547
+ var TimelineBase = class {
548
+ constructor(rootNode) {
549
+ this.rootNode = rootNode;
550
+ this._currentExecutionPlan = null;
551
+ this.duration = rootNode.duration;
552
+ this._baseExecutionPlan = this._constructExecutionPlan(rootNode);
553
+ this.play = this.play.bind(this);
554
+ this.pause = this.pause.bind(this);
555
+ this.seek = this.seek.bind(this);
556
+ this.reset = this.reset.bind(this);
557
+ this.resume = this.resume.bind(this);
558
+ }
559
+ /**
560
+ * flatten the AST into a linear execution plan.
561
+ */
562
+ _constructExecutionPlan(rootNode) {
563
+ const plan = [];
564
+ rootNode.construct(plan, 0);
565
+ return plan;
566
+ }
567
+ /**
568
+ * Merges the base plan with runtime dynamic overrides.
569
+ */
570
+ _resolveExecutionPlan(keyframes, durations) {
571
+ if (!keyframes && !durations) {
572
+ return [...this._baseExecutionPlan];
573
+ }
574
+ const segmentNodes = this._baseExecutionPlan.filter(
575
+ (segment) => segment.node.type === "SEGMENT"
576
+ );
577
+ const segLength = segmentNodes.length;
578
+ if (keyframes && keyframes.length !== segLength) {
579
+ throw new Error(
580
+ `[Timeline] Keyframe mismatch: Expected ${segLength}, received ${keyframes.length}.`
581
+ );
582
+ }
583
+ if (durations && durations.length !== segLength) {
584
+ throw new Error(
585
+ `[Timeline] Duration mismatch: Expected ${segLength}, received ${durations.length}.`
586
+ );
587
+ }
588
+ const newPlan = [];
589
+ let keyframeIndex = 0;
590
+ for (const segment of this._baseExecutionPlan) {
591
+ if (segment.node.type === "SEGMENT") {
592
+ const dynamicTo = keyframes?.[keyframeIndex];
593
+ const dynamicDuration = durations?.[keyframeIndex];
594
+ const newSegmentProps = {
595
+ ...segment.node.props,
596
+ ...dynamicTo && dynamicTo !== "keep" && {
597
+ to: dynamicTo
598
+ },
599
+ ...dynamicDuration && dynamicDuration !== "keep" && {
600
+ duration: dynamicDuration
601
+ }
602
+ };
603
+ const newSegment = new SegmentNode(
604
+ newSegmentProps,
605
+ segment.node.id
606
+ );
607
+ newPlan.push({ ...segment, node: newSegment });
608
+ keyframeIndex++;
609
+ } else {
610
+ newPlan.push({ ...segment });
611
+ }
612
+ }
613
+ return newPlan;
614
+ }
615
+ };
616
+
403
617
  // src/loop/clock.ts
404
618
  var AnimationClock = class _AnimationClock {
405
619
  constructor(maxDeltaTime) {
@@ -455,46 +669,86 @@ var AnimationClock = class _AnimationClock {
455
669
  }
456
670
  };
457
671
 
458
- // src/utils/time/is_end.ts
459
- function isEndOfAnimation(currentT, duration, tolerance = 1e-3) {
460
- return currentT === duration || currentT - duration >= tolerance;
672
+ // src/ani/core/resolver.ts
673
+ function resolveGroup(group) {
674
+ if (Array.isArray(group)) {
675
+ return { keyMap: null, values: group };
676
+ }
677
+ const typedGroup = group;
678
+ const keys = Object.keys(typedGroup);
679
+ const keyMap = new Map(keys.map((key, i) => [key, i]));
680
+ const values = keys.map((key) => typedGroup[key]);
681
+ return { keyMap, values };
461
682
  }
462
-
463
- // src/ani/engine.ts
464
- function calculateSegmentState(localTime, segmentDef, dt = 0) {
465
- const t = Math.max(0, Math.min(localTime, segmentDef.duration));
466
- const animeValues = [];
467
- let allComplete = true;
468
- const isMultipleTiming = Array.isArray(segmentDef.timing);
469
- if (isMultipleTiming && segmentDef.timing.length !== segmentDef.from.length) {
470
- throw new TypeError(
471
- `[calculateSegmentState] timing does not correctly set. It requires multiple timing for ${segmentDef.from}, but received ${segmentDef.timing}`
472
- );
683
+ function resolveStateToGroup(state, keyMap) {
684
+ if (!keyMap) {
685
+ return state;
473
686
  }
474
- for (let i = 0; i < segmentDef.from.length; i++) {
475
- const timingFunction = isMultipleTiming ? segmentDef.timing[i] : segmentDef.timing;
476
- const animeResponse = timingFunction.step(t, {
477
- dt,
478
- from: segmentDef.from[i],
479
- to: segmentDef.to[i],
480
- duration: segmentDef.duration
481
- });
482
- animeValues.push(animeResponse.value);
483
- if (!animeResponse.endOfAnimation) {
484
- allComplete = false;
687
+ const group = {};
688
+ for (const [key, index] of keyMap.entries()) {
689
+ group[key] = state[index];
690
+ }
691
+ return group;
692
+ }
693
+ function resolvePlanState(plan, initialValues, keyMap, targetTime, dt = 0) {
694
+ const nextState = [...initialValues];
695
+ let stateAtLastStartTime = [...initialValues];
696
+ for (const segment of plan) {
697
+ if (targetTime < segment.startTime) {
698
+ continue;
699
+ }
700
+ stateAtLastStartTime = [...nextState];
701
+ const { keyMap: segKeyMap, values: toValues } = resolveGroup(
702
+ segment.node.props.to
703
+ );
704
+ const isRecordAni = keyMap !== null;
705
+ let fromValues = [];
706
+ const timings = [];
707
+ const t = segment.node.props.timing;
708
+ const isRecordTiming = t && !(t instanceof TimingFunction);
709
+ if (isRecordAni) {
710
+ for (const key of segKeyMap.keys()) {
711
+ const index = keyMap.get(key);
712
+ fromValues.push(stateAtLastStartTime[index]);
713
+ if (isRecordTiming) {
714
+ timings.push(t[key]);
715
+ }
716
+ }
717
+ } else {
718
+ fromValues = stateAtLastStartTime;
719
+ }
720
+ const localTime = targetTime - segment.startTime;
721
+ const segmentDef = {
722
+ from: fromValues,
723
+ to: toValues,
724
+ duration: segment.node.duration,
725
+ // default fallback = linear
726
+ timing: isRecordAni && isRecordTiming ? timings : t ?? T.linear()
727
+ };
728
+ const result = calculateSegmentState(localTime, segmentDef, dt);
729
+ const finalValues = result.isComplete ? toValues : result.values;
730
+ if (isRecordAni) {
731
+ let i = 0;
732
+ for (const key of segKeyMap.keys()) {
733
+ const stateIndex = keyMap.get(key);
734
+ if (stateIndex !== void 0 && stateIndex !== -1) {
735
+ nextState[stateIndex] = finalValues[i];
736
+ }
737
+ i++;
738
+ }
739
+ } else {
740
+ for (let i = 0; i < finalValues.length; i++) {
741
+ nextState[i] = finalValues[i];
742
+ }
485
743
  }
486
744
  }
487
- return {
488
- values: animeValues,
489
- isComplete: allComplete || isEndOfAnimation(t, segmentDef.duration)
490
- };
745
+ return nextState;
491
746
  }
492
747
 
493
- // src/ani/timeline.ts
494
- var Timeline = class {
748
+ // src/ani/raf/timeline.ts
749
+ var RafAniTimeline = class extends TimelineBase {
495
750
  constructor(rootNode, clock) {
496
- this.rootNode = rootNode;
497
- this._currentExecutionPlan = null;
751
+ super(rootNode);
498
752
  this._masterTime = 0;
499
753
  this._delay = 0;
500
754
  this._status = "IDLE";
@@ -503,194 +757,86 @@ var Timeline = class {
503
757
  this._initialState = [];
504
758
  this._repeatCount = 0;
505
759
  this._propertyKeyMap = null;
506
- this._segmentStartStates = /* @__PURE__ */ new Map();
507
760
  this._onUpdateCallbacks = /* @__PURE__ */ new Set();
508
- this.duration = rootNode.duration;
509
- this._baseExecutionPlan = this._constructExecutionPlan(rootNode);
510
761
  this._clock = clock ?? AnimationClock.create();
511
- this.play.bind(this);
512
- this.pause.bind(this);
513
- this.seek.bind(this);
514
- this.resume.bind(this);
515
- this.reset.bind(this);
762
+ this.play = this.play.bind(this);
763
+ this.pause = this.pause.bind(this);
764
+ this.seek = this.seek.bind(this);
765
+ this.resume = this.resume.bind(this);
766
+ this.reset = this.reset.bind(this);
516
767
  }
517
- /**
518
- * Current animation running config.
519
- */
520
768
  get currentConfig() {
521
769
  return this._currentConfig;
522
770
  }
523
- /**
524
- * Resolves a Group (like {x, y}) into keys and values.
525
- */
526
- _resolveGroup(group) {
527
- if (Array.isArray(group)) {
528
- return { keyMap: null, values: group };
529
- }
530
- const keyMap = new Map(Object.keys(group).map((key, i) => [key, i]));
531
- const values = Object.values(group);
532
- return { keyMap, values };
771
+ getCurrentValue() {
772
+ if (this._state.length === 0) return null;
773
+ return resolveStateToGroup(
774
+ this._state,
775
+ this._propertyKeyMap
776
+ );
533
777
  }
534
- /**
535
- * Resolves the internal state (a number array) back into Group.
536
- */
537
- _resolveStateToGroup(state) {
538
- if (!this._propertyKeyMap) {
539
- return state;
540
- }
541
- const group = {};
542
- let i = 0;
543
- for (const key of this._propertyKeyMap.keys()) {
544
- group[key] = state[i];
545
- i++;
778
+ _calculateStateAtTime(targetTime, dt = 0) {
779
+ if (this._initialState.length === 0 || !this._currentExecutionPlan) {
780
+ return [];
546
781
  }
547
- return group;
782
+ return resolvePlanState(
783
+ this._currentExecutionPlan,
784
+ this._initialState,
785
+ this._propertyKeyMap,
786
+ // Using the class property directly
787
+ targetTime,
788
+ dt
789
+ );
548
790
  }
549
- /**
550
- * Compile animation execution plan
551
- */
552
- _constructExecutionPlan(rootNode) {
553
- const plan = [];
554
- rootNode.construct(plan, 0);
555
- return plan;
791
+ notify() {
792
+ for (const subscriber of this._onUpdateCallbacks) {
793
+ subscriber({
794
+ state: resolveStateToGroup(
795
+ this._state,
796
+ this._propertyKeyMap
797
+ ),
798
+ status: this._status
799
+ });
800
+ }
556
801
  }
557
802
  /**
558
- * Calculates the exact state of the animation at point.
803
+ * @private Internal clock subscription callback.
559
804
  */
560
- _calculateStateAtTime(targetTime, dt = 0) {
561
- if (this._initialState.length === 0 || !this._currentExecutionPlan) {
562
- return [];
563
- }
564
- const nextState = [...this._initialState];
565
- let stateAtLastStartTime = [...this._initialState];
566
- for (const segment of this._currentExecutionPlan) {
567
- if (targetTime < segment.startTime) {
568
- continue;
569
- }
570
- if (!segment.node.props.timing) {
571
- throw new Error(
572
- `[Timeline] timing should be provided. Please specify timing using a.timing.(...). Check target segment: ${JSON.stringify(segment, null, 2)}.`,
573
- { cause: segment }
574
- );
575
- }
576
- stateAtLastStartTime = [...nextState];
577
- const { keyMap, values: toValues } = this._resolveGroup(
578
- segment.node.props.to
579
- );
580
- const isRecordAni = this._propertyKeyMap !== null && keyMap !== null;
581
- let fromValues = [];
582
- const timings = [];
583
- const t = segment.node.props.timing;
584
- const isRecordTiming = t && !(t instanceof TimingFunction);
585
- if (isRecordAni) {
586
- for (const key of keyMap.keys()) {
587
- const index = this._propertyKeyMap.get(key);
588
- fromValues.push(stateAtLastStartTime[index]);
589
- if (isRecordTiming) {
590
- timings.push(
591
- t[key]
592
- );
593
- }
594
- }
595
- } else {
596
- fromValues = stateAtLastStartTime;
597
- }
598
- let finalAnimeValues = [];
599
- const localTime = targetTime - segment.startTime;
600
- const segmentDef = {
601
- from: fromValues,
602
- to: toValues,
603
- duration: segment.node.duration,
604
- timing: isRecordAni && isRecordTiming ? timings : t
605
- };
606
- const segmentState = calculateSegmentState(
607
- localTime,
608
- segmentDef,
609
- dt
610
- );
611
- if (segmentState.isComplete) {
612
- finalAnimeValues = toValues;
805
+ update(dt) {
806
+ if (this._status !== "PLAYING") return;
807
+ if (this._delay > 0) {
808
+ this._delay -= dt;
809
+ if (this._delay < 0) {
810
+ dt = -this._delay;
811
+ this._delay = 0;
613
812
  } else {
614
- finalAnimeValues = segmentState.values;
813
+ return;
615
814
  }
616
- if (isRecordAni) {
617
- let i = 0;
618
- for (const key of keyMap.keys()) {
619
- const stateIndex = this._propertyKeyMap.get(key);
620
- if (stateIndex === -1) {
621
- continue;
622
- }
623
- nextState[stateIndex] = finalAnimeValues[i];
624
- i++;
625
- }
815
+ }
816
+ this._masterTime += dt;
817
+ if (this._masterTime >= this.duration) this._masterTime = this.duration;
818
+ this._state = this._calculateStateAtTime(this._masterTime, dt);
819
+ this.notify();
820
+ if (isEndOfAnimation(this._masterTime, this.duration)) {
821
+ this._repeatCount += 1;
822
+ const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
823
+ if (noRepeat) {
824
+ this._status = "ENDED";
825
+ this._clock.unsubscribe(this);
826
+ this.notify();
626
827
  } else {
627
- for (let i = 0; i < finalAnimeValues.length; i++) {
628
- nextState[i] = finalAnimeValues[i];
629
- }
828
+ this.play(this._currentConfig);
630
829
  }
631
830
  }
632
- return nextState;
633
- }
634
- _resolveExecutionPlan(keyframes, durations) {
635
- if (!keyframes && !durations) {
636
- return [...this._baseExecutionPlan];
637
- }
638
- const segmentNodes = this._baseExecutionPlan.filter(
639
- (segment) => segment.node.type === "SEGMENT"
640
- );
641
- const segLength = segmentNodes.length;
642
- if (keyframes && keyframes.length !== segLength) {
643
- throw new Error(
644
- `Timeline keyframe mismatch: Expected ${segLength} keyframes, but received ${keyframes.length}.`
645
- );
646
- }
647
- if (durations && durations.length !== segLength) {
648
- throw new Error(
649
- `Timeline keyframe mismatch: Expected ${segLength} durations, but received ${durations.length}.`
650
- );
651
- }
652
- const newPlan = [];
653
- let keyframeIndex = 0;
654
- for (const segment of this._baseExecutionPlan) {
655
- if (segment.node.type === "SEGMENT") {
656
- const dynamicTo = keyframes?.[keyframeIndex];
657
- const dynamicDuration = durations?.[keyframeIndex];
658
- const newSegmentProps = {
659
- ...segment.node.props,
660
- // >> dynamic to
661
- ...dynamicTo && {
662
- to: dynamicTo === "keep" ? segment.node.props.to : dynamicTo
663
- },
664
- // >> dynamic duration
665
- ...dynamicDuration && {
666
- duration: dynamicDuration === "keep" ? segment.node.props.duration : dynamicDuration
667
- }
668
- };
669
- const newSegment = new SegmentNode(
670
- newSegmentProps,
671
- segment.node.id
672
- );
673
- newPlan.push({ ...segment, node: newSegment });
674
- keyframeIndex++;
675
- } else {
676
- newPlan.push({ ...segment });
677
- }
678
- }
679
- return newPlan;
680
- }
681
- notify() {
682
- for (const subscriber of this._onUpdateCallbacks) {
683
- subscriber({
684
- state: this._resolveStateToGroup(this._state),
685
- status: this._status
686
- });
687
- }
688
831
  }
832
+ /**
833
+ * Plays animation.
834
+ * @param config {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame RequestAnimationFrame API} based config.
835
+ * @param canBeIntercepted if `true` animation can be intercepted even if already animation started.
836
+ */
689
837
  play(config, canBeIntercepted = true) {
690
- if (this._status === "PLAYING" && !canBeIntercepted) {
691
- return;
692
- }
693
- const isRepeating = this._currentConfig?.repeat && this._currentConfig?.repeat >= 1;
838
+ if (this._status === "PLAYING" && !canBeIntercepted) return;
839
+ const isRepeating = (this._currentConfig?.repeat ?? 0) >= 1;
694
840
  const savedRepeatCount = isRepeating ? this._repeatCount : 0;
695
841
  this.reset(false);
696
842
  this._repeatCount = savedRepeatCount;
@@ -706,8 +852,8 @@ var Timeline = class {
706
852
  config.keyframes,
707
853
  config.durations
708
854
  );
709
- const { keyMap: keys, values } = this._resolveGroup(config.from);
710
- this._propertyKeyMap = keys;
855
+ const { keyMap, values } = resolveGroup(config.from);
856
+ this._propertyKeyMap = keyMap;
711
857
  this._state = values;
712
858
  this._initialState = values;
713
859
  this._status = "PLAYING";
@@ -731,13 +877,10 @@ var Timeline = class {
731
877
  this._state = [];
732
878
  this._initialState = [];
733
879
  this._propertyKeyMap = null;
734
- this._segmentStartStates.clear();
735
880
  this._currentExecutionPlan = null;
736
881
  this._clock.unsubscribe(this);
737
882
  this._repeatCount = 0;
738
- if (notify) {
739
- this.notify();
740
- }
883
+ if (notify) this.notify();
741
884
  }
742
885
  seek(targetTime) {
743
886
  if (this._status === "PLAYING" || this._status === "ENDED") {
@@ -748,87 +891,50 @@ var Timeline = class {
748
891
  this._state = this._calculateStateAtTime(seekTime, 0);
749
892
  this.notify();
750
893
  }
751
- getCurrentValue() {
752
- if (this._state.length === 0) return null;
753
- return this._resolveStateToGroup(this._state);
754
- }
894
+ /**
895
+ * When timeline updates, subscribes on update callback.
896
+ * @param callback Subscription callback.
897
+ * @returns Unsubscribe.
898
+ */
755
899
  onUpdate(callback) {
756
900
  this._onUpdateCallbacks.add(callback);
757
901
  return () => {
758
902
  this._onUpdateCallbacks.delete(callback);
759
903
  };
760
904
  }
761
- update(dt) {
762
- if (this._status !== "PLAYING") {
763
- return;
764
- }
765
- if (this._delay > 0) {
766
- this._delay -= dt;
767
- if (this._delay < 0) {
768
- dt = -this._delay;
769
- this._delay = 0;
770
- } else {
771
- return;
772
- }
773
- }
774
- this._masterTime += dt;
775
- if (this._masterTime >= this.duration) {
776
- this._masterTime = this.duration;
777
- }
778
- this._state = this._calculateStateAtTime(this._masterTime, dt);
779
- this.notify();
780
- if (isEndOfAnimation(this._masterTime, this.duration)) {
781
- this._repeatCount += 1;
782
- if (!this._currentConfig) {
783
- throw new Error(
784
- `[Timeline] currentConfig can not be null when update(dt)`
785
- );
786
- }
787
- const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
788
- if (noRepeat) {
789
- this._status = "ENDED";
790
- this._clock.unsubscribe(this);
791
- this.notify();
792
- } else {
793
- this.play(this._currentConfig);
794
- }
795
- }
796
- }
797
905
  };
798
- function timeline(rootNode, clock) {
799
- return new Timeline(rootNode, clock);
906
+ function rafTimeline(rootNode, clock) {
907
+ return new RafAniTimeline(rootNode, clock);
800
908
  }
801
909
 
802
- // src/ani/states.ts
910
+ // src/ani/raf/states.ts
803
911
  function createStates(config) {
804
912
  let State = config.initial;
805
- let Timeline2 = timeline(
913
+ let Timeline = rafTimeline(
806
914
  config.states[State],
807
915
  config.clock
808
916
  );
809
917
  const subs = /* @__PURE__ */ new Set();
810
- const notify = (timeline2) => {
918
+ const notify = (timeline) => {
811
919
  for (const Sub of subs) {
812
- Sub(timeline2);
920
+ Sub(timeline);
813
921
  }
814
922
  };
815
923
  return {
816
- timeline: () => Timeline2,
924
+ timeline: () => Timeline,
925
+ state: () => State,
817
926
  onTimelineChange(callback) {
818
927
  subs.add(callback);
819
928
  return () => subs.delete(callback);
820
929
  },
821
930
  transitionTo(newState, timelineConfig, canBeIntercepted) {
822
- if (!config.states[newState] || State === newState) {
823
- return;
824
- }
825
931
  const from = timelineConfig?.from ?? // 1. config
826
- Timeline2.getCurrentValue() ?? // 2. last value
932
+ Timeline.getCurrentValue() ?? // 2. last value
827
933
  config.initialFrom;
828
934
  State = newState;
829
- Timeline2 = timeline(config.states[State], config.clock);
830
- notify(Timeline2);
831
- Timeline2.play(
935
+ Timeline = rafTimeline(config.states[State], config.clock);
936
+ notify(Timeline);
937
+ Timeline.play(
832
938
  {
833
939
  ...timelineConfig,
834
940
  from
@@ -839,77 +945,6 @@ function createStates(config) {
839
945
  };
840
946
  }
841
947
 
842
- // src/event/manager.ts
843
- var EventManager = class _EventManager {
844
- constructor(supportedEvents) {
845
- this.supportedEvents = supportedEvents;
846
- this._element = null;
847
- this._animeGetter = null;
848
- this.setAnimeGetter = (animeGetter) => {
849
- this._animeGetter = animeGetter;
850
- };
851
- this.eventMap = /* @__PURE__ */ new Map();
852
- this.withAnimeValue = (listener) => {
853
- const withAnime = (e) => {
854
- listener(this.animeGetter(), e);
855
- };
856
- return withAnime;
857
- };
858
- this.add = (eventName, listener, options) => {
859
- const withAnime = this.withAnimeValue(listener);
860
- this.eventMap.set(eventName, withAnime);
861
- this.targetElement.addEventListener(
862
- eventName,
863
- this.eventMap.get(eventName),
864
- options
865
- );
866
- };
867
- this.cleanupOne = (eventName) => {
868
- const removeListener = this.eventMap.get(eventName);
869
- if (!removeListener) return false;
870
- this.targetElement.removeEventListener(eventName, removeListener);
871
- return true;
872
- };
873
- this.cleanupAll = () => {
874
- const clearResponse = [];
875
- for (const evtName of this.eventMap.keys()) {
876
- const res = this.cleanupOne(evtName);
877
- clearResponse.push(res);
878
- }
879
- return clearResponse.some((t) => t === false) === false;
880
- };
881
- this.attach = (handlers) => {
882
- Object.entries(handlers).forEach(([eventKey, handler]) => {
883
- this.add(
884
- _EventManager.getEvtKey(eventKey),
885
- handler
886
- );
887
- });
888
- };
889
- }
890
- get targetElement() {
891
- if (!this._element) throw new Error("EventManger, bind element first");
892
- return this._element;
893
- }
894
- get animeGetter() {
895
- if (!this._animeGetter)
896
- throw new Error("EventManager, animeGetter should be provided");
897
- return this._animeGetter;
898
- }
899
- /**
900
- * get pure `{event_name}`
901
- * @param key onX`{event_name}`
902
- */
903
- static getEvtKey(key) {
904
- const removed = key.substring(2, key.length);
905
- const Capitalized = `${removed[0].toLowerCase()}${removed.substring(1, key.length)}`;
906
- return Capitalized;
907
- }
908
- bind(element) {
909
- this._element = element;
910
- }
911
- };
912
-
913
948
  // src/style/create_sheet.ts
914
949
  var TransformFunctionMap = {
915
950
  // deg
@@ -997,40 +1032,285 @@ function createStyleSheet(animeStyleValue, resolver) {
997
1032
  return styleAccumulator;
998
1033
  }
999
1034
 
1035
+ // src/ani/waapi/compiler/resolver.ts
1036
+ function resolveStateAt(plan, initialFrom, targetTime, dt) {
1037
+ const { keyMap, values: initialValues } = resolveGroup(initialFrom);
1038
+ const rawResultState = resolvePlanState(
1039
+ plan,
1040
+ initialValues,
1041
+ keyMap,
1042
+ targetTime,
1043
+ dt
1044
+ );
1045
+ return resolveStateToGroup(rawResultState, keyMap);
1046
+ }
1047
+
1048
+ // src/ani/waapi/compiler/timing_compiler.ts
1049
+ function compileTiming(timing) {
1050
+ if (!timing) {
1051
+ return "linear";
1052
+ }
1053
+ if (timing instanceof LinearTimingFunction) {
1054
+ return "linear";
1055
+ }
1056
+ if (timing instanceof BezierTimingFunction) {
1057
+ const { p2, p3 } = timing.opt;
1058
+ return `cubic-bezier(${p2.x}, ${p2.y}, ${p3.x}, ${p3.y})`;
1059
+ }
1060
+ return null;
1061
+ }
1062
+
1063
+ // src/ani/waapi/compiler/keyframe_compiler.ts
1064
+ function compileToKeyframes(plan, initialFrom) {
1065
+ if (plan.length === 0) {
1066
+ return [];
1067
+ }
1068
+ const FPS = 60;
1069
+ const SAMPLE_RATE = 1 / FPS;
1070
+ const duration = Math.max(...plan.map((s) => s.endTime));
1071
+ if (duration === 0) {
1072
+ const state = resolveStateAt(plan, initialFrom, 0, SAMPLE_RATE);
1073
+ const style = createStyleSheet(state);
1074
+ return [
1075
+ { offset: 0, ...style },
1076
+ { offset: 1, ...style }
1077
+ ];
1078
+ }
1079
+ const timePoints = /* @__PURE__ */ new Set([0, duration]);
1080
+ for (const seg of plan) {
1081
+ timePoints.add(seg.startTime);
1082
+ timePoints.add(seg.endTime);
1083
+ }
1084
+ const sortedTimes = Array.from(timePoints).sort((a2, b) => a2 - b);
1085
+ const keyframes = [];
1086
+ const getEasingForInterval = (t, nextT) => {
1087
+ const activeSegments = plan.filter(
1088
+ (s) => s.startTime <= t && s.endTime >= nextT
1089
+ );
1090
+ if (activeSegments.length === 0) return "linear";
1091
+ const timings = activeSegments.map((s) => s.node.props.timing).filter((t2) => t2 !== void 0);
1092
+ if (timings.length === 0) return "linear";
1093
+ const firstTiming = timings[0];
1094
+ const allSame = timings.every((t2) => t2 === firstTiming);
1095
+ if (allSame && firstTiming instanceof TimingFunction) {
1096
+ return compileTiming(firstTiming);
1097
+ }
1098
+ return null;
1099
+ };
1100
+ for (let i = 0; i < sortedTimes.length; i++) {
1101
+ const currT = sortedTimes[i];
1102
+ const state = resolveStateAt(plan, initialFrom, currT, SAMPLE_RATE);
1103
+ const style = createStyleSheet(state);
1104
+ const keyframe = {
1105
+ offset: currT / duration,
1106
+ ...style
1107
+ };
1108
+ keyframes.push(keyframe);
1109
+ if (i < sortedTimes.length - 1) {
1110
+ const nextT = sortedTimes[i + 1];
1111
+ const easing = getEasingForInterval(currT, nextT);
1112
+ if (easing === null) {
1113
+ let sampleT = currT + SAMPLE_RATE;
1114
+ while (sampleT < nextT) {
1115
+ const sampleState = resolveStateAt(
1116
+ plan,
1117
+ initialFrom,
1118
+ sampleT,
1119
+ SAMPLE_RATE
1120
+ );
1121
+ const sampleStyle = createStyleSheet(
1122
+ sampleState
1123
+ );
1124
+ keyframes.push({
1125
+ offset: sampleT / duration,
1126
+ ...sampleStyle,
1127
+ easing: "linear"
1128
+ });
1129
+ sampleT += SAMPLE_RATE;
1130
+ }
1131
+ keyframe.easing = "linear";
1132
+ } else {
1133
+ keyframe.easing = easing;
1134
+ }
1135
+ }
1136
+ }
1137
+ return keyframes;
1138
+ }
1139
+
1140
+ // src/ani/waapi/timeline.ts
1141
+ var WebAniTimeline = class extends TimelineBase {
1142
+ constructor(rootNode) {
1143
+ super(rootNode);
1144
+ this._animation = null;
1145
+ this._keyframes = [];
1146
+ }
1147
+ /**
1148
+ * Plays animation.
1149
+ * @param target Target element.
1150
+ * @param config {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API Web Animations API} based config.
1151
+ */
1152
+ play(target, config) {
1153
+ if (this._animation) {
1154
+ this._animation.cancel();
1155
+ }
1156
+ this._currentExecutionPlan = this._resolveExecutionPlan(
1157
+ config.keyframes,
1158
+ config.durations
1159
+ );
1160
+ this._keyframes = compileToKeyframes(
1161
+ this._currentExecutionPlan,
1162
+ config.from
1163
+ );
1164
+ if (this._keyframes.length === 0) {
1165
+ return null;
1166
+ }
1167
+ const totalDurationMs = this._currentExecutionPlan.reduce(
1168
+ (max, seg) => Math.max(max, seg.endTime),
1169
+ 0
1170
+ ) * 1e3;
1171
+ const effect = new KeyframeEffect(target, this._keyframes, {
1172
+ duration: totalDurationMs,
1173
+ iterations: config.repeat ?? 1,
1174
+ delay: config.delay ?? 0,
1175
+ fill: "forwards"
1176
+ });
1177
+ this._animation = new Animation(effect, document.timeline);
1178
+ this._animation.play();
1179
+ return this._animation;
1180
+ }
1181
+ pause() {
1182
+ this._animation?.pause();
1183
+ }
1184
+ resume() {
1185
+ this._animation?.play();
1186
+ }
1187
+ reset() {
1188
+ this._animation?.cancel();
1189
+ this._animation = null;
1190
+ }
1191
+ seek(targetTime) {
1192
+ if (this._animation) {
1193
+ this._animation.currentTime = targetTime * 1e3;
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Native animation object.
1198
+ *
1199
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Animation Animation}.
1200
+ */
1201
+ get nativeAnimation() {
1202
+ return this._animation;
1203
+ }
1204
+ };
1205
+ function webTimeline(rootNode) {
1206
+ return new WebAniTimeline(rootNode);
1207
+ }
1208
+
1209
+ // src/event/manager.ts
1210
+ var EventManager = class _EventManager {
1211
+ constructor(supportedEvents) {
1212
+ this.supportedEvents = supportedEvents;
1213
+ this._element = null;
1214
+ this._animeGetter = null;
1215
+ this.setAnimeGetter = (animeGetter) => {
1216
+ this._animeGetter = animeGetter;
1217
+ };
1218
+ this.eventMap = /* @__PURE__ */ new Map();
1219
+ this.withAnimeValue = (listener) => {
1220
+ const withAnime = (e) => {
1221
+ listener(this.animeGetter(), e);
1222
+ };
1223
+ return withAnime;
1224
+ };
1225
+ this.add = (eventName, listener, options) => {
1226
+ const withAnime = this.withAnimeValue(listener);
1227
+ this.eventMap.set(eventName, withAnime);
1228
+ this.targetElement.addEventListener(
1229
+ eventName,
1230
+ this.eventMap.get(eventName),
1231
+ options
1232
+ );
1233
+ };
1234
+ this.cleanupOne = (eventName) => {
1235
+ const removeListener = this.eventMap.get(eventName);
1236
+ if (!removeListener) return false;
1237
+ this.targetElement.removeEventListener(eventName, removeListener);
1238
+ return true;
1239
+ };
1240
+ this.cleanupAll = () => {
1241
+ const clearResponse = [];
1242
+ for (const evtName of this.eventMap.keys()) {
1243
+ const res = this.cleanupOne(evtName);
1244
+ clearResponse.push(res);
1245
+ }
1246
+ return clearResponse.some((t) => t === false) === false;
1247
+ };
1248
+ this.attach = (handlers) => {
1249
+ Object.entries(handlers).forEach(([eventKey, handler]) => {
1250
+ this.add(
1251
+ _EventManager.getEvtKey(eventKey),
1252
+ handler
1253
+ );
1254
+ });
1255
+ };
1256
+ }
1257
+ get targetElement() {
1258
+ if (!this._element) throw new Error("EventManger, bind element first");
1259
+ return this._element;
1260
+ }
1261
+ get animeGetter() {
1262
+ if (!this._animeGetter)
1263
+ throw new Error("EventManager, animeGetter should be provided");
1264
+ return this._animeGetter;
1265
+ }
1266
+ /**
1267
+ * get pure `{event_name}`
1268
+ * @param key onX`{event_name}`
1269
+ */
1270
+ static getEvtKey(key) {
1271
+ const removed = key.substring(2, key.length);
1272
+ const Capitalized = `${removed[0].toLowerCase()}${removed.substring(1, key.length)}`;
1273
+ return Capitalized;
1274
+ }
1275
+ bind(element) {
1276
+ this._element = element;
1277
+ }
1278
+ };
1279
+
1000
1280
  // src/index.ts
1001
1281
  var a = {
1002
1282
  timing: T,
1283
+ dynamicTimeline: rafTimeline,
1284
+ timeline: webTimeline,
1285
+ /**
1286
+ * Create animation segment.
1287
+ */
1003
1288
  ani,
1004
- createStates,
1289
+ /**
1290
+ * Add delay
1291
+ */
1005
1292
  delay,
1006
1293
  loop,
1007
1294
  parallel,
1008
1295
  sequence,
1009
1296
  stagger,
1010
- timeline
1297
+ createStates
1011
1298
  };
1012
1299
  export {
1013
1300
  AnimationClock,
1014
- AnimationNode,
1015
- CompositionNode,
1301
+ BezierTimingFunction,
1016
1302
  EventManager,
1017
1303
  LinearTimingFunction,
1018
- ParallelNode,
1019
- SegmentNode,
1020
- SequenceNode,
1021
- StaggerNode,
1304
+ RafAniTimeline,
1022
1305
  T,
1023
- Timeline,
1306
+ TimelineBase,
1024
1307
  TimingFunction,
1308
+ WebAniTimeline,
1025
1309
  a,
1026
- ani,
1310
+ calculateSegmentState,
1311
+ compileToKeyframes,
1027
1312
  createStates,
1028
1313
  createStyleSheet,
1029
- delay,
1030
- loop,
1031
- parallel,
1032
- sequence,
1033
- stagger,
1034
- timeline
1314
+ rafTimeline,
1315
+ webTimeline
1035
1316
  };
1036
- //# sourceMappingURL=index.js.map