Skip to content

Commit 7b8e4d1

Browse files
committed
feat: compat.native variable resolver and inheritedTextStyle
Summary: Add a second argument to the react.compat callback which exposes StrictReactNativeMetaProps StrictReactNativeMetaProps has: # resolveStyleValue A function that will resolve variables to their concrete values ``` resolveStyleValue('red') // → 'red' (no var → returned unchanged) resolveStyleValue('10px') // → '10px' (no var → unchanged) resolveStyleValue(vars.brand) // → 'magenta' (token resolved through the theme cascade) resolveStyleValue('var(--brand)') // → 'magenta' (explicit var() resolved) resolveStyleValue('var(--missing)') // → null (unresolvable reference) ``` - Ancestor/ingerited theme tokens correctly resolve using this function - Light/dark tokens resolve correctly - Correctly resolves fallbacks, nested vars, non color vars, etc - Avoid exposing any private internals # inheritedTextStyle Provides access to the text style that would be inherrited by child component Importantly, it provide access to the color which is used on things like `svg` elements # A more real world example: ``` import { compat } from 'react-strict-dom'; import { Path as RNPath } from 'react-native-svg'; function Path({ style, fill, stroke, ...rest }) { return ( <compat.native style={style}> {(nativeProps, { inheritedTextStyle, resolveStyleValue }) => ( <RNPath {...rest} // RSD doesn't know about SVG paint props, so resolve var()/tokens yourself: fill={fill != null ? resolveStyleValue(fill) : undefined} stroke={stroke != null ? resolveStyleValue(stroke) : undefined} // wire CSS `color` inheritance so `currentColor` works: color={inheritedTextStyle.color} /> )} </compat.native> ); } ```
1 parent ae706ce commit 7b8e4d1

8 files changed

Lines changed: 198 additions & 44 deletions

File tree

packages/react-strict-dom/src/native/compat.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type { StrictProps } from '../types/StrictProps';
11+
import type { StrictReactNativeMetaProps } from '../types/renderer.native';
1112

1213
import * as React from 'react';
1314

