Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: copy native components properties #533

Merged
merged 1 commit into from
Jan 29, 2025
Merged
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
5 changes: 4 additions & 1 deletion src/components/native/ImageBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import type { UnistylesValues } from '../../types'
import { getClassName } from '../../core'
import { isServer } from '../../web/utils'
import { UnistylesShadowRegistry } from '../../web'
import { copyComponentProperties } from '../../utils'

type Props = {
style?: UnistylesValues
imageStyle?: UnistylesValues
}

export const ImageBackground = forwardRef<unknown, Props>((props, forwardedRef) => {
const UnistylesImageBackground = forwardRef<unknown, Props>((props, forwardedRef) => {
let storedRef: NativeImageBackground | null = null
let storedImageRef: NativeImageBackground | null = null
const styleClassNames = getClassName(props.style)
Expand Down Expand Up @@ -54,3 +55,5 @@ export const ImageBackground = forwardRef<unknown, Props>((props, forwardedRef)
/>
)
})

export const ImageBackground = copyComponentProperties(NativeImageBackground, UnistylesImageBackground)
51 changes: 28 additions & 23 deletions src/core/createUnistylesElement.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useLayoutEffect, useRef } from 'react'
import { UnistylesShadowRegistry } from '../specs'
import { passForwardedRef } from './passForwardRef'
import { maybeWarnAboutMultipleUnistyles } from './warn'
import { copyComponentProperties } from '../utils'

const getNativeRef = (Component: any, ref: any) => {
switch (Component.name) {
Expand All @@ -16,31 +17,35 @@ const getNativeRef = (Component: any, ref: any) => {
}
}

export const createUnistylesElement = (Component: any) => React.forwardRef((props, forwardedRef) => {
const storedRef = useRef<unknown>(null)
export const createUnistylesElement = (Component: any) => {
const UnistylesComponent = React.forwardRef((props, forwardedRef) => {
const storedRef = useRef<unknown>(null)

useLayoutEffect(() => {
return () => {
if (storedRef.current) {
// @ts-ignore
UnistylesShadowRegistry.remove(storedRef.current)
useLayoutEffect(() => {
return () => {
if (storedRef.current) {
// @ts-ignore
UnistylesShadowRegistry.remove(storedRef.current)
}
}
}
}, [])
}, [])

return (
<Component
{...props}
ref={(ref: unknown) => {
if (ref) {
storedRef.current = getNativeRef(Component, ref)
}
return (
<Component
{...props}
ref={(ref: unknown) => {
if (ref) {
storedRef.current = getNativeRef(Component, ref)
}

passForwardedRef(props, ref, forwardedRef)
passForwardedRef(props, ref, forwardedRef)

// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, Component.displayName)
}}
/>
)
})
// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, Component.displayName)
}}
/>
)
})

return copyComponentProperties(Component, UnistylesComponent)
}
63 changes: 34 additions & 29 deletions src/core/createUnistylesElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,45 @@ import { getClassName } from './getClassname'
import { isServer } from '../web/utils'
import { UnistylesShadowRegistry } from '../web'
import { maybeWarnAboutMultipleUnistyles } from './warn'
import { copyComponentProperties } from '../utils'

type ComponentProps = {
style?: UnistylesValues | Array<UnistylesValues>
}

export const createUnistylesElement = (Component: any) => React.forwardRef<unknown, ComponentProps>((props, forwardedRef) => {
let storedRef: HTMLElement | null = null
const classNames = getClassName(props.style)
export const createUnistylesElement = (Component: any) => {
const UnistylesComponent = React.forwardRef<unknown, ComponentProps>((props, forwardedRef) => {
let storedRef: HTMLElement | null = null
const classNames = getClassName(props.style)

return (
<Component
{...props}
style={classNames}
ref={isServer() ? undefined : (ref: HTMLElement | null) => {
// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, Component.displayName)
return (
<Component
{...props}
style={classNames}
ref={isServer() ? undefined : (ref: HTMLElement | null) => {
// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, Component.displayName)

if (!ref) {
if (!ref) {
// @ts-expect-error hidden from TS
UnistylesShadowRegistry.remove(storedRef, classNames?.hash)
}

storedRef = ref
// @ts-expect-error hidden from TS
UnistylesShadowRegistry.remove(storedRef, classNames?.hash)
}

storedRef = ref
// @ts-expect-error hidden from TS
UnistylesShadowRegistry.add(ref, classNames?.hash)

if (typeof forwardedRef === 'function') {
return forwardedRef(ref)
}

if (forwardedRef) {
forwardedRef.current = ref
}
}}
/>
)
})
UnistylesShadowRegistry.add(ref, classNames?.hash)

if (typeof forwardedRef === 'function') {
return forwardedRef(ref)
}

if (forwardedRef) {
forwardedRef.current = ref
}
}}
/>
)
})

return copyComponentProperties(Component, UnistylesComponent)
}
71 changes: 38 additions & 33 deletions src/core/createUnistylesImageBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,48 @@ import type { Image, ImageBackground, ImageBackgroundProps } from 'react-native'
import { UnistylesShadowRegistry } from '../specs'
import { passForwardedRef } from './passForwardRef'
import { maybeWarnAboutMultipleUnistyles } from './warn'
import { copyComponentProperties } from '../utils'

export const createUnistylesImageBackground = (Component: typeof ImageBackground) => React.forwardRef<ImageBackground, ImageBackgroundProps>((props, forwardedRef) => {
const storedImageRef = useRef<Image | null>(null)
export const createUnistylesImageBackground = (Component: typeof ImageBackground) => {
const UnistylesImageBackground = React.forwardRef<ImageBackground, ImageBackgroundProps>((props, forwardedRef) => {
const storedImageRef = useRef<Image | null>(null)

useLayoutEffect(() => {
return () => {
if (storedImageRef.current) {
// @ts-ignore
UnistylesShadowRegistry.remove(storedImageRef.current)
useLayoutEffect(() => {
return () => {
if (storedImageRef.current) {
// @ts-ignore
UnistylesShadowRegistry.remove(storedImageRef.current)
}
}
}
}, [])
}, [])

// @ts-expect-error we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, 'ImageBackground')
// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.imageStyle, 'ImageBackground')
// @ts-expect-error we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.style, 'ImageBackground')
// @ts-ignore we don't know the type of the component
maybeWarnAboutMultipleUnistyles(props.imageStyle, 'ImageBackground')

return (
<Component
{...props}
ref={ref => {
passForwardedRef(props, ref, forwardedRef)
return (
<Component
{...props}
ref={ref => {
passForwardedRef(props, ref, forwardedRef)

return () => {
// @ts-ignore
UnistylesShadowRegistry.remove(ref)
}
}}
imageRef={ref => {
if (ref) {
storedImageRef.current = ref
}
return () => {
// @ts-ignore
UnistylesShadowRegistry.remove(ref)
}
}}
imageRef={ref => {
if (ref) {
storedImageRef.current = ref
}

// @ts-expect-error web types are not compatible with RN styles
UnistylesShadowRegistry.add(ref, props.imageStyle)
}}
/>
)
})

// @ts-expect-error web types are not compatible with RN styles
UnistylesShadowRegistry.add(ref, props.imageStyle)
}}
/>
)
})
return copyComponentProperties(Component, UnistylesImageBackground)
}
14 changes: 14 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ export const deepMergeObjects = <T extends Record<PropertyKey, any>>(...sources:

return target
}

export const copyComponentProperties = (Component: any, UnistylesComponent: any) => {
Object.entries(Object.getOwnPropertyDescriptors(Component)).forEach(([key, propertyDescriptor]) => {
// Filter out the keys we don't want to copy
if (['$$typeof', 'render'].includes(key)) {
return
}

// @ts-expect-error Copy extra component properties - example: Image.getSize, Image.displayName
UnistylesComponent[key] = propertyDescriptor.value ?? propertyDescriptor.get()
})

return UnistylesComponent
}