diff --git a/packages/playground/src/shared/custom-editable-components/App.tsx b/packages/playground/src/shared/custom-editable-components/App.tsx index b00c5cff4..1a4c16c87 100644 --- a/packages/playground/src/shared/custom-editable-components/App.tsx +++ b/packages/playground/src/shared/custom-editable-components/App.tsx @@ -1,9 +1,33 @@ import {editable as e, SheetProvider} from '@theatre/r3f' import {getProject} from '@theatre/core' import React from 'react' -import {Canvas} from '@react-three/fiber' +import type {Object3DNode} from '@react-three/fiber' +import {Canvas, extend} from '@react-three/fiber' +import type {BufferGeometry, Material} from 'three' +import {Mesh} from 'three' -const EditablePoints = e('points', 'mesh') +class MyMesh extends Mesh { + constructor(geometry: BufferGeometry, material: Material) { + super(geometry, material) + this.name = 'MyMesh' + } +} + +extend({MyMesh}) + +interface MyElements { + myMesh: Object3DNode +} + +declare global { + namespace JSX { + interface IntrinsicElements extends MyElements {} + } +} + +declare module '@theatre/r3f' { + interface ThreeElements extends MyElements {} +} function App() { return ( @@ -19,11 +43,12 @@ function App() { frameloop="demand" > - - + - + + + diff --git a/packages/r3f/src/index.ts b/packages/r3f/src/index.ts index 5939ac692..14cfa8749 100644 --- a/packages/r3f/src/index.ts +++ b/packages/r3f/src/index.ts @@ -23,3 +23,4 @@ export {makeStoreKey as __private_makeStoreKey} from './main/utils' export {default as SheetProvider, useCurrentSheet} from './main/SheetProvider' export {refreshSnapshot} from './main/utils' export {default as RefreshSnapshot} from './main/RefreshSnapshot' +export type {ThreeElements} from './main/ThreeElements' diff --git a/packages/r3f/src/main/ThreeElements.ts b/packages/r3f/src/main/ThreeElements.ts new file mode 100644 index 000000000..c7b834a2b --- /dev/null +++ b/packages/r3f/src/main/ThreeElements.ts @@ -0,0 +1,277 @@ +import type { + AmbientLightProbeProps, + AmbientLightProps, + ArrayCameraProps, + ArrowHelperProps, + AudioListenerProps, + AxesHelperProps, + BoneProps, + Box3HelperProps, + BoxBufferGeometryProps, + BoxGeometryProps, + BoxHelperProps, + BufferAttributeProps, + BufferGeometryProps, + CameraHelperProps, + CameraProps, + CanvasTextureProps, + CircleBufferGeometryProps, + CircleGeometryProps, + ColorProps, + CompressedTextureProps, + ConeBufferGeometryProps, + ConeGeometryProps, + CubeCameraProps, + CubeTextureProps, + CylinderBufferGeometryProps, + CylinderGeometryProps, + DataTexture3DProps, + DataTextureProps, + DepthTextureProps, + DirectionalLightHelperProps, + DirectionalLightProps, + DirectionalLightShadowProps, + DodecahedronBufferGeometryProps, + DodecahedronGeometryProps, + EdgesGeometryProps, + EulerProps, + ExtrudeBufferGeometryProps, + ExtrudeGeometryProps, + FogExp2Props, + FogProps, + GridHelperProps, + GroupProps, + HemisphereLightHelperProps, + HemisphereLightProbeProps, + HemisphereLightProps, + IcosahedronBufferGeometryProps, + IcosahedronGeometryProps, + InstancedBufferAttributeProps, + InstancedBufferGeometryProps, + InstancedMeshProps, + LatheBufferGeometryProps, + LatheGeometryProps, + LightProbeProps, + LightProps, + LightShadowProps, + LineBasicMaterialProps, + LineDashedMaterialProps, + LineLoopProps, + LineSegmentsProps, + LODProps, + MaterialProps, + Matrix3Props, + Matrix4Props, + MeshBasicMaterialProps, + MeshDepthMaterialProps, + MeshDistanceMaterialProps, + MeshLambertMaterialProps, + MeshMatcapMaterialProps, + MeshNormalMaterialProps, + MeshPhongMaterialProps, + MeshPhysicalMaterialProps, + MeshProps, + MeshStandardMaterialProps, + MeshToonMaterialProps, + Object3DNode, + OctahedronBufferGeometryProps, + OctahedronGeometryProps, + OrthographicCameraProps, + PerspectiveCameraProps, + PlaneBufferGeometryProps, + PlaneGeometryProps, + PlaneHelperProps, + PointLightHelperProps, + PointLightProps, + PointsMaterialProps, + PointsProps, + PolarGridHelperProps, + PolyhedronBufferGeometryProps, + PolyhedronGeometryProps, + PositionalAudioProps, + PrimitiveProps, + QuaternionProps, + RawShaderMaterialProps, + RaycasterProps, + RectAreaLightProps, + RingBufferGeometryProps, + RingGeometryProps, + SceneProps, + ShaderMaterialProps, + ShadowMaterialProps, + ShapeBufferGeometryProps, + ShapeGeometryProps, + ShapeProps, + SkeletonHelperProps, + SkeletonProps, + SkinnedMeshProps, + SphereBufferGeometryProps, + SphereGeometryProps, + SpotLightHelperProps, + SpotLightProps, + SpotLightShadowProps, + SpriteMaterialProps, + SpriteProps, + TetrahedronBufferGeometryProps, + TetrahedronGeometryProps, + TextureProps, + TorusBufferGeometryProps, + TorusGeometryProps, + TorusKnotBufferGeometryProps, + TorusKnotGeometryProps, + TubeBufferGeometryProps, + TubeGeometryProps, + Vector2Props, + Vector3Props, + Vector4Props, + VideoTextureProps, + WireframeGeometryProps, +} from '@react-three/fiber' +import type {Line} from 'three' + +export interface ThreeElements { + audioListener: AudioListenerProps + positionalAudio: PositionalAudioProps + + mesh: MeshProps + instancedMesh: InstancedMeshProps + scene: SceneProps + sprite: SpriteProps + lOD: LODProps + skinnedMesh: SkinnedMeshProps + skeleton: SkeletonProps + bone: BoneProps + lineSegments: LineSegmentsProps + lineLoop: LineLoopProps + line: Object3DNode + points: PointsProps + group: GroupProps + + // cameras + camera: CameraProps + perspectiveCamera: PerspectiveCameraProps + orthographicCamera: OrthographicCameraProps + cubeCamera: CubeCameraProps + arrayCamera: ArrayCameraProps + + // geometry + instancedBufferGeometry: InstancedBufferGeometryProps + bufferGeometry: BufferGeometryProps + boxBufferGeometry: BoxBufferGeometryProps + circleBufferGeometry: CircleBufferGeometryProps + coneBufferGeometry: ConeBufferGeometryProps + cylinderBufferGeometry: CylinderBufferGeometryProps + dodecahedronBufferGeometry: DodecahedronBufferGeometryProps + extrudeBufferGeometry: ExtrudeBufferGeometryProps + icosahedronBufferGeometry: IcosahedronBufferGeometryProps + latheBufferGeometry: LatheBufferGeometryProps + octahedronBufferGeometry: OctahedronBufferGeometryProps + planeBufferGeometry: PlaneBufferGeometryProps + polyhedronBufferGeometry: PolyhedronBufferGeometryProps + ringBufferGeometry: RingBufferGeometryProps + shapeBufferGeometry: ShapeBufferGeometryProps + sphereBufferGeometry: SphereBufferGeometryProps + tetrahedronBufferGeometry: TetrahedronBufferGeometryProps + torusBufferGeometry: TorusBufferGeometryProps + torusKnotBufferGeometry: TorusKnotBufferGeometryProps + tubeBufferGeometry: TubeBufferGeometryProps + wireframeGeometry: WireframeGeometryProps + tetrahedronGeometry: TetrahedronGeometryProps + octahedronGeometry: OctahedronGeometryProps + icosahedronGeometry: IcosahedronGeometryProps + dodecahedronGeometry: DodecahedronGeometryProps + polyhedronGeometry: PolyhedronGeometryProps + tubeGeometry: TubeGeometryProps + torusKnotGeometry: TorusKnotGeometryProps + torusGeometry: TorusGeometryProps + sphereGeometry: SphereGeometryProps + ringGeometry: RingGeometryProps + planeGeometry: PlaneGeometryProps + latheGeometry: LatheGeometryProps + shapeGeometry: ShapeGeometryProps + extrudeGeometry: ExtrudeGeometryProps + edgesGeometry: EdgesGeometryProps + coneGeometry: ConeGeometryProps + cylinderGeometry: CylinderGeometryProps + circleGeometry: CircleGeometryProps + boxGeometry: BoxGeometryProps + + // materials + material: MaterialProps + shadowMaterial: ShadowMaterialProps + spriteMaterial: SpriteMaterialProps + rawShaderMaterial: RawShaderMaterialProps + shaderMaterial: ShaderMaterialProps + pointsMaterial: PointsMaterialProps + meshPhysicalMaterial: MeshPhysicalMaterialProps + meshStandardMaterial: MeshStandardMaterialProps + meshPhongMaterial: MeshPhongMaterialProps + meshToonMaterial: MeshToonMaterialProps + meshNormalMaterial: MeshNormalMaterialProps + meshLambertMaterial: MeshLambertMaterialProps + meshDepthMaterial: MeshDepthMaterialProps + meshDistanceMaterial: MeshDistanceMaterialProps + meshBasicMaterial: MeshBasicMaterialProps + meshMatcapMaterial: MeshMatcapMaterialProps + lineDashedMaterial: LineDashedMaterialProps + lineBasicMaterial: LineBasicMaterialProps + + // primitive + primitive: PrimitiveProps + + // lights and other + light: LightProps + spotLightShadow: SpotLightShadowProps + spotLight: SpotLightProps + pointLight: PointLightProps + rectAreaLight: RectAreaLightProps + hemisphereLight: HemisphereLightProps + directionalLightShadow: DirectionalLightShadowProps + directionalLight: DirectionalLightProps + ambientLight: AmbientLightProps + lightShadow: LightShadowProps + ambientLightProbe: AmbientLightProbeProps + hemisphereLightProbe: HemisphereLightProbeProps + lightProbe: LightProbeProps + + // helpers + spotLightHelper: SpotLightHelperProps + skeletonHelper: SkeletonHelperProps + pointLightHelper: PointLightHelperProps + hemisphereLightHelper: HemisphereLightHelperProps + gridHelper: GridHelperProps + polarGridHelper: PolarGridHelperProps + directionalLightHelper: DirectionalLightHelperProps + cameraHelper: CameraHelperProps + boxHelper: BoxHelperProps + box3Helper: Box3HelperProps + planeHelper: PlaneHelperProps + arrowHelper: ArrowHelperProps + axesHelper: AxesHelperProps + + // textures + texture: TextureProps + videoTexture: VideoTextureProps + dataTexture: DataTextureProps + dataTexture3D: DataTexture3DProps + compressedTexture: CompressedTextureProps + cubeTexture: CubeTextureProps + canvasTexture: CanvasTextureProps + depthTexture: DepthTextureProps + + // misc + raycaster: RaycasterProps + vector2: Vector2Props + vector3: Vector3Props + vector4: Vector4Props + euler: EulerProps + matrix3: Matrix3Props + matrix4: Matrix4Props + quaternion: QuaternionProps + bufferAttribute: BufferAttributeProps + instancedBufferAttribute: InstancedBufferAttributeProps + color: ColorProps + fog: FogProps + fogExp2: FogExp2Props + shape: ShapeProps +} diff --git a/packages/r3f/src/main/editable.tsx b/packages/r3f/src/main/editable.tsx index 07f5a8bbb..358341235 100644 --- a/packages/r3f/src/main/editable.tsx +++ b/packages/r3f/src/main/editable.tsx @@ -9,28 +9,30 @@ import defaultEditableFactoryConfig from './defaultEditableFactoryConfig' import type {EditableFactoryConfig} from './editableFactoryConfigUtils' import {makeStoreKey} from './utils' import type {$FixMe} from '../types' +import type {ThreeElements} from './ThreeElements' -const createEditable = ( +const createEditable = ( config: EditableFactoryConfig, ) => { const editable = < - T extends ComponentType | keyof JSX.IntrinsicElements | 'primitive', - U extends Keys, + T extends ComponentType | keyof ThreeElements | 'primitive', + U extends Keys | null, >( Component: T, - type: T extends 'primitive' ? null : U, + type: U, ) => { - type Props = Omit, 'visible'> & { + type Props = Omit, 'visible' | 'editableType'> & { uniqueName: string visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe - } & (T extends 'primitive' + } & (U extends null ? { - editableType: U + editableType: Keys } - : {}) & - RefAttributes + : U extends Keys + ? RefAttributes + : {}) return forwardRef( ( @@ -44,9 +46,27 @@ const createEditable = ( }: Props, ref, ) => { - const actualType = type ?? editableType + // For some reason we have to cast editableType here even though we omitted it from ComponentProps. + const actualType = type ?? (editableType as Keys) - const objectRef = useRef() + if (typeof actualType !== 'string') { + throw new Error( + `The editableType prop must be a valid editable type (${Object.keys( + config, + ) + .slice(0, 3) + .join(', ')}...), got ${actualType}.`, + ) + } + + if (typeof uniqueName !== 'string') { + throw new Error( + `The uniqueName prop must be a string, got ${uniqueName}.`, + ) + } + + // This will work for everything except for primitives and custom stuff. Okay for now. + const objectRef = useRef]>() const sheet = useCurrentSheet()! @@ -160,12 +180,12 @@ const createEditable = ( } as unknown as { [Property in Keys]: React.ForwardRefExoticComponent< React.PropsWithoutRef< - Omit & { + Omit & { uniqueName: string visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe - } & React.RefAttributes + } & React.RefAttributes > > } & { @@ -177,8 +197,8 @@ const createEditable = ( visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe - editableType: keyof JSX.IntrinsicElements - } & React.RefAttributes + editableType: keyof ThreeElements + } & React.RefAttributes > & { // Have to reproduce the primitive component's props here because we need to // lift this index type here to the outside to make auto-complete work @@ -187,7 +207,35 @@ const createEditable = ( > } - return Object.assign(editable, extensions) + const extendedEditable = Object.assign(editable, extensions) + + const extendedEditableProxy = new Proxy(extendedEditable, { + get(target, prop, receiver) { + if (!Reflect.has(target, prop)) { + // @ts-ignore + return editable(prop as keyof JSX.IntrinsicElements, null) + } + return Reflect.get(target, prop, receiver) + }, + }) as typeof extendedEditable & + { + [Key in Exclude< + keyof ThreeElements, + keyof typeof extendedEditable + >]: React.ForwardRefExoticComponent< + React.PropsWithoutRef< + Omit & { + uniqueName: string + visible?: boolean | 'editor' + additionalProps?: $FixMe + objRef?: $FixMe + editableType: Keys + } & React.RefAttributes + > + > + } + + return extendedEditableProxy } const editable = createEditable( diff --git a/packages/r3f/src/main/editableFactoryConfigUtils.ts b/packages/r3f/src/main/editableFactoryConfigUtils.ts index 48962e686..ffd859428 100644 --- a/packages/r3f/src/main/editableFactoryConfigUtils.ts +++ b/packages/r3f/src/main/editableFactoryConfigUtils.ts @@ -3,6 +3,7 @@ import {types} from '@theatre/core' import type {Object3D} from 'three' import type {IconID} from '../extension/icons' import {Color} from 'three' +import type {ThreeElements} from './ThreeElements' export type Helper = Object3D & { update?: () => void @@ -22,7 +23,7 @@ type Meta = { } export type ObjectConfig = {props: Props} & Meta export type EditableFactoryConfig = Partial< - Record> + Record> > type Vector3 = {