@@ -31,7 +32,9 @@ import { createStrictDOMTextInputComponent as createStrictTextInput } from './mo
3132
* aria-label="label"
3233
* as="text"
3334
* >
34-
* {(nativeProps: React.PropsOf<Text>)) => (
35+
* {(nativeProps: React.PropsOf<Text>, meta) => (
36+
* // `meta.inheritedTextStyle` and `meta.resolveStyleValue` allow resolving
37+
* // CSS inheritance and `var(...)` for non-RSD host components.
3538
* <Text {...nativeProps} />
3639
* )}
3740
* </compat.native>
@@ -42,7 +45,7 @@ const defaultProps = {};
4245
type StrictPropsOnlyCompat<T> = {
4346
...StrictProps,
4447
as?: 'div' | 'img' | 'input' | 'span' | 'textarea',
45-
children: (nativeProps: T) => React.Node
48+
children: (nativeProps: T, meta: StrictReactNativeMetaProps) => React.Node
4649
};
4750

4851
const StrictText = createStrictText('span', defaultProps) as $FlowFixMe;

packages/react-strict-dom/src/native/modules/createStrictDOMComponent.js

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,29 @@
88
*/
99

1010
import type { ReactNativeProps } from '../../types/renderer.native';
11+
import type { StrictReactNativeMetaProps } from '../../types/renderer.native';
1112
import type { StrictProps as StrictPropsOriginal } from '../../types/StrictProps';
1213

1314
import * as React from 'react';
1415
import * as ReactNative from '../react-native';
1516

1617
import { ProvideCustomProperties } from './ContextCustomProperties';
1718
import { ProvideDisplayInside, useDisplayInside } from './ContextDisplayInside';
18-
import { ProvideInheritedStyles } from './ContextInheritedStyles';
19+
import {
20+
ProvideInheritedStyles,
21+
useInheritedStyles
22+
} from './ContextInheritedStyles';
23+
import { flattenStyle } from './flattenStyle';
1924
import { TextString } from './TextString';
2025
import { errorMsg } from '../../shared/logUtils';
2126
import { useNativeProps } from './useNativeProps';
2227
import { useStrictDOMElement } from './useStrictDOMElement';
2328

2429
type StrictProps = Readonly<{
2530
...StrictPropsOriginal,
26-
children?: React.Node | ((ReactNativeProps) => React.Node)
31+
children?:
32+
| React.Node
33+
| ((ReactNativeProps, StrictReactNativeMetaProps) => React.Node)
2734
}>;
2835

2936
const AnimatedPressable = ReactNative.Animated.createAnimatedComponent(
@@ -54,15 +61,22 @@ export function createStrictDOMComponent<T, P extends StrictProps>(
5461
* Resolve global HTML and style props
5562
*/
5663

57-
const { customProperties, nativeProps, inheritableStyle } = useNativeProps(
58-
defaultProps,
59-
props,
60-
{
61-
provideInheritableStyle,
62-
withInheritedStyle: false,
63-
withTextStyle: false
64-
}
65-
);
64+
const {
65+
customProperties,
66+
resolveStyleValue,
67+
nativeProps,
68+
inheritableStyle
69+
} = useNativeProps(defaultProps, props, {
70+
provideInheritableStyle,
71+
withInheritedStyle: false,
72+
withTextStyle: false
73+
});
74+
75+
const ancestorInheritedStyle = useInheritedStyles();
76+
const inheritedTextStyle = flattenStyle([
77+
ancestorInheritedStyle,
78+
inheritableStyle
79+
]);
6680

6781
if (
6882
nativeProps.onPress != null &&
@@ -193,7 +207,7 @@ export function createStrictDOMComponent<T, P extends StrictProps>(
193207

194208
let element: React.Node =
195209
typeof props.children === 'function' ? (
196-
props.children(nativeProps)
210+
props.children(nativeProps, { inheritedTextStyle, resolveStyleValue })
197211
) : (
198212
// $FlowFixMe[incompatible-type]
199213
<NativeComponent {...nativeProps} />

packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import * as React from 'react';
1313
import * as ReactNative from '../react-native';
1414

1515
import { useNativeProps } from './useNativeProps';
16+
import { useInheritedStyles } from './ContextInheritedStyles';
17+
import { flattenStyle } from './flattenStyle';
1618
import { useStrictDOMElement } from './useStrictDOMElement';
1719
import * as css from '../css';
1820

@@ -52,11 +54,21 @@ export function createStrictDOMImageComponent<
5254
]
5355
};
5456

55-
const { nativeProps } = useNativeProps(defaultProps, props, {
56-
provideInheritableStyle: false,
57-
withInheritedStyle: false,
58-
withTextStyle: false
59-
});
57+
const { resolveStyleValue, nativeProps, inheritableStyle } = useNativeProps(
58+
defaultProps,
59+
props,
60+
{
61+
provideInheritableStyle: false,
62+
withInheritedStyle: false,
63+
withTextStyle: false
64+
}
65+
);
66+
67+
const ancestorInheritedStyle = useInheritedStyles();
68+
const inheritedTextStyle = flattenStyle([
69+
ancestorInheritedStyle,
70+
inheritableStyle
71+
]);
6072

6173
// Tag-specific props
6274

@@ -122,7 +134,7 @@ export function createStrictDOMImageComponent<
122134

123135
const element: React.Node =
124136
typeof props.children === 'function' ? (
125-
props.children(nativeProps)
137+
props.children(nativeProps, { inheritedTextStyle, resolveStyleValue })
126138
) : (
127139
// strict-dom's wide ReactNativeProps spreads onto RN 0.83's exact
128140
// ImageProps; harmless extras are ignored at runtime.

packages/react-strict-dom/src/native/modules/createStrictDOMTextComponent.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,27 @@
88
*/
99

1010
import type { ReactNativeProps } from '../../types/renderer.native';
11+
import type { StrictReactNativeMetaProps } from '../../types/renderer.native';
1112
import type { StrictProps as StrictPropsOriginal } from '../../types/StrictProps';
1213

1314
import * as React from 'react';
1415
import * as ReactNative from '../react-native';
1516

1617
import { ProvideCustomProperties } from './ContextCustomProperties';
17-
import { ProvideInheritedStyles } from './ContextInheritedStyles';
18+
import {
19+
ProvideInheritedStyles,
20+
useInheritedStyles
21+
} from './ContextInheritedStyles';
22+
import { flattenStyle } from './flattenStyle';
1823
import { errorMsg } from '../../shared/logUtils';
1924
import { useNativeProps } from './useNativeProps';
2025
import { useStrictDOMElement } from './useStrictDOMElement';
2126

2227
type StrictProps = Readonly<{
2328
...StrictPropsOriginal,
24-
children?: React.Node | ((ReactNativeProps) => React.Node)
29+
children?:
30+
| React.Node
31+
| ((ReactNativeProps, StrictReactNativeMetaProps) => React.Node)
2532
}>;
2633

2734
function hasElementChildren(children: unknown): boolean {
@@ -44,19 +51,26 @@ export function createStrictDOMTextComponent<T, P extends StrictProps>(
4451
* Resolve global HTML and style props
4552
*/
4653

47-
const { customProperties, nativeProps, inheritableStyle } = useNativeProps(
48-
defaultProps,
49-
props,
50-
{
51-
provideInheritableStyle:
52-
tagName !== 'br' ||
53-
// $FlowFixMe[invalid-compare]
54-
tagName !== 'option' ||
55-
hasElementChildren(props.children),
56-
withInheritedStyle: true,
57-
withTextStyle: true
58-
}
59-
);
54+
const {
55+
customProperties,
56+
resolveStyleValue,
57+
nativeProps,
58+
inheritableStyle
59+
} = useNativeProps(defaultProps, props, {
60+
provideInheritableStyle:
61+
tagName !== 'br' ||
62+
// $FlowFixMe[invalid-compare]
63+
tagName !== 'option' ||
64+
hasElementChildren(props.children),
65+
withInheritedStyle: true,
66+
withTextStyle: true
67+
});
68+
69+
const ancestorInheritedStyle = useInheritedStyles();
70+
const inheritedTextStyle = flattenStyle([
71+
ancestorInheritedStyle,
72+
inheritableStyle
73+
]);
6074

6175
// Tag-specific props
6276

@@ -128,7 +142,7 @@ export function createStrictDOMTextComponent<T, P extends StrictProps>(
128142

129143
let element: React.Node =
130144
typeof props.children === 'function' ? (
131-
props.children(nativeProps)
145+
props.children(nativeProps, { inheritedTextStyle, resolveStyleValue })
132146
) : (
133147
// $FlowFixMe[incompatible-type]
134148
<NativeComponent {...nativeProps} />

packages/react-strict-dom/src/native/modules/createStrictDOMTextInputComponent.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import * as ReactNative from '../react-native';
1515

1616
import { errorMsg } from '../../shared/logUtils';
1717
import { mergeRefs } from '../../shared/mergeRefs';
18+
import { useInheritedStyles } from './ContextInheritedStyles';
19+
import { flattenStyle } from './flattenStyle';
1820
import { useNativeProps } from './useNativeProps';
1921
import { useStrictDOMElement } from './useStrictDOMElement';
2022

@@ -76,11 +78,21 @@ export function createStrictDOMTextInputComponent<
7678
* Resolve global HTML and style props
7779
*/
7880

79-
const { nativeProps } = useNativeProps(defaultProps, props, {
80-
provideInheritableStyle: false,
81-
withInheritedStyle: false,
82-
withTextStyle: true
83-
});
81+
const { resolveStyleValue, nativeProps, inheritableStyle } = useNativeProps(
82+
defaultProps,
83+
props,
84+
{
85+
provideInheritableStyle: false,
86+
withInheritedStyle: false,
87+
withTextStyle: true
88+
}
89+
);
90+
91+
const ancestorInheritedStyle = useInheritedStyles();
92+
const inheritedTextStyle = flattenStyle([
93+
ancestorInheritedStyle,
94+
inheritableStyle
95+
]);
8496

8597
// Tag-specific props
8698

@@ -246,7 +258,7 @@ export function createStrictDOMTextInputComponent<
246258

247259
const element =
248260
typeof props.children === 'function' ? (
249-
props.children(nativeProps)
261+
props.children(nativeProps, { inheritedTextStyle, resolveStyleValue })
250262
) : (
251263
// strict-dom's wide ReactNativeProps spreads onto RN 0.83's exact
252264
// TextInputProps; harmless extras are ignored at runtime.

packages/react-strict-dom/src/native/modules/useNativeProps.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,32 @@
88
*/
99

1010
import type { CustomProperties } from '../../types/styles';
11-
import type { ReactNativeProps } from '../../types/renderer.native';
11+
import type {
12+
ReactNativeProps,
13+
StrictReactNativeMetaProps
14+
} from '../../types/renderer.native';
1215
import type { StrictProps as StrictPropsOriginal } from '../../types/StrictProps';
1316
import type { Style } from '../../types/styles';
1417

18+
import * as ReactNative from '../react-native';
19+
import { CSSUnparsedValue } from '../css/typed-om/CSSUnparsedValue';
20+
import {
21+
resolveVariableReferences,
22+
stringContainsVariables
23+
} from '../css/customProperties';
1524
import { errorMsg, warnMsg } from '../../shared/logUtils';
1625
import { extractStyleThemes } from './extractStyleThemes';
1726
import { isPropAllowed } from '../../shared/isPropAllowed';
1827
import { useCustomProperties } from './ContextCustomProperties';
1928
import { useStyleProps } from './useStyleProps';
2029

30+
export type ResolveStyleValue = (value: string) => string | number | null;
31+
2132
type StrictProps = Readonly<{
2233
...StrictPropsOriginal,
23-
children?: React.Node | ((ReactNativeProps) => React.Node)
34+
children?:
35+
| React.Node
36+
| ((ReactNativeProps, StrictReactNativeMetaProps) => React.Node)
2437
}>;
2538

2639
/**
@@ -83,6 +96,7 @@ type OptionsType = {|
8396
|};
8497
type ReturnType = {|
8598
customProperties: ?CustomProperties,
99+
resolveStyleValue: ResolveStyleValue,
86100
nativeProps: ReactNativeProps,
87101
inheritableStyle: ?Style
88102
|};
@@ -158,6 +172,19 @@ export function useNativeProps(
158172
extractStyleThemes(renderStyle);
159173
const customProperties = useCustomProperties(customPropertiesFromThemes);
160174

175+
const colorScheme = ReactNative.useColorScheme();
176+
const resolveStyleValue: ResolveStyleValue = (value) => {
177+
if (typeof value !== 'string' || !stringContainsVariables(value)) {
178+
return value;
179+
}
180+
return resolveVariableReferences(
181+
'styleValue',
182+
CSSUnparsedValue.parse('styleValue', value),
183+
customProperties,
184+
colorScheme === 'dark' ? 'dark' : 'light'
185+
);
186+
};
187+
161188
const { nativeProps, inheritableStyle } = useStyleProps(extractedStyle, {
162189
customProperties,
163190
provideInheritableStyle: options.provideInheritableStyle,
@@ -440,6 +467,7 @@ export function useNativeProps(
440467
return {
441468
customProperties:
442469
customPropertiesFromThemes != null ? customProperties : null,
470+
resolveStyleValue,
443471
nativeProps,
444472
inheritableStyle
445473
};

packages/react-strict-dom/src/types/renderer.native.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {
3232
// $FlowFixMe[nonstrict-import]
3333
ViewProps
3434
} from 'react-native/Libraries/Components/View/ViewPropTypes';
35+
import type { Style } from './styles';
3536

3637
type ReactNativeProps = {
3738
accessible?: ViewProps['accessible'],
@@ -147,11 +148,17 @@ type ReactNativeStyleValue =
147148

148149
type ReactNativeStyle = { [string]: ?ReactNativeStyleValue };
149150

151+
type StrictReactNativeMetaProps = {
152+
+inheritedTextStyle: Style,
153+
+resolveStyleValue: (value: string) => string | number | null
154+
};
155+
150156
export type {
151157
CompositeAnimation,
152158
ReactNativeProps,
153159
ReactNativeStyle,
154160
ReactNativeStyleValue,
155161
ReactNativeTransform,
162+
StrictReactNativeMetaProps,
156163
SyntheticEvent
157164
};

0 commit comments

Comments
 (0)