@depup/lottie-react 2.4.1-depup.0 → 3.0.0-depup.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.
Files changed (154) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +4 -10
  3. package/build/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/build/animation/Lottie.cjs +39 -0
  5. package/build/animation/Lottie.d.cts +47 -0
  6. package/build/animation/Lottie.d.ts +47 -0
  7. package/build/animation/Lottie.js +37 -0
  8. package/build/animation/LottieDisplay.cjs +55 -0
  9. package/build/animation/LottieDisplay.d.cts +46 -0
  10. package/build/animation/LottieDisplay.d.ts +46 -0
  11. package/build/animation/LottieDisplay.js +53 -0
  12. package/build/animation/LottieInstanceContext.cjs +16 -0
  13. package/build/animation/LottieInstanceContext.js +17 -0
  14. package/build/animation/LottieLight.cjs +19 -0
  15. package/build/animation/LottieLight.d.cts +23 -0
  16. package/build/animation/LottieLight.d.ts +23 -0
  17. package/build/animation/LottieLight.js +17 -0
  18. package/build/animation/LottieRegistryContext.cjs +36 -0
  19. package/build/animation/LottieRegistryContext.js +35 -0
  20. package/build/animation/LottieSvg.cjs +20 -0
  21. package/build/animation/LottieSvg.d.cts +24 -0
  22. package/build/animation/LottieSvg.d.ts +24 -0
  23. package/build/animation/LottieSvg.js +18 -0
  24. package/build/animation/collectMarkerCrossings.cjs +38 -0
  25. package/build/animation/collectMarkerCrossings.js +38 -0
  26. package/build/animation/createLottieComponent.cjs +131 -0
  27. package/build/animation/createLottieComponent.d.cts +45 -0
  28. package/build/animation/createLottieComponent.d.ts +45 -0
  29. package/build/animation/createLottieComponent.js +131 -0
  30. package/build/animation/hasExpressions.cjs +25 -0
  31. package/build/animation/hasExpressions.js +25 -0
  32. package/build/animation/normalizeAnimationSource.cjs +38 -0
  33. package/build/animation/normalizeAnimationSource.js +38 -0
  34. package/build/animation/polymorphicForwardRef.cjs +18 -0
  35. package/build/animation/polymorphicForwardRef.js +19 -0
  36. package/build/animation/renderStyledElement.cjs +31 -0
  37. package/build/animation/renderStyledElement.js +31 -0
  38. package/build/animation/resolveSeekTarget.cjs +97 -0
  39. package/build/animation/resolveSeekTarget.js +95 -0
  40. package/build/animation/styleLayer.cjs +24 -0
  41. package/build/animation/styleLayer.js +24 -0
  42. package/build/animation/stylePrecedence.cjs +12 -0
  43. package/build/animation/stylePrecedence.js +12 -0
  44. package/build/animation/types.cjs +71 -0
  45. package/build/animation/types.d.cts +461 -0
  46. package/build/animation/types.d.ts +461 -0
  47. package/build/animation/types.js +68 -0
  48. package/build/animation/useLottie.cjs +40 -0
  49. package/build/animation/useLottie.d.cts +36 -0
  50. package/build/animation/useLottie.d.ts +36 -0
  51. package/build/animation/useLottie.js +38 -0
  52. package/build/animation/useLottieAnimation.cjs +434 -0
  53. package/build/animation/useLottieAnimation.d.cts +40 -0
  54. package/build/animation/useLottieAnimation.d.ts +40 -0
  55. package/build/animation/useLottieAnimation.js +434 -0
  56. package/build/animation/useLottieInstance.cjs +20 -0
  57. package/build/animation/useLottieInstance.d.cts +14 -0
  58. package/build/animation/useLottieInstance.d.ts +14 -0
  59. package/build/animation/useLottieInstance.js +20 -0
  60. package/build/animation/useLottieLight.cjs +20 -0
  61. package/build/animation/useLottieLight.d.cts +16 -0
  62. package/build/animation/useLottieLight.d.ts +16 -0
  63. package/build/animation/useLottieLight.js +18 -0
  64. package/build/animation/useLottieSvg.cjs +18 -0
  65. package/build/animation/useLottieSvg.d.cts +14 -0
  66. package/build/animation/useLottieSvg.d.ts +14 -0
  67. package/build/animation/useLottieSvg.js +16 -0
  68. package/build/controls/LottieControls.cjs +180 -0
  69. package/build/controls/LottieControls.d.cts +58 -0
  70. package/build/controls/LottieControls.d.ts +58 -0
  71. package/build/controls/LottieControls.js +178 -0
  72. package/build/controls/LottieDirectionButton.cjs +36 -0
  73. package/build/controls/LottieDirectionButton.js +35 -0
  74. package/build/controls/LottieFullscreenButton.cjs +35 -0
  75. package/build/controls/LottieFullscreenButton.js +34 -0
  76. package/build/controls/LottieLoopButton.cjs +32 -0
  77. package/build/controls/LottieLoopButton.js +31 -0
  78. package/build/controls/LottiePlayButton.cjs +34 -0
  79. package/build/controls/LottiePlayButton.js +33 -0
  80. package/build/controls/LottieReadout.cjs +61 -0
  81. package/build/controls/LottieReadout.d.cts +13 -0
  82. package/build/controls/LottieReadout.d.ts +13 -0
  83. package/build/controls/LottieReadout.js +60 -0
  84. package/build/controls/LottieSeekBar.cjs +87 -0
  85. package/build/controls/LottieSeekBar.js +86 -0
  86. package/build/controls/LottieSpeedSelect.cjs +51 -0
  87. package/build/controls/LottieSpeedSelect.js +50 -0
  88. package/build/controls/LottieStopButton.cjs +29 -0
  89. package/build/controls/LottieStopButton.js +28 -0
  90. package/build/controls/controlIcon.cjs +25 -0
  91. package/build/controls/controlIcon.js +25 -0
  92. package/build/controls/useFullscreen.cjs +46 -0
  93. package/build/controls/useFullscreen.js +46 -0
  94. package/build/controls/useShortcuts.cjs +70 -0
  95. package/build/controls/useShortcuts.js +70 -0
  96. package/build/index.cjs +36 -0
  97. package/build/index.d.cts +19 -0
  98. package/build/index.d.ts +19 -80
  99. package/build/index.js +17 -667
  100. package/build/interactions/LottieInteractions.cjs +41 -0
  101. package/build/interactions/LottieInteractions.d.cts +37 -0
  102. package/build/interactions/LottieInteractions.d.ts +37 -0
  103. package/build/interactions/LottieInteractions.js +41 -0
  104. package/build/interactions/coverProgress.cjs +55 -0
  105. package/build/interactions/coverProgress.js +53 -0
  106. package/build/interactions/lottieInView.cjs +109 -0
  107. package/build/interactions/lottieInView.d.cts +44 -0
  108. package/build/interactions/lottieInView.d.ts +44 -0
  109. package/build/interactions/lottieInView.js +109 -0
  110. package/build/interactions/lottieScrollScrub.cjs +152 -0
  111. package/build/interactions/lottieScrollScrub.d.cts +47 -0
  112. package/build/interactions/lottieScrollScrub.d.ts +47 -0
  113. package/build/interactions/lottieScrollScrub.js +152 -0
  114. package/build/interactions/types.d.cts +46 -0
  115. package/build/interactions/types.d.ts +46 -0
  116. package/build/interactions/useInteractionsRunner.cjs +142 -0
  117. package/build/interactions/useInteractionsRunner.js +141 -0
  118. package/build/interactions/useLottieInteractions.cjs +21 -0
  119. package/build/interactions/useLottieInteractions.d.cts +19 -0
  120. package/build/interactions/useLottieInteractions.d.ts +19 -0
  121. package/build/interactions/useLottieInteractions.js +21 -0
  122. package/build/overlays/LottieError.cjs +74 -0
  123. package/build/overlays/LottieError.d.cts +40 -0
  124. package/build/overlays/LottieError.d.ts +40 -0
  125. package/build/overlays/LottieError.js +72 -0
  126. package/build/overlays/LottieLoading.cjs +89 -0
  127. package/build/overlays/LottieLoading.d.cts +45 -0
  128. package/build/overlays/LottieLoading.d.ts +45 -0
  129. package/build/overlays/LottieLoading.js +87 -0
  130. package/build/overlays/overlayStyles.cjs +33 -0
  131. package/build/overlays/overlayStyles.js +33 -0
  132. package/build/utils/SubscriptionManager.cjs +42 -0
  133. package/build/utils/SubscriptionManager.d.cts +30 -0
  134. package/build/utils/SubscriptionManager.d.ts +30 -0
  135. package/build/utils/SubscriptionManager.js +42 -0
  136. package/build/utils/createLogger.cjs +25 -0
  137. package/build/utils/createLogger.js +25 -0
  138. package/build/utils/isSameJson.cjs +37 -0
  139. package/build/utils/isSameJson.js +37 -0
  140. package/build/utils/mergeRefs.cjs +37 -0
  141. package/build/utils/mergeRefs.js +37 -0
  142. package/build/utils/useStableValue.cjs +26 -0
  143. package/build/utils/useStableValue.js +26 -0
  144. package/changes.json +3 -8
  145. package/package.json +144 -89
  146. package/LICENSE +0 -46
  147. package/build/index.es.js +0 -653
  148. package/build/index.es.js.map +0 -1
  149. package/build/index.es.min.js +0 -3
  150. package/build/index.js.map +0 -1
  151. package/build/index.min.js +0 -3
  152. package/build/index.umd.js +0 -670
  153. package/build/index.umd.js.map +0 -1
  154. package/build/index.umd.min.js +0 -3
