diff --git a/packages/fiber/src/core/events.ts b/packages/fiber/src/core/events.ts index 7b629655b5..d64a3a4377 100644 --- a/packages/fiber/src/core/events.ts +++ b/packages/fiber/src/core/events.ts @@ -1,7 +1,8 @@ import * as THREE from 'three' -import { getRootState, type Properties } from './utils' +import { getRootState } from './utils' import type { Instance } from './reconciler' import type { RootState, RootStore } from './store' +import type { Properties } from '../three-types' export interface Intersection extends THREE.Intersection { /** The event source (the object which registered the handler) */ diff --git a/packages/fiber/src/core/hooks.tsx b/packages/fiber/src/core/hooks.tsx index fe77d07e49..ded95df8d6 100644 --- a/packages/fiber/src/core/hooks.tsx +++ b/packages/fiber/src/core/hooks.tsx @@ -108,8 +108,7 @@ function loadingFn(extensions?: Extensions, onProgress?: (event: ProgressE new Promise>((res, reject) => loader.load( input, - (data) => - res(data?.scene instanceof THREE.Object3D ? Object.assign(data, buildGraph(data.scene)) : data), + (data) => res(data?.scene instanceof THREE.Object3D ? Object.assign(data, buildGraph(data.scene)) : data), onProgress, (error) => reject(new Error(`Could not load ${input}: ${(error as ErrorEvent)?.message}`)), ), diff --git a/packages/fiber/src/core/reconciler.ts b/packages/fiber/src/core/reconciler.tsx similarity index 95% rename from packages/fiber/src/core/reconciler.ts rename to packages/fiber/src/core/reconciler.tsx index 6f92d6e2b1..bffa474edd 100644 --- a/packages/fiber/src/core/reconciler.ts +++ b/packages/fiber/src/core/reconciler.tsx @@ -1,10 +1,12 @@ import * as THREE from 'three' +import * as React from 'react' import Reconciler from 'react-reconciler' import { ContinuousEventPriority, DiscreteEventPriority, DefaultEventPriority } from 'react-reconciler/constants' import { unstable_IdlePriority as idlePriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler' import { diffProps, applyProps, invalidateInstance, attach, detach, prepare, globalScope, isObject3D } from './utils' import type { RootStore } from './store' import { removeInteractivity, type EventHandlers } from './events' +import type { ThreeElement } from '../three-types' export interface Root { fiber: Reconciler.FiberRoot @@ -14,7 +16,7 @@ export interface Root { export type AttachFnType = (parent: any, self: O) => () => void export type AttachType = string | AttachFnType -export type ConstructorRepresentation = new (...args: any[]) => any +export type ConstructorRepresentation = new (...args: any[]) => T export interface Catalogue { [name: string]: ConstructorRepresentation @@ -69,7 +71,23 @@ interface HostConfig { } export const catalogue: Catalogue = {} -export const extend = (objects: Partial): void => void Object.assign(catalogue, objects) + +let i = 0 + +export const extend = ( + objects: T, +): T extends ConstructorRepresentation ? React.ExoticComponent> : void => { + if (typeof objects === 'function') { + const Component = `${i++}` + catalogue[Component] = objects + + // Returns a component whose name will be inferred in devtools + // @ts-ignore + return React.forwardRef({ [objects.name]: (props, ref) => }[objects.name]) + } else { + return void Object.assign(catalogue, objects) as any + } +} function createInstance(type: string, props: HostConfig['props'], root: RootStore): HostConfig['instance'] { // Get target from catalogue diff --git a/packages/fiber/src/core/renderer.tsx b/packages/fiber/src/core/renderer.tsx index 0844f51a2c..7449240852 100644 --- a/packages/fiber/src/core/renderer.tsx +++ b/packages/fiber/src/core/renderer.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { ConcurrentRoot } from 'react-reconciler/constants' import create from 'zustand' -import { ThreeElement } from '../three-types' +import type { Properties, ThreeElement } from '../three-types' import { Renderer, createStore, @@ -46,8 +46,6 @@ export const _roots = new Map() const shallowLoose = { objects: 'shallow', strict: false } as EquConfig -type Properties = Pick any ? never : K }[keyof T]> - export type GLProps = | Renderer | ((canvas: Canvas) => Renderer) diff --git a/packages/fiber/src/core/utils.tsx b/packages/fiber/src/core/utils.tsx index e8374f1570..4c44984ae8 100644 --- a/packages/fiber/src/core/utils.tsx +++ b/packages/fiber/src/core/utils.tsx @@ -24,11 +24,6 @@ export type ColorManagementRepresentation = { enabled: boolean | never } | { leg */ export const getColorManagement = (): ColorManagementRepresentation | null => (catalogue as any).ColorManagement ?? null -export type NonFunctionKeys

= { [K in keyof P]-?: P[K] extends Function ? never : K }[keyof P] -export type Overwrite = Omit> & O -export type Properties = Pick> -export type Mutable

= { [K in keyof P]: P[K] | Readonly } - export type Act = (cb: () => Promise) => Promise /** diff --git a/packages/fiber/src/three-types.ts b/packages/fiber/src/three-types.ts index 86b7ee51b4..a276ef1cf3 100644 --- a/packages/fiber/src/three-types.ts +++ b/packages/fiber/src/three-types.ts @@ -1,13 +1,15 @@ import type * as THREE from 'three' import type { Args, EventHandlers, InstanceProps, ConstructorRepresentation } from './core' -import type { Mutable, Overwrite } from './core/utils' -export { Overwrite } +type NonFunctionKeys

= { [K in keyof P]-?: P[K] extends Function ? never : K }[keyof P] +export type Overwrite = Omit> & O +export type Properties = Pick> +export type Mutable

= { [K in keyof P]: P[K] | Readonly } -interface MathRepresentation { +export interface MathRepresentation { set(...args: number[]): any } -interface VectorRepresentation extends MathRepresentation { +export interface VectorRepresentation extends MathRepresentation { setScalar(s: number): any } @@ -25,12 +27,12 @@ export type Layers = MathType export type Quaternion = MathType export type Euler = MathType -type WithMathProps

= { [K in keyof P]: P[K] extends MathRepresentation | THREE.Euler ? MathType : P[K] } +export type WithMathProps

= { [K in keyof P]: P[K] extends MathRepresentation | THREE.Euler ? MathType : P[K] } -interface RaycastableRepresentation { +export interface RaycastableRepresentation { raycast(raycaster: THREE.Raycaster, intersects: THREE.Intersection[]): void } -type EventProps

= P extends RaycastableRepresentation ? Partial : {} +export type EventProps

= P extends RaycastableRepresentation ? Partial : {} export interface ReactProps

{ children?: React.ReactNode @@ -38,7 +40,7 @@ export interface ReactProps

{ key?: React.Key } -type ElementProps> = Partial< +export type ElementProps> = Partial< Overwrite, ReactProps

& EventProps

> > diff --git a/packages/fiber/tests/__snapshots__/index.test.tsx.snap b/packages/fiber/tests/__snapshots__/index.test.tsx.snap index 3bd8b54805..ac6e443492 100644 --- a/packages/fiber/tests/__snapshots__/index.test.tsx.snap +++ b/packages/fiber/tests/__snapshots__/index.test.tsx.snap @@ -17,9 +17,11 @@ Array [ "Disposable", "DomEvent", "Dpr", + "ElementProps", "Euler", "EventHandlers", "EventManager", + "EventProps", "Events", "Extensions", "FilterFunction", @@ -40,12 +42,16 @@ Array [ "Loader", "LoaderProto", "LoaderResult", + "MathRepresentation", "MathType", + "Mutable", "ObjectMap", "Overwrite", "Performance", + "Properties", "Props", "Quaternion", + "RaycastableRepresentation", "ReactProps", "ReactThreeFiber", "ReconcilerRoot", @@ -67,7 +73,9 @@ Array [ "Vector2", "Vector3", "Vector4", + "VectorRepresentation", "Viewport", + "WithMathProps", "XRManager", "_roots", "act", diff --git a/packages/fiber/tests/renderer.test.tsx b/packages/fiber/tests/renderer.test.tsx index c9ffc7d1b7..c69f55a6ca 100644 --- a/packages/fiber/tests/renderer.test.tsx +++ b/packages/fiber/tests/renderer.test.tsx @@ -70,6 +70,12 @@ describe('renderer', () => { expect(scene.children.length).toBe(1) expect(scene.children[0]).toBeInstanceOf(Mock) expect(scene.children[0].name).toBe('mock') + + const Component = extend(THREE.Mesh) + await act(async () => root.render()) + + expect(scene.children.length).toBe(1) + expect(scene.children[0]).toBeInstanceOf(THREE.Mesh) }) it('should render primitives', async () => { diff --git a/packages/fiber/tests/utils.test.ts b/packages/fiber/tests/utils.test.ts index ccab93967f..d90e23ef2d 100644 --- a/packages/fiber/tests/utils.test.ts +++ b/packages/fiber/tests/utils.test.ts @@ -1,5 +1,5 @@ import * as THREE from 'three' -import { extend, Instance, RootStore, ThreeElement } from '../src' +import { Instance, RootStore } from '../src' import { is, dispose, @@ -15,21 +15,6 @@ import { updateCamera, } from '../src/core/utils' -class TestElement { - public value: string - constructor() { - this.value = 'initial' - } -} - -declare module '@react-three/fiber' { - interface ThreeElements { - testElement: ThreeElement - } -} - -extend({ TestElement }) - // Mocks a Zustand store const storeMock: RootStore = Object.assign(() => null!, { getState: () => null!, @@ -412,7 +397,7 @@ describe('applyProps', () => { }) it('should not apply a prop if it is undefined', async () => { - const target = new TestElement() + const target = { value: 'initial' } applyProps(target, { value: undefined }) expect(target.value).toBe('initial')