Skip to content

fix(core): resolve percentage translate transforms against element size - #11481

Open
aleclarson wants to merge 1 commit into
NativeScript:mainfrom
aleclarson:fix/css-translate-percent
Open

aleclarson wants to merge 1 commit into
NativeScript:mainfrom
aleclarson:fix/css-translate-percent

Conversation

@aleclarson

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

Percentage arguments to translate()/translateX()/translateY()/translate3d() in a transform declaration are parsed with parseFloat, which silently drops the % and applies the number as dips. transform: translate(-50%, -50%) moves a view by -50dip instead of -50% of its own size. The translateX/translateY longhand properties mis-parse the same way via FixedLength.parse, and keyframe transform declarations hit the same path.

What is the new behavior?

  • Translate values are parsed and stored as CoreTypes.PercentLengthType (the same mechanism width/height/margin already use) via PercentLength.parse, so %, dip, px, and plain numbers are all represented in the style value.
  • Percentages are resolved against the element's own size when the native transform is applied: resolveTranslate(value, basis) in css-transform.ts is used by iOS updateNativeTransform() and Android translateX/YProperty.setNative / layout (layoutNativeView). Resolution re-runs on every size change (_onSizeChanged on iOS, every native layout on Android), so translate(-50%, -50%) stays centered across resizes.
  • Duplicate translate functions in one transform (e.g. translateX(-50%) translateY(-50%), which both map to property: 'translate') are merged per-axis instead of going through the affine-matrix path. Percent units are preserved when an axis has a single contribution or same-unit contributions; unrepresentable mixed-unit sums (e.g. translate(10) translateX(-50%)) degrade to the previous numeric coercion rather than producing NaN, so nothing regressively breaks in the matrix path.
  • transform: inside @keyframes and view.animate({ translate }) normalize translate through PercentLength.parse, and the platform animators resolve percentages against the target's bounds — so @keyframes { transform: translate(0, -100%) } slide-in animations work.
  • view.translateX/translateY are typed CoreTypes.PercentLengthType (numbers still accepted); translateX: 10px, which previously produced NaN on Android, now resolves correctly.

Fixes #11457

Note on existing quirks kept intact: scale/rotate parsing is unchanged, and the matrix-composition path (multiple scale/rotate functions) keeps its prior semantics — percentage translates hitting it degrade to the old numeric behavior rather than erroring.

Test plan

  • New ui/styling/css-transform.spec.ts covers percent/dip/px translate parsing, translateX/translateY functions, duplicate-translate merging, mixed-unit degradation, the translateX/Y property converters, and resolveTranslate basis resolution
  • npx nx run core:test — 515 tests pass
  • npx nx run apps-automated:ios / :android — native transform application on device/simulator (not runnable in this environment; percentage re-resolution on resize happens in layoutNativeView (Android) / _onSizeChanged (iOS))

translate() arguments in `transform` declarations were parsed with
parseFloat, so `translate(-50%, -50%)` silently became -50dip instead of
-50% of the element's own size. translateX/translateY longhands had the
same problem via FixedLength.parse.

Translate values are now parsed and stored as PercentLengthType and
resolved against the view's own width/height when the native transform
is applied and on every subsequent layout, matching CSS semantics.
Duplicate translate functions in a transform list are merged per-axis
so percentage units survive where they don't mix with other units on
the same axis.

Fixes NativeScript#11457
@nx-cloud

nx-cloud Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 85d5f5b

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

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


☁️ Nx Cloud last updated this comment at 2026-09-30 18:10:51 UTC

@pkg-pr-new

pkg-pr-new Bot commented Sep 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 85d5f5b


const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
const TRANSFORMATIONS = Object.freeze<TransformationType[]>(['rotate', 'rotateX', 'rotateY', 'rotate3d', 'translate', 'translate3d', 'translateX', 'translateY', 'scale', 'scale3d', 'scaleX', 'scaleY']);
const TRANSLATE_TRANSFORMATIONS = Object.freeze<TransformationType[]>(['translate', 'translate3d', 'translateX', 'translateY']);

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.

We could have translate array defined on top and concatenate/merge it with the other prop array to avoid keep track of props twice.

* against the element's own size.
*/
export function isPercentTranslate(value: CoreTypes.PercentLengthType): boolean {
return typeof value === 'object' && value !== null && value.unit === '%';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

return value?.unit === '%'; should be enough

* @param value The translate axis value as a PercentLengthType.
* @param basis The element's size in dips along the translated axis.
*/
export function resolveTranslate(value: CoreTypes.PercentLengthType, basis: number): CoreTypes.dip {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

wouldnt it be better to define PercentLength.toDpi or something?

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.

CSS transform: percentage translate values silently parsed as dips (translate(-50%) → -50dip)

3 participants