@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.
- package/LICENSE.md +9 -0
- package/README.md +4 -10
- package/build/_virtual/_rolldown/runtime.cjs +23 -0
- package/build/animation/Lottie.cjs +39 -0
- package/build/animation/Lottie.d.cts +47 -0
- package/build/animation/Lottie.d.ts +47 -0
- package/build/animation/Lottie.js +37 -0
- package/build/animation/LottieDisplay.cjs +55 -0
- package/build/animation/LottieDisplay.d.cts +46 -0
- package/build/animation/LottieDisplay.d.ts +46 -0
- package/build/animation/LottieDisplay.js +53 -0
- package/build/animation/LottieInstanceContext.cjs +16 -0
- package/build/animation/LottieInstanceContext.js +17 -0
- package/build/animation/LottieLight.cjs +19 -0
- package/build/animation/LottieLight.d.cts +23 -0
- package/build/animation/LottieLight.d.ts +23 -0
- package/build/animation/LottieLight.js +17 -0
- package/build/animation/LottieRegistryContext.cjs +36 -0
- package/build/animation/LottieRegistryContext.js +35 -0
- package/build/animation/LottieSvg.cjs +20 -0
- package/build/animation/LottieSvg.d.cts +24 -0
- package/build/animation/LottieSvg.d.ts +24 -0
- package/build/animation/LottieSvg.js +18 -0
- package/build/animation/collectMarkerCrossings.cjs +38 -0
- package/build/animation/collectMarkerCrossings.js +38 -0
- package/build/animation/createLottieComponent.cjs +131 -0
- package/build/animation/createLottieComponent.d.cts +45 -0
- package/build/animation/createLottieComponent.d.ts +45 -0
- package/build/animation/createLottieComponent.js +131 -0
- package/build/animation/hasExpressions.cjs +25 -0
- package/build/animation/hasExpressions.js +25 -0
- package/build/animation/normalizeAnimationSource.cjs +38 -0
- package/build/animation/normalizeAnimationSource.js +38 -0
- package/build/animation/polymorphicForwardRef.cjs +18 -0
- package/build/animation/polymorphicForwardRef.js +19 -0
- package/build/animation/renderStyledElement.cjs +31 -0
- package/build/animation/renderStyledElement.js +31 -0
- package/build/animation/resolveSeekTarget.cjs +97 -0
- package/build/animation/resolveSeekTarget.js +95 -0
- package/build/animation/styleLayer.cjs +24 -0
- package/build/animation/styleLayer.js +24 -0
- package/build/animation/stylePrecedence.cjs +12 -0
- package/build/animation/stylePrecedence.js +12 -0
- package/build/animation/types.cjs +71 -0
- package/build/animation/types.d.cts +461 -0
- package/build/animation/types.d.ts +461 -0
- package/build/animation/types.js +68 -0
- package/build/animation/useLottie.cjs +40 -0
- package/build/animation/useLottie.d.cts +36 -0
- package/build/animation/useLottie.d.ts +36 -0
- package/build/animation/useLottie.js +38 -0
- package/build/animation/useLottieAnimation.cjs +434 -0
- package/build/animation/useLottieAnimation.d.cts +40 -0
- package/build/animation/useLottieAnimation.d.ts +40 -0
- package/build/animation/useLottieAnimation.js +434 -0
- package/build/animation/useLottieInstance.cjs +20 -0
- package/build/animation/useLottieInstance.d.cts +14 -0
- package/build/animation/useLottieInstance.d.ts +14 -0
- package/build/animation/useLottieInstance.js +20 -0
- package/build/animation/useLottieLight.cjs +20 -0
- package/build/animation/useLottieLight.d.cts +16 -0
- package/build/animation/useLottieLight.d.ts +16 -0
- package/build/animation/useLottieLight.js +18 -0
- package/build/animation/useLottieSvg.cjs +18 -0
- package/build/animation/useLottieSvg.d.cts +14 -0
- package/build/animation/useLottieSvg.d.ts +14 -0
- package/build/animation/useLottieSvg.js +16 -0
- package/build/controls/LottieControls.cjs +180 -0
- package/build/controls/LottieControls.d.cts +58 -0
- package/build/controls/LottieControls.d.ts +58 -0
- package/build/controls/LottieControls.js +178 -0
- package/build/controls/LottieDirectionButton.cjs +36 -0
- package/build/controls/LottieDirectionButton.js +35 -0
- package/build/controls/LottieFullscreenButton.cjs +35 -0
- package/build/controls/LottieFullscreenButton.js +34 -0
- package/build/controls/LottieLoopButton.cjs +32 -0
- package/build/controls/LottieLoopButton.js +31 -0
- package/build/controls/LottiePlayButton.cjs +34 -0
- package/build/controls/LottiePlayButton.js +33 -0
- package/build/controls/LottieReadout.cjs +61 -0
- package/build/controls/LottieReadout.d.cts +13 -0
- package/build/controls/LottieReadout.d.ts +13 -0
- package/build/controls/LottieReadout.js +60 -0
- package/build/controls/LottieSeekBar.cjs +87 -0
- package/build/controls/LottieSeekBar.js +86 -0
- package/build/controls/LottieSpeedSelect.cjs +51 -0
- package/build/controls/LottieSpeedSelect.js +50 -0
- package/build/controls/LottieStopButton.cjs +29 -0
- package/build/controls/LottieStopButton.js +28 -0
- package/build/controls/controlIcon.cjs +25 -0
- package/build/controls/controlIcon.js +25 -0
- package/build/controls/useFullscreen.cjs +46 -0
- package/build/controls/useFullscreen.js +46 -0
- package/build/controls/useShortcuts.cjs +70 -0
- package/build/controls/useShortcuts.js +70 -0
- package/build/index.cjs +36 -0
- package/build/index.d.cts +19 -0
- package/build/index.d.ts +19 -80
- package/build/index.js +17 -667
- package/build/interactions/LottieInteractions.cjs +41 -0
- package/build/interactions/LottieInteractions.d.cts +37 -0
- package/build/interactions/LottieInteractions.d.ts +37 -0
- package/build/interactions/LottieInteractions.js +41 -0
- package/build/interactions/coverProgress.cjs +55 -0
- package/build/interactions/coverProgress.js +53 -0
- package/build/interactions/lottieInView.cjs +109 -0
- package/build/interactions/lottieInView.d.cts +44 -0
- package/build/interactions/lottieInView.d.ts +44 -0
- package/build/interactions/lottieInView.js +109 -0
- package/build/interactions/lottieScrollScrub.cjs +152 -0
- package/build/interactions/lottieScrollScrub.d.cts +47 -0
- package/build/interactions/lottieScrollScrub.d.ts +47 -0
- package/build/interactions/lottieScrollScrub.js +152 -0
- package/build/interactions/types.d.cts +46 -0
- package/build/interactions/types.d.ts +46 -0
- package/build/interactions/useInteractionsRunner.cjs +142 -0
- package/build/interactions/useInteractionsRunner.js +141 -0
- package/build/interactions/useLottieInteractions.cjs +21 -0
- package/build/interactions/useLottieInteractions.d.cts +19 -0
- package/build/interactions/useLottieInteractions.d.ts +19 -0
- package/build/interactions/useLottieInteractions.js +21 -0
- package/build/overlays/LottieError.cjs +74 -0
- package/build/overlays/LottieError.d.cts +40 -0
- package/build/overlays/LottieError.d.ts +40 -0
- package/build/overlays/LottieError.js +72 -0
- package/build/overlays/LottieLoading.cjs +89 -0
- package/build/overlays/LottieLoading.d.cts +45 -0
- package/build/overlays/LottieLoading.d.ts +45 -0
- package/build/overlays/LottieLoading.js +87 -0
- package/build/overlays/overlayStyles.cjs +33 -0
- package/build/overlays/overlayStyles.js +33 -0
- package/build/utils/SubscriptionManager.cjs +42 -0
- package/build/utils/SubscriptionManager.d.cts +30 -0
- package/build/utils/SubscriptionManager.d.ts +30 -0
- package/build/utils/SubscriptionManager.js +42 -0
- package/build/utils/createLogger.cjs +25 -0
- package/build/utils/createLogger.js +25 -0
- package/build/utils/isSameJson.cjs +37 -0
- package/build/utils/isSameJson.js +37 -0
- package/build/utils/mergeRefs.cjs +37 -0
- package/build/utils/mergeRefs.js +37 -0
- package/build/utils/useStableValue.cjs +26 -0
- package/build/utils/useStableValue.js +26 -0
- package/changes.json +3 -8
- package/package.json +144 -89
- package/LICENSE +0 -46
- package/build/index.es.js +0 -653
- package/build/index.es.js.map +0 -1
- package/build/index.es.min.js +0 -3
- package/build/index.js.map +0 -1
- package/build/index.min.js +0 -3
- package/build/index.umd.js +0 -670
- package/build/index.umd.js.map +0 -1
- 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;
|