@motion.page/sdk 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -636,7 +636,7 @@ Motion('parallax-depth', '.layer', {
636
636
  }).onMouseMove({ type: 'axis', smooth: 0.1 });
637
637
  ```
638
638
 
639
- #### `.onPageLoad()`
639
+ #### `.onPageLoad(config?)`
640
640
 
641
641
  Play the animation automatically when the page finishes loading. If called after `DOMContentLoaded` has already fired (common in SPAs or scripts placed at the bottom of `<body>`), the animation plays immediately.
642
642
 
@@ -647,6 +647,24 @@ Motion('page-intro', [
647
647
  ]).onPageLoad();
648
648
  ```
649
649
 
650
+ **`paused` option** — build the timeline (applies initial states) but don't auto-play. Start it manually with `Motion(name).play()`:
651
+
652
+ ```ts
653
+ // Load timeline paused — user triggers manually
654
+ Motion('hero', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
655
+ .onPageLoad({ paused: true })
656
+
657
+ // Later, trigger manually:
658
+ Motion('hero').play()
659
+ ```
660
+
661
+ Can combine with `timing`:
662
+
663
+ ```ts
664
+ Motion('hero', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
665
+ .onPageLoad({ timing: 'after', paused: true })
666
+ ```
667
+
650
668
  #### `.onPageExit(config?)`
651
669
 
652
670
  Intercept link clicks, play the exit animation, then navigate to the destination URL after the timeline completes. Works on any website with no server-side dependencies.
@@ -770,7 +788,6 @@ Replace the native cursor with a fully animated custom cursor.
770
788
 
771
789
  ```ts
772
790
  interface CursorConfig {
773
- target?: string | Element;
774
791
  type?: 'basic' | 'text' | 'media';
775
792
  smooth?: number;
776
793
  squeeze?: boolean | { min?: number; max?: number; multiplier?: number };
@@ -800,7 +817,6 @@ Motion('custom-cursor', 'body', {
800
817
  to: { opacity: 1 },
801
818
  duration: 0,
802
819
  }).onCursor({
803
- target: '#cursor-dot',
804
820
  smooth: 0.08,
805
821
  hideNative: true,
806
822
  default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
@@ -818,7 +834,6 @@ Motion('custom-cursor', 'body', {
818
834
 
819
835
  ```ts
820
836
  Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
821
- target: '#cursor',
822
837
  type: 'text',
823
838
  hideNative: true,
824
839
  default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
@@ -836,7 +851,6 @@ Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
836
851
 
837
852
  ```ts
838
853
  Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
839
- target: '#cursor',
840
854
  type: 'media',
841
855
  hideNative: true,
842
856
  default: { width: 48, height: 48, borderRadius: '50%' },
@@ -885,7 +899,7 @@ Motion('slide-in', '.card', { from: { opacity: 0 }, duration: 0.6 })
885
899
  | Start | `.onStart(cb)` | `onStart` | none |
886
900
  | Update | `.onUpdate(cb)` | `onUpdate` | `(progress: number, time: number)` via method; `(progress: number)` via config |
887
901
  | Complete | `.onComplete(cb)` | `onComplete` | none |
888
- | Repeat | | `onRepeat` | `(repeatCount: number)` |
902
+ | Repeat | `.onRepeat(cb)` | `onRepeat` | `(repeatCount: number)` |
889
903
  | Reverse complete | — | `onReverseComplete` | none |
890
904
 
891
905
  ---
@@ -1055,6 +1069,55 @@ repeat: { times: -1, yoyo: true, delay: 0.2 } // infinite yoyo with pause betwe
1055
1069
  repeat: { times: 2, yoyo: true, delay: 0.5 } // 2 extra cycles, yoyo, 0.5s pause
1056
1070
  ```
1057
1071
 
1072
+ ### Timeline Repeat
1073
+
1074
+ `.withRepeat()` loops the **entire timeline** after all its animations complete — distinct from per-animation `repeat` in `AnimationConfig`, which only loops an individual animation within the timeline.
1075
+
1076
+ ```ts
1077
+ // Shorthand — loop count (-1 = infinite, 0 = no extra loops)
1078
+ tl.withRepeat(-1): this
1079
+
1080
+ // Full config
1081
+ tl.withRepeat(config: TimelineRepeatConfig): this
1082
+
1083
+ interface TimelineRepeatConfig {
1084
+ times: number; // Number of additional repetitions (-1 = infinite)
1085
+ yoyo?: boolean; // Alternate direction each cycle
1086
+ delay?: number; // Seconds between timeline repetitions
1087
+ }
1088
+ ```
1089
+
1090
+ ```ts
1091
+ // Loop the entire timeline infinitely
1092
+ Motion('hero', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
1093
+ .withRepeat(-1)
1094
+ .onPageLoad()
1095
+
1096
+ // Loop 3 times with yoyo and delay
1097
+ Motion('bounce', [
1098
+ { target: '.a', from: { y: '100%' }, to: { y: '0%' }, duration: 0.5 },
1099
+ { target: '.b', from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.3, position: 0.2 }
1100
+ ])
1101
+ .withRepeat({ times: 3, yoyo: true, delay: 0.5 })
1102
+ .onPageLoad()
1103
+ ```
1104
+
1105
+ Use `.onRepeat()` on the `Timeline` instance to react to each completed cycle:
1106
+
1107
+ ```ts
1108
+ Motion('loop', '.el', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.8 })
1109
+ .withRepeat(-1)
1110
+ .onRepeat((count) => console.log(`Cycle ${count} complete`))
1111
+ .onPageLoad()
1112
+ ```
1113
+
1114
+ **Per-animation vs. timeline repeat:**
1115
+
1116
+ | Scope | API | Effect |
1117
+ |-------|-----|--------|
1118
+ | Individual animation | `repeat` in `AnimationConfig` | Loops just that one animation within the timeline |
1119
+ | Entire timeline | `.withRepeat()` on `Timeline` | Loops all animations in the timeline together |
1120
+
1058
1121
  ### SplitType
