@gtkx/animated 1.2.2

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 (42) hide show
  1. package/README.md +172 -0
  2. package/dist/animated.d.ts +20 -0
  3. package/dist/animated.d.ts.map +1 -0
  4. package/dist/animated.js +44 -0
  5. package/dist/animated.js.map +1 -0
  6. package/dist/apply-animated-values.d.ts +4 -0
  7. package/dist/apply-animated-values.d.ts.map +1 -0
  8. package/dist/apply-animated-values.js +84 -0
  9. package/dist/apply-animated-values.js.map +1 -0
  10. package/dist/bootstrap.d.ts +2 -0
  11. package/dist/bootstrap.d.ts.map +1 -0
  12. package/dist/bootstrap.js +11 -0
  13. package/dist/bootstrap.js.map +1 -0
  14. package/dist/index.d.ts +9 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +5 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/reduced-motion.d.ts +13 -0
  19. package/dist/reduced-motion.d.ts.map +1 -0
  20. package/dist/reduced-motion.js +59 -0
  21. package/dist/reduced-motion.js.map +1 -0
  22. package/dist/request-frame.d.ts +4 -0
  23. package/dist/request-frame.d.ts.map +1 -0
  24. package/dist/request-frame.js +155 -0
  25. package/dist/request-frame.js.map +1 -0
  26. package/dist/types.d.ts +38 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +2 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/with-animated.d.ts +6 -0
  31. package/dist/with-animated.d.ts.map +1 -0
  32. package/dist/with-animated.js +181 -0
  33. package/dist/with-animated.js.map +1 -0
  34. package/package.json +78 -0
  35. package/src/animated.ts +62 -0
  36. package/src/apply-animated-values.ts +116 -0
  37. package/src/bootstrap.ts +12 -0
  38. package/src/index.ts +110 -0
  39. package/src/reduced-motion.ts +82 -0
  40. package/src/request-frame.ts +214 -0
  41. package/src/types.ts +64 -0
  42. package/src/with-animated.tsx +254 -0