@@ -0,0 +1,152 @@
1
+ const require_types = require("../animation/types.cjs");
2
+ const require_coverProgress = require("./coverProgress.cjs");
3
+ //#region src/interactions/lottieScrollScrub.ts
4
+ function isRange(value) {
5
+ return Array.isArray(value) && value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number";
6
+ }
7
+ function isScrubOptions(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ if ("range" in value && !isRange(value.range)) return false;
10
+ if ("axis" in value && value.axis !== "block" && value.axis !== "inline") return false;
11
+ if ("onRangeEnter" in value && typeof value.onRangeEnter !== "function") return false;
12
+ return !("onRangeLeave" in value && typeof value.onRangeLeave !== "function");
13
+ }
14
+ function attachScrollScrub(context, raw) {
15
+ const options = isScrubOptions(raw) ? raw : {};
16
+ const range = options.range ?? [0, 1];
17
+ const axis = options.axis ?? "block";
18
+ let observer = null;
19
+ let observedRoot = null;
20
+ let timeline = null;
21
+ let frameHandle = null;
22
+ let scrubbing = false;
23
+ let gestureStarted = false;
24
+ let previousBand = null;
25
+ let warnedInactive = false;
26
+ const readProgress = (root) => {
27
+ if (timeline !== null) {
28
+ const time = timeline.currentTime;
29
+ if (typeof time === "number") return Math.min(Math.max(time / 100, 0), 1);
30
+ if (time !== null && typeof time === "object" && "value" in time && typeof time.value === "number") return Math.min(Math.max(time.value / 100, 0), 1);
31
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
32
+ if (!warnedInactive) {
33
+ warnedInactive = true;
34
+ console.warn("[lottie-react] the scroll timeline reported no position, so the scrub fell back to measuring the element itself. This happens when nothing around the animation scrolls.");
35
+ }
36
+ }
37
+ }
38
+ return require_coverProgress.coverProgress(root.getBoundingClientRect(), {
39
+ width: window.innerWidth,
40
+ height: window.innerHeight
41
+ }, axis);
42
+ };
43
+ const sample = () => {
44
+ frameHandle = null;
45
+ const root = context.lottie.root;
46
+ if (!scrubbing || root === null) return;
47
+ if (context.lottie.state === require_types.LottieState.loading) {
48
+ frameHandle = requestAnimationFrame(sample);
49
+ return;
50
+ }
51
+ const band = require_coverProgress.bandProgress(readProgress(root), range);
52
+ const events = require_coverProgress.bandEdges(previousBand, band);
53
+ previousBand = band;
54
+ context.lottie.scrubTo(band * context.lottie.playableFrames);
55
+ if (events.enter !== void 0 || events.leave !== void 0) {
56
+ const liveRaw = context.options();
57
+ const live = isScrubOptions(liveRaw) ? liveRaw : {};
58
+ if (events.enter !== void 0) live.onRangeEnter?.(events.enter, context.lottie);
59
+ if (events.leave !== void 0) live.onRangeLeave?.(events.leave, context.lottie);
60
+ }
61
+ frameHandle = requestAnimationFrame(sample);
62
+ };
63
+ const startScrubbing = () => {
64
+ if (scrubbing) return;
65
+ scrubbing = true;
66
+ previousBand = null;
67
+ if (context.lottie.state !== require_types.LottieState.loading) {
68
+ gestureStarted = true;
69
+ context.lottie.scrubStart();
70
+ }
71
+ sample();
72
+ };
73
+ const stopScrubbing = () => {
74
+ if (!scrubbing) return;
75
+ scrubbing = false;
76
+ gestureStarted = false;
77
+ if (frameHandle !== null) {
78
+ cancelAnimationFrame(frameHandle);
79
+ frameHandle = null;
80
+ }
81
+ context.lottie.scrubEnd();
82
+ };
83
+ const handleEntries = (entries) => {
84
+ const entry = entries.at(-1);
85
+ if (entry === void 0) return;
86
+ if (entry.isIntersecting) startScrubbing();
87
+ else stopScrubbing();
88
+ };
89
+ const arm = () => {
90
+ const root = context.lottie.root;
91
+ if (root === observedRoot) return;
92
+ stopScrubbing();
93
+ observer?.disconnect();
94
+ observer = null;
95
+ timeline = null;
96
+ observedRoot = root;
97
+ if (root === null) return;
98
+ const ViewTimelineCtor = globalThis.ViewTimeline;
99
+ if (ViewTimelineCtor !== void 0) timeline = new ViewTimelineCtor({
100
+ subject: root,
101
+ axis
102
+ });
103
+ observer = new IntersectionObserver(handleEntries);
104
+ observer.observe(root);
105
+ };
106
+ const stopListening = context.onChange(() => {
107
+ if (scrubbing && !gestureStarted && context.lottie.state !== require_types.LottieState.loading && context.lottie.state !== require_types.LottieState.error) {
108
+ gestureStarted = true;
109
+ context.lottie.scrubStart();
110
+ }
111
+ arm();
112
+ });
113
+ arm();
114
+ return () => {
115
+ stopListening();
116
+ stopScrubbing();
117
+ observer?.disconnect();
118
+ };
119
+ }
120
+ /**
121
+ * Ties the playhead to how far the animation has travelled through the
122
+ * viewport, so scrolling scrubs it instead of time playing it.
123
+ *
124
+ * ```jsx
125
+ * <LottieInteractions
126
+ * interactions={[
127
+ * lottieScrollScrub({
128
+ * range: [0.2, 0.45],
129
+ * onRangeLeave: (direction, lottie) => {
130
+ * if (direction === "forward") lottie.playSegments([[45, 60]]);
131
+ * },
132
+ * }),
133
+ * ]}
134
+ * >
135
+ * <Lottie src={story} autoplay={false} />
136
+ * </LottieInteractions>
137
+ * ```
138
+ *
139
+ * Progress is the platform's own scroll timeline where the browser has one,
140
+ * and the same arithmetic measured by hand where it does not; the numbers
141
+ * agree by construction. While the animation is out of view nothing samples
142
+ * at all, and playback state is held by the scrub gesture, so whatever was
143
+ * true before the scrub is restored when it leaves.
144
+ */
145
+ function lottieScrollScrub(options = {}) {
146
+ return {
147
+ attach: attachScrollScrub,
148
+ options
149
+ };
150
+ }
151
+ //#endregion
152
+ exports.lottieScrollScrub = lottieScrollScrub;
@@ -0,0 +1,47 @@
1
+ import { LottieInstance } from "../animation/types.cjs";
2
+ import { LottieInteraction } from "./types.cjs";
3
+ //#region src/interactions/lottieScrollScrub.d.ts
4
+ /** What {@link lottieScrollScrub} can be told. */
5
+ interface LottieScrollScrubOptions {
6
+ /**
7
+ * The stretch of the element's journey the animation maps onto, as
8
+ * fractions 0 to 1 of it. Below the range the animation holds its first
9
+ * frame and above it its last, which is what clamping does. The whole
10
+ * journey unless narrowed.
11
+ */
12
+ range?: readonly [number, number];
13
+ /** Which axis the journey runs along. The block axis unless said otherwise. */
14
+ axis?: "block" | "inline";
15
+ /** Called as the playhead's band is entered, with the travel direction. */
16
+ onRangeEnter?: (direction: "forward" | "backward", lottie: LottieInstance) => void;
17
+ /** Called as the band is left: at its far edge forward, its near edge back. */
18
+ onRangeLeave?: (direction: "forward" | "backward", lottie: LottieInstance) => void;
19
+ }
20
+ /**
21
+ * Ties the playhead to how far the animation has travelled through the
22
+ * viewport, so scrolling scrubs it instead of time playing it.
23
+ *
24
+ * ```jsx
25
+ * <LottieInteractions
26
+ * interactions={[
27
+ * lottieScrollScrub({
28
+ * range: [0.2, 0.45],
29
+ * onRangeLeave: (direction, lottie) => {
30
+ * if (direction === "forward") lottie.playSegments([[45, 60]]);
31
+ * },
32
+ * }),
33
+ * ]}
34
+ * >
35
+ * <Lottie src={story} autoplay={false} />
36
+ * </LottieInteractions>
37
+ * ```
38
+ *
39
+ * Progress is the platform's own scroll timeline where the browser has one,
40
+ * and the same arithmetic measured by hand where it does not; the numbers
41
+ * agree by construction. While the animation is out of view nothing samples
42
+ * at all, and playback state is held by the scrub gesture, so whatever was
43
+ * true before the scrub is restored when it leaves.
44
+ */
45
+ declare function lottieScrollScrub(options?: LottieScrollScrubOptions): LottieInteraction;
46
+ //#endregion
47
+ export { LottieScrollScrubOptions, lottieScrollScrub };
@@ -0,0 +1,47 @@
1
+ import { LottieInstance } from "../animation/types.js";
2
+ import { LottieInteraction } from "./types.js";
3
+ //#region src/interactions/lottieScrollScrub.d.ts
4
+ /** What {@link lottieScrollScrub} can be told. */
5
+ interface LottieScrollScrubOptions {
6
+ /**
7
+ * The stretch of the element's journey the animation maps onto, as
8
+ * fractions 0 to 1 of it. Below the range the animation holds its first
9
+ * frame and above it its last, which is what clamping does. The whole
10
+ * journey unless narrowed.
11
+ */
12
+ range?: readonly [number, number];
13
+ /** Which axis the journey runs along. The block axis unless said otherwise. */
14
+ axis?: "block" | "inline";
15
+ /** Called as the playhead's band is entered, with the travel direction. */
16
+ onRangeEnter?: (direction: "forward" | "backward", lottie: LottieInstance) => void;
17
+ /** Called as the band is left: at its far edge forward, its near edge back. */
18
+ onRangeLeave?: (direction: "forward" | "backward", lottie: LottieInstance) => void;
19
+ }
20
+ /**
21
+ * Ties the playhead to how far the animation has travelled through the
22
+ * viewport, so scrolling scrubs it instead of time playing it.
23
+ *
24
+ * ```jsx
25
+ * <LottieInteractions
26
+ * interactions={[
27
+ * lottieScrollScrub({
28
+ * range: [0.2, 0.45],
29
+ * onRangeLeave: (direction, lottie) => {
30
+ * if (direction === "forward") lottie.playSegments([[45, 60]]);
31
+ * },
32
+ * }),
33
+ * ]}
34
+ * >
35
+ * <Lottie src={story} autoplay={false} />
36
+ * </LottieInteractions>
37
+ * ```
38
+ *
39
+ * Progress is the platform's own scroll timeline where the browser has one,
40
+ * and the same arithmetic measured by hand where it does not; the numbers
41
+ * agree by construction. While the animation is out of view nothing samples
42
+ * at all, and playback state is held by the scrub gesture, so whatever was
43
+ * true before the scrub is restored when it leaves.
44
+ */
45
+ declare function lottieScrollScrub(options?: LottieScrollScrubOptions): LottieInteraction;
46
+ //#endregion
47
+ export { LottieScrollScrubOptions, lottieScrollScrub };
@@ -0,0 +1,152 @@
1
+ import { LottieState } from "../animation/types.js";
2
+ import { bandEdges, bandProgress, coverProgress } from "./coverProgress.js";
3
+ //#region src/interactions/lottieScrollScrub.ts
4
+ function isRange(value) {
5
+ return Array.isArray(value) && value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number";
6
+ }
7
+ function isScrubOptions(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ if ("range" in value && !isRange(value.range)) return false;
10
+ if ("axis" in value && value.axis !== "block" && value.axis !== "inline") return false;
11
+ if ("onRangeEnter" in value && typeof value.onRangeEnter !== "function") return false;
12
+ return !("onRangeLeave" in value && typeof value.onRangeLeave !== "function");
13
+ }
14
+ function attachScrollScrub(context, raw) {
15
+ const options = isScrubOptions(raw) ? raw : {};
16
+ const range = options.range ?? [0, 1];
17
+ const axis = options.axis ?? "block";
18
+ let observer = null;
19
+ let observedRoot = null;
20
+ let timeline = null;
21
+ let frameHandle = null;
22
+ let scrubbing = false;
23
+ let gestureStarted = false;
24
+ let previousBand = null;
25
+ let warnedInactive = false;
26
+ const readProgress = (root) => {
27
+ if (timeline !== null) {
28
+ const time = timeline.currentTime;
29
+ if (typeof time === "number") return Math.min(Math.max(time / 100, 0), 1);
30
+ if (time !== null && typeof time === "object" && "value" in time && typeof time.value === "number") return Math.min(Math.max(time.value / 100, 0), 1);
31
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
32
+ if (!warnedInactive) {
33
+ warnedInactive = true;
34
+ console.warn("[lottie-react] the scroll timeline reported no position, so the scrub fell back to measuring the element itself. This happens when nothing around the animation scrolls.");
35
+ }
36
+ }
37
+ }
38
+ return coverProgress(root.getBoundingClientRect(), {
39
+ width: window.innerWidth,
40
+ height: window.innerHeight
41
+ }, axis);
42
+ };
43
+ const sample = () => {
44
+ frameHandle = null;
45
+ const root = context.lottie.root;
46
+ if (!scrubbing || root === null) return;
47
+ if (context.lottie.state === LottieState.loading) {
48
+ frameHandle = requestAnimationFrame(sample);
49
+ return;
50
+ }
51
+ const band = bandProgress(readProgress(root), range);
52
+ const events = bandEdges(previousBand, band);
53
+ previousBand = band;
54
+ context.lottie.scrubTo(band * context.lottie.playableFrames);
55
+ if (events.enter !== void 0 || events.leave !== void 0) {
56
+ const liveRaw = context.options();
57
+ const live = isScrubOptions(liveRaw) ? liveRaw : {};
58
+ if (events.enter !== void 0) live.onRangeEnter?.(events.enter, context.lottie);
59
+ if (events.leave !== void 0) live.onRangeLeave?.(events.leave, context.lottie);
60
+ }
61
+ frameHandle = requestAnimationFrame(sample);
62
+ };
63
+ const startScrubbing = () => {
64
+ if (scrubbing) return;
65
+ scrubbing = true;
66
+ previousBand = null;
67
+ if (context.lottie.state !== LottieState.loading) {
68
+ gestureStarted = true;
69
+ context.lottie.scrubStart();
70
+ }
71
+ sample();
72
+ };
73
+ const stopScrubbing = () => {
74
+ if (!scrubbing) return;
75
+ scrubbing = false;
76
+ gestureStarted = false;
77
+ if (frameHandle !== null) {
78
+ cancelAnimationFrame(frameHandle);
79
+ frameHandle = null;
80
+ }
81
+ context.lottie.scrubEnd();
82
+ };
83
+ const handleEntries = (entries) => {
84
+ const entry = entries.at(-1);
85
+ if (entry === void 0) return;
86
+ if (entry.isIntersecting) startScrubbing();
87
+ else stopScrubbing();
88
+ };
89
+ const arm = () => {
90
+ const root = context.lottie.root;
91
+ if (root === observedRoot) return;
92
+ stopScrubbing();
93
+ observer?.disconnect();
94
+ observer = null;
95
+ timeline = null;
96
+ observedRoot = root;
97
+ if (root === null) return;
98
+ const ViewTimelineCtor = globalThis.ViewTimeline;
99
+ if (ViewTimelineCtor !== void 0) timeline = new ViewTimelineCtor({
100
+ subject: root,
101
+ axis
102
+ });
103
+ observer = new IntersectionObserver(handleEntries);
104
+ observer.observe(root);
105
+ };
106
+ const stopListening = context.onChange(() => {
107
+ if (scrubbing && !gestureStarted && context.lottie.state !== LottieState.loading && context.lottie.state !== LottieState.error) {
108
+ gestureStarted = true;
109
+ context.lottie.scrubStart();
110
+ }
111
+ arm();
112
+ });
113
+ arm();
114
+ return () => {
115
+ stopListening();
116
+ stopScrubbing();
117
+ observer?.disconnect();
118
+ };
119
+ }
120
+ /**
121
+ * Ties the playhead to how far the animation has travelled through the
122
+ * viewport, so scrolling scrubs it instead of time playing it.
123
+ *
124
+ * ```jsx
125
+ * <LottieInteractions
126
+ * interactions={[
127
+ * lottieScrollScrub({
128
+ * range: [0.2, 0.45],
129
+ * onRangeLeave: (direction, lottie) => {
130
+ * if (direction === "forward") lottie.playSegments([[45, 60]]);
131
+ * },
132
+ * }),
133
+ * ]}
134
+ * >
135
+ * <Lottie src={story} autoplay={false} />
136
+ * </LottieInteractions>
137
+ * ```
138
+ *
139
+ * Progress is the platform's own scroll timeline where the browser has one,
140
+ * and the same arithmetic measured by hand where it does not; the numbers
141
+ * agree by construction. While the animation is out of view nothing samples
142
+ * at all, and playback state is held by the scrub gesture, so whatever was
143
+ * true before the scrub is restored when it leaves.
144
+ */
145
+ function lottieScrollScrub(options = {}) {
146
+ return {
147
+ attach: attachScrollScrub,
148
+ options
149
+ };
150
+ }
151
+ //#endregion
152
+ export { lottieScrollScrub };
@@ -0,0 +1,46 @@
1
+ import { LottieInstance } from "../animation/types.cjs";
2
+ //#region src/interactions/types.d.ts
3
+ /**
4
+ * What a behaviour is handed when it attaches to one animation.
5
+ *
6
+ * Everything here reads live. `lottie` is the animation as it is right now,
7
+ * never a captured copy, because the instance object is rebuilt every render
8
+ * and anything held across renders would be the past. `options` is the same
9
+ * for the descriptor's options, which is how a callback option is read fresh
10
+ * at the moment an event fires without the behaviour ever re-attaching.
11
+ */
12
+ interface LottieInteractionContext {
13
+ /** The animation: values, commands, `subscribe`, `root`. Read fresh each use. */
14
+ readonly lottie: LottieInstance;
15
+ /** The descriptor's options as they are right now. */
16
+ options: () => unknown;
17
+ /**
18
+ * Fires when something about the animation changed: its root arrived, its
19
+ * values moved, or its options were edited. A behaviour re-checks what it
20
+ * armed against and returns early when nothing it uses has changed.
21
+ */
22
+ onChange: (listener: () => void) => () => void;
23
+ /**
24
+ * Scratch that survives an option change, for state that must outlive the
25
+ * re-attach an option change causes: a once-latch is the worked example.
26
+ * Cleared when the interaction leaves the list or everything unmounts.
27
+ */
28
+ memory: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * One behaviour in an `interactions` list: a stable implementation plus the
32
+ * plain data it was configured with. Factories such as `lottieInView` make
33
+ * these; nothing else needs to.
34
+ *
35
+ * The split is load-bearing. Implementations compare by identity and options
36
+ * by content, so a list written inline at the call site re-arms nothing, and
37
+ * an option that really changed re-arms only its own behaviour. `attach`
38
+ * narrows `options` itself with its own guard and returns the cleanup that
39
+ * detaches, or nothing when there was nothing to attach.
40
+ */
41
+ interface LottieInteraction {
42
+ attach: (context: LottieInteractionContext, options: unknown) => (() => void) | undefined;
43
+ options: unknown;
44
+ }
45
+ //#endregion
46
+ export { LottieInteraction, LottieInteractionContext };
@@ -0,0 +1,46 @@
1
+ import { LottieInstance } from "../animation/types.js";
2
+ //#region src/interactions/types.d.ts
3
+ /**
4
+ * What a behaviour is handed when it attaches to one animation.
5
+ *
6
+ * Everything here reads live. `lottie` is the animation as it is right now,
7
+ * never a captured copy, because the instance object is rebuilt every render
8
+ * and anything held across renders would be the past. `options` is the same
9
+ * for the descriptor's options, which is how a callback option is read fresh
10
+ * at the moment an event fires without the behaviour ever re-attaching.
11
+ */
12
+ interface LottieInteractionContext {
13
+ /** The animation: values, commands, `subscribe`, `root`. Read fresh each use. */
14
+ readonly lottie: LottieInstance;
15
+ /** The descriptor's options as they are right now. */
16
+ options: () => unknown;
17
+ /**
18
+ * Fires when something about the animation changed: its root arrived, its
19
+ * values moved, or its options were edited. A behaviour re-checks what it
20
+ * armed against and returns early when nothing it uses has changed.
21
+ */
22
+ onChange: (listener: () => void) => () => void;
23
+ /**
24
+ * Scratch that survives an option change, for state that must outlive the
25
+ * re-attach an option change causes: a once-latch is the worked example.
26
+ * Cleared when the interaction leaves the list or everything unmounts.
27
+ */
28
+ memory: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * One behaviour in an `interactions` list: a stable implementation plus the
32
+ * plain data it was configured with. Factories such as `lottieInView` make
33
+ * these; nothing else needs to.
34
+ *
35
+ * The split is load-bearing. Implementations compare by identity and options
36
+ * by content, so a list written inline at the call site re-arms nothing, and
37
+ * an option that really changed re-arms only its own behaviour. `attach`
38
+ * narrows `options` itself with its own guard and returns the cleanup that
39
+ * detaches, or nothing when there was nothing to attach.
40
+ */
41
+ interface LottieInteraction {
42
+ attach: (context: LottieInteractionContext, options: unknown) => (() => void) | undefined;
43
+ options: unknown;
44
+ }
45
+ //#endregion
46
+ export { LottieInteraction, LottieInteractionContext };
@@ -0,0 +1,142 @@
1
+ const require_isSameJson = require("../utils/isSameJson.cjs");
2
+ const require_useStableValue = require("../utils/useStableValue.cjs");
3
+ const require_LottieRegistryContext = require("../animation/LottieRegistryContext.cjs");
4
+ let react = require("react");
5
+ //#region src/interactions/useInteractionsRunner.ts
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ /**
10
+ * The options with every function replaced by a marker, so the whole slot can
11
+ * be compared by content. A callback's identity changes on every render at an
12
+ * inline call site, which must not read as a configuration change; a callback
13
+ * appearing or disappearing must.
14
+ */
15
+ function projectData(value) {
16
+ if (typeof value === "function") return "[function]";
17
+ if (Array.isArray(value)) return value.map(projectData);
18
+ if (isRecord(value)) {
19
+ const projected = {};
20
+ for (const [key, entry] of Object.entries(value)) projected[key] = projectData(entry);
21
+ return projected;
22
+ }
23
+ return value;
24
+ }
25
+ /**
26
+ * Arms every interaction on every animation a source holds, and keeps that
27
+ * true as animations come and go, options change, and values move.
28
+ *
29
+ * The granularity is deliberate: an animation arriving arms only itself, a
30
+ * changed option re-arms only its own slot on each animation, and a moved
31
+ * value re-arms nothing and only signals `onChange`. A re-arm caused by an
32
+ * option change hands the new attachment the old one's `memory` when the
33
+ * implementation is the same one, which is what lets a once-latch survive
34
+ * reconfiguration.
35
+ */
36
+ function useInteractionsRunner(source, interactions) {
37
+ const latest = (0, react.useRef)(interactions);
38
+ latest.current = interactions;
39
+ const projected = require_useStableValue.useStableValue(interactions.map((interaction) => ({
40
+ attach: interaction.attach,
41
+ options: projectData(interaction.options)
42
+ })));
43
+ const runtime = (0, react.useRef)(/* @__PURE__ */ new Map()).current;
44
+ const armSlot = (box, slot, memory) => {
45
+ const interaction = latest.current[slot];
46
+ const record = {
47
+ key: projected[slot],
48
+ attachIdentity: interaction.attach,
49
+ cleanup: void 0,
50
+ listeners: /* @__PURE__ */ new Set(),
51
+ memory: memory ?? {}
52
+ };
53
+ const context = {
54
+ get lottie() {
55
+ return box.current;
56
+ },
57
+ options: () => latest.current[slot]?.options,
58
+ onChange: (listener) => {
59
+ record.listeners.add(listener);
60
+ return () => {
61
+ record.listeners.delete(listener);
62
+ };
63
+ },
64
+ memory: record.memory
65
+ };
66
+ record.cleanup = interaction.attach(context, interaction.options);
67
+ return record;
68
+ };
69
+ const armRef = (0, react.useRef)(armSlot);
70
+ armRef.current = armSlot;
71
+ (0, react.useEffect)(() => {
72
+ const attachBox = (box) => {
73
+ runtime.set(box, projected.map((_, slot) => armRef.current(box, slot)));
74
+ };
75
+ const syncSlots = () => {
76
+ for (const [box, attachments] of runtime) {
77
+ for (let slot = 0; slot < projected.length; slot += 1) {
78
+ const existing = attachments[slot];
79
+ if (existing !== void 0 && require_isSameJson.isSameJson(existing.key, projected[slot])) continue;
80
+ const inherited = existing !== void 0 && existing.attachIdentity === latest.current[slot]?.attach ? existing.memory : void 0;
81
+ existing?.cleanup?.();
82
+ attachments[slot] = armRef.current(box, slot, inherited);
83
+ }
84
+ for (const removed of attachments.splice(projected.length)) removed.cleanup?.();
85
+ }
86
+ };
87
+ const syncBoxes = () => {
88
+ const current = new Set(source.boxes());
89
+ for (const box of current) if (!runtime.has(box)) attachBox(box);
90
+ for (const [box, attachments] of [...runtime]) if (!current.has(box)) {
91
+ for (const attachment of attachments) attachment.cleanup?.();
92
+ runtime.delete(box);
93
+ }
94
+ };
95
+ const notifyChange = () => {
96
+ for (const attachments of runtime.values()) for (const attachment of attachments) for (const listener of [...attachment.listeners]) listener();
97
+ };
98
+ syncSlots();
99
+ syncBoxes();
100
+ return source.subscribe(() => {
101
+ syncBoxes();
102
+ notifyChange();
103
+ });
104
+ }, [
105
+ source,
106
+ projected,
107
+ runtime
108
+ ]);
109
+ (0, react.useEffect)(() => {
110
+ return () => {
111
+ for (const attachments of runtime.values()) for (const attachment of attachments) attachment.cleanup?.();
112
+ runtime.clear();
113
+ };
114
+ }, [runtime]);
115
+ }
116
+ /**
117
+ * A source holding exactly one animation, for the positions where the
118
+ * component or the hook was handed an instance rather than wrapping a subtree.
119
+ * Mirrors the registration the animation itself performs toward a surrounding
120
+ * wrapper, so the runner cannot tell the two apart.
121
+ */
122
+ function useSingleInstanceSource(instance) {
123
+ const [store] = (0, react.useState)(require_LottieRegistryContext.createLottieRegistry);
124
+ const box = (0, react.useRef)(null);
125
+ if (instance !== null) {
126
+ if (box.current === null) box.current = { current: instance };
127
+ else box.current.current = instance;
128
+ }
129
+ const present = instance !== null;
130
+ (0, react.useEffect)(() => {
131
+ const current = box.current;
132
+ if (!present || current === null) return;
133
+ return store.register(current);
134
+ }, [store, present]);
135
+ (0, react.useEffect)(() => {
136
+ if (present) store.bump();
137
+ });
138
+ return store;
139
+ }
140
+ //#endregion
141
+ exports.useInteractionsRunner = useInteractionsRunner;
142
+ exports.useSingleInstanceSource = useSingleInstanceSource;