Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/card/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export function CardPage() {
<Root flexDirection="column" pixelSize={0.01} sizeX={4.4}>
<Defaults>
<Container
style={{ backgroundColor: 'transparent' }}
backgroundColor={0xffffff}
dark={{ backgroundColor: 0x0 }}
borderRadius={20}
Expand Down
1 change: 1 addition & 0 deletions examples/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@preact/signals-core": "^1.5.1",
"@react-three/uikit": "workspace:^",
"@react-three/uikit-lucide": "workspace:^",
"deepsignal": "^1.6.0",
"vite-plugin-mkcert": "^1.17.4",
"zustand": "^4.4.7"
},
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,7 @@
"core-js",
"sharp"
]
}
},
"workspaces": ["packages/*"],
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b"
}
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@preact/signals-core": "^1.5.1",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"deepsignal": "^1.6.0",
"ora": "^8.0.1",
"prettier": "^3.2.5",
"prompts": "^2.4.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const Container: (props: ContainerProperties & RefAttributes<ContainerRef
}
const abortController = new AbortController()
setupContainer<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -55,12 +56,14 @@ export const Container: (props: ContainerProperties & RefAttributes<ContainerRef
return () => abortController.abort()
}, [parent, propertySignals, internals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
<AddHandlers handlers={internals.handlers} ref={outerRef}>
<primitive object={internals.interactionPanel} />
<object3D matrixAutoUpdate={false} ref={innerRef}>
{/* @ts-expect-error */}
<DefaultProperties {...internals.defaultProperties}>
<ParentProvider value={internals}>{properties.children}</ParentProvider>
</DefaultProperties>
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const Content: (props: ContentProperties & RefAttributes<ContentRef>) =>
}
const abortController = new AbortController()
setupContent<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -49,6 +50,7 @@ export const Content: (props: ContentProperties & RefAttributes<ContentRef>) =>
return () => abortController.abort()
}, [internals, parent, propertySignals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const CustomContainer: (props: CustomContainerProperties & RefAttributes<
}
const abortController = new AbortController()
setupCustomContainer<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -52,6 +53,7 @@ export const CustomContainer: (props: CustomContainerProperties & RefAttributes<
return () => abortController.abort()
}, [internals, parent, propertySignals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, innerRef)

useEffect(() => {
Expand Down
74 changes: 60 additions & 14 deletions packages/react/src/default.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AllOptionalProperties } from '@pmndrs/uikit'
import { ReactNode, createContext, useContext } from 'react'
import { Signal } from '@preact/signals-core'
import { ReactNode, createContext, useContext, useMemo } from 'react'

const DefaultPropertiesContext = createContext<AllOptionalProperties | undefined>(undefined)

Expand All @@ -9,19 +10,64 @@ export function useDefaultProperties(): AllOptionalProperties | undefined {

export type DefaultPropertiesProperties = { children?: ReactNode } & AllOptionalProperties

export function DefaultProperties(properties: DefaultPropertiesProperties) {
const createDefaultPropertiesHash = (properties: AllOptionalProperties | undefined) => {
if (properties === undefined) {
return ''
}
return Array.from(Object.entries(properties))
.sort(([ka], [kb]) => (ka > kb ? 1 : ka < kb ? -1 : 0))
.map(([k, v]) => {
switch (typeof v) {
case 'function':
console.warn('no function in default props please', k)
return ''
case 'object':
if (Array.isArray(v)) {
return v.join(',')
}
if (v instanceof Signal) {
return `${k}=${v.peek()}`
}
for (let nk of Object.keys(v)) {
if (typeof v[nk] === 'object') {
console.warn('no deeply nested objects in default props please', k, v)
return ''
}
if (typeof v[nk] === 'function') {
console.warn('no functions nested in objects in default props please', k, v)
return ''
}
}
return `${k}=${JSON.stringify(v)}`
default:
if (v === undefined) return ''
return `${k}=${v}`
}
})
.join('&')
}

export function DefaultProperties({ children, ...properties }: DefaultPropertiesProperties) {
const existingDefaultProperties = useContext(DefaultPropertiesContext)
const result: any = { ...existingDefaultProperties }
for (const key in properties) {
if (key === 'children') {
continue
}
//TODO: this is not correctly merged but rather overwritten
const value = properties[key as keyof AllOptionalProperties]
if (value == null) {
continue
const existingDefaultPropertiesHash = createDefaultPropertiesHash(existingDefaultProperties)
const propertiesHash = createDefaultPropertiesHash(properties)

const result: any = useMemo(() => {
const result: any = { ...existingDefaultProperties }
for (const key in properties) {
if (key === 'children') {
continue
}
//TODO: this is not correctly merged but rather overwritten
const value = properties[key as keyof AllOptionalProperties]
if (value == null) {
continue
}
result[key] = value as any
}
result[key] = value as any
}
return <DefaultPropertiesContext.Provider value={result}>{properties.children}</DefaultPropertiesContext.Provider>
return result
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [existingDefaultPropertiesHash, propertiesHash])

return <DefaultPropertiesContext.Provider value={result}>{children}</DefaultPropertiesContext.Provider>
}
1 change: 1 addition & 0 deletions packages/react/src/font.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function useMeasureText(fontFamily?: string, fontWeight?: FontWeight) {
const fontFamilies = useMemo(() => signal<FontFamilies | undefined>(undefined as any), [])
fontFamilies.value = useFontFamilies()
const font = useMemo(
// @ts-expect-error
() => computedFont(propertiesSignal, fontFamilies, renderer),
[fontFamilies, propertiesSignal, renderer],
)
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/fullscreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const Fullscreen: (props: FullscreenProperties & RefAttributes<Fullscreen
)
return createPortal(
<group position-z={-distanceToCamera}>
{/* @ts-expect-error */}
<Root ref={ref} {...properties} sizeX={sizeX} sizeY={sizeY} pixelSize={pixelSize}>
{properties.children}
</Root>
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const Icon: (props: IconProperties & RefAttributes<IconRef>) => ReactNode
}
const abortController = new AbortController()
setupIcon<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -51,6 +52,7 @@ export const Icon: (props: IconProperties & RefAttributes<IconRef>) => ReactNode
return () => abortController.abort()
}, [parent, propertySignals, internals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const Image: (props: ImageProperties & RefAttributes<ImageRef>) => ReactN
}
const abortController = new AbortController()
setupImage<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -49,12 +50,14 @@ export const Image: (props: ImageProperties & RefAttributes<ImageRef>) => ReactN
return () => abortController.abort()
}, [parent, propertySignals, internals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
<AddHandlers ref={outerRef} handlers={internals.handlers}>
<primitive object={internals.interactionPanel} />
<object3D matrixAutoUpdate={false} ref={innerRef}>
{/* @ts-expect-error */}
<DefaultProperties {...internals.defaultProperties}>
<ParentProvider value={internals}>{properties.children}</ParentProvider>
</DefaultProperties>
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { forwardRef, ReactNode, RefAttributes, useEffect, useMemo, useRef } from
import { Object3D, Vector2Tuple } from 'three'
import { useParent } from './context.js'
import { AddHandlers, R3FEventMap, usePropertySignals } from './utils.js'

import {
FontFamilies,
InputProperties as BaseInputProperties,
Expand Down Expand Up @@ -58,6 +59,7 @@ export const Input: (props: InputProperties & RefAttributes<InputRef>) => ReactN
}
const abortController = new AbortController()
setupInput<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -73,6 +75,7 @@ export const Input: (props: InputProperties & RefAttributes<InputRef>) => ReactN
ref,
parent.root,
propertySignals.style,
// @ts-expect-error
internals,
internals.interactionPanel,
useMemo(
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export const Portal: (props: PortalProperties & RefAttributes<PortalRef>) => Rea
usePortalStore,
null,
)}
{/* @ts-expect-error */}
<Image src={texture} objectFit="fill" keepAspectRatio={false} {...props} ref={imageRef} />
</>
)
Expand Down
12 changes: 6 additions & 6 deletions packages/react/src/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
createInputState,
createContentState,
RootContext,
ReadonlyDeepSignalObject,
} from '@pmndrs/uikit/internals'
import { ForwardedRef, RefObject, useImperativeHandle } from 'react'
import { Vector2Tuple, Mesh, Matrix4 } from 'three'
Expand Down Expand Up @@ -84,7 +85,7 @@ export type ComponentInternals<T = ContainerProperties> = {
getComputedProperty<K extends keyof T>(key: K): T[K] | undefined
}

export function useComponentInternals<T, O = {}>(
export function useComponentInternals<T extends object, O = {}>(
ref: ForwardedRef<ComponentInternals<T> & O>,
root: RootContext,
styleSignal: Signal<T | undefined>,
Expand All @@ -101,7 +102,7 @@ export function useComponentInternals<T, O = {}>(
> & {
isClipped?: Signal<boolean>
scrollPosition?: Signal<Vector2Tuple>
mergedProperties: Signal<MergedProperties>
mergedProperties: ReadonlyDeepSignalObject<T>
},
interactionPanel: Mesh | RefObject<Mesh | null>,
additional?: O,
Expand All @@ -111,11 +112,10 @@ export function useComponentInternals<T, O = {}>(
internals
return {
isVisible: internals.isVisible,
setStyle: (style: T | undefined, replace?: boolean) =>
(styleSignal.value = replace ? style : ({ ...styleSignal.value, ...style } as T)),
setStyle: (style, replace) => (styleSignal.value = replace ? style : ({ ...styleSignal.value, ...style } as T)),
getStyle: () => styleSignal.peek(),
getComputedProperty: <K extends keyof T>(key: K) =>
untracked(() => internals.mergedProperties.value.read<T[K] | undefined>(key as string, undefined)),
// @ts-expect-error
getComputedProperty: (key) => internals.mergedProperties['$' + key]!.peek(),
pixelSize: root.pixelSize,
root,
borderInset,
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const Root: (props: RootProperties & RefAttributes<RootRef>) => ReactNode
}
const abortController = new AbortController()
setupRoot<R3FEventMap>(
// @ts-expect-error
internals,
propertySignals.style,
propertySignals.properties,
Expand All @@ -88,12 +89,14 @@ export const Root: (props: RootProperties & RefAttributes<RootRef>) => ReactNode
whileOnFrameRef.current = false
})

// @ts-expect-error
useComponentInternals(ref, internals.root, propertySignals.style, internals, internals.interactionPanel)

return (
<AddHandlers handlers={internals.handlers} ref={outerRef}>
<primitive object={internals.interactionPanel} />
<object3D matrixAutoUpdate={false} ref={innerRef}>
{/* @ts-expect-error */}
<DefaultProperties {...internals.defaultProperties}>
<ParentProvider value={internals}>{properties.children}</ParentProvider>
</DefaultProperties>
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const Svg: (props: SvgProperties & RefAttributes<SvgRef>) => ReactNode =
}
const abortController = new AbortController()
setupSvg<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -49,13 +50,15 @@ export const Svg: (props: SvgProperties & RefAttributes<SvgRef>) => ReactNode =
return () => abortController.abort()
}, [parent, propertySignals, internals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
<AddHandlers ref={outerRef} handlers={internals.handlers}>
<primitive object={internals.interactionPanel} />
<primitive object={internals.centerGroup} />
<object3D matrixAutoUpdate={false} ref={innerRef}>
{/* @ts-expect-error */}
<DefaultProperties {...internals.defaultProperties}>
<ParentProvider value={internals}>{properties.children}</ParentProvider>
</DefaultProperties>
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const Text: (props: TextProperties & RefAttributes<TextRef>) => ReactNode
}
const abortController = new AbortController()
setupText<R3FEventMap>(
// @ts-expect-error
internals,
parent,
propertySignals.style,
Expand All @@ -56,6 +57,7 @@ export const Text: (props: TextProperties & RefAttributes<TextRef>) => ReactNode
return () => abortController.abort()
}, [parent, propertySignals, internals])

// @ts-expect-error
useComponentInternals(ref, parent.root, propertySignals.style, internals, internals.interactionPanel)

return (
Expand Down
Loading