@freestylejs/ani-core 1.1.0 → 1.2.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.
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,196 +757,89 @@ 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
- this.reset(false);
841
+ const isPlaying = this._status === "PLAYING";
842
+ this.reset(false, !isPlaying);
696
843
  this._repeatCount = savedRepeatCount;
697
844
  if (isRepeating && this._repeatCount >= config.repeat) {
698
845
  this._repeatCount = 0;
@@ -706,8 +853,8 @@ var Timeline = class {
706
853
  config.keyframes,
707
854
  config.durations
708
855
  );
709
- const { keyMap: keys, values } = this._resolveGroup(config.from);
710
- this._propertyKeyMap = keys;
856
+ const { keyMap, values } = resolveGroup(config.from);
857
+ this._propertyKeyMap = keyMap;
711
858
  this._state = values;
712
859
  this._initialState = values;
713
860
  this._status = "PLAYING";
@@ -723,7 +870,7 @@ var Timeline = class {
723
870
  this._status = "PLAYING";
724
871
  this._clock.subscribe(this);
725
872
  }
726
- reset(notify = true) {
873
+ reset(notify = true, unsubscribeClock = true) {
727
874
  this._status = "IDLE";
728
875
  this._currentConfig = null;
729
876
  this._masterTime = 0;
@@ -731,13 +878,12 @@ var Timeline = class {
731
878
  this._state = [];
732
879
  this._initialState = [];
733
880
  this._propertyKeyMap = null;
734
- this._segmentStartStates.clear();
735
881
  this._currentExecutionPlan = null;
736
- this._clock.unsubscribe(this);
737
- this._repeatCount = 0;
738
- if (notify) {
739
- this.notify();
882
+ if (unsubscribeClock) {
883
+ this._clock.unsubscribe(this);
740
884
  }
885
+ this._repeatCount = 0;
886
+ if (notify) this.notify();
741
887
  }
742
888
  seek(targetTime) {
743
889
  if (this._status === "PLAYING" || this._status === "ENDED") {
@@ -748,87 +894,50 @@ var Timeline = class {
748
894
  this._state = this._calculateStateAtTime(seekTime, 0);
749
895
  this.notify();
750
896
  }
751
- getCurrentValue() {
752
- if (this._state.length === 0) return null;
753
- return this._resolveStateToGroup(this._state);
754
- }
897
+ /**
898
+ * When timeline updates, subscribes on update callback.
899
+ * @param callback Subscription callback.
900
+ * @returns Unsubscribe.
901
+ */
755
902
  onUpdate(callback) {
756
903
  this._onUpdateCallbacks.add(callback);
757
904
  return () => {
758
905
  this._onUpdateCallbacks.delete(callback);
759
906
  };
760
907
  }
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
908
  };
798
- function timeline(rootNode, clock) {
799
- return new Timeline(rootNode, clock);
909
+ function rafTimeline(rootNode, clock) {
910
+ return new RafAniTimeline(rootNode, clock);
800
911
  }
801
912
 
802
- // src/ani/states.ts
913
+ // src/ani/raf/states.ts
803
914
  function createStates(config) {
804
915
  let State = config.initial;
805
- let Timeline2 = timeline(
916
+ let Timeline = rafTimeline(
806
917
  config.states[State],
807
918
  config.clock
808
919
  );
809
920
  const subs = /* @__PURE__ */ new Set();
810
- const notify = (timeline2) => {
921
+ const notify = (timeline) => {
811
922
  for (const Sub of subs) {
812
- Sub(timeline2);
923
+ Sub(timeline);
813
924
  }
814
925
  };
815
926
  return {
816
- timeline: () => Timeline2,
927
+ timeline: () => Timeline,
928
+ state: () => State,
817
929
  onTimelineChange(callback) {
818
930
  subs.add(callback);
819
931
  return () => subs.delete(callback);
820
932
  },
821
933
  transitionTo(newState, timelineConfig, canBeIntercepted) {
822
- if (!config.states[newState] || State === newState) {
823
- return;
824
- }
825
934
  const from = timelineConfig?.from ?? // 1. config
826
- Timeline2.getCurrentValue() ?? // 2. last value
935
+ Timeline.getCurrentValue() ?? // 2. last value
827
936
  config.initialFrom;
828
937
  State = newState;
829
- Timeline2 = timeline(config.states[State], config.clock);
830
- notify(Timeline2);
831
- Timeline2.play(
938
+ Timeline = rafTimeline(config.states[State], config.clock);
939
+ notify(Timeline);
940
+ Timeline.play(
832
941
  {
833
942
  ...timelineConfig,
834
943
  from
@@ -839,77 +948,6 @@ function createStates(config) {
839
948
  };
840
949
  }
841
950
 
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
951
  // src/style/create_sheet.ts
914
952
  var TransformFunctionMap = {
915
953
  // deg
@@ -997,40 +1035,285 @@ function createStyleSheet(animeStyleValue, resolver) {
997
1035
  return styleAccumulator;
998
1036
  }
999
1037
 
1038
+ // src/ani/waapi/compiler/resolver.ts
1039
+ function resolveStateAt(plan, initialFrom, targetTime, dt) {
1040
+ const { keyMap, values: initialValues } = resolveGroup(initialFrom);
1041
+ const rawResultState = resolvePlanState(
1042
+ plan,
1043
+ initialValues,
1044
+ keyMap,
1045
+ targetTime,
1046
+ dt
1047
+ );
1048
+ return resolveStateToGroup(rawResultState, keyMap);
1049
+ }
1050
+
1051
+ // src/ani/waapi/compiler/timing_compiler.ts
1052
+ function compileTiming(timing) {
1053
+ if (!timing) {
1054
+ return "linear";
1055
+ }
1056
+ if (timing instanceof LinearTimingFunction) {
1057
+ return "linear";
1058
+ }
1059
+ if (timing instanceof BezierTimingFunction) {
1060
+ const { p2, p3 } = timing.opt;
1061
+ return `cubic-bezier(${p2.x}, ${p2.y}, ${p3.x}, ${p3.y})`;
1062
+ }
1063
+ return null;
1064
+ }
1065
+
1066
+ // src/ani/waapi/compiler/keyframe_compiler.ts
1067
+ function compileToKeyframes(plan, initialFrom) {
1068
+ if (plan.length === 0) {
1069
+ return [];
1070
+ }
1071
+ const FPS = 60;
1072
+ const SAMPLE_RATE = 1 / FPS;
1073
+ const duration = Math.max(...plan.map((s) => s.endTime));
1074
+ if (duration === 0) {
1075
+ const state = resolveStateAt(plan, initialFrom, 0, SAMPLE_RATE);
1076
+ const style = createStyleSheet(state);
1077
+ return [
1078
+ { offset: 0, ...style },
1079
+ { offset: 1, ...style }
1080
+ ];
1081
+ }
1082
+ const timePoints = /* @__PURE__ */ new Set([0, duration]);
1083
+ for (const seg of plan) {
1084
+ timePoints.add(seg.startTime);
1085
+ timePoints.add(seg.endTime);
1086
+ }
1087
+ const sortedTimes = Array.from(timePoints).sort((a2, b) => a2 - b);
1088
+ const keyframes = [];
1089
+ const getEasingForInterval = (t, nextT) => {
1090
+ const activeSegments = plan.filter(
1091
+ (s) => s.startTime <= t && s.endTime >= nextT
1092
+ );
1093
+ if (activeSegments.length === 0) return "linear";
1094
+ const timings = activeSegments.map((s) => s.node.props.timing).filter((t2) => t2 !== void 0);
1095
+ if (timings.length === 0) return "linear";
1096
+ const firstTiming = timings[0];
1097
+ const allSame = timings.every((t2) => t2 === firstTiming);
1098
+ if (allSame && firstTiming instanceof TimingFunction) {
1099
+ return compileTiming(firstTiming);
1100
+ }
1101
+ return null;
1102
+ };
1103
+ for (let i = 0; i < sortedTimes.length; i++) {
1104
+ const currT = sortedTimes[i];
1105
+ const state = resolveStateAt(plan, initialFrom, currT, SAMPLE_RATE);
1106
+ const style = createStyleSheet(state);
1107
+ const keyframe = {
1108
+ offset: currT / duration,
1109
+ ...style
1110
+ };
1111
+ keyframes.push(keyframe);
1112
+ if (i < sortedTimes.length - 1) {
1113
+ const nextT = sortedTimes[i + 1];
1114
+ const easing = getEasingForInterval(currT, nextT);
1115
+ if (easing === null) {
1116
+ let sampleT = currT + SAMPLE_RATE;
1117
+ while (sampleT < nextT) {
1118
+ const sampleState = resolveStateAt(
1119
+ plan,
1120
+ initialFrom,
1121
+ sampleT,
1122
+ SAMPLE_RATE
1123
+ );
1124
+ const sampleStyle = createStyleSheet(
1125
+ sampleState
1126
+ );
1127
+ keyframes.push({
1128
+ offset: sampleT / duration,
1129
+ ...sampleStyle,
1130
+ easing: "linear"
1131
+ });
1132
+ sampleT += SAMPLE_RATE;
1133
+ }
1134
+ keyframe.easing = "linear";
1135
+ } else {
1136
+ keyframe.easing = easing;
1137
+ }
1138
+ }
1139
+ }
1140
+ return keyframes;
1141
+ }
1142
+
1143
+ // src/ani/waapi/timeline.ts
1144
+ var WebAniTimeline = class extends TimelineBase {
1145
+ constructor(rootNode) {
1146
+ super(rootNode);
1147
+ this._animation = null;
1148
+ this._keyframes = [];
1149
+ }
1150
+ /**
1151
+ * Plays animation.
1152
+ * @param target Target element.
1153
+ * @param config {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API Web Animations API} based config.
1154
+ */
1155
+ play(target, config) {
1156
+ if (this._animation) {
1157
+ this._animation.cancel();
1158
+ }
1159
+ this._currentExecutionPlan = this._resolveExecutionPlan(
1160
+ config.keyframes,
1161
+ config.durations
1162
+ );
1163
+ this._keyframes = compileToKeyframes(
1164
+ this._currentExecutionPlan,
1165
+ config.from
1166
+ );
1167
+ if (this._keyframes.length === 0) {
1168
+ return null;
1169
+ }
1170
+ const totalDurationMs = this._currentExecutionPlan.reduce(
1171
+ (max, seg) => Math.max(max, seg.endTime),
1172
+ 0
1173
+ ) * 1e3;
1174
+ const effect = new KeyframeEffect(target, this._keyframes, {
1175
+ duration: totalDurationMs,
1176
+ iterations: config.repeat ?? 1,
1177
+ delay: config.delay ?? 0,
1178
+ fill: "forwards"
1179
+ });
1180
+ this._animation = new Animation(effect, document.timeline);
1181
+ this._animation.play();
1182
+ return this._animation;
1183
+ }
1184
+ pause() {
1185
+ this._animation?.pause();
1186
+ }
1187
+ resume() {
1188
+ this._animation?.play();
1189
+ }
1190
+ reset() {
1191
+ this._animation?.cancel();
1192
+ this._animation = null;
1193
+ }
1194
+ seek(targetTime) {
1195
+ if (this._animation) {
1196
+ this._animation.currentTime = targetTime * 1e3;
1197
+ }
1198
+ }
1199
+ /**
1200
+ * Native animation object.
1201
+ *
1202
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Animation Animation}.
1203
+ */
1204
+ get nativeAnimation() {
1205
+ return this._animation;
1206
+ }
1207
+ };
1208
+ function webTimeline(rootNode) {
1209
+ return new WebAniTimeline(rootNode);
1210
+ }
1211
+
1212
+ // src/event/manager.ts
1213
+ var EventManager = class _EventManager {
1214
+ constructor(supportedEvents) {
1215
+ this.supportedEvents = supportedEvents;
1216
+ this._element = null;
1217
+ this._animeGetter = null;
1218
+ this.setAnimeGetter = (animeGetter) => {
1219
+ this._animeGetter = animeGetter;
1220
+ };
1221
+ this.eventMap = /* @__PURE__ */ new Map();
1222
+ this.withAnimeValue = (listener) => {
1223
+ const withAnime = (e) => {
1224
+ listener(this.animeGetter(), e);
1225
+ };
1226
+ return withAnime;
1227
+ };
1228
+ this.add = (eventName, listener, options) => {
1229
+ const withAnime = this.withAnimeValue(listener);
1230
+ this.eventMap.set(eventName, withAnime);
1231
+ this.targetElement.addEventListener(
1232
+ eventName,
1233
+ this.eventMap.get(eventName),
1234
+ options
1235
+ );
1236
+ };
1237
+ this.cleanupOne = (eventName) => {
1238
+ const removeListener = this.eventMap.get(eventName);
1239
+ if (!removeListener) return false;
1240
+ this.targetElement.removeEventListener(eventName, removeListener);
1241
+ return true;
1242
+ };
1243
+ this.cleanupAll = () => {
1244
+ const clearResponse = [];
1245
+ for (const evtName of this.eventMap.keys()) {
1246
+ const res = this.cleanupOne(evtName);
1247
+ clearResponse.push(res);
1248
+ }
1249
+ return clearResponse.some((t) => t === false) === false;
1250
+ };
1251
+ this.attach = (handlers) => {
1252
+ Object.entries(handlers).forEach(([eventKey, handler]) => {
1253
+ this.add(
1254
+ _EventManager.getEvtKey(eventKey),
1255
+ handler
1256
+ );
1257
+ });
1258
+ };
1259
+ }
1260
+ get targetElement() {
1261
+ if (!this._element) throw new Error("EventManger, bind element first");
1262
+ return this._element;
1263
+ }
1264
+ get animeGetter() {
1265
+ if (!this._animeGetter)
1266
+ throw new Error("EventManager, animeGetter should be provided");
1267
+ return this._animeGetter;
1268
+ }
1269
+ /**
1270
+ * get pure `{event_name}`
1271
+ * @param key onX`{event_name}`
1272
+ */
1273
+ static getEvtKey(key) {
1274
+ const removed = key.substring(2, key.length);
1275
+ const Capitalized = `${removed[0].toLowerCase()}${removed.substring(1, key.length)}`;
1276
+ return Capitalized;
1277
+ }
1278
+ bind(element) {
1279
+ this._element = element;
1280
+ }
1281
+ };
1282
+
1000
1283
  // src/index.ts
1001
1284
  var a = {
1002
1285
  timing: T,
1286
+ dynamicTimeline: rafTimeline,
1287
+ timeline: webTimeline,
1288
+ /**
1289
+ * Create animation segment.
1290
+ */
1003
1291
  ani,
1004
- createStates,
1292
+ /**
1293
+ * Add delay
1294
+ */
1005
1295
  delay,
1006
1296
  loop,
1007
1297
  parallel,
1008
1298
  sequence,
1009
1299
  stagger,
1010
- timeline
1300
+ createStates
1011
1301
  };
1012
1302
  export {
1013
1303
  AnimationClock,
1014
- AnimationNode,
1015
- CompositionNode,
1304
+ BezierTimingFunction,
1016
1305
  EventManager,
1017
1306
  LinearTimingFunction,
1018
- ParallelNode,
1019
- SegmentNode,
1020
- SequenceNode,
1021
- StaggerNode,
1307
+ RafAniTimeline,
1022
1308
  T,
1023
- Timeline,
1309
+ TimelineBase,
1024
1310
  TimingFunction,
1311
+ WebAniTimeline,
1025
1312
  a,
1026
- ani,
1313
+ calculateSegmentState,
1314
+ compileToKeyframes,
1027
1315
  createStates,
1028
1316
  createStyleSheet,
1029
- delay,
1030
- loop,
1031
- parallel,
1032
- sequence,
1033
- stagger,
1034
- timeline
1317
+ rafTimeline,
1318
+ webTimeline
1035
1319
  };
1036
- //# sourceMappingURL=index.js.map