Skip to content

feat: calc support#56162

Open
paradowstack wants to merge 5 commits intofacebook:mainfrom
paradowstack:feat/calc
Open

feat: calc support#56162
paradowstack wants to merge 5 commits intofacebook:mainfrom
paradowstack:feat/calc

Conversation

@paradowstack
Copy link
Copy Markdown
Contributor

Why?

calc() is a core CSS primitive and a common expectation for developers. Adding it to React Native enables more expressive and maintainable styles without JS-side manual calculations. It improves parity across web and native worlds, reducing friction when switching from another platform.

My tweet about this potential feature in React Native was really well received and many people expressed excitement about it.

Yoga PR link

Summary:

Bring Yoga dynamic value resolution into React Native and wire calc()-driven values.

Yoga resolves dynamic values during layout by calling back into React Native with layout context and dynamic id, allowing mixed-unit expressions to be evaluated just-in-time.

Changelog:

[GENERAL] [ADDED] - CSS calc() support

This PR includes RN-side integration needed to make Yoga dynamic values usable in renderer layout paths.

  • Wire Yoga dynamic callback resolution to RN calc expression storage and lookup.
  • Apply dynamic calc resolution for layout properties: dimensions, min/max, flex-basis, gap, position, margin, and padding.
  • Add viewport-aware resolution flow for vw/vh-based expressions.

Missing:

To keep this PR focused, some areas are intentionally out of scope and will follow in separate work.

  • Missing calc support for other CSS properties areas:
    • borders & outlines
    • transforms
    • typography
  • Missing calc support for transform-related layout properties.
  • Missing full JNI/Java/JS API surface completion.

Test Plan:

Added focused C++ tests for calc parsing/evaluation.

Broader platform and API-level testing will be expanded in follow-up PRs.

cc @NickGerleman

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Mar 19, 2026
@paradowstack paradowstack marked this pull request as ready for review March 19, 2026 19:15
@facebook-github-tools facebook-github-tools bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Mar 19, 2026
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::All, "inset");

#define APPLY_CALC_COMMON(fieldName, setPoints, setPercent, setDynamic) \
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.

nit: why does this need to be a macro?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It does not - I wrote it as macros because this file already achieve similar functionality using macros (REBUILD_FIELD_YG_* stuff) and I got the feeling it would be more consistent.
Would you like me to rewrite it using template functions?

/*
* Viewport size is size of the React Native's root view.
*/
Size viewportSize{};
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.

Ooh, this may be helpful in more places in the future 👍. If you had a PR for just the plumbing for e.g. this part of the change, would be quick to import and accept.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I extracted it to the separated PR: #56209

}

TEST(CSSCalc, simple_pixel_value) {
auto result = parseCalc("calc(10px)");
Copy link
Copy Markdown
Contributor

@NickGerleman NickGerleman Mar 24, 2026

Choose a reason for hiding this comment

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

I'm trying to figure out how CSSCalc works, more generically.

Ideally, any place where we use a specific CSS type, valid calc expression should work. So e.g. if we are parsing the result of a box-shadow, which expects a <length> in the middle, that length should be able to be some arbitrary calc() expression.

Like 0 calc(16px - 2px) should be valid, for reading something that requires two lengths.

Can we make that sort of scenario work? If we can do this sort of more generic substitution, it also gives us a way later to introduce variables.

Comment on lines +287 to +297
auto opResult =
parser.syntaxParser().consumeComponentValue<std::optional<char>>(
CSSDelimiter::None, [](const CSSPreservedToken &token) {
if (token.type() == CSSTokenType::Delim) {
auto sv = token.stringValue();
if (!sv.empty() && (sv[0] == '+' || sv[0] == '-')) {
return std::optional<char>{sv[0]};
}
}
return std::optional<char>{};
});
Copy link
Copy Markdown
Contributor

@NickGerleman NickGerleman Mar 24, 2026

Choose a reason for hiding this comment

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

IIRC these aren't actually Delim tokens in the spec, but CSSTokenizer, didn't add any of the tokens for calc(), calc-constants, etc. This change will need to update tokenizer layer a bit. Adding tokenizer awareness of calc tokens, could be its own change, that would be easy to merge independently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Please take a look at my attempt to do that: #56234

I am just getting familiar with CSS spec, so let me know if things should land in different files or shape, I was a little confused. I explored a few potential solution and thought that this would be the most correct - but I am happy to revisit and adjust it.

meta-codesync bot pushed a commit that referenced this pull request Mar 27, 2026
Summary:
Add viewport size to `LayoutContext` and wire it through Android and iOS layout setup so viewport dimensions are available during layout.

This was extracted from the larger `calc()` work in PR #56162.

## Changelog:

[GENERAL] [ADDED] - Add viewport size to LayoutContext

Pull Request resolved: #56209

Test Plan:
- This change is limited to internal layout plumbing in React Native core and does not alter external behavior on its own.
 - Validation for the actual `calc()` use cases will be covered in the follow-up work that consumes this plumbing.

Reviewed By: christophpurrer

Differential Revision: D98004758

Pulled By: NickGerleman

fbshipit-source-id: 3fd6257b2c280442a41308af3e6eff30b51a3397
@paradowstack
Copy link
Copy Markdown
Contributor Author

Hello @NickGerleman,

I am working right now on adding the support for calc() for all style properties. The goal is to be able to use calc() expressions in every place that expects value of type <length>, <number>, <percentage>, etc.

For expressions that can be evaluated in the parse time during Render phase that should be pretty straightforward and borderRadius: "calc(10px)" will be directly transformed to borderRadius: 10px. However, parse time evaluation cannot be achieved for properties with relative units (vw and vh for now, but later also em, rem, ex, etc), and for properties mixing percentage and dimensions (<type-percentage>, e.g. backgroundPosition: "calc(10px + 25%) calc(25% + 10vw)").

After the investigation I came up to some conclusions and below is my implementation plan. I would appreciate your feedback on it.

  1. Absolute value calc() expression should be simply evaluated in the parse time.
    • This requires changes mostly in conversion area, such as:
      • process<Property>.js* files
        • Pass calc() string equation to C++ layer, not only number | '${number}%' format as it is now
      • conversion.h files
        • Add auto calc() -> ValueUnit conversion
      • BoxShadowPropsConversions.h and similar conversion files
        • Modify all places that expects Float type
  2. Mixed/Combined calc() expressions cannot be evaluated at this stage due to lack of information about reference values. Therefore, calc() expression has to be stored. Potential solutions I considered:
    • Extend existing types, such as ValueUnit, to support new dynamic calculation.
      • Can be achieved similarly to yoga::StyleLength, by:
        • Holding a calc() ID (and store equation coefficients in separated place), or
        • Holding a full calc() equation.
      • Later on during props diffing and mounting these values has to be obtained using dedicated convenience resolve methods (e.g. BaseViewProps::resolveTransform(...) method and new ones)
      • This requires:
        • Introducing new type and use it everywhere currently Float or ValueUnit types are used in property classes.
        • Provide resolve<Property>() convenience functions
    • Add a "post-process" operation (when required information are available) on Props that replaces all unresolved calc() properties with resolved values.
      • During parsing property values that cannot be evaluated has to be marked as Undefined/Unresolved, calc() equation has to be stored and connected with property.

Please let me know if you agree with conclusions on point 1, maybe I am missing something here?
How do you see implementation and solution of problem 2? Perhaps there is another, better solution.

Thanks in advance!

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Callstack Partner: Callstack Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants