Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import processBoxShadow from '../../StyleSheet/processBoxShadow';
import processColor from '../../StyleSheet/processColor';
import processFilter from '../../StyleSheet/processFilter';
import processFontVariant from '../../StyleSheet/processFontVariant';
import {processStyleConditionsProp} from '../../StyleSheet/processStyleConditions';
import processTransform from '../../StyleSheet/processTransform';
import processTransformOrigin from '../../StyleSheet/processTransformOrigin';
import sizesDiffer from '../../Utilities/differ/sizesDiffer';
Expand Down Expand Up @@ -282,6 +283,11 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
resizeMode: true,
tintColor: colorAttribute,
objectFit: true,

/**
* Conditional (media-query) styles.
*/
styleConditions: {process: processStyleConditionsProp},
};

export default ReactNativeStyleAttributes;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import type {AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes';

import flattenStyle from '../../StyleSheet/flattenStyle';
import {processStyleConditionsInStyleProp} from '../../StyleSheet/processStyleConditions';
import deepDiffer from '../../Utilities/differ/deepDiffer';

const emptyObject = {};
Expand Down Expand Up @@ -155,6 +156,20 @@ function diffNestedProperty(
return updatePayload;
}

// process style conditions
if (validAttributesAllowStyleConditions(validAttributes)) {
const processedPrevProp = processStyleConditionsInStyleProp(prevProp);
const processedNextProp = processStyleConditionsInStyleProp(nextProp);
if (processedPrevProp !== prevProp || processedNextProp !== nextProp) {
return diffProperties(
updatePayload,
normalizeProcessedStylePropForDiff(processedPrevProp),
normalizeProcessedStylePropForDiff(processedNextProp),
validAttributes,
);
}
}

if (!prevProp || !nextProp) {
if (nextProp) {
return addNestedProperty(updatePayload, nextProp, validAttributes);
Expand Down Expand Up @@ -210,6 +225,16 @@ function clearNestedProperty(
return updatePayload;
}

// process style conditions
if (validAttributesAllowStyleConditions(validAttributes)) {
const processedPrevProp = processStyleConditionsInStyleProp(prevProp);
if (processedPrevProp !== prevProp) {
prevProp = normalizeProcessedStylePropForDiff(
processedPrevProp,
) as $FlowFixMe as NestedNode;
}
}

if (!Array.isArray(prevProp)) {
// Add each property of the leaf.
return clearProperties(updatePayload, prevProp, validAttributes);
Expand Down Expand Up @@ -418,6 +443,21 @@ function addNestedProperty(
props: Object,
validAttributes: AttributeConfiguration,
): null | Object {
if (props === null || typeof props !== 'object') {
return payload;
}

// process style conditions
if (validAttributesAllowStyleConditions(validAttributes)) {
const processedProps = processStyleConditionsInStyleProp(props);
if (processedProps !== props) {
if (processedProps === null || typeof processedProps !== 'object') {
return payload;
}
props = processedProps as Object;
}
}

// Flatten nested style props.
if (Array.isArray(props)) {
for (let i = 0; i < props.length; i++) {
Expand Down Expand Up @@ -509,3 +549,21 @@ export function diff(
validAttributes,
);
}

function validAttributesAllowStyleConditions(
validAttributes: AttributeConfiguration,
): boolean {
return validAttributes.styleConditions != null;
}

function normalizeProcessedStylePropForDiff(prop: unknown): Object {
if (prop === null || typeof prop !== 'object') {
return emptyObject;
}

if (Array.isArray(prop)) {
return flattenStyle(prop) ?? emptyObject;
}

return prop;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import type {AttributeConfiguration} from '../../../Renderer/shims/ReactNativeTypes';

const ReactNativeStyleAttributes =
require('../../../Components/View/ReactNativeStyleAttributes').default;
const {create, diff} = require('../ReactNativeAttributePayload');

describe('ReactNativeAttributePayload.create', () => {
Expand Down Expand Up @@ -493,3 +495,146 @@ describe('ReactNativeAttributePayload.diff', () => {
expect(process).toBeCalled();
});
});

describe('ReactNativeAttributePayload conditional (media-query) styles', () => {
const validAttributes: AttributeConfiguration = {
style: ReactNativeStyleAttributes,
};

it('create: inlines the default and collects a styleConditions payload', () => {
expect(
create(
{
style: {
width: {default: 100, '@media (orientation: landscape)': 300},
},
},
validAttributes,
),
).toEqual({
width: 100,
styleConditions: {
width: [{query: {orientation: 'landscape'}, value: 300}],
},
});
});

it('create: runs each condition value through its property processor', () => {
const backgroundColorConfig = ReactNativeStyleAttributes.backgroundColor;
const processBackgroundColor =
typeof backgroundColorConfig === 'object' &&
typeof backgroundColorConfig.process === 'function'
? backgroundColorConfig.process
: (value: unknown) => value;

expect(
create(
{
style: {
backgroundColor: {
default: 'red',
'@media (prefers-color-scheme: dark)': 'black',
},
},
},
validAttributes,
),
).toEqual({
backgroundColor: processBackgroundColor('red'),
styleConditions: {
backgroundColor: [
{
query: {colorScheme: 'dark'},
value: processBackgroundColor('black'),
},
],
},
});
});

it('create: flattens before hoisting so array precedence is respected', () => {
expect(
create(
{
style: [
{width: {default: 100, '@media (orientation: landscape)': 300}},
{width: 200},
],
},
validAttributes,
),
).toEqual({width: 200});

// A later conditional value wins -> its conditions survive.
expect(
create(
{
style: [
{width: 200},
{width: {default: 100, '@media (orientation: landscape)': 300}},
],
},
validAttributes,
),
).toEqual({
width: 100,
styleConditions: {
width: [{query: {orientation: 'landscape'}, value: 300}],
},
});
});

it('diff: adds styleConditions when a conditional value appears', () => {
expect(
diff(
{style: {width: 100}},
{
style: {
width: {default: 100, '@media (orientation: landscape)': 300},
},
},
validAttributes,
),
).toEqual({
styleConditions: {
width: [{query: {orientation: 'landscape'}, value: 300}],
},
});
});

it('diff: clears styleConditions (null) when the conditional value is removed', () => {
expect(
diff(
{
style: {
width: {default: 100, '@media (orientation: landscape)': 300},
},
},
{style: {width: 100}},
validAttributes,
),
).toEqual({styleConditions: null});
});

it('diff: emits updated styleConditions when a condition value changes', () => {
expect(
diff(
{
style: {
width: {default: 100, '@media (orientation: landscape)': 300},
},
},
{
style: {
width: {default: 100, '@media (orientation: landscape)': 400},
},
},
validAttributes,
),
).toEqual({
styleConditions: {
width: [{query: {orientation: 'landscape'}, value: 400}],
},
});
});
});
Loading
Loading