@freestylejs/ani-core 1.2.0 → 1.3.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.cjs CHANGED
@@ -34,7 +34,9 @@ __export(src_exports, {
34
34
  compileToKeyframes: () => compileToKeyframes,
35
35
  createStates: () => createStates,
36
36
  createStyleSheet: () => createStyleSheet,
37
+ normalizeRepeatCount: () => normalizeRepeatCount,
37
38
  rafTimeline: () => rafTimeline,
39
+ toIterationCount: () => toIterationCount,
38
40
  webTimeline: () => webTimeline
39
41
  });
40
42
  module.exports = __toCommonJS(src_exports);
@@ -436,6 +438,14 @@ function ani(props, id) {
436
438
  }
437
439
 
438
440
  // src/nodes/composition.ts
441
+ function cloneCompositionNodeWithChildren(source, children) {
442
+ const clone = Object.create(
443
+ Object.getPrototypeOf(source)
444
+ );
445
+ Object.assign(clone, source);
446
+ clone.children = children;
447
+ return clone;
448
+ }
439
449
  var CompositionNode = class _CompositionNode extends AnimationNode {
440
450
  constructor(children, timing, id) {
441
451
  super(id);
@@ -453,8 +463,11 @@ var CompositionNode = class _CompositionNode extends AnimationNode {
453
463
  );
454
464
  }
455
465
  if (child instanceof _CompositionNode) {
456
- child.children = adjustTiming(child.children);
457
- return child;
466
+ const adjustedChildren = adjustTiming(child.children);
467
+ return cloneCompositionNodeWithChildren(
468
+ child,
469
+ adjustedChildren
470
+ );
458
471
  }
459
472
  return child;
460
473
  });
@@ -585,6 +598,17 @@ function stagger(children, offset, timing, id) {
585
598
  }
586
599
 
587
600
  // src/ani/core/interface/timeline_interface.ts