1059
1122
 
1060
1123
  ```ts
@@ -9,7 +9,7 @@
9
9
  * - Control methods (play, pause, reverse, seek, etc.)
10
10
  * - Trigger system integration
11
11
  */
12
- import type { HoverConfig, ClickConfig, ScrollConfig, MouseMoveConfig, GestureConfig, CursorConfig, PageExitConfig, TargetInput, AnimationConfig } from '../types';
12
+ import type { HoverConfig, ClickConfig, ScrollConfig, MouseMoveConfig, GestureConfig, CursorConfig, PageExitConfig, TargetInput, AnimationConfig, TimelineConfig, RepeatConfig } from '../types';
13
13
  import { Animation } from './Animation';
14
14
  export declare class Timeline {
15
15
  /** Monotonic counter for generating unique each-mode instance names */
@@ -34,9 +34,18 @@ export declare class Timeline {
34
34
  private _isActive;
35
35
  private _isReversed;
36
36
  private _killed;
37
+ private _atZeroState;
38
+ private _repeat;
39
+ private _repeatDelay;
40
+ private _yoyo;
41
+ private _currentRepeat;
42
+ private _completeFired;
43
+ private _inRepeatDelay;
44
+ private _repeatDelayCounter;
37
45
  private _onStart?;
38
46
  private _onUpdate?;
39
47
  private _onComplete?;
48
+ private _onRepeat?;
40
49
  private _startFired;
41
50
  /**
42
51
  * Reference to attached trigger (for cleanup in kill()).
@@ -62,11 +71,17 @@ export declare class Timeline {
62
71
  private _savedTransitions;
63
72
  private _transitionsDisabled;
64
73
  private _savedWillChange;
65
- constructor(name?: string);
74
+ private _willChangeApplied;
75
+ constructor(name?: string, config?: TimelineConfig);
66
76
  /**
67
- * Get timeline duration
77
+ * Get timeline duration (single cycle)
68
78
  */
69
79
  duration(): number;
80
+ /**
81
+ * Get total duration including all repeats.
82
+ * Returns Infinity when repeat is -1 (infinite loop).
83
+ */
84
+ totalDuration(): number;
70
85
  /**
71
86
  * Clear timeline state for rebuild (used for re-triggerable animations)
72
87
  * Kills existing animations, clears children, resets state
@@ -86,6 +101,12 @@ export declare class Timeline {
86
101
  * from fighting frame-by-frame SDK rendering.
87
102
  */
88
103
  private _disableTransitions;
104
+ /**
105
+ * Apply will-change: transform to elements with transform animations.
106
+ * Deferred from build time to first tick so the browser has at least one
107
+ * frame to layout newly-created elements (e.g. text split spans).
108
+ */
109
+ private _applyWillChange;
89
110
  /**
90
111
  * Restore original CSS transition values on all animated elements.
91
112
  * Called on timeline completion and kill to re-enable CSS hover/focus
@@ -174,9 +195,17 @@ export declare class Timeline {
174
195
  */
175
196
  private _setupMouseMoveEachMode;
176
197
  /**
177
- * Trigger timeline on page load
198
+ * Trigger timeline on page load.
199
+ *
200
+ * @param config - Optional configuration
201
+ * @param config.timing - When to play: 'before' (immediately), 'during' (DOMContentLoaded), 'after' (window load). Defaults to 'during'.
202
+ * @param config.paused - When true, the timeline is built but NOT played automatically.
203
+ * Start it manually with `Motion('timelineName').play()`.
178
204
  */
179
- onPageLoad(): this;
205
+ onPageLoad(config?: {
206
+ timing?: 'before' | 'during' | 'after';
207
+ paused?: boolean;
208
+ }): this;
180
209
  /**
181
210
  * Trigger timeline when user navigates away from the page (clicks a link).
182
211
  * Intercepts the click, plays the exit animation, then navigates to the target URL.
@@ -215,6 +244,8 @@ export declare class Timeline {
215
244
  /**
216
245
  * Play timeline from position.
217
246
  * If already playing forward, continues from current position.
247
+ * No-op if already completed forward (at the end) and no `from` is specified —
248
+ * prevents spurious onStart/onComplete re-firing during continuous gesture events.
218
249
  */
219
250
  play(from?: number): this;
220
251
  /**
@@ -224,12 +255,20 @@ export declare class Timeline {
224
255
  /**
225
256
  * Reverse timeline from position.
226
257
  * If already reversing, continues from current position.
258
+ * No-op if already completed reverse (at the start) and no `from` is specified —
259
+ * prevents spurious onStart/onComplete re-firing during continuous gesture events.
227
260
  */
228
261
  reverse(from?: number): this;
229
262
  /**
230
263
  * Restart timeline
231
264
  */
232
265
  restart(): this;
266
+ /**
267
+ * Reset all children for the start of a new repeat cycle.
268
+ * Restores elements to their captured initial values and re-renders
269
+ * position-0 animations at their FROM state.
270
+ */
271
+ private _resetChildrenForCycle;
233
272
  /**
234
273
  * Render only position-0 (non-lazy) animations at their start state
235
274
  */
@@ -258,6 +297,24 @@ export declare class Timeline {
258
297
  * Set callback for when timeline completes
259
298
  */
260
299
  onComplete(callback: () => void): this;
300
+ /**
301
+ * Set callback for each repeat cycle completion
302
+ * @param callback - Receives the current repeat count (starts at 1)
303
+ */
304
+ onRepeat(callback: (repeatCount: number) => void): this;
305
+ /**
306
+ * Configure timeline-level repeat behavior (fluent).
307
+ * Use -1 for infinite repeat, or a RepeatConfig for advanced options.
308
+ * @example
309
+ * Motion('loop', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.5 })
310
+ * .withRepeat(-1)
311
+ * .onPageLoad()
312
+ *
313
+ * Motion('bounce', [entries])
314
+ * .withRepeat({ times: 3, yoyo: true, delay: 0.2 })
315
+ * .onPageLoad()
316
+ */
317
+ withRepeat(config: number | RepeatConfig): this;
261
318
  /**
262
319
  * Get or set progress (0-1)
263
320
  */
@@ -282,6 +339,10 @@ export declare class Timeline {
282
339
  * Check if timeline is currently active
283
340
  */
284
341
  isActive(): boolean;
342
+ /**
343
+ * Check if timeline is currently playing in reverse direction
344
+ */
345
+ reversed(): boolean;
285
346
  /**
286
347
  * Get timeline name
287
348
  */
@@ -2,7 +2,10 @@
2
2
  * Easing Functions
3
3
  *
4
4
  * All easings are based on Robert Penner's easing equations
5
- * and normalized to accept/return values in the 0-1 range
5
+ * and normalized to accept/return values in the 0-1 range.
6
+ *
7
+ * Includes GSAP-compatible aliases (quad, cubic, quart, quint, strong)
8
+ * and special easings (slow, rough) for migration compatibility.
6
9
  */
7
10
  import type { EasingFunction } from '../types';
8
11
  export declare const linear: (t: number) => number;
@@ -27,6 +30,31 @@ export declare const power4: {
27
30
  out: (t: number) => number;
28
31
  inOut: (t: number) => number;
29
32
  };
33
+ export declare const quad: {
34
+ in: (t: number) => number;
35
+ out: (t: number) => number;
36
+ inOut: (t: number) => number;
37
+ };
38
+ export declare const cubic: {
39
+ in: (t: number) => number;
40
+ out: (t: number) => number;
41
+ inOut: (t: number) => number;
42
+ };
43
+ export declare const quart: {
44
+ in: (t: number) => number;
45
+ out: (t: number) => number;
46
+ inOut: (t: number) => number;
47
+ };
48
+ export declare const quint: {
49
+ in: (t: number) => number;
50
+ out: (t: number) => number;
51
+ inOut: (t: number) => number;
52
+ };
53
+ export declare const strong: {
54
+ in: (t: number) => number;
55
+ out: (t: number) => number;
56
+ inOut: (t: number) => number;
57
+ };
30
58
  export declare const sine: {
31
59
  in: (t: number) => number;
32
60
  out: (t: number) => number;
@@ -57,6 +85,21 @@ export declare const bounce: {
57
85
  out: (t: number) => number;
58
86
  inOut: (t: number) => number;
59
87
  };
88
+ export declare const slow: {
89
+ in: EasingFunction;
90
+ out: EasingFunction;
91
+ inOut: EasingFunction;
92
+ };
93
+ export declare const rough: {
94
+ in: EasingFunction;
95
+ out: EasingFunction;
96
+ inOut: EasingFunction;
97
+ };
98
+ export declare const steps: {
99
+ in: EasingFunction;
100
+ out: EasingFunction;
101
+ inOut: EasingFunction;
102
+ };
60
103
  /**
61
104
  * Get an easing function by string name
62
105
  * @param name Easing name (case insensitive)