Skip to content

fix(core): resolve css inherit on non-inherited properties to the parent value - #11521

Draft
edusperoni wants to merge 1 commit into
mainfrom
fix/css-inherit-non-inherited-properties
Draft

edusperoni wants to merge 1 commit into
mainfrom
fix/css-inherit-non-inherited-properties

Conversation

@edusperoni

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

Every css-wide keyword goes through one reset branch (isResetValue). InheritedCssProperty (color, font-size, ...) resolves the keywords per spec, but a plain CssProperty and a CssAnimationProperty map every keyword to the default. For a non-inherited property, initial, unset and revert → initial value is correct, but inherit must take the parent's value:

StackLayout { background-color: red; margin-top: 8; }
Button { background-color: inherit; margin-top: inherit; } /* default, not red / 8 */

Shorthands are worse: their converters only parse values, so margin: inherit, border-color: inherit, padding: inherit, background: inherit, ... fail to expand and are not applied at all, and font: inherit / transform: inherit turn into fixed values (font-style: normal, translateX: 0, ...).

What is the new behavior?

A stylesheet inherit on a non-inherited property resolves to the parent view's current value, or the default when there is no parent or the parent has no value.

  • CssProperty: the css setter resolves inherit against the parent style and marks the value ValueSource.Inherited (also while the parent is at its default, so it keeps following it). _isCssValueStillApplied treats a recorded inherit as applied only while that mark is there, so a released local value makes the cascade write it again, while an untouched one is still skipped.
  • CssAnimationProperty (background-color, opacity, width, height, transforms): the css slot holds the parent's computed value. It keeps following the parent under a local or keyframe value, so it is current when they are released.
  • Live tracking: non-inherited values are never pushed down the tree, so a child that resolves inherit subscribes to the parent style's <property>Change event for that one property and writes inherit again on each change. The subscription is created only when a stylesheet resolves a property to inherit, and dropped as soon as the stylesheet (or, for a CssProperty, a local value) stops doing so. The common path adds no listeners, allocations or child walks: the parent's setter already checks hasListeners before notifying, and the setters only gain a source comparison.
    • Ordering: the parent's own css or a later local set reaches the child through the same event, whichever runs first.
    • Re-parenting: clearInheritedProperties (removal) and propagateInheritableCssProperties (add) re-resolve the followed properties, which moves the subscription to the new parent, or resolves to the default with none.
    • Native updates go through the existing setter paths unchanged (suspended updates, defaultValueKey capture/restore, valueChanged, equalityComparer, change events). A child following a parent at its default takes the reset path, so its own native default is restored.
  • Shorthands: _expandCssShorthand expands a css-wide keyword into the same keyword on every longhand (margin: inherit → margin-top: inherit, ...), so each longhand applies it with its own semantics. This covers stylesheet rules, inline styles and var() substitutions that resolve to a keyword.
  • Keywords set from code are unchanged and documented. view.style.backgroundColor = 'inherit' (or 'initial', ...) still just releases the local value. Unlike an inline CSS declaration it is not a sticky value that outranks the stylesheet; that is intentional and now documented on isResetValue and the CssProperty local setter.

Tests:

  • packages/core/ui/core/properties/css-inherit-non-inherited.spec.ts (26 tests, 15 of them fail on main): parent value, following later parent changes, the parent's css applied after the child, chained inherit, no parent / parent at default, initial/unset/revert still → default, re-parenting (incl. dropping the old parent's listener), the stylesheet changing away from inherit, local value over inherit and release, the unchanged-inherit skip on the next pass, native writes, no listener on the parent when no child uses inherit, background-color/opacity, and margin/border-color/font: inherit plus margin: initial.
  • apps/automated/src/ui/styling/css-keywords-tests.ts: added test_css_inherit_on_non_inherited_property and test_css_inherit_on_shorthand. test_value_after_inherit (code-set 'inherit' releases the local value) still holds, as it pins the intended local-keyword behavior; its stale TODO now says so. I reviewed the other automated style tests that use keywords or unsetValue (value-source-tests.ts, style-tests.ts); they exercise inherited properties or local resets and are not affected. The automated app was not run on a device or simulator for this PR.

Related: #11520 (local reset restores the cascaded value) and #11413 (inherited children reset cascade). This branch merges cleanly with each of them and with both together, and the core suite passes on the three-way merge. One interaction to settle once both land: #11520's cascadedCssValue treats a cascaded inherit as "no css value", so a local reset over a stylesheet inherit on a CssProperty falls back to the default until the next cascade pass, which then re-resolves it (a CssAnimationProperty is already correct). Routing a cascaded inherit through style[cssName] = 'inherit' in that path would close it.

…ent value

A stylesheet `inherit` on a property that is not inherited by default
(margin, border, background-color, opacity, width, ...) went through the same
reset branch as `initial` and `unset`, so it always produced the default
value instead of the parent's.

The css setters of CssProperty and CssAnimationProperty now resolve `inherit`
against the parent style. Since non-inherited values are never pushed down
the tree, the child subscribes to the parent's change event for that one
property, re-resolves on every parent change, and re-subscribes when it is
moved to another parent. The subscription only exists while a stylesheet
resolves the property to `inherit`, so the common path adds no listeners,
allocations or child walks.

Shorthands now expand a css-wide keyword to the same keyword on every
longhand. Their converters only parse values, so `margin: inherit` and
`border-color: inherit` failed to apply, and `font: inherit` and
`transform: inherit` turned into fixed values.

A keyword set from code keeps releasing the local value; that is
documented on isResetValue.
@nx-cloud

nx-cloud Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 236c81f

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 3m 8s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded <1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-12 18:45:21 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11521
npm i https://pkg.pr.new/@nativescript/vite@11521
npm i https://pkg.pr.new/@nativescript/webpack@11521

commit: 236c81f

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant