fix(core): resolve percentage translate transforms against element size - #11481
Open
aleclarson wants to merge 1 commit into
Open
aleclarson wants to merge 1 commit into
aleclarson wants to merge 1 commit into
Conversation
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
|
View your CI Pipeline Execution ↗ for commit 85d5f5b
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
CatchABus
reviewed
Sep 30, 2026
|
|
||
| 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']); |
Contributor
There was a problem hiding this comment.
We could have translate array defined on top and concatenate/merge it with the other prop array to avoid keep track of props twice.
CatchABus
requested changes
Sep 30, 2026
farfromrefug
reviewed
Oct 1, 2026
| * against the element's own size. | ||
| */ | ||
| export function isPercentTranslate(value: CoreTypes.PercentLengthType): boolean { | ||
| return typeof value === 'object' && value !== null && value.unit === '%'; |
Collaborator
There was a problem hiding this comment.
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 { |
Collaborator
There was a problem hiding this comment.
wouldnt it be better to define PercentLength.toDpi or something?
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
What is the current behavior?
Percentage arguments to
translate()/translateX()/translateY()/translate3d()in atransformdeclaration are parsed withparseFloat, 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. ThetranslateX/translateYlonghand properties mis-parse the same way viaFixedLength.parse, and keyframetransformdeclarations hit the same path.What is the new behavior?
CoreTypes.PercentLengthType(the same mechanismwidth/height/marginalready use) viaPercentLength.parse, so%,dip,px, and plain numbers are all represented in the style value.resolveTranslate(value, basis)incss-transform.tsis used by iOSupdateNativeTransform()and AndroidtranslateX/YProperty.setNative/ layout (layoutNativeView). Resolution re-runs on every size change (_onSizeChangedon iOS, every native layout on Android), sotranslate(-50%, -50%)stays centered across resizes.translatefunctions in onetransform(e.g.translateX(-50%) translateY(-50%), which both map toproperty: '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@keyframesandview.animate({ translate })normalize translate throughPercentLength.parse, and the platform animators resolve percentages against the target's bounds — so@keyframes { transform: translate(0, -100%) }slide-in animations work.view.translateX/translateYare typedCoreTypes.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/rotatefunctions) keeps its prior semantics — percentage translates hitting it degrade to the old numeric behavior rather than erroring.Test plan
ui/styling/css-transform.spec.tscovers percent/dip/px translate parsing,translateX/translateYfunctions, duplicate-translate merging, mixed-unit degradation, thetranslateX/Yproperty converters, andresolveTranslatebasis resolutionnpx nx run core:test— 515 tests passnpx nx run apps-automated:ios/:android— native transform application on device/simulator (not runnable in this environment; percentage re-resolution on resize happens inlayoutNativeView(Android) /_onSizeChanged(iOS))