Skip to content

Commit 507bd9b

Browse files
committed
Add support for setting a custom viewport width
1 parent c5251b8 commit 507bd9b

14 files changed

Lines changed: 310 additions & 23 deletions

File tree

packages/benchmarks/perf/tests/css-props-tests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function runSuite(opts) {
2020
hover: true,
2121
inheritedFontSize: 16,
2222
viewportHeight: 600,
23+
viewportScale: 1,
2324
viewportWidth: 1024
2425
};
2526

@@ -29,6 +30,7 @@ function runSuite(opts) {
2930
hover: true,
3031
inheritedFontSize: 16,
3132
viewportHeight: 600,
33+
viewportScale: 1,
3234
viewportWidth: 1024
3335
};
3436

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ import type {
1818
import typeof * as TStyleX from '@stylexjs/stylex';
1919

2020
import * as React from 'react';
21+
import { useMemo } from 'react';
2122
import * as compat from './compat';
2223
import * as html from './html';
2324
import * as stylex from './stylex';
2425
import { ProvideCustomProperties } from './modules/ContextCustomProperties';
26+
import { ProvideViewportScale } from './modules/ContextViewportScale';
27+
import * as ReactNative from './react-native';
2528

2629
type StyleTheme<V, T> = Theme<V, T>;
2730
type StyleVars<T> = VarGroup<T>;
@@ -35,6 +38,11 @@ type ProviderProps = $ReadOnly<{
3538
customProperties: ProviderValue
3639
}>;
3740

41+
type ViewportProviderProps = $ReadOnly<{
42+
children: React.Node,
43+
viewportWidth: number
44+
}>;
45+
3846
export type { StaticStyles, StyleTheme, StyleVars, Styles, StylesWithout };
3947

4048
function ThemeProvider(props: ProviderProps): React.Node {
@@ -47,8 +55,29 @@ function ThemeProvider(props: ProviderProps): React.Node {
4755
);
4856
}
4957

58+
function ViewportProvider({
59+
viewportWidth: logicalViewportWidth,
60+
children
61+
}: ViewportProviderProps): React.Node {
62+
const { width: viewportWidth } = ReactNative.useWindowDimensions();
63+
64+
const viewportScale = useMemo(
65+
() => ({
66+
scale: viewportWidth / logicalViewportWidth
67+
}),
68+
[logicalViewportWidth, viewportWidth]
69+
);
70+
71+
return (
72+
<ProvideViewportScale value={viewportScale}>
73+
{children}
74+
</ProvideViewportScale>
75+
);
76+
}
77+
5078
const contexts = {
51-
ThemeProvider: ThemeProvider as typeof ThemeProvider
79+
ThemeProvider: ThemeProvider as typeof ThemeProvider,
80+
ViewportProvider: ViewportProvider as typeof ViewportProvider
5281
};
5382

5483
// Export using StyleX types as the shim has divergent types internally.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
*/
9+
10+
import React from 'react';
11+
12+
type ViewportScale = $ReadOnly<{
13+
scale: number
14+
}>;
15+
16+
const ContextViewportScale: React.Context<ViewportScale> = React.createContext({
17+
scale: 1
18+
});
19+
20+
if (__DEV__) {
21+
ContextViewportScale.displayName = 'ContextViewportScale';
22+
}
23+
24+
export const ProvideViewportScale = ContextViewportScale.Provider;
25+
26+
export function useViewportScale(): ViewportScale {
27+
return React.useContext(ContextViewportScale);
28+
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as React from 'react';
1313

1414
import { useElementCallback } from '../../shared/useElementCallback';
1515
import { errorMsg } from '../../shared/logUtils';
16+
import { useViewportScale } from './ContextViewportScale';
1617

1718
function errorUnimplemented(name: string) {
1819
if (__DEV__) {
@@ -33,6 +34,7 @@ type Options = {
3334
};
3435

3536
export function useStrictDOMElement<T>({ tagName }: Options): CallbackRef<T> {
37+
const { scale: viewportScale } = useViewportScale();
3638
const elementCallback = useElementCallback(
3739
React.useCallback(
3840
// $FlowFixMe[unclear-type]
@@ -83,6 +85,20 @@ export function useStrictDOMElement<T>({ tagName }: Options): CallbackRef<T> {
8385
};
8486
}
8587

88+
if (viewportScale !== 1) {
89+
const getBoundingClientRect = node.getBoundingClientRect;
90+
node.getBoundingClientRect = function () {
91+
const rect = getBoundingClientRect.call(node);
92+
93+
return new DOMRect(
94+
rect.x / viewportScale,
95+
rect.y / viewportScale,
96+
rect.width / viewportScale,
97+
rect.height / viewportScale
98+
);
99+
};
100+
}
101+
86102
const { getRootNode } = node;
87103
if (getRootNode == null) {
88104
node.getRootNode = () => errorUnimplemented('getRootNode');
@@ -157,7 +173,7 @@ export function useStrictDOMElement<T>({ tagName }: Options): CallbackRef<T> {
157173
}
158174
}
159175
},
160-
[tagName]
176+
[tagName, viewportScale]
161177
)
162178
);
163179

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { flattenStyle } from './flattenStyle';
2020
import { useInheritedStyles } from './ContextInheritedStyles';
2121
import { usePseudoStates } from './usePseudoStates';
2222
import { useStyleTransition } from './useStyleTransition';
23+
import { useViewportScale } from './ContextViewportScale';
2324

2425
type StyleOptions = {
2526
customProperties: ?CustomProperties,
@@ -66,6 +67,7 @@ export function useStyleProps(
6667

6768
const { fontScale, height, width } = ReactNative.useWindowDimensions();
6869
const colorScheme = ReactNative.useColorScheme();
70+
const { scale: viewportScale } = useViewportScale();
6971

7072
// These values are already computed
7173
const {
@@ -104,6 +106,7 @@ export function useStyleProps(
104106
inheritedFontSize:
105107
typeof inheritedFontSize === 'number' ? inheritedFontSize : undefined,
106108
viewportHeight: height,
109+
viewportScale,
107110
viewportWidth: width
108111
},
109112
flatStyle as $FlowFixMe

packages/react-strict-dom/src/native/stylex/CSSLengthUnitValue.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ type ResolvePixelValueOptions = $ReadOnly<{
1717
fontScale: number | void,
1818
inheritedFontSize: ?number,
1919
viewportHeight: number,
20+
viewportScale: number,
2021
viewportWidth: number
2122
}>;
2223

23-
type ParsedValue = [+value: number, +unit: CSSLengthUnitType] | null;
24-
25-
const memoizedValues = new Map<string, ParsedValue>();
24+
const memoizedValues = new Map<string, CSSLengthUnitValue | null>();
2625

2726
// TODO: this only works on simple values
2827
export class CSSLengthUnitValue {
29-
static parse(input: string): ParsedValue {
28+
static parse(input: string): CSSLengthUnitValue | null {
3029
const memoizedValue = memoizedValues.get(input);
3130
if (memoizedValue !== undefined) {
3231
return memoizedValue;
@@ -40,9 +39,9 @@ export class CSSLengthUnitValue {
4039
const value = match[1];
4140
const unit: $FlowFixMe = match[2];
4241
const parsedFloat: number = parseFloat(value);
43-
const parsedValue: ParsedValue = [parsedFloat, unit];
44-
memoizedValues.set(input, parsedValue);
45-
return parsedValue;
42+
const cssLengthUnitValue = new CSSLengthUnitValue(parsedFloat, unit)
43+
memoizedValues.set(input, cssLengthUnitValue);
44+
return cssLengthUnitValue;
4645
}
4746

4847
value: number;
@@ -58,7 +57,8 @@ export class CSSLengthUnitValue {
5857
viewportWidth,
5958
viewportHeight,
6059
fontScale = 1,
61-
inheritedFontSize
60+
inheritedFontSize,
61+
viewportScale
6262
} = options;
6363
const unit = this.unit;
6464
const value = this.value;
@@ -72,10 +72,10 @@ export class CSSLengthUnitValue {
7272
}
7373
}
7474
case 'px': {
75-
return value;
75+
return value * viewportScale;
7676
}
7777
case 'rem': {
78-
return fontScale * 16 * value;
78+
return fontScale * 16 * value * viewportScale;
7979
}
8080
case 'vh': {
8181
return viewportHeight * valuePercent;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
*/
9+
10+
import type { ReactNativeTransform } from '../../types/renderer.native';
11+
12+
export class CSSTransformValue {
13+
value: $ReadOnlyArray<ReactNativeTransform>;
14+
cachedScaledTransform: void | {
15+
viewportScale: number,
16+
value: $ReadOnlyArray<ReactNativeTransform>
17+
};
18+
19+
constructor(value: $ReadOnlyArray<ReactNativeTransform>) {
20+
this.value = value;
21+
}
22+
23+
resolveTransformValue(
24+
viewportScale: number
25+
): $ReadOnlyArray<ReactNativeTransform> {
26+
if (viewportScale === 1) {
27+
return this.value;
28+
}
29+
if (
30+
this.cachedScaledTransform != null &&
31+
this.cachedScaledTransform.viewportScale === viewportScale
32+
) {
33+
return this.cachedScaledTransform.value;
34+
}
35+
36+
const scaledTransform = this.value.map((transform) => {
37+
if (
38+
transform.translateX != null &&
39+
typeof transform.translateX === 'number'
40+
) {
41+
return { translateX: transform.translateX * viewportScale };
42+
}
43+
if (
44+
transform.translateY != null &&
45+
typeof transform.translateY === 'number'
46+
) {
47+
return { translateY: transform.translateY * viewportScale };
48+
}
49+
return transform;
50+
});
51+
52+
this.cachedScaledTransform = {
53+
viewportScale,
54+
value: scaledTransform
55+
};
56+
57+
return scaledTransform;
58+
}
59+
}

packages/react-strict-dom/src/native/stylex/__tests__/parseTransform-test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import { parseTransform } from '../parseTransform';
8+
import { parseTransform as parseTransformImpl } from '../parseTransform';
9+
10+
function parseTransform(transform) {
11+
return parseTransformImpl(transform).resolveTransformValue(1);
12+
}
913

1014
describe('parseTransform', () => {
1115
test('perspective', () => {

packages/react-strict-dom/src/native/stylex/index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
stringContainsVariables
3535
} from './customProperties';
3636
import { version } from '../modules/version';
37+
import { CSSTransformValue } from './CSSTransformValue';
38+
import { LENGTH_PROPS } from './lengthProps';
3739

3840
type ResolveStyleOptions = $ReadOnly<{
3941
active?: ?boolean,
@@ -44,6 +46,7 @@ type ResolveStyleOptions = $ReadOnly<{
4446
hover?: ?boolean,
4547
inheritedFontSize: ?number,
4648
viewportHeight: number,
49+
viewportScale: number,
4750
viewportWidth: number,
4851
writingDirection?: ?'ltr' | 'rtl'
4952
}>;
@@ -197,7 +200,7 @@ function processStyle(
197200

198201
const maybeLengthUnitValue = CSSLengthUnitValue.parse(styleValue);
199202
if (maybeLengthUnitValue != null) {
200-
result[propName] = new CSSLengthUnitValue(...maybeLengthUnitValue);
203+
result[propName] = maybeLengthUnitValue;
201204
continue;
202205
// React Native doesn't support these keywords or functions
203206
} else if (styleValue === 'inherit' || styleValue === 'unset') {
@@ -295,6 +298,7 @@ function resolveStyle(
295298
hover,
296299
inheritedFontSize,
297300
viewportHeight,
301+
viewportScale,
298302
viewportWidth
299303
} = options;
300304
const colorScheme = options.colorScheme || 'light';
@@ -331,6 +335,7 @@ function resolveStyle(
331335
fontScale,
332336
inheritedFontSize: inheritedFontSize,
333337
viewportHeight,
338+
viewportScale,
334339
viewportWidth
335340
});
336341
continue;
@@ -343,6 +348,7 @@ function resolveStyle(
343348
fontScale,
344349
inheritedFontSize: fontSize,
345350
viewportHeight,
351+
viewportScale,
346352
viewportWidth
347353
});
348354
} else {
@@ -352,6 +358,19 @@ function resolveStyle(
352358
}
353359
}
354360

361+
if (styleValue instanceof CSSTransformValue) {
362+
result[propName] = styleValue.resolveTransformValue(viewportScale);
363+
continue;
364+
}
365+
if (
366+
viewportScale !== 1 &&
367+
typeof styleValue === 'number' &&
368+
LENGTH_PROPS.has(propName)
369+
) {
370+
result[propName] = styleValue * viewportScale;
371+
continue;
372+
}
373+
355374
// Resolve the stylex object-value syntax
356375
if (
357376
styleValue != null &&

0 commit comments

Comments
 (0)