|
1 | | -import { type Accessor, type Resource, createContext, createResource, useContext } from "solid-js" |
| 1 | +import { |
| 2 | + type Accessor, |
| 3 | + type Resource, |
| 4 | + children, |
| 5 | + createContext, |
| 6 | + createRenderEffect, |
| 7 | + createResource, |
| 8 | + onCleanup, |
| 9 | + splitProps, |
| 10 | + untrack, |
| 11 | + useContext, |
| 12 | +} from "solid-js" |
| 13 | +import { applyProps } from "./props.ts" |
2 | 14 | import type { Context } from "./types" |
3 | | - |
4 | | -/**********************************************************************************/ |
5 | | -/* */ |
6 | | -/* Use Three */ |
7 | | -/* */ |
8 | | -/**********************************************************************************/ |
9 | | - |
10 | | -export const threeContext = createContext<Context>(null!) |
11 | | - |
12 | | -/** |
13 | | - * Custom hook to access all necessary Three.js objects needed to manage a 3D scene. |
14 | | - * This hook must be used within a component that is a descendant of the `<Canvas/>` component. |
15 | | - * |
16 | | - * @template T The expected return type after applying the callback to the context. |
17 | | - * @param [callback] - Optional callback function that processes and returns a part of the context. |
18 | | - * @returns Returns `Context` directly, or as a selector if a callback is provided. |
19 | | - * @throws Throws an error if used outside of the Canvas component context. |
20 | | - */ |
21 | | -export function useThree(): Context |
22 | | -export function useThree<T>(callback: (value: Context) => T): Accessor<T> |
23 | | -export function useThree(callback?: (value: Context) => any) { |
24 | | - const store = useContext(threeContext) |
25 | | - if (!store) { |
26 | | - throw new Error("S3: Hooks can only be used within the Canvas component!") |
27 | | - } |
28 | | - if (callback) return () => callback(store) |
29 | | - return store |
30 | | -} |
| 15 | +import { check } from "./utils/conditionals.ts" |
31 | 16 |
|
32 | 17 | /**********************************************************************************/ |
33 | 18 | /* */ |
@@ -130,3 +115,82 @@ export function useLoader< |
130 | 115 | ? Resource<LoaderResult<TLoader>> |
131 | 116 | : */ Resource<{ [K in keyof TArgs]: LoaderResult<TLoader> }> |
132 | 117 | } |
| 118 | + |
| 119 | +/**********************************************************************************/ |
| 120 | +/* */ |
| 121 | +/* Use Props */ |
| 122 | +/* */ |
| 123 | +/**********************************************************************************/ |
| 124 | + |
| 125 | +/** |
| 126 | + * Manages and applies `solid-three` props to its Three.js object. This function sets up reactive effects |
| 127 | + * to ensure that properties are correctly applied and updated in response to changes. It also manages the |
| 128 | + * attachment of children and the disposal of the object. |
| 129 | + * |
| 130 | + * @template T - The type of the augmented element. |
| 131 | + * @param object - An accessor function that returns the target object to which properties will be applied. |
| 132 | + * @param props - An object containing the props to apply. This includes both direct properties |
| 133 | + * and special properties like `ref` and `children`. |
| 134 | + */ |
| 135 | +export function useProps<T extends object>(object: Accessor<T>, props: any) { |
| 136 | + const [local, instanceProps] = splitProps(props, ["ref", "args", "object", "attach", "children"]) |
| 137 | + |
| 138 | + // Assign ref |
| 139 | + createRenderEffect(() => { |
| 140 | + if (local.ref instanceof Function) local.ref(object()) |
| 141 | + else local.ref = object() |
| 142 | + }) |
| 143 | + |
| 144 | + createRenderEffect(() => { |
| 145 | + if ("children" in props) { |
| 146 | + // Connect or attach children to THREE-instance |
| 147 | + const childrenAccessor = children(() => props.children) |
| 148 | + // @ts-expect-error TODO: fix type-error |
| 149 | + manageSceneGraph(object(), childrenAccessor as unknown as Accessor<Instance>) |
| 150 | + } |
| 151 | + }) |
| 152 | + |
| 153 | + // Apply the props to THREE-instance |
| 154 | + createRenderEffect(() => { |
| 155 | + applyProps(object(), instanceProps) |
| 156 | + // NOTE: see "onUpdate should not update itself"-test |
| 157 | + untrack(() => props.onUpdate)?.(object()) |
| 158 | + }) |
| 159 | + |
| 160 | + // Automatically dispose |
| 161 | + onCleanup(() => |
| 162 | + check(object, object => { |
| 163 | + if ("dispose" in object && typeof object.dispose === "function") { |
| 164 | + object.dispose() |
| 165 | + } |
| 166 | + }), |
| 167 | + ) |
| 168 | +} |
| 169 | + |
| 170 | +/**********************************************************************************/ |
| 171 | +/* */ |
| 172 | +/* Use Three */ |
| 173 | +/* */ |
| 174 | +/**********************************************************************************/ |
| 175 | + |
| 176 | +export const threeContext = createContext<Context>(null!) |
| 177 | + |
| 178 | +/** |
| 179 | + * Custom hook to access all necessary Three.js objects needed to manage a 3D scene. |
| 180 | + * This hook must be used within a component that is a descendant of the `<Canvas/>` component. |
| 181 | + * |
| 182 | + * @template T The expected return type after applying the callback to the context. |
| 183 | + * @param [callback] - Optional callback function that processes and returns a part of the context. |
| 184 | + * @returns Returns `Context` directly, or as a selector if a callback is provided. |
| 185 | + * @throws Throws an error if used outside of the Canvas component context. |
| 186 | + */ |
| 187 | +export function useThree(): Context |
| 188 | +export function useThree<T>(callback: (value: Context) => T): Accessor<T> |
| 189 | +export function useThree(callback?: (value: Context) => any) { |
| 190 | + const store = useContext(threeContext) |
| 191 | + if (!store) { |
| 192 | + throw new Error("S3: Hooks can only be used within the Canvas component!") |
| 193 | + } |
| 194 | + if (callback) return () => callback(store) |
| 195 | + return store |
| 196 | +} |
0 commit comments