Skip to content

Commit c6e2ec9

Browse files
committed
solid api
1 parent 9fbcc4d commit c6e2ec9

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

spring/src/solid/createSpring.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Accessor, createEffect, createMemo } from "solid-js";
2+
import { SpringRef } from "../SpringRef";
3+
import type { SpringRef as SpringRefType } from "../SpringRef";
4+
import {
5+
ControllerUpdate,
6+
is,
7+
PickAnimated,
8+
Remap,
9+
SpringValues,
10+
Valid,
11+
} from "../utils";
12+
import { createSprings } from "./createSprings";
13+
import { Controller } from "../Controller";
14+
15+
/**
16+
* The props that `useSpring` recognizes.
17+
*/
18+
export type CreateSpringProps<Props extends object = any> = unknown &
19+
PickAnimated<Props> extends infer State
20+
? Remap<
21+
ControllerUpdate<State> & {
22+
/**
23+
* Used to access the imperative API.
24+
*
25+
* When defined, the render animation won't auto-start.
26+
*/
27+
ref?: SpringRef<State>;
28+
}
29+
>
30+
: never;
31+
/* export function createSpring<Props extends object>(
32+
props: (...arg: any) => ((Props & Valid<Props, CreateSpringProps<Props>>) | CreateSpringProps)
33+
): Accessor<
34+
[SpringValues<PickAnimated<Props>>, SpringRefType<PickAnimated<Props>>]
35+
>; */
36+
export function createSpring<Props extends object>(
37+
props: () =>
38+
| (Props & Valid<Props, CreateSpringProps<Props>>)
39+
| CreateSpringProps
40+
): Accessor<
41+
[SpringValues<PickAnimated<Props>>, SpringRefType<PickAnimated<Props>>]
42+
>;
43+
44+
export function createSpring<Props extends object>(
45+
props: (Props & Valid<Props, CreateSpringProps<Props>>) | CreateSpringProps
46+
): Accessor<
47+
[SpringValues<PickAnimated<Props>>, SpringRefType<PickAnimated<Props>>]
48+
>;
49+
50+
export function createSpring<Props extends object>(props: any): any {
51+
const fn = is.fun(props) ? props : () => props;
52+
53+
const springsFn = createSprings(() => 1, fn);
54+
const springMemo = createMemo(() => {
55+
const [[value], ref] = springsFn();
56+
57+
return [value as SpringValues<PickAnimated<Props>>, ref];
58+
});
59+
return springMemo;
60+
}
61+
62+
/*
63+
* const props = createSpring(() => ({
64+
to: { opacity: 1 },
65+
from: { opacity: 0 },
66+
reset: true,
67+
onRest: () => set(!flip())
68+
}))
69+
*/

