import React, { forwardRef, useEffect, useRef, type ComponentProps, type ComponentRef, type ComponentType } from 'react'
import type { UnistylesTheme, UnistylesValues } from '../../types'
import type { Mappings } from './types'
import { type UnistyleDependency, UnistylesShadowRegistry } from '../../specs'
import { deepMergeObjects } from '../../utils'
import { useProxifiedUnistyles } from '../useProxifiedUnistyles'
import { maybeWarnAboutMultipleUnistyles } from '../warn'
// @ts-expect-error
type GenericComponentProps
= ComponentProps
// @ts-expect-error
type GenericComponentRef = ComponentRef
type UnistylesSecrets = {
uni__getStyles: () => Record
uni__dependencies: Array
}
type MappedSecrets = {
styles: Record
dependencies: Array
}
export const withUnistyles = >(
Component: TComponent,
mappings?: Mappings,
) => {
type TProps = GenericComponentProps
type PropsWithUnistyles = Partial & {
uniProps?: Mappings
}
type UnistyleStyles = {
style?: UnistylesValues
contentContainerStyle?: UnistylesValues
}
const getSecrets = (styleProps: Record = {}): MappedSecrets => {
const styles = Array.isArray(styleProps) ? styleProps.flat() : [styleProps]
const secrets: Array = styles.filter(Boolean).reduce((acc, style) => {
const unistyleKey = Object.keys(style).find((key) => key.startsWith('unistyles_'))
return acc.concat([
unistyleKey
? style[unistyleKey]
: {
uni__getStyles: () => style,
uni__dependencies: [],
},
])
}, [])
return {
styles: secrets.reduce(
(acc, secret) => Object.assign(acc, secret.uni__getStyles()),
{} as Record,
),
dependencies: secrets.flatMap((secret) => secret.uni__dependencies),
}
}
return forwardRef, PropsWithUnistyles>((props, ref) => {
const narrowedProps = props as PropsWithUnistyles & UnistyleStyles
const NativeComponent = Component as ComponentType
// @ts-ignore we don't know the type of the component
// prettier-ignore
maybeWarnAboutMultipleUnistyles(narrowedProps.style, `withUnistyles(${Component.displayName ?? Component.name ?? 'Unknown'})`)
// @ts-ignore we don't know the type of the component
// prettier-ignore
maybeWarnAboutMultipleUnistyles(narrowedProps.contentContainerStyle, `withUnistyles(${Component.displayName ?? Component.name ?? 'Unknown'})`)
const scopedTheme = useRef(UnistylesShadowRegistry.getScopedTheme() as UnistylesTheme)
const { proxifiedRuntime, proxifiedTheme, addDependencies } = useProxifiedUnistyles(scopedTheme.current)
// Always track Theme dependency — accessing any property on proxifiedTheme
// triggers the Proxy get trap which adds Theme to the dependency Set
void (proxifiedTheme as Record).__uni
useEffect(() => {
const styleSecrets = getSecrets(narrowedProps.style)
const contentContainerStyleSecrets = getSecrets(narrowedProps.contentContainerStyle)
addDependencies(
Array.from(new Set([...styleSecrets.dependencies, ...contentContainerStyleSecrets.dependencies])),
)
}, [narrowedProps.style, narrowedProps.contentContainerStyle])
const { key: mappingsKey, ...mappingsProps } = mappings ? mappings(proxifiedTheme, proxifiedRuntime) : {}
const { key: uniPropsKey, ...unistyleProps } = narrowedProps.uniProps
? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime)
: {}
const styleSecrets = getSecrets(narrowedProps.style)
const contentContainerStyleSecrets = getSecrets(narrowedProps.contentContainerStyle)
const finalProps = {
...deepMergeObjects(mappingsProps, unistyleProps, props),
...(narrowedProps.style
? {
style: styleSecrets.styles,
}
: {}),
...(narrowedProps.contentContainerStyle
? {
contentContainerStyle: contentContainerStyleSecrets.styles,
}
: {}),
} as any
return
})
}