Skip to content

Commit a69afd1

Browse files
committed
refactor: major internal restructuring and API improvements
- Replace $S3C constant with getMeta/hasMeta utilities for cleaner metadata access - Rename augment to meta for consistency with new utility functions - Refactor manageSceneGraph to useSceneGraph hook for better composability - Extract applySceneGraph logic into dedicated function - Improve type safety with Prettify utility and proper generic constraints - Update event system to use new metadata utilities - Clean up prop application and scene graph management logic - Remove autolisten export (internal implementation detail) - Fix currentIntersection handling in event bubbling
1 parent da7c70e commit a69afd1

File tree

10 files changed

+382
-357
lines changed

10 files changed

+382
-357
lines changed

src/components.tsx

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ import {
55
type ParentProps,
66
Show,
77
createMemo,
8-
createRenderEffect,
98
createResource,
109
mergeProps,
1110
splitProps,
1211
} from "solid-js"
1312
import { Object3D } from "three"
1413
import { threeContext, useThree } from "./hooks.ts"
15-
import { manageSceneGraph, useProps } from "./props.ts"
14+
import { useProps } from "./props.ts"
1615
import type { Constructor, Loader, Meta, Overwrite, Props } from "./types.ts"
1716
import { type InstanceOf } from "./types.ts"
18-
import { augment, autodispose, isConstructor, isInstance, load, withContext } from "./utils.ts"
17+
import { autodispose, hasMeta, isConstructor, load, meta, withContext } from "./utils.ts"
1918
import { whenMemo } from "./utils/conditionals.ts"
2019

2120
/**********************************************************************************/
@@ -24,8 +23,9 @@ import { whenMemo } from "./utils/conditionals.ts"
2423
/* */
2524
/**********************************************************************************/
2625