spring/src/solid/createSprings.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import {
2+
ControllerFlushFn,
3+
ControllerUpdate,
4+
detachRefs,
5+
is,
6+
Lookup,
7+
PickAnimated,
8+
replaceRef,
9+
SpringValues,
10+
} from "../utils";
11+
import { SpringRef } from "../SpringRef";
12+
import type { SpringRef as SpringRefType } from "../SpringRef";
13+
import { each } from "../utils";
14+
import {
15+
Controller,
16+
flushUpdateQueue,
17+
getSprings,
18+
setSprings,
19+
} from "../Controller";
20+
import {
21+
Accessor,
22+
createEffect,
23+
createMemo,
24+
createRenderEffect,
25+
onCleanup,
26+
} from "solid-js";
27+
import { declareUpdate } from "../SpringValue";
28+
29+
export type CreateSpringsProps<State extends Lookup = Lookup> = unknown &
30+
ControllerUpdate<State> & {
31+
ref?: SpringRefType<State>;
32+
};
33+
34+
export function createSprings<Props extends CreateSpringsProps>(
35+
lengthFn: () => number,
36+
props: Props[] & CreateSpringsProps<PickAnimated<Props>>[]
37+
): Accessor<
38+
[SpringValues<PickAnimated<Props>>[], SpringRefType<PickAnimated<Props>>]
39+
>;
40+
41+
export function createSprings<Props extends CreateSpringsProps>(
42+
lengthFn: () => number,
43+
props: (i: number, ctrl: Controller) => Props
44+
): Accessor<
45+
[SpringValues<PickAnimated<Props>>[], SpringRefType<PickAnimated<Props>>]
46+
>;
47+
48+
export function createSprings<Props extends CreateSpringsProps>(
49+
lengthFn: () => number,
50+
props: any[] | ((i: number, ctrl: Controller) => any)
51+
): Accessor<
52+
[SpringValues<PickAnimated<Props>>[], SpringRefType<PickAnimated<Props>>]
53+
> {
54+
const propsFn = is.fun(props) ? props : undefined;
55+
const ref = SpringRef();
56+
57+
interface State {
58+
// The controllers used for applying updates.
59+
ctrls: Controller[];
60+
// The queue of changes to make on commit.
61+
queue: Array<() => void>;
62+
// The flush function used by controllers.
63+
flush: ControllerFlushFn;
64+
}
65+
let layoutId = 0;
66+
67+
const state: State = {
68+
ctrls: [],
69+
queue: [],
70+
flush(ctrl, updates) {
71+
const springs = getSprings(ctrl, updates);
72+
73+
// Flushing is postponed until the component's commit phase
74+
// if a spring was created since the last commit.
75+
const canFlushSync =
76+
layoutId > 0 &&
77+
!state.queue.length &&
78+
!Object.keys(springs).some((key) => !ctrl.springs[key]);
79+
80+
return canFlushSync
81+
? flushUpdateQueue(ctrl, updates)
82+
: new Promise<any>((resolve) => {
83+
setSprings(ctrl, springs);
84+
state.queue.push(() => {
85+
resolve(flushUpdateQueue(ctrl, updates));
86+
});
87+
// forceUpdate()
88+
});
89+
},
90+
};
91+
92+
const ctrls = [...state.ctrls];
93+
94+
const updates: any[] = [];
95+
// Create new controllers when "length" increases, and destroy
96+
// the affected controllers when "length" decreases.
97+
createEffect(() => {
98+
const length = lengthFn();
99+
// Clean up any unused controllers
100+
each(ctrls.slice(length, prevLength), (ctrl) => {
101+
detachRefs(ctrl, ref);
102+
ctrl.stop(true);
103+
});
104+
ctrls.length = length;
105+
106+
declareUpdates(prevLength, length);
107+
});
108+
109+
// Cache old controllers to dispose in the commit phase.
110+
const prevLength = lengthFn() || 0;
111+
112+
// Update existing controllers when "deps" are changed.
113+
createRenderEffect(() => {
114+
const length = lengthFn();
115+
declareUpdates(0, Math.min(prevLength, length));
116+
});
117+
118+
/** Fill the `updates` array with declarative updates for the given index range. */
119+
function declareUpdates(startIndex: number, endIndex: number) {
120+
for (let i = startIndex; i < endIndex; i++) {
121+
const ctrl = ctrls[i] || (ctrls[i] = new Controller(null, state.flush));
122+
123+
const update: CreateSpringsProps<any> = propsFn
124+
? propsFn(i, ctrl)
125+
: (props as any)[i];
126+
127+
if (update) {
128+
updates[i] = declareUpdate(update);
129+
}
130+
}
131+
}
132+
133+
// New springs are created during render so users can pass them to
134+
// their animated components, but new springs aren't cached until the
135+
// commit phase (see the `useLayoutEffect` callback below).
136+
const springs = ctrls.map((ctrl, i) => getSprings(ctrl, updates[i]));
137+
138+
createRenderEffect(() => {
139+
layoutId++;
140+
141+
// Replace the cached controllers.
142+
state.ctrls = ctrls;
143+
144+
// Flush the commit queue.
145+
const { queue } = state;
146+
if (queue.length) {
147+
state.queue = [];
148+
each(queue, (cb) => cb());
149+
}
150+
151+
// Update existing controllers.
152+
each(ctrls, (ctrl, i) => {
153+
// Attach the controller to the local ref.
154+
ref.add(ctrl);
155+
156+
// Update the default props.
157+
/* if (hasContext) {
158+
ctrl.start({ default: context })
159+
} */
160+
161+
// Apply updates created during render.
162+
const update = updates[i];
163+
if (update) {
164+
// Update the injected ref if needed.
165+
replaceRef(ctrl, update.ref);
166+
167+
// When an injected ref exists, the update is postponed
168+
// until the ref has its `start` method called.
169+
if (ctrl.ref) {
170+
ctrl.queue.push(update);
171+
} else {
172+
ctrl.start(update);
173+
}
174+
}
175+
});
176+
});
177+
178+
onCleanup(() => {
179+
each(state.ctrls, (ctrl) => ctrl.stop(true));
180+
});
181+
182+
const value = createMemo(() => [springs.map((x) => ({ ...x })), ref]);
183+
184+
return value as any;
185+
}

0 commit comments

Comments
 (0)