601
+ function normalizeRepeatCount(repeat) {
602
+ if (repeat === Infinity) return Infinity;
603
+ if (!Number.isFinite(repeat) || repeat === void 0 || repeat <= 0) {
604
+ return 0;
605
+ }
606
+ return Math.floor(repeat);
607
+ }
608
+ function toIterationCount(repeat) {
609
+ const normalized = normalizeRepeatCount(repeat);
610
+ return normalized === Infinity ? Infinity : normalized + 1;
611
+ }
588
612
  var TimelineBase = class {
589
613
  constructor(rootNode) {
590
614
  this.rootNode = rootNode;
@@ -634,10 +658,10 @@ var TimelineBase = class {
634
658
  const dynamicDuration = durations?.[keyframeIndex];
635
659
  const newSegmentProps = {
636
660
  ...segment.node.props,
637
- ...dynamicTo && dynamicTo !== "keep" && {
661
+ ...dynamicTo !== void 0 && dynamicTo !== "keep" && {
638
662
  to: dynamicTo
639
663
  },
640
- ...dynamicDuration && dynamicDuration !== "keep" && {
664
+ ...dynamicDuration !== void 0 && dynamicDuration !== "keep" && {
641
665
  duration: dynamicDuration
642
666
  }
643
667
  };
@@ -683,9 +707,20 @@ var AnimationClock = class _AnimationClock {
683
707
  * @returns clock
684
708
  */
685
709
  static create(maxDeltaTime = 1 / 10) {
686
- _AnimationClock.clock = new _AnimationClock(maxDeltaTime);
710
+ if (!_AnimationClock.clock) {
711
+ _AnimationClock.clock = new _AnimationClock(maxDeltaTime);
712
+ }
687
713
  return _AnimationClock.clock;
688
714
  }
715
+ /**
716
+ * Reconfigure singleton clock.
717
+ * If not created yet, creates one with provided maxDeltaTime.
718
+ */
719
+ static configure(maxDeltaTime) {
720
+ const clock = _AnimationClock.create(maxDeltaTime);
721
+ clock.maxDeltaTime = maxDeltaTime;
722
+ return clock;
723
+ }
689
724
  subscribe(animatable) {
690
725
  this.subscribers.add(animatable);
691
726
  if (!this.animationFrameId) {
@@ -796,7 +831,7 @@ var RafAniTimeline = class extends TimelineBase {
796
831
  this._currentConfig = null;
797
832
  this._state = [];
798
833
  this._initialState = [];
799
- this._repeatCount = 0;
834
+ this._remainingRepeats = 0;
800
835
  this._propertyKeyMap = null;
801
836
  this._onUpdateCallbacks = /* @__PURE__ */ new Set();
802
837
  this._clock = clock ?? AnimationClock.create();
@@ -829,6 +864,26 @@ var RafAniTimeline = class extends TimelineBase {
829
864
  dt
830
865
  );
831
866
  }
867
+ _resolveRuntimeState(config) {
868
+ this._currentExecutionPlan = this._resolveExecutionPlan(
869
+ config.keyframes,
870
+ config.durations
871
+ );
872
+ const { keyMap, values } = resolveGroup(config.from);
873
+ this._propertyKeyMap = keyMap;
874
+ this._state = values;
875
+ this._initialState = values;
876
+ this._masterTime = 0;
877
+ }
878
+ _restartForRepeat() {
879
+ if (!this._currentConfig) {
880
+ return;
881
+ }
882
+ this._delay = 0;
883
+ this._resolveRuntimeState(this._currentConfig);
884
+ this._status = "PLAYING";
885
+ this.notify();
886
+ }
832
887
  notify() {
833
888
  for (const subscriber of this._onUpdateCallbacks) {
834
889
  subscriber({
@@ -859,14 +914,15 @@ var RafAniTimeline = class extends TimelineBase {
859
914
  this._state = this._calculateStateAtTime(this._masterTime, dt);
860
915
  this.notify();
861
916
  if (isEndOfAnimation(this._masterTime, this.duration)) {
862
- this._repeatCount += 1;
863
- const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
864
- if (noRepeat) {
917
+ if (this._remainingRepeats === Infinity || this._remainingRepeats > 0) {
918
+ if (this._remainingRepeats !== Infinity) {
919
+ this._remainingRepeats -= 1;
920
+ }
921
+ this._restartForRepeat();
922
+ } else {
865
923
  this._status = "ENDED";
866
924
  this._clock.unsubscribe(this);
867
925
  this.notify();
868
- } else {
869
- this.play(this._currentConfig);
870
926
  }
871
927
  }
872
928
  }
@@ -877,26 +933,11 @@ var RafAniTimeline = class extends TimelineBase {
877
933
  */
878
934
  play(config, canBeIntercepted = true) {
879
935
  if (this._status === "PLAYING" && !canBeIntercepted) return;
880
- const isRepeating = (this._currentConfig?.repeat ?? 0) >= 1;
881
- const savedRepeatCount = isRepeating ? this._repeatCount : 0;
882
- this.reset(false);
883
- this._repeatCount = savedRepeatCount;
884
- if (isRepeating && this._repeatCount >= config.repeat) {
885
- this._repeatCount = 0;
886
- return;
887
- }
936
+ this.reset(false, true);
888
937
  this._currentConfig = config;
889
- if (this._repeatCount === 0) {
890
- this._delay = (this._currentConfig.delay ?? 0) * 1e-3;
891
- }
892
- this._currentExecutionPlan = this._resolveExecutionPlan(
893
- config.keyframes,
894
- config.durations
895
- );
896
- const { keyMap, values } = resolveGroup(config.from);
897
- this._propertyKeyMap = keyMap;
898
- this._state = values;
899
- this._initialState = values;
938
+ this._remainingRepeats = normalizeRepeatCount(config.repeat);
939
+ this._delay = (this._currentConfig.delay ?? 0) * 1e-3;
940
+ this._resolveRuntimeState(config);
900
941
  this._status = "PLAYING";
901
942
  this._clock.subscribe(this);
902
943
  this.notify();
@@ -910,7 +951,7 @@ var RafAniTimeline = class extends TimelineBase {
910
951
  this._status = "PLAYING";
911
952
  this._clock.subscribe(this);
912
953
  }
913
- reset(notify = true) {
954
+ reset(notify = true, unsubscribeClock = true) {
914
955
  this._status = "IDLE";
915
956
  this._currentConfig = null;
916
957
  this._masterTime = 0;
@@ -919,8 +960,10 @@ var RafAniTimeline = class extends TimelineBase {
919
960
  this._initialState = [];
920
961
  this._propertyKeyMap = null;
921
962
  this._currentExecutionPlan = null;
922
- this._clock.unsubscribe(this);
923
- this._repeatCount = 0;
963
+ if (unsubscribeClock) {
964
+ this._clock.unsubscribe(this);
965
+ }
966
+ this._remainingRepeats = 0;
924
967
  if (notify) this.notify();
925
968
  }
926
969
  seek(targetTime) {
@@ -969,9 +1012,11 @@ function createStates(config) {
969
1012
  return () => subs.delete(callback);
970
1013
  },
971
1014
  transitionTo(newState, timelineConfig, canBeIntercepted) {
1015
+ const previousTimeline = Timeline;
972
1016
  const from = timelineConfig?.from ?? // 1. config
973
1017
  Timeline.getCurrentValue() ?? // 2. last value
974
1018
  config.initialFrom;
1019
+ previousTimeline.reset(false);
975
1020
  State = newState;
976
1021
  Timeline = rafTimeline(config.states[State], config.clock);
977
1022
  notify(Timeline);
@@ -1102,7 +1147,7 @@ function compileTiming(timing) {
1102
1147
  }
1103
1148
 
1104
1149
  // src/ani/waapi/compiler/keyframe_compiler.ts
1105
- function compileToKeyframes(plan, initialFrom) {
1150
+ function compileToKeyframes(plan, initialFrom, resolver) {
1106
1151
  if (plan.length === 0) {
1107
1152
  return [];
1108
1153
  }
@@ -1111,7 +1156,10 @@ function compileToKeyframes(plan, initialFrom) {
1111
1156
  const duration = Math.max(...plan.map((s) => s.endTime));
1112
1157
  if (duration === 0) {
1113
1158
  const state = resolveStateAt(plan, initialFrom, 0, SAMPLE_RATE);
1114
- const style = createStyleSheet(state);
1159
+ const style = createStyleSheet(
1160
+ state,
1161
+ resolver
1162
+ );
1115
1163
  return [
1116
1164
  { offset: 0, ...style },
1117
1165
  { offset: 1, ...style }
@@ -1141,7 +1189,10 @@ function compileToKeyframes(plan, initialFrom) {
1141
1189
  for (let i = 0; i < sortedTimes.length; i++) {
1142
1190
  const currT = sortedTimes[i];
1143
1191
  const state = resolveStateAt(plan, initialFrom, currT, SAMPLE_RATE);
1144
- const style = createStyleSheet(state);
1192
+ const style = createStyleSheet(
1193
+ state,
1194
+ resolver
1195
+ );
1145
1196
  const keyframe = {
1146
1197
  offset: currT / duration,
1147
1198
  ...style
@@ -1160,7 +1211,8 @@ function compileToKeyframes(plan, initialFrom) {
1160
1211
  SAMPLE_RATE
1161
1212
  );
1162
1213
  const sampleStyle = createStyleSheet(
1163
- sampleState
1214
+ sampleState,
1215
+ resolver
1164
1216
  );
1165
1217
  keyframes.push({
1166
1218
  offset: sampleT / duration,
@@ -1200,7 +1252,8 @@ var WebAniTimeline = class extends TimelineBase {
1200
1252
  );
1201
1253
  this._keyframes = compileToKeyframes(
1202
1254
  this._currentExecutionPlan,
1203
- config.from
1255
+ config.from,
1256
+ config.propertyResolver
1204
1257
  );
1205
1258
  if (this._keyframes.length === 0) {
1206
1259
  return null;
@@ -1211,9 +1264,10 @@ var WebAniTimeline = class extends TimelineBase {
1211
1264
  ) * 1e3;
1212
1265
  const effect = new KeyframeEffect(target, this._keyframes, {
1213
1266
  duration: totalDurationMs,
1214
- iterations: config.repeat ?? 1,
1267
+ iterations: toIterationCount(config.repeat),
1215
1268
  delay: config.delay ?? 0,
1216
- fill: "forwards"
1269
+ fill: "forwards",
1270
+ ...config.keyframeEffect
1217
1271
  });
1218
1272
  this._animation = new Animation(effect, document.timeline);
1219
1273
  this._animation.play();
@@ -1229,6 +1283,12 @@ var WebAniTimeline = class extends TimelineBase {
1229
1283
  this._animation?.cancel();
1230
1284
  this._animation = null;
1231
1285
  }
1286
+ /**
1287
+ * Alias of reset() for WAAPI naming parity.
1288
+ */
1289
+ cancel() {
1290
+ this.reset();
1291
+ }
1232
1292
  seek(targetTime) {
1233
1293
  if (this._animation) {
1234
1294
  this._animation.currentTime = targetTime * 1e3;
@@ -1276,6 +1336,7 @@ var EventManager = class _EventManager {
1276
1336
  const removeListener = this.eventMap.get(eventName);
1277
1337
  if (!removeListener) return false;
1278
1338
  this.targetElement.removeEventListener(eventName, removeListener);
1339
+ this.eventMap.delete(eventName);
1279
1340
  return true;
1280
1341
  };
1281
1342
  this.cleanupAll = () => {
@@ -1321,8 +1382,17 @@ var EventManager = class _EventManager {
1321
1382
  // src/index.ts
1322
1383
  var a = {
1323
1384
  timing: T,
1385
+ /**
1386
+ * @deprecated Use `rafTimeline` alias for explicit engine naming.
1387
+ */
1324
1388
  dynamicTimeline: rafTimeline,
1389
+ /**
1390
+ * @deprecated Use `waapiTimeline` alias for explicit engine naming.
1391
+ */
1325
1392
  timeline: webTimeline,
1393
+ webTimeline,
1394
+ rafTimeline,
1395
+ waapiTimeline: webTimeline,
1326
1396
  /**
1327
1397
  * Create animation segment.
1328
1398
  */
@@ -1353,6 +1423,8 @@ var a = {
1353
1423
  compileToKeyframes,
1354
1424
  createStates,
1355
1425
  createStyleSheet,
1426
+ normalizeRepeatCount,
1356
1427
  rafTimeline,
1428
+ toIterationCount,
1357
1429
  webTimeline
1358
1430
  });
package/dist/index.d.cts CHANGED
@@ -467,6 +467,15 @@ interface TimelineCommonConfig<G extends Groupable> {
467
467
  */
468
468
  propertyResolver?: G extends AnimePrimitive ? never : Resolver<G>;
469
469
  }
470
+ /**
471
+ * Normalize repeat count as "additional iterations after first play".
472
+ */
473
+ declare function normalizeRepeatCount(repeat?: number): number;
474
+ /**
475
+ * Converts repeat count to WAAPI iteration count.
476
+ * repeat=0 -> iterations=1, repeat=1 -> iterations=2
477
+ */
478
+ declare function toIterationCount(repeat?: number): number;
470
479
  declare abstract class TimelineBase<G extends Groupable> {
471
480
  protected readonly rootNode: AnimationNode<G>;
472
481
  readonly duration: number;
@@ -527,6 +536,11 @@ declare class AnimationClock implements AnimationClockInterface {
527
536
  * @returns clock
528
537
  */
529
538
  static create(maxDeltaTime?: number): AnimationClock;
539
+ /**
540
+ * Reconfigure singleton clock.
541
+ * If not created yet, creates one with provided maxDeltaTime.
542
+ */
543
+ static configure(maxDeltaTime: number): AnimationClock;
530
544
  private constructor();
531
545
  subscribe(animatable: Animatable): void;
532
546
  unsubscribe(animatable: Animatable): void;
@@ -561,12 +575,14 @@ declare class RafAniTimeline<G extends Groupable, Ctx = any> extends TimelineBas
561
575
  get currentConfig(): RafTimelineConfig<G, Ctx> | null;
562
576
  private _state;
563
577
  private _initialState;
564
- private _repeatCount;
578
+ private _remainingRepeats;
565
579
  private _propertyKeyMap;
566
580
  private _onUpdateCallbacks;
567
581
  constructor(rootNode: AnimationNode<G>, clock?: AnimationClockInterface);
568
582
  getCurrentValue(): AniGroup<G> | null;
569
583
  private _calculateStateAtTime;
584
+ private _resolveRuntimeState;
585
+ private _restartForRepeat;
570
586
  private notify;
571
587
  /**
572
588
  * @private Internal clock subscription callback.
@@ -580,7 +596,7 @@ declare class RafAniTimeline<G extends Groupable, Ctx = any> extends TimelineBas
580
596
  play(config: RafTimelineConfig<G, Ctx>, canBeIntercepted?: boolean): void;
581
597
  pause(): void;
582
598
  resume(): void;
583
- reset(notify?: boolean): void;
599
+ reset(notify?: boolean, unsubscribeClock?: boolean): void;
584
600
  seek(targetTime: number): void;
585
601
  /**
586
602
  * When timeline updates, subscribes on update callback.
@@ -659,7 +675,7 @@ interface WebAniKeyframe extends Record<string, string | number | null> {
659
675
  * @param plan The resolved execution plan (from TimelineBase).
660
676
  * @param initialFrom The initial state (values).
661
677
  */
662
- declare function compileToKeyframes<G extends Groupable>(plan: ExecutionPlan<G>, initialFrom: G): WebAniKeyframe[];
678
+ declare function compileToKeyframes<G extends Groupable>(plan: ExecutionPlan<G>, initialFrom: G, resolver?: Resolver<Record<string, number>>): WebAniKeyframe[];
663
679
 
664
680
  interface WebAniTimelineConfig<G extends Groupable> extends TimelineCommonConfig<G> {
665
681
  /**
@@ -682,6 +698,10 @@ declare class WebAniTimeline<G extends Groupable> extends TimelineBase<G> {
682
698
  pause(): void;
683
699
  resume(): void;
684
700
  reset(): void;
701
+ /**
702
+ * Alias of reset() for WAAPI naming parity.
703
+ */
704
+ cancel(): void;
685
705
  seek(targetTime: number): void;
686
706
  /**
687
707
  * Native animation object.
@@ -759,8 +779,17 @@ declare const a: {
759
779
  readonly easeOut: () => BezierTimingFunction;
760
780
  readonly easeInOut: () => BezierTimingFunction;
761
781
  };
782
+ /**
783
+ * @deprecated Use `rafTimeline` alias for explicit engine naming.
784
+ */
762
785
  readonly dynamicTimeline: typeof rafTimeline;
786
+ /**
787
+ * @deprecated Use `waapiTimeline` alias for explicit engine naming.
788
+ */
763
789
  readonly timeline: typeof webTimeline;
790
+ readonly webTimeline: typeof webTimeline;
791
+ readonly rafTimeline: typeof rafTimeline;
792
+ readonly waapiTimeline: typeof webTimeline;
764
793
  /**
765
794
  * Create animation segment.
766
795
  */
@@ -776,4 +805,4 @@ declare const a: {
776
805
  readonly createStates: typeof createStates;
777
806
  };
778
807
 
779
- export { type AniGroup, type AniRefContext, type AniRefProps, type Animatable, AnimationClock, type AnimationClockInterface, type AnimationStateShape, type AnimePrimitive, BezierTimingFunction, type BezierTimingFunctionOpt, type Coord, type EventHandler, type EventHandlerRegistration, type EventKey, EventManager, type ExecutionPlan, type ExecutionSegment, type GetTimeline, type Groupable, type GroupableRecord, type GroupableRecordKey, LinearTimingFunction, type OnUpdateCallback, RafAniTimeline, type RafTimelineConfig, type Resolver, type SegmentDefinition, type SegmentState, type SegmentTiming, type StateController, type StateProps, type StylesheetSupportedLiteral, T, TimelineBase, type TimelineCommonConfig, TimingFunction, type TimingFunctionContext, type WebAniKeyframe, WebAniTimeline, type WebAniTimelineConfig, a, calculateSegmentState, compileToKeyframes, createStates, createStyleSheet, rafTimeline, webTimeline };
808
+ export { type AniGroup, type AniRefContext, type AniRefProps, type Animatable, AnimationClock, type AnimationClockInterface, type AnimationStateShape, type AnimePrimitive, BezierTimingFunction, type BezierTimingFunctionOpt, type Coord, type EventHandler, type EventHandlerRegistration, type EventKey, EventManager, type ExecutionPlan, type ExecutionSegment, type GetTimeline, type Groupable, type GroupableRecord, type GroupableRecordKey, LinearTimingFunction, type OnUpdateCallback, RafAniTimeline, type RafTimelineConfig, type Resolver, type SegmentDefinition, type SegmentState, type SegmentTiming, type StateController, type StateProps, type StylesheetSupportedLiteral, T, TimelineBase, type TimelineCommonConfig, TimingFunction, type TimingFunctionContext, type WebAniKeyframe, WebAniTimeline, type WebAniTimelineConfig, a, calculateSegmentState, compileToKeyframes, createStates, createStyleSheet, normalizeRepeatCount, rafTimeline, toIterationCount, webTimeline };
package/dist/index.d.ts CHANGED
@@ -467,6 +467,15 @@ interface TimelineCommonConfig<G extends Groupable> {
467
467
  */
468
468
  propertyResolver?: G extends AnimePrimitive ? never : Resolver<G>;
469
469
  }
470
+ /**
471
+ * Normalize repeat count as "additional iterations after first play".
472
+ */
473
+ declare function normalizeRepeatCount(repeat?: number): number;
474
+ /**
475
+ * Converts repeat count to WAAPI iteration count.
476
+ * repeat=0 -> iterations=1, repeat=1 -> iterations=2
477
+ */
478
+ declare function toIterationCount(repeat?: number): number;
470
479
  declare abstract class TimelineBase<G extends Groupable> {
471
480
  protected readonly rootNode: AnimationNode<G>;
472
481
  readonly duration: number;
@@ -527,6 +536,11 @@ declare class AnimationClock implements AnimationClockInterface {
527
536
  * @returns clock
528
537
  */
529
538
  static create(maxDeltaTime?: number): AnimationClock;
539
+ /**
540
+ * Reconfigure singleton clock.
541
+ * If not created yet, creates one with provided maxDeltaTime.
542
+ */
543
+ static configure(maxDeltaTime: number): AnimationClock;
530
544
  private constructor();
531
545
  subscribe(animatable: Animatable): void;
532
546
  unsubscribe(animatable: Animatable): void;
@@ -561,12 +575,14 @@ declare class RafAniTimeline<G extends Groupable, Ctx = any> extends TimelineBas
561
575
  get currentConfig(): RafTimelineConfig<G, Ctx> | null;
562
576
  private _state;
563
577
  private _initialState;
564
- private _repeatCount;
578
+ private _remainingRepeats;
565
579
  private _propertyKeyMap;
566
580
  private _onUpdateCallbacks;
567
581
  constructor(rootNode: AnimationNode<G>, clock?: AnimationClockInterface);
568
582
  getCurrentValue(): AniGroup<G> | null;
569
583
  private _calculateStateAtTime;
584
+ private _resolveRuntimeState;
585
+ private _restartForRepeat;
570
586
  private notify;
571
587
  /**
572
588
  * @private Internal clock subscription callback.
@@ -580,7 +596,7 @@ declare class RafAniTimeline<G extends Groupable, Ctx = any> extends TimelineBas
580
596
  play(config: RafTimelineConfig<G, Ctx>, canBeIntercepted?: boolean): void;
581
597
  pause(): void;
582
598
  resume(): void;
583
- reset(notify?: boolean): void;
599
+ reset(notify?: boolean, unsubscribeClock?: boolean): void;
584
600
  seek(targetTime: number): void;
585
601
  /**
586
602
  * When timeline updates, subscribes on update callback.
@@ -659,7 +675,7 @@ interface WebAniKeyframe extends Record<string, string | number | null> {
659
675
  * @param plan The resolved execution plan (from TimelineBase).
660
676
  * @param initialFrom The initial state (values).
661
677
  */
662
- declare function compileToKeyframes<G extends Groupable>(plan: ExecutionPlan<G>, initialFrom: G): WebAniKeyframe[];
678
+ declare function compileToKeyframes<G extends Groupable>(plan: ExecutionPlan<G>, initialFrom: G, resolver?: Resolver<Record<string, number>>): WebAniKeyframe[];
663
679
 
664
680
  interface WebAniTimelineConfig<G extends Groupable> extends TimelineCommonConfig<G> {
665
681
  /**
@@ -682,6 +698,10 @@ declare class WebAniTimeline<G extends Groupable> extends TimelineBase<G> {
682
698
  pause(): void;
683
699
  resume(): void;
684
700
  reset(): void;
701
+ /**
702
+ * Alias of reset() for WAAPI naming parity.
703
+ */
704
+ cancel(): void;
685
705
  seek(targetTime: number): void;
686
706
  /**
687
707
  * Native animation object.
@@ -759,8 +779,17 @@ declare const a: {
759
779
  readonly easeOut: () => BezierTimingFunction;
760
780
  readonly easeInOut: () => BezierTimingFunction;
761
781
  };
782
+ /**
783
+ * @deprecated Use `rafTimeline` alias for explicit engine naming.
784
+ */
762
785
  readonly dynamicTimeline: typeof rafTimeline;
786
+ /**
787
+ * @deprecated Use `waapiTimeline` alias for explicit engine naming.
788
+ */
763
789
  readonly timeline: typeof webTimeline;
790
+ readonly webTimeline: typeof webTimeline;
791
+ readonly rafTimeline: typeof rafTimeline;
792
+ readonly waapiTimeline: typeof webTimeline;
764
793
  /**
765
794
  * Create animation segment.
766
795
  */
@@ -776,4 +805,4 @@ declare const a: {
776
805
  readonly createStates: typeof createStates;
777
806
  };
778
807
 
779
- export { type AniGroup, type AniRefContext, type AniRefProps, type Animatable, AnimationClock, type AnimationClockInterface, type AnimationStateShape, type AnimePrimitive, BezierTimingFunction, type BezierTimingFunctionOpt, type Coord, type EventHandler, type EventHandlerRegistration, type EventKey, EventManager, type ExecutionPlan, type ExecutionSegment, type GetTimeline, type Groupable, type GroupableRecord, type GroupableRecordKey, LinearTimingFunction, type OnUpdateCallback, RafAniTimeline, type RafTimelineConfig, type Resolver, type SegmentDefinition, type SegmentState, type SegmentTiming, type StateController, type StateProps, type StylesheetSupportedLiteral, T, TimelineBase, type TimelineCommonConfig, TimingFunction, type TimingFunctionContext, type WebAniKeyframe, WebAniTimeline, type WebAniTimelineConfig, a, calculateSegmentState, compileToKeyframes, createStates, createStyleSheet, rafTimeline, webTimeline };
808
+ export { type AniGroup, type AniRefContext, type AniRefProps, type Animatable, AnimationClock, type AnimationClockInterface, type AnimationStateShape, type AnimePrimitive, BezierTimingFunction, type BezierTimingFunctionOpt, type Coord, type EventHandler, type EventHandlerRegistration, type EventKey, EventManager, type ExecutionPlan, type ExecutionSegment, type GetTimeline, type Groupable, type GroupableRecord, type GroupableRecordKey, LinearTimingFunction, type OnUpdateCallback, RafAniTimeline, type RafTimelineConfig, type Resolver, type SegmentDefinition, type SegmentState, type SegmentTiming, type StateController, type StateProps, type StylesheetSupportedLiteral, T, TimelineBase, type TimelineCommonConfig, TimingFunction, type TimingFunctionContext, type WebAniKeyframe, WebAniTimeline, type WebAniTimelineConfig, a, calculateSegmentState, compileToKeyframes, createStates, createStyleSheet, normalizeRepeatCount, rafTimeline, toIterationCount, webTimeline };
package/dist/index.js CHANGED
@@ -395,6 +395,14 @@ function ani(props, id) {
395
395
  }
396
396
 
397
397
  // src/nodes/composition.ts
398
+ function cloneCompositionNodeWithChildren(source, children) {
399
+ const clone = Object.create(
400
+ Object.getPrototypeOf(source)
401
+ );
402
+ Object.assign(clone, source);
403
+ clone.children = children;
404
+ return clone;
405
+ }
398
406
  var CompositionNode = class _CompositionNode extends AnimationNode {
399
407
  constructor(children, timing, id) {
400
408
  super(id);
@@ -412,8 +420,11 @@ var CompositionNode = class _CompositionNode extends AnimationNode {
412
420
  );
413
421
  }
414
422
  if (child instanceof _CompositionNode) {
415
- child.children = adjustTiming(child.children);
416
- return child;
423
+ const adjustedChildren = adjustTiming(child.children);
424
+ return cloneCompositionNodeWithChildren(
425
+ child,
426
+ adjustedChildren
427
+ );
417
428
  }
418
429
  return child;
419
430
  });
@@ -544,6 +555,17 @@ function stagger(children, offset, timing, id) {
544
555
  }
545
556
 
546
557
  // src/ani/core/interface/timeline_interface.ts
558
+ function normalizeRepeatCount(repeat) {
559
+ if (repeat === Infinity) return Infinity;
560
+ if (!Number.isFinite(repeat) || repeat === void 0 || repeat <= 0) {
561
+ return 0;
562
+ }
563
+ return Math.floor(repeat);
564
+ }
565
+ function toIterationCount(repeat) {
566
+ const normalized = normalizeRepeatCount(repeat);
567
+ return normalized === Infinity ? Infinity : normalized + 1;
568
+ }
547
569
  var TimelineBase = class {
548
570
  constructor(rootNode) {
549
571
  this.rootNode = rootNode;
@@ -593,10 +615,10 @@ var TimelineBase = class {
593
615
  const dynamicDuration = durations?.[keyframeIndex];
594
616
  const newSegmentProps = {
595
617
  ...segment.node.props,
596
- ...dynamicTo && dynamicTo !== "keep" && {
618
+ ...dynamicTo !== void 0 && dynamicTo !== "keep" && {
597
619
  to: dynamicTo
598
620
  },
599
- ...dynamicDuration && dynamicDuration !== "keep" && {
621
+ ...dynamicDuration !== void 0 && dynamicDuration !== "keep" && {
600
622
  duration: dynamicDuration
601
623
  }
602
624
  };
@@ -642,9 +664,20 @@ var AnimationClock = class _AnimationClock {
642
664
  * @returns clock
643
665
  */
644
666
  static create(maxDeltaTime = 1 / 10) {
645
- _AnimationClock.clock = new _AnimationClock(maxDeltaTime);
667
+ if (!_AnimationClock.clock) {
668
+ _AnimationClock.clock = new _AnimationClock(maxDeltaTime);
669
+ }
646
670
  return _AnimationClock.clock;
647
671
  }
672
+ /**
673
+ * Reconfigure singleton clock.
674
+ * If not created yet, creates one with provided maxDeltaTime.
675
+ */
676
+ static configure(maxDeltaTime) {
677
+ const clock = _AnimationClock.create(maxDeltaTime);
678
+ clock.maxDeltaTime = maxDeltaTime;
679
+ return clock;
680
+ }
648
681
  subscribe(animatable) {
649
682
  this.subscribers.add(animatable);
650
683
  if (!this.animationFrameId) {
@@ -755,7 +788,7 @@ var RafAniTimeline = class extends TimelineBase {
755
788
  this._currentConfig = null;
756
789
  this._state = [];
757
790
  this._initialState = [];
758
- this._repeatCount = 0;
791
+ this._remainingRepeats = 0;
759
792
  this._propertyKeyMap = null;
760
793
  this._onUpdateCallbacks = /* @__PURE__ */ new Set();
761
794
  this._clock = clock ?? AnimationClock.create();
@@ -788,6 +821,26 @@ var RafAniTimeline = class extends TimelineBase {
788
821
  dt
789
822
  );
790
823
  }
824
+ _resolveRuntimeState(config) {
825
+ this._currentExecutionPlan = this._resolveExecutionPlan(
826
+ config.keyframes,
827
+ config.durations
828
+ );
829
+ const { keyMap, values } = resolveGroup(config.from);
830
+ this._propertyKeyMap = keyMap;
831
+ this._state = values;
832
+ this._initialState = values;
833
+ this._masterTime = 0;
834
+ }
835
+ _restartForRepeat() {
836
+ if (!this._currentConfig) {
837
+ return;
838
+ }
839
+ this._delay = 0;
840
+ this._resolveRuntimeState(this._currentConfig);
841
+ this._status = "PLAYING";
842
+ this.notify();
843
+ }
791
844
  notify() {
792
845
  for (const subscriber of this._onUpdateCallbacks) {
793
846
  subscriber({
@@ -818,14 +871,15 @@ var RafAniTimeline = class extends TimelineBase {
818
871
  this._state = this._calculateStateAtTime(this._masterTime, dt);
819
872
  this.notify();
820
873
  if (isEndOfAnimation(this._masterTime, this.duration)) {
821
- this._repeatCount += 1;
822
- const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
823
- if (noRepeat) {
874
+ if (this._remainingRepeats === Infinity || this._remainingRepeats > 0) {
875
+ if (this._remainingRepeats !== Infinity) {
876
+ this._remainingRepeats -= 1;
877
+ }
878
+ this._restartForRepeat();
879
+ } else {
824
880
  this._status = "ENDED";
825
881
  this._clock.unsubscribe(this);
826
882
  this.notify();
827
- } else {
828
- this.play(this._currentConfig);
829
883
  }
830
884
  }
831
885
  }
@@ -836,26 +890,11 @@ var RafAniTimeline = class extends TimelineBase {
836
890
  */
837
891
  play(config, canBeIntercepted = true) {
838
892
  if (this._status === "PLAYING" && !canBeIntercepted) return;
839
- const isRepeating = (this._currentConfig?.repeat ?? 0) >= 1;
840
- const savedRepeatCount = isRepeating ? this._repeatCount : 0;
841
- this.reset(false);
842
- this._repeatCount = savedRepeatCount;
843
- if (isRepeating && this._repeatCount >= config.repeat) {
844
- this._repeatCount = 0;
845
- return;
846
- }
893
+ this.reset(false, true);
847
894
  this._currentConfig = config;
848
- if (this._repeatCount === 0) {
849
- this._delay = (this._currentConfig.delay ?? 0) * 1e-3;
850
- }
851
- this._currentExecutionPlan = this._resolveExecutionPlan(
852
- config.keyframes,
853
- config.durations
854
- );
855
- const { keyMap, values } = resolveGroup(config.from);
856
- this._propertyKeyMap = keyMap;
857
- this._state = values;
858
- this._initialState = values;
895
+ this._remainingRepeats = normalizeRepeatCount(config.repeat);
896
+ this._delay = (this._currentConfig.delay ?? 0) * 1e-3;
897
+ this._resolveRuntimeState(config);
859
898
  this._status = "PLAYING";
860
899
  this._clock.subscribe(this);
861
900
  this.notify();
@@ -869,7 +908,7 @@ var RafAniTimeline = class extends TimelineBase {
869
908
  this._status = "PLAYING";
870
909
  this._clock.subscribe(this);
871
910
  }
872
- reset(notify = true) {
911
+ reset(notify = true, unsubscribeClock = true) {
873
912
  this._status = "IDLE";
874
913
  this._currentConfig = null;
875
914
  this._masterTime = 0;
@@ -878,8 +917,10 @@ var RafAniTimeline = class extends TimelineBase {
878
917
  this._initialState = [];
879
918
  this._propertyKeyMap = null;
880
919
  this._currentExecutionPlan = null;
881
- this._clock.unsubscribe(this);
882
- this._repeatCount = 0;
920
+ if (unsubscribeClock) {
921
+ this._clock.unsubscribe(this);
922
+ }
923
+ this._remainingRepeats = 0;
883
924
  if (notify) this.notify();
884
925
  }
885
926
  seek(targetTime) {
@@ -928,9 +969,11 @@ function createStates(config) {
928
969
  return () => subs.delete(callback);
929
970
  },
930
971
  transitionTo(newState, timelineConfig, canBeIntercepted) {
972
+ const previousTimeline = Timeline;
931
973
  const from = timelineConfig?.from ?? // 1. config
932
974
  Timeline.getCurrentValue() ?? // 2. last value
933
975
  config.initialFrom;
976
+ previousTimeline.reset(false);
934
977
  State = newState;
935
978
  Timeline = rafTimeline(config.states[State], config.clock);
936
979
  notify(Timeline);
@@ -1061,7 +1104,7 @@ function compileTiming(timing) {
1061
1104
  }
1062
1105
 
1063
1106
  // src/ani/waapi/compiler/keyframe_compiler.ts
1064
- function compileToKeyframes(plan, initialFrom) {
1107
+ function compileToKeyframes(plan, initialFrom, resolver) {
1065
1108
  if (plan.length === 0) {
1066
1109
  return [];
1067
1110
  }
@@ -1070,7 +1113,10 @@ function compileToKeyframes(plan, initialFrom) {
1070
1113
  const duration = Math.max(...plan.map((s) => s.endTime));
1071
1114
  if (duration === 0) {
1072
1115
  const state = resolveStateAt(plan, initialFrom, 0, SAMPLE_RATE);
1073
- const style = createStyleSheet(state);
1116
+ const style = createStyleSheet(
1117
+ state,
1118
+ resolver
1119
+ );
1074
1120
  return [
1075
1121
  { offset: 0, ...style },
1076
1122
  { offset: 1, ...style }
@@ -1100,7 +1146,10 @@ function compileToKeyframes(plan, initialFrom) {
1100
1146
  for (let i = 0; i < sortedTimes.length; i++) {
1101
1147
  const currT = sortedTimes[i];
1102
1148
  const state = resolveStateAt(plan, initialFrom, currT, SAMPLE_RATE);
1103
- const style = createStyleSheet(state);
1149
+ const style = createStyleSheet(
1150
+ state,
1151
+ resolver
1152
+ );
1104
1153
  const keyframe = {
1105
1154
  offset: currT / duration,
1106
1155
  ...style
@@ -1119,7 +1168,8 @@ function compileToKeyframes(plan, initialFrom) {
1119
1168
  SAMPLE_RATE
1120
1169
  );
1121
1170
  const sampleStyle = createStyleSheet(
1122
- sampleState
1171
+ sampleState,
1172
+ resolver
1123
1173
  );
1124
1174
  keyframes.push({
1125
1175
  offset: sampleT / duration,
@@ -1159,7 +1209,8 @@ var WebAniTimeline = class extends TimelineBase {
1159
1209
  );
1160
1210
  this._keyframes = compileToKeyframes(
1161
1211
  this._currentExecutionPlan,
1162
- config.from
1212
+ config.from,
1213
+ config.propertyResolver
1163
1214
  );
1164
1215
  if (this._keyframes.length === 0) {
1165
1216
  return null;
@@ -1170,9 +1221,10 @@ var WebAniTimeline = class extends TimelineBase {
1170
1221
  ) * 1e3;
1171
1222
  const effect = new KeyframeEffect(target, this._keyframes, {
1172
1223
  duration: totalDurationMs,
1173
- iterations: config.repeat ?? 1,
1224
+ iterations: toIterationCount(config.repeat),
1174
1225
  delay: config.delay ?? 0,
1175
- fill: "forwards"
1226
+ fill: "forwards",
1227
+ ...config.keyframeEffect
1176
1228
  });
1177
1229
  this._animation = new Animation(effect, document.timeline);
1178
1230
  this._animation.play();
@@ -1188,6 +1240,12 @@ var WebAniTimeline = class extends TimelineBase {
1188
1240
  this._animation?.cancel();
1189
1241
  this._animation = null;
1190
1242
  }
1243
+ /**
1244
+ * Alias of reset() for WAAPI naming parity.
1245
+ */
1246
+ cancel() {
1247
+ this.reset();
1248
+ }
1191
1249
  seek(targetTime) {
1192
1250
  if (this._animation) {
1193
1251
  this._animation.currentTime = targetTime * 1e3;
@@ -1235,6 +1293,7 @@ var EventManager = class _EventManager {
1235
1293
  const removeListener = this.eventMap.get(eventName);
1236
1294
  if (!removeListener) return false;
1237
1295
  this.targetElement.removeEventListener(eventName, removeListener);
1296
+ this.eventMap.delete(eventName);
1238
1297
  return true;
1239
1298
  };
1240
1299
  this.cleanupAll = () => {
@@ -1280,8 +1339,17 @@ var EventManager = class _EventManager {
1280
1339
  // src/index.ts
1281
1340
  var a = {
1282
1341
  timing: T,
1342
+ /**
1343
+ * @deprecated Use `rafTimeline` alias for explicit engine naming.
1344
+ */
1283
1345
  dynamicTimeline: rafTimeline,
1346
+ /**
1347
+ * @deprecated Use `waapiTimeline` alias for explicit engine naming.
1348
+ */
1284
1349
  timeline: webTimeline,
1350
+ webTimeline,
1351
+ rafTimeline,
1352
+ waapiTimeline: webTimeline,
1285
1353
  /**
1286
1354
  * Create animation segment.
1287
1355
  */
@@ -1311,6 +1379,8 @@ export {
1311
1379
  compileToKeyframes,
1312
1380
  createStates,
1313
1381
  createStyleSheet,
1382
+ normalizeRepeatCount,
1314
1383
  rafTimeline,
1384
+ toIterationCount,
1315
1385
  webTimeline
1316
1386
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@freestylejs/ani-core",
3
3
  "author": "freestyle",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "description": "Core functionality for the Ani animation library.",
6
6
  "license": "MIT",
7
7
  "type": "module",