27-
type PortalProps = ParentProps<{
28-
element?: InstanceOf<Object3D> | Meta<Object3D>
26+
type PortalProps<T extends Object3D> = ParentProps<{
27+
element?: InstanceOf<T> | Meta<T>
28+
onUpdate?(value: T): void
2929
}>
3030
/**
3131
* A component for placing its children outside the regular `solid-three` scene graph managed by Solid's reactive system.
@@ -35,30 +35,34 @@ type PortalProps = ParentProps<{
3535
* @param props - The component props containing `children` to be rendered and an optional Object3D `element` to be rendered into.
3636
* @returns An empty JSX element.
3737
*/
38-
export const Portal = (props: PortalProps) => {
38+
export function Portal<T extends Object3D>(props: PortalProps<T>) {
3939
const context = useThree()
40-
const scene = createMemo(() => {
40+
41+
const element = createMemo(() => {
4142
return props.element
42-
? isInstance(props.element)
43-
? (props.element as Meta<Object3D>)
44-
: augment(props.element, { props: {} })
43+
? hasMeta(props.element)
44+
? props.element
45+
: meta(props.element, { props: {} })
4546
: context.scene
4647
})
4748

48-
createRenderEffect(() => {
49-
// @ts-expect-error TODO: fix type-error
50-
manageSceneGraph(scene(), () =>
51-
withContext(
52-
() => props.children as unknown as Meta | Meta[],
53-
// @ts-expect-error TODO: fix type-error
54-
threeContext,
55-
mergeProps(context, {
56-
get scene() {
57-
return scene()
58-
},
59-
}),
60-
),
61-
)
49+
useProps(element, {
50+
get onUpdate() {
51+
return props.onUpdate
52+
},
53+
get children() {
54+
return () =>
55+
withContext(
56+
() => props.children as unknown as Meta | Meta[],
57+
// @ts-expect-error TODO: fix type-error
58+
threeContext,
59+
mergeProps(context, {
60+
get scene() {
61+
return element()
62+
},
63+
}),
64+
)
65+
},
6266
})
6367

6468
return null
@@ -93,7 +97,9 @@ export function Entity<T extends object | Constructor<object>>(props: EntityProp
9397
const memo = whenMemo(
9498
() => config.from,
9599
from => {
96-
const instance = augment(
100+
// listen to key changes
101+
props.key
102+
const instance = meta(
97103
isConstructor(from) ? autodispose(new from(...(config.args ?? []))) : from,
98104
{
99105
props,
@@ -119,14 +125,16 @@ type ResourceProps<TSource, TResult extends object> = Omit<Props<TResult>, "chil
119125
children?: (result: Accessor<TResult>) => JSXElement
120126
}
121127

122-
export function Resource<TSource, TResult extends object>(props: ResourceProps<TSource, TResult>) {
128+
export function Resource<TSource extends string | string[], TResult extends object>(
129+
props: ResourceProps<TSource, TResult>,
130+
) {
123131
const [config, rest] = splitProps(props, ["loader", "path", "url"])
124132
const loader = createMemo(() => new config.loader())
125133
const [resource] = createResource(
126134
() => [config.url, loader(), config.path] as const,
127135
([url, loader, path]) => {
128136
loader.setPath?.(path ?? "")
129-
return load(loader, url) as Promise<TResult>
137+
return load(loader, url)
130138
},
131139
)
132140
return (

src/create-events.ts

Lines changed: 82 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { Object3D, type Intersection } from "three"
2-
import type { Intersect } from "../playground/controls/type-utils.ts"
3-
import { $S3C } from "./constants.ts"
4-
import type { BaseEvent, Context, EventName, Meta, StoppableEvent } from "./types.ts"
5-
import { isInstance } from "./utils.ts"
2+
import type { Context, EventName, Meta, Prettify, ThreeEvent } from "./types.ts"
3+
import { getMeta } from "./utils.ts"
64

75
const eventNameMap = {
86
onClick: "click",
@@ -60,7 +58,7 @@ export const isEventType = (type: string): type is EventName =>
6058
/** Creates a `ThreeEvent` (intersection excluded) from the current `MouseEvent` | `WheelEvent`. */
6159
function createThreeEvent<
6260
TEvent extends Event,
63-
TConfig extends { stoppable?: boolean; intersections?: Intersection[] },
61+
TConfig extends { stoppable?: boolean; intersections?: Array<Intersection> },
6462
>(nativeEvent: TEvent, { stoppable = true, intersections }: TConfig = {}) {
6563
const event: Record<string, any> = stoppable
6664
? {
@@ -77,21 +75,21 @@ function createThreeEvent<
7775
event.intersection = intersections[0]
7876
}
7977

80-
return event as Intersect<
81-
[
82-
TConfig["stoppable"] extends false
83-
? TConfig["stoppable"] extends undefined
84-
? StoppableEvent<TEvent>
85-
: BaseEvent<TEvent>
86-
: StoppableEvent<TEvent>,
87-
TConfig["intersections"] extends Intersection[]
88-
? {
89-
intersections: Intersection[]
90-
intersection: Intersection
91-
currentIntersection?: Intersection
92-
}
93-
: unknown,
94-
]
78+
return event as Prettify<
79+
Omit<
80+
ThreeEvent<
81+
TEvent,
82+
{
83+
stoppable: TConfig["stoppable"] extends false
84+
? TConfig["stoppable"] extends true
85+
? true
86+
: false
87+
: true
88+
intersections: TConfig["intersections"] extends Intersection[] ? true : false
89+
}
90+
>,
91+
"currentIntersection"
92+
>
9593
>
9694
}
9795

@@ -121,9 +119,12 @@ function raycast<TNativeEvent extends MouseEvent | WheelEvent>(
121119
for (const object of stack) {
122120
if (visitedSet.has(object)) continue
123121
visitedSet.add(object)
124-
if (isInstance(object) && object[$S3C].props?.raycastable !== false) {
122+
123+
const meta = getMeta(object)
124+
if (meta && meta.props.raycastable !== false) {
125125
nodeSet.add(object)
126126
}
127+
127128
stack.push(...object.children)
128129
}
129130

@@ -162,26 +163,29 @@ function createMissableEventRegistry(
162163
const stoppableEvent = createThreeEvent(nativeEvent, { intersections })
163164

164165
for (const intersection of intersections) {
165-
let node: Object3D | null = intersection.object
166-
166+
// Update currentIntersection
167+
// @ts-expect-error TODO: fix type-error
167168
stoppableEvent.currentIntersection = intersection
168169

170+
// Bubble down
171+
let node: Object3D | null = intersection.object
169172
while (node && !stoppableEvent.stopped && !visitedObjects.has(node)) {
170173
missedObjects.delete(node)
171174
visitedObjects.add(node)
172-
if (isInstance(node)) {
173-
node[$S3C].props[type]?.(stoppableEvent)
174-
}
175+
getMeta(node)?.props[type]?.(
176+
// @ts-expect-error TODO: fix type-error
177+
stoppableEvent,
178+
)
175179
node = node.parent
176180
}
177181
}
178182

179183
// Call the respective canvas event-handler
180184
// if event propagated all the way down
181185
if (!stoppableEvent.stopped) {
182-
if ("currentIntersection" in stoppableEvent) {
183-
delete stoppableEvent.currentIntersection
184-
}
186+
// Remove currentIntersection
187+
// @ts-expect-error TODO: fix type-error
188+
delete stoppableEvent.currentIntersection
185189
context.props[type]?.(stoppableEvent)
186190
}
187191

@@ -207,9 +211,7 @@ function createMissableEventRegistry(
207211
const missedEvent = createThreeEvent(nativeEvent, { stoppable: false })
208212

209213
for (const object of missedObjects) {
210-
if (isInstance(object)) {
211-
object[$S3C].props[missedType]?.(missedEvent)
212-
}
214+
getMeta(object)?.props[missedType]?.(missedEvent)
213215
}
214216

215217
if (visitedObjects.size > 0) {
@@ -251,24 +253,31 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
251253
const enterSet = new Set<Object3D>()
252254

253255
for (const intersection of intersections) {
254-
// Mutate event
255-
enterEvent.intersection = intersection
256+
// Update currentIntersection
257+
// @ts-expect-error TODO: fix type-error
258+
enterEvent.currentIntersection = intersection
256259

257260
// Bubble up
258261
let current: Object3D | null = intersection.object
259262
while (current && !enterSet.has(current)) {
260263
enterSet.add(current)
261-
262-
if (isInstance(current) && !hoveredSet.has(current)) {
263-
current[$S3C].props[`on${type}Enter`]?.(enterEvent)
264+
if (!hoveredSet.has(current)) {
265+
getMeta(current)?.props[`on${type}Enter`]?.(
266+
// @ts-expect-error TODO: fix type-error
267+
enterEvent,
268+
)
264269
}
270+
265271
// We bubble a layer down.
266272
current = current.parent
267273
}
268274
}
269275

270276
if (hoveredCanvas === false) {
271-
context.props[`on${type}Enter`]?.(enterEvent)
277+
context.props[`on${type}Enter`]?.(
278+
// @ts-expect-error TODO: fix type-error
279+
enterEvent,
280+
)
272281
hoveredCanvas = true
273282
}
274283

@@ -277,18 +286,21 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
277286
const moveSet = new Set()
278287

279288
for (const intersection of intersections) {
280-
// Mutate currentIntersection
289+
// Update currentIntersection
290+
// @ts-expect-error TODO: fix type-error
281291
moveEvent.currentIntersection = intersection
282292

283293
// Bubble up
284294
let current: Object3D | null = intersection.object
285295

286296
while (current && !moveSet.has(current)) {
287297
moveSet.add(current)
288-
289-
if (isInstance(current)) {
290-
// @ts-expect-error TODO: fix type-error
291-
current[$S3C].props[`on${type}Move`]?.(moveEvent)
298+
const meta = getMeta(current)
299+
if (meta) {
300+
meta.props[`on${type}Move`]?.(
301+
// @ts-expect-error TODO: fix type-error
302+
moveEvent,
303+
)
292304
// Break if event was
293305
if (moveEvent.stopped) {
294306
break
@@ -300,32 +312,39 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
300312
}
301313

302314
if (!moveEvent.stopped) {
303-
// Remove currentIntersection from moveEvent
315+
// Remove currentIntersection
316+
// @ts-expect-error TODO: fix type-error
304317
delete moveEvent.currentIntersection
305-
context.props[`on${type}Move`]?.(moveEvent)
318+
context.props[`on${type}Move`]?.(
319+
// @ts-expect-error TODO: fix type-error
320+
moveEvent,
321+
)
306322
}
307323

308324
// Handle leave-event
309-
const leaveEvent = createThreeEvent(nativeEvent)
325+
const leaveEvent = createThreeEvent(nativeEvent, { intersections, stoppable: false })
310326
const leaveSet = hoveredSet.difference(enterSet)
311327
hoveredSet = enterSet
312328

313329
for (const object of leaveSet.values()) {
314-
if (isInstance(object)) {
315-
object[$S3C].props?.[`on${type}Leave`]?.(leaveEvent)
316-
}
330+
getMeta(object)?.props[`on${type}Leave`]?.(
331+
// @ts-expect-error TODO: fix type-error
332+
leaveEvent,
333+
)
317334
}
318335
})
319336

320337
context.canvas.addEventListener(eventNameMap[`on${type}Leave`], nativeEvent => {
321338
const leaveEvent = createThreeEvent(nativeEvent, { stoppable: false })
339+
// @ts-expect-error TODO: fix type-error
322340
context.props[`on${type}Leave`]?.(leaveEvent)
323341
hoveredCanvas = false
324342

325343
for (const object of hoveredSet) {
326-
if (isInstance(object)) {
327-
object[$S3C].props[`on${type}Leave`]?.(leaveEvent)
328-
}
344+
getMeta(object)?.props[`on${type}Leave`]?.(
345+
// @ts-expect-error TODO: fix type-error
346+
leaveEvent,
347+
)
329348
}
330349
hoveredSet.clear()
331350
})
@@ -361,25 +380,27 @@ function createDefaultEventRegistry(
361380
const event = createThreeEvent(nativeEvent, { intersections })
362381

363382
for (const intersection of intersections) {
383+
// Update currentIntersection
384+
// @ts-expect-error TODO: fix type-error
385+
event.currentIntersection = intersection
386+
364387
// Bubble up
365388
let node: Object3D | null = intersection.object
366389

367-
event.intersection = intersection
368-
369390
while (node && !event.stopped) {
370-
if (isInstance(intersection.object)) {
391+
getMeta(intersection.object)?.props[type]?.(
371392
// @ts-expect-error TODO: fix type-error
372-
intersection.object[$S3C].props[type]?.(event)
373-
}
393+
event,
394+
)
374395
node = node.parent
375396
}
376397
}
377398

378399
if (!event.stopped) {
379-
// Remove trailing intersection from event
380-
if ("intersection" in event) {
381-
delete event.intersection
382-
}
400+
// Remove currentIntersection
401+
// @ts-expect-error TODO: fix type-error
402+
delete event.currentIntersection
403+
383404
// @ts-expect-error TODO: fix type-error
384405
context.props[type]?.(event)
385406
}

0 commit comments

Comments
 (0)