import { useIsomorphicLayoutEffect } from '@tamagui/constants' import React from 'react' import { getConfig } from '../config' import { ComponentContext } from '../contexts/ComponentContext' import { GroupContext } from '../contexts/GroupContext' import { useSplitStyles } from '../helpers/getSplitStyles' import { subscribeToContextGroup } from '../helpers/subscribeToContextGroup' import type { SplitStyleProps, StaticConfig, ThemeParsed, UseMediaState } from '../types' import type { ViewProps, ViewStyle } from '../views/View' import { View } from '../views/View' import { useComponentState } from './useComponentState' import { mediaState } from '../helpers/mediaState' import { useMedia } from './useMedia' import { useThemeWithState } from './useTheme' type UsePropsOptions = Pick< SplitStyleProps, 'noExpand' | 'noNormalize' | 'noClass' | 'resolveValues' > & { disableExpandShorthands?: boolean forComponent?: { staticConfig: StaticConfig } noClass?: boolean /** * Disable watching for media queries */ noMedia?: boolean } export type PropsWithoutMediaStyles = { // remove all media [Key in keyof A as Key extends `$${string}` ? never : Key]?: A[Key] } type PropsLikeObject = (ViewProps & Record) | object type StyleLikeObject = (ViewStyle & Record) | object /** * Returns props and style as a single object, expanding and merging shorthands and media queries. * * Use sparingly, it will loop props and trigger re-render on all media queries you access. * * */ export function useProps( props: A, opts?: UsePropsOptions ): PropsWithoutMediaStyles { const [propsOut, styleOut] = usePropsAndStyle(props, { ...opts, noExpand: true, noNormalize: true, resolveValues: 'none', }) return { ...propsOut, ...styleOut, } } /** * Returns only style values fully resolved and flattened with merged media queries and shorthands with all theme and token values resolved. * * Use sparingly, it will loop props and trigger re-render on all media queries you access. * * */ export function useStyle( props: A, opts?: UsePropsOptions ): PropsWithoutMediaStyles { return usePropsAndStyle(props, opts)[1] || {} } /** * Returns [props, styles, theme, media] fully resolved and flattened with merged media queries and shorthands with all theme and token values resolved. * * Use sparingly, it will loop props and trigger re-render on all media queries you access. * * */ export function usePropsAndStyle( props: A, opts?: UsePropsOptions ): [PropsWithoutMediaStyles, PropsWithoutMediaStyles, ThemeParsed, UseMediaState] { const staticConfig = opts?.forComponent?.staticConfig ?? View.staticConfig const [theme, themeState] = useThemeWithState({ componentName: staticConfig.componentName, name: 'theme' in props ? props.theme : undefined, needsUpdate() { return true }, }) const componentContext = React.useContext(ComponentContext) const groupContext = React.useContext(GroupContext) const { state, disabled, setStateShallow } = useComponentState( props, componentContext.animationDriver, staticConfig, getConfig() ) const mediaStateNow = opts?.noMedia ? // not safe to use mediaState but really marginal to hit this mediaState : useMedia() const splitStyles = useSplitStyles( props, staticConfig, theme, themeState?.name || '', state, { isAnimated: false, mediaState: mediaStateNow, noSkip: true, noMergeStyle: true, noClass: true, resolveValues: 'auto', ...opts, }, null, componentContext, groupContext ) const { mediaGroups, pseudoGroups } = splitStyles || {} useIsomorphicLayoutEffect(() => { if (disabled) { return } if (state.unmounted) { setStateShallow({ unmounted: false }) return } if (groupContext) { return subscribeToContextGroup({ groupContext, setStateShallow, mediaGroups, pseudoGroups, }) } }, [ disabled, groupContext, pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0, mediaGroups ? Object.keys([...mediaGroups]).join('') : 0, ]) return [ splitStyles?.viewProps || {}, splitStyles?.style || {}, theme, mediaState, ] as any }