Skip to content

feat: Enable default prop value in pseudoselectors#9631

Open
wisniewskij wants to merge 6 commits into
@wisniewskij/pseudo-selectors-webfrom
@wisniewskij/pseudo-defaults
Open

feat: Enable default prop value in pseudoselectors#9631
wisniewskij wants to merge 6 commits into
@wisniewskij/pseudo-selectors-webfrom
@wisniewskij/pseudo-defaults

Conversation

@wisniewskij

Copy link
Copy Markdown
Contributor

Summary

Previously passing 'undefined' as value to a pseudoselector would result in dropping it - now it's pushed towards the c++ layer anyways, which interprets 'undefined' as 'default prop value'.

Test plan

There are tests for that under CSS -> Transitions -> Pseudoselectors -> Active

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the CSS pseudoselector pipeline so that an explicit undefined selector value (and a missing default) is forwarded through to native, allowing the C++ interpolator layer to interpret it as “use the platform/default prop value” during transitions.

Changes:

  • Preserve default entries for pseudoselector props even when default is omitted (so missing defaults can be represented downstream).
  • After building native props, reinstate undefined pseudoselector/default values as null so native can treat them as “default value”.
  • Extend unit tests and add an example screen section demonstrating “reset to default” behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/react-native-reanimated/src/css/utils/props.ts Keeps per-selector defaultStyle[prop] even when the default is omitted (undefined).
packages/react-native-reanimated/src/css/native/managers/CSSPseudoStylesManager.ts Reintroduces stripped undefined values post-build as null before constructing transition configs.
packages/react-native-reanimated/src/css/native/managers/tests/CSSPseudoStylesManager.test.ts Adds/updates tests to assert undefined/missing-default is forwarded to native as null.
apps/common-app/src/apps/css/examples/transitions/screens/pseudoSelectors/Active.tsx Adds demo cases showing reset-to-default behavior for undefined selector values and omitted defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread packages/react-native-reanimated/src/css/utils/__tests__/props.test.ts Outdated

@MatiPl01 MatiPl01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few suggestions to implement

Comment thread packages/react-native-reanimated/src/css/utils/props.ts
Comment thread packages/react-native-reanimated/src/css/utils/guards.ts
Comment on lines +167 to +191
selectorStyle: { backgroundColor: null },
defaultStyle: { backgroundColor: 'red' },
transition: {
backgroundColor: expect.objectContaining({
value: ['red', null],
}),
},
})
);
});

test('forwards an omitted default to native as null so C++ can inject the default', () => {
pushStyle(manager, {
backgroundColor: { ':hover': 'red' },
});

expect(registerPseudoStyle).toHaveBeenCalledWith(
shadowNodeWrapper,
expect.objectContaining({
selector: ':hover',
selectorStyle: { backgroundColor: 'red' },
defaultStyle: { backgroundColor: null },
transition: {
backgroundColor: expect.objectContaining({
value: ['red', undefined],
value: [null, 'red'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we better pass undefined for consistency with animation keyframes that can also contain undefined values? It should be automatically converted to null in the folly::dynamic then so the resulting behavior should be unchanged.

});
});

describe('undefined / missing-default resolution (against a stripping builder)', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need these stripping builder tests. I think that the pseudo selector processing logic is a bit unrelated to the builder and these tests don't add much value. What do you think? Can you see any value in keeping them?

Comment on lines 107 to +163
@@ -133,6 +146,22 @@ export default class CSSPseudoStylesManager implements ICSSPseudoStylesManager {
}
}

/* Re-adds keys whose value was `undefined` in the source style (and thus dropped by the props builder)
* as `null`, which is interpreted on the C++ side as “use default value”.
* Only keys present in `supportedKeys` are revived.
*/
function reinstateResolvedUndefinedProps(
source: UnknownRecord,
built: UnknownRecord,
supportedKeys: Set<string>
): void {
for (const key in source) {
if (source[key] === undefined && supportedKeys.has(key)) {
built[key] = null;
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this entire complex logic might be replaced with just a proper build call on the props builder which is passed the includeUnprocessed: true option in its config. Since undefined values aren't processed, they are normally includeUnprocessed` to true changes this behavior and we include any value, even if it is unchanged during processing.

Comment on lines +55 to +58
const resets = Object.keys(selectorStyle)
.filter((prop) => selectorStyle[prop] === undefined)
.map((prop) => `${kebabizeCamelCase(prop)}: unset !important`)
.join('; ');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this filter/map combo. Can't we maybe extend the web builder in such a way that we replace undefined with initial? Maybe it could do this just when we pass includeUnprocessed: true?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants