Skip to content

Commit 6b0447f

Browse files
committed
update
1 parent 2385c93 commit 6b0447f

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

spring/src/scheduleProps.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import * as G from './globals'
2+
import { raf } from "./rafz";
3+
import { InferProps, InferState, RunAsyncProps, RunAsyncState } from "./runAsync";
4+
import { AnimationResolver, AnimationTarget, AsyncResult, callProp, is, matchProp, MatchProp, Timeout } from "./utils";
5+
6+
// The `scheduleProps` function only handles these defaults.
7+
type DefaultProps<T> = { cancel?: MatchProp<T>; pause?: MatchProp<T> }
8+
9+
interface ScheduledProps<T extends AnimationTarget> {
10+
key?: string
11+
props: InferProps<T>
12+
defaultProps?: DefaultProps<InferState<T>>
13+
state: RunAsyncState<T>
14+
actions: {
15+
pause: () => void
16+
resume: () => void
17+
start: (props: RunAsyncProps<T>, resolve: AnimationResolver<T>) => void
18+
}
19+
}
20+
21+
/**
22+
* This function sets a timeout if both the `delay` prop exists and
23+
* the `cancel` prop is not `true`.
24+
*
25+
* The `actions.start` function must handle the `cancel` prop itself,
26+
* but the `pause` prop is taken care of.
27+
*/
28+
export function scheduleProps<T extends AnimationTarget>(
29+
callId: number,
30+
{ key, props, defaultProps, state, actions }: ScheduledProps<T>
31+
): AsyncResult<T> {
32+
return new Promise((resolve, reject) => {
33+
let delay: number
34+
let timeout: Timeout
35+
36+
let cancel = matchProp(props.cancel ?? defaultProps?.cancel, key)
37+
if (cancel) {
38+
onStart()
39+
} else {
40+
// The `pause` prop updates the paused flag.
41+
if (!is.und(props.pause)) {
42+
state.paused = matchProp(props.pause, key)
43+
}
44+
// The default `pause` takes precedence when true,
45+
// which allows `SpringContext` to work as expected.
46+
let pause = defaultProps?.pause
47+
if (pause !== true) {
48+
pause = state.paused || matchProp(pause, key)
49+
}
50+
51+
delay = callProp(props.delay || 0, key)
52+
if (pause) {
53+
state.resumeQueue.add(onResume)
54+
actions.pause()
55+
} else {
56+
actions.resume()
57+
onResume()
58+
}
59+
}
60+
61+
function onPause() {
62+
state.resumeQueue.add(onResume)
63+
state.timeouts.delete(timeout)
64+
timeout.cancel()
65+
// Cache the remaining delay.
66+
delay = timeout.time - raf.now()
67+
}
68+
69+
function onResume() {
70+
if (delay > 0 && !G.skipAnimation) {
71+
state.delayed = true
72+
timeout = raf.setTimeout(onStart, delay)
73+
state.pauseQueue.add(onPause)
74+
state.timeouts.add(timeout)
75+
} else {
76+
onStart()
77+
}
78+
}
79+
80+
function onStart() {
81+
if (state.delayed) {
82+
state.delayed = false
83+
}
84+
85+
state.pauseQueue.delete(onPause)
86+
state.timeouts.delete(timeout)
87+
88+
// Maybe cancelled during its delay.
89+
if (callId <= (state.cancelId || 0)) {
90+
cancel = true
91+
}
92+
93+
try {
94+
actions.start({ ...props, callId, cancel }, resolve)
95+
} catch (err) {
96+
reject(err)
97+
}
98+
}
99+
})
100+
}

spring/src/withAnimated.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Dynamic } from "solid-js/web";
2+
import {
3+
children,
4+
createComponent,
5+
createRenderEffect,
6+
JSX,
7+
onCleanup,
8+
onMount,
9+
} from "solid-js";
10+
import { AnimatedObject } from "./AnimatedObject";
11+
import { TreeContext } from "./context";
12+
import { HostConfig } from "./createHost";
13+
import {
14+
addFluidObserver,
15+
FluidEvent,
16+
FluidValue,
17+
removeFluidObserver,
18+
} from "./fluids";
19+
import { each } from "./utils";
20+
import { raf } from "./rafz";
21+
22+
export type AnimatableComponent = string;
23+
24+
// Shout out to @Otonashi & @Alex Lohr: https://discord.com/channels/722131463138705510/817960620736380928/961505601039523880
25+
export const withAnimated = (Component: string, host: HostConfig) => {
26+
return (props: any) => {
27+
const c = children(() =>
28+
createComponent(Dynamic, { component: Component, ...props })
29+
);
30+
const instanceRef: Element = c() as any;
31+
onMount(() => console.log("here"));
32+
const [_props, deps] = getAnimatedState(props, host);
33+
console.log(_props, deps);
34+
35+
const callback = () => {
36+
const didUpdate = instanceRef
37+
? host.applyAnimatedValues(instanceRef, _props.getValue(true))
38+
: false;
39+
40+
// Re-render the component when native updates fail.
41+
if (didUpdate === false) {
42+
console.log(didUpdate);
43+
// forceUpdate()
44+
}
45+
};
46+
47+
const observer = new PropsObserver(callback, deps);
48+
49+
createRenderEffect(() => {
50+
// Observe the latest dependencies.
51+
each(deps, (dep) => addFluidObserver(dep, observer));
52+
53+
// if (lastObserver) {
54+
// each(observer.deps, (dep) => removeFluidObserver(dep, observer));
55+
// raf.cancel(observer.update);
56+
// }
57+
});
58+
callback();
59+
onCleanup(() => {
60+
each(observer.deps, (dep) => removeFluidObserver(dep, observer));
61+
});
62+
63+
return c;
64+
};
65+
};
66+
67+
class PropsObserver {
68+
constructor(readonly update: () => void, readonly deps: Set<FluidValue>) {}
69+
eventObserved(event: FluidEvent) {
70+
if (event.type == "change") {
71+
raf.write(this.update);
72+
}
73+
}
74+
}
75+
76+
type AnimatedState = [props: AnimatedObject, dependencies: Set<FluidValue>];
77+
78+
function getAnimatedState(props: any, host: HostConfig): AnimatedState {
79+
const dependencies = new Set<FluidValue>();
80+
TreeContext.dependencies = dependencies;
81+
82+
// Search the style for dependencies.
83+
if (props.style)
84+
props = {
85+
...props,
86+
style: host.createAnimatedStyle(props.style),
87+
};
88+
89+
// Search the props for dependencies.
90+
props = new AnimatedObject(props);
91+
92+
TreeContext.dependencies = null;
93+
return [props, dependencies];
94+
}

0 commit comments

Comments
 (0)