@@ -0,0 +1,254 @@
1
+ import type { Lookup } from "@react-spring/types";
2
+ import { useMergedRef } from "@gtkx/react/internal";
3
+ import {
4
+ addFluidObserver,
5
+ type FluidEvent,
6
+ type FluidValue,
7
+ getFluidValue,
8
+ hasFluidValue,
9
+ raf,
10
+ removeFluidObserver,
11
+ useForceUpdate,
12
+ } from "@react-spring/shared";
13
+ import { type ElementType, type ReactNode, type Ref, type RefObject, useLayoutEffect, useRef } from "react";
14
+ import type { AnimatedComponent } from "./types.js";
15
+ import { didApplyAnimatedValues } from "./apply-animated-values.js";
16
+ import { trackReducedMotion } from "./reduced-motion.js";
17
+
18
+ type AnimatedInput = { ref?: Ref<object> | undefined; [key: string]: unknown };
19
+ type Wrappable = Exclude<ElementType, string>;
20
+ type ObserverRef = { current: PropsObserver | null };
21
+
22
+ const STYLE_PROP = "style";
23
+ const cache: WeakMap<object, AnimatedComponent<Wrappable>> = new WeakMap();
24
+
25
+ const getDisplayName = (component: Wrappable): string => {
26
+ const { displayName, name } = component as { displayName?: unknown; name?: unknown };
27
+
28
+ if (typeof displayName === "string" && displayName !== "") {
29
+ return displayName;
30
+ }
31
+
32
+ return typeof name === "string" && name !== "" ? name : "Anonymous";
33
+ };
34
+
35
+ const isFluidProp = (value: unknown): boolean => hasFluidValue(value) || isFluidArray(value);
36
+
37
+ const isBlock = (value: unknown): value is Lookup =>
38
+ typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
39
+
40
+ function isFluidArray(value: unknown): value is unknown[] {
41
+ return Array.isArray(value) && value.some((item) => isFluidProp(item));
42
+ }
43
+
44
+ function isFluidStyle(value: unknown): value is Lookup {
45
+ return isBlock(value) && Object.values(value).some((item) => hasFluidValue(item) || isFluidStyle(item));
46
+ }
47
+
48
+ function resolveStyle(style: Lookup): Lookup {
49
+ const resolved: Lookup = {};
50
+
51
+ for (const name in style) {
52
+ const value: unknown = style[name];
53
+ const next: unknown = isFluidStyle(value) ? resolveStyle(value) : getFluidValue(value);
54
+ resolved[name] = next;
55
+ }
56
+
57
+ return resolved;
58
+ }
59
+
60
+ function resolveValue(value: unknown): unknown {
61
+ return isFluidArray(value) ? value.map((item) => resolveValue(item)) : getFluidValue(value);
62
+ }
63
+
64
+ const isFluidNamed = (name: string, value: unknown): boolean =>
65
+ isFluidProp(value) || (name === STYLE_PROP && isFluidStyle(value));
66
+
67
+ const resolveNamed = (name: string, value: unknown): unknown =>
68
+ name === STYLE_PROP && isFluidStyle(value) ? resolveStyle(value) : resolveValue(value);
69
+
70
+ const resolveProps = (props: Lookup, isAnimatedOnly: boolean): Lookup => {
71
+ const resolved: Lookup = {};
72
+
73
+ for (const name in props) {
74
+ const value: unknown = props[name];
75
+
76
+ if (isFluidNamed(name, value)) {
77
+ resolved[name] = resolveNamed(name, value);
78
+ } else if (!isAnimatedOnly) {
79
+ resolved[name] = value;
80
+ }
81
+ }
82
+
83
+ return resolved;
84
+ };
85
+
86
+ function collectEach(items: unknown[], dependencies: Set<FluidValue>): void {
87
+ for (const item of items) {
88
+ collectDependencies(item, dependencies);
89
+ }
90
+ }
91
+
92
+ function collectDependencies(value: unknown, dependencies: Set<FluidValue>): void {
93
+ if (hasFluidValue(value)) {
94
+ dependencies.add(value);
95
+ } else if (isFluidArray(value)) {
96
+ collectEach(value, dependencies);
97
+ } else if (isFluidStyle(value)) {
98
+ collectEach(Object.values(value), dependencies);
99
+ }
100
+ }
101
+
102
+ const collectNamed = (name: string, value: unknown, dependencies: Set<FluidValue>): void => {
103
+ if (isFluidNamed(name, value)) {
104
+ collectDependencies(value, dependencies);
105
+ }
106
+ };
107
+
108
+ const getDependencies = (props: Lookup): Set<FluidValue> => {
109
+ const dependencies: Set<FluidValue> = new Set();
110
+
111
+ for (const name in props) {
112
+ collectNamed(name, props[name], dependencies);
113
+ }
114
+
115
+ return dependencies;
116
+ };
117
+
118
+ const getFluidNames = (props: Lookup): Set<string> => {
119
+ const names: Set<string> = new Set();
120
+
121
+ for (const name in props) {
122
+ if (isFluidNamed(name, props[name])) {
123
+ names.add(name);
124
+ }
125
+ }
126
+
127
+ return names;
128
+ };
129
+
130
+ const observe = (observer: PropsObserver): void => {
131
+ for (const dependency of observer.dependencies) {
132
+ addFluidObserver(dependency, observer);
133
+ }
134
+ };
135
+
136
+ const unobserve = (observer: PropsObserver): void => {
137
+ for (const dependency of observer.dependencies) {
138
+ removeFluidObserver(dependency, observer);
139
+ }
140
+
141
+ raf.cancel(observer.update);
142
+ };
143
+
144
+ const useObserver = (observer: PropsObserver): void => {
145
+ const observerRef: ObserverRef = useRef<PropsObserver | null>(null);
146
+
147
+ useLayoutEffect(() => {
148
+ observerRef.current = observer;
149
+ observe(observer);
150
+
151
+ return () => {
152
+ if (observerRef.current === null) {
153
+ return;
154
+ }
155
+
156
+ unobserve(observerRef.current);
157
+ observerRef.current = null;
158
+ };
159
+ });
160
+ };
161
+
162
+ const collectStaticReplacements = (
163
+ props: Lookup,
164
+ previous: Set<string>,
165
+ current: Set<string>,
166
+ values: Lookup,
167
+ ): void => {
168
+ for (const name of previous) {
169
+ const value: unknown = props[name];
170
+
171
+ if (value !== undefined && !current.has(name)) {
172
+ values[name] = value;
173
+ }
174
+ }
175
+ };
176
+
177
+ const useCommitSync = (instanceRef: RefObject<object | null>, props: Lookup): void => {
178
+ const fluidNamesRef = useRef<Set<string>>(new Set());
179
+
180
+ useLayoutEffect(() => {
181
+ const previous = fluidNamesRef.current;
182
+ const current = getFluidNames(props);
183
+ fluidNamesRef.current = current;
184
+ trackReducedMotion();
185
+ const instance = instanceRef.current;
186
+
187
+ if (instance === null) {
188
+ return;
189
+ }
190
+
191
+ const values = resolveProps(props, true);
192
+ collectStaticReplacements(props, previous, current, values);
193
+ didApplyAnimatedValues(instance, values);
194
+ });
195
+ };
196
+
197
+ const useAnimatedUpdate = (instanceRef: RefObject<object | null>, props: Lookup): (() => void) => {
198
+ const forceUpdate = useForceUpdate();
199
+
200
+ return () => {
201
+ const instance = instanceRef.current;
202
+ const isApplied = instance !== null && didApplyAnimatedValues(instance, resolveProps(props, true));
203
+
204
+ if (!isApplied) {
205
+ forceUpdate();
206
+ }
207
+ };
208
+ };
209
+
210
+ const createAnimatedComponent = (Component: Wrappable): AnimatedComponent<Wrappable> => {
211
+ const Animated = ({ ref: givenRef, ...props }: AnimatedInput): ReactNode => {
212
+ const instanceRef = useRef<object | null>(null);
213
+ const ref = useMergedRef(givenRef, instanceRef);
214
+ const update = useAnimatedUpdate(instanceRef, props);
215
+ useObserver(new PropsObserver(update, getDependencies(props)));
216
+ useCommitSync(instanceRef, props);
217
+
218
+ return <Component {...resolveProps(props, false)} ref={ref} />;
219
+ };
220
+
221
+ Animated.displayName = `Animated(${getDisplayName(Component)})`;
222
+
223
+ return Animated as AnimatedComponent<Wrappable>;
224
+ };
225
+
226
+ function withAnimated<T extends Wrappable>(component: T): AnimatedComponent<T> {
227
+ let cached = cache.get(component);
228
+
229
+ if (cached === undefined) {
230
+ cached = createAnimatedComponent(component);
231
+ cache.set(component, cached);
232
+ }
233
+
234
+ return cached as AnimatedComponent<T>;
235
+ }
236
+
237
+ class PropsObserver {
238
+ readonly update: () => void;
239
+
240
+ readonly dependencies: Set<FluidValue>;
241
+
242
+ constructor(update: () => void, dependencies: Set<FluidValue>) {
243
+ this.update = update;
244
+ this.dependencies = dependencies;
245
+ }
246
+
247
+ eventObserved(event: FluidEvent): void {
248
+ if (event.type === "change") {
249
+ raf.write(this.update);
250
+ }
251
+ }
252
+ }
253
+
254
+ export { withAnimated };