Repository navigation
fix: evaluate aspect-ratio media and container queries - #425
YevheniiKotyrlo wants to merge 2 commits into
Conversation
|
Outdated: #426 is now re-written on top of #425, so the two no longer conflict. The original noteCross-referencing #425 and #426, which conflict — flagging it now rather than at merge time. They are independent siblings ( The hazard is in the
Taking #426's arm wholesale therefore un-fixes #425's range container queries. Taking #425's wholesale loses #426's three-valued handling, where The resolution that is correct in both directions evaluates the interval and returns One more line worth keeping whichever way it lands: #426's boolean-context arm uses A two-case test pins both halves, and neither branch passes it alone:
|
|
Outdated: #426 is now re-written on top of #425, so the two no longer conflict. The original noteCorrection to my note above — I undercounted the conflict set, and the omission was the load-bearing file.
A second resolution needs the same care, in Taking #426's arm onto #425's types does not compile — ["!", query.type === "condition" ? query.condition : (["?"] as MediaCondition)]And one more, in |
…unanswerable value `parseMediaFeatureOperand`'s doc comment lists what has no compile-time answer as `env()`, a ratio, an unsupported `calc()`. On this branch that is true, and it stops being true the moment nativewind#425 lands: there a reducible ratio compiles to its quotient and only a degenerate one stays unanswerable. Naming the ratio more precisely does not fix it. A restrictive qualifier — "a ratio the compiler cannot reduce to a finite quotient" — implies a non-empty complement, and on this branch the complement is empty: every ratio, `16/9` and bare `1` included, answers `undefined`, because `case "ratio"` falls through to `case "env"`. So the qualified form is truth-conditionally fine and tells a reader of THIS branch something false about it. There is no wording that names ratios here and is right both before and after nativewind#425. Dropping the ratio is not a narrower list, it is an honest one, because the enumeration was never the case table it reads as. `parseLength` refuses 43 units by name — every physical unit, every font-relative unit but `em`/`rem`, every viewport and container variant but bare `vw`/`vh` — so `(min-width: 1lh)` already answers `null` here with no `calc()` anywhere in it, unlisted. The two members kept are the two verified stable: `env()` and an unsupported `calc()` answer `null` on this branch and after nativewind#425 alike. `such as` is what stops the next reader trusting the list as exhaustive, which is the failure this comment already had. The ratio's own explanation belongs at the hop that owns the fact, and nativewind#425 puts it there, in `case "ratio"`. Comments only.
387fa75 to
769f216
Compare
769f216 to
31518ac
Compare
31518ac to
222b927
Compare
…decide with unknown Media and container queries now evaluate through one walk and one comparison in three-valued logic (MQ5 §3.1): a term the runtime cannot decide is unknown, its negation too, and a rule applies only when its condition is true, as in Chromium, Firefox and WebKit. - A range pair is its two comparisons, where both runtimes answered it false. - A term is unknown when its feature is unmeasured, its value is outside the feature's syntax (a keyword or a type Media Queries 5 does not give it, env(), a negative resolution or ratio component) or its operand is unresolved, which compiles to null so JSON carries it. A condition the compiler's unknown terms leave no way to be true compiles to no rule, as does a container condition CSS Conditional 5 §5.4 discards. - A number past the largest float compiles to the largest safe integer and a length to the same bound rounded, since JSON writes Infinity as null. - A feature in a boolean context is false only for zero, none and no-preference. - A viewport length in a condition resolves against the window. - hover, any-hover, pointer and any-pointer answer for the primary input, as Safari and Chrome do on a phone; color, color-index, monochrome, grid, scripting, update, overflow-block, overflow-inline, scan, color-gamut, dynamic-range and their video- forms answer as every React Native screen does, forced-colors as none off Windows, and -webkit-device-pixel-ratio as resolution. - A keyword and a media type match in any case. - A compiled condition is readonly at every depth, and the condition compiler reads lightningcss's AST through DeepReadonly.
parseMediaFeatureValue had no arm for a <ratio>, so every aspect-ratio condition holding one compiled to an unknown term, and the media evaluator never measured the screen's aspect ratio. A ratio compiles to its numerator and denominator, each the decimal the stylesheet wrote, since lightningcss hands them over as float32, and 0/0 to 1/0. Both evaluators compare an aspect ratio by cross-multiplying, as Chromium and Firefox do, so a box of no height equals 1/0 and a box of no size equals every ratio; a container answers no ratio before its first layout. A negative component is outside a ratio's syntax and leaves the feature unknown. A feature name that is neither one lightningcss parses nor this library's dir or platform compiles to an unknown term, so a condition's feature names are closed and a ratio feature's operand is a ratio by type.
222b927 to
9d58fdf
Compare


Problem
On
main,aspect-rationever matches, in@mediaor@container.parseMediaFeatureValuehas no arm for a<ratio>, so the condition compiles to nothing and its block is dropped, and the media evaluator never measures the screen's aspect ratio.Solution
<ratio>compiles to its numerator and denominator, each the decimal the stylesheet wrote, since lightningcss hands them over as float32, and the media evaluator measures the screen as the container evaluator measures a container.(max-aspect-ratio: 1/0)matches and(min-aspect-ratio: 1/0)does not, and0/0compiles to1/0; a box of no height equals1/0, and a box of no size equals every ratio. A container answers no ratio before its first layout, where it used to read as 0x0.isOutsideValueSyntaxdoes, so(aspect-ratio: -2/-1)does not compare as 2.dirorplatformcompiles to an unknown term, which it already evaluated to, so a condition's feature names are closed: each ratio feature has its own variant, and a reader that leaves one unanswered fails the type check.Tests
src/__tests__/native/aspect-ratio.test.tsxrenders each condition against a screen and a container — 400x200, decimal ratios at their boundaries, and boxes of 400x0, 0x0 and 0x200 — from the compiler's object and after the JSON round trip a device's stylesheet takes, and reads the screen's ratio again after its height changes and a container's after its first layout and after its layout changes.compiler/condition-semantics.test.tspins what each condition compiles to, #426's rows hold a negative component unknown, and its cases comparingcanMatchwith the runtime, on a screen and in a container, take the ratio terms;compiler/condition-types.test.tspins that feature names are closed and that each ratio feature compares a ratio in a variant of its own. The render helpers move to_conditions.tsx, which both native files share. Each expected value is what Chromium 156, Firefox 157 and WebKit 27.2 compute, and a test says where one engine departs from the others, or the runtime from all three. 85 of the 113 cases inaspect-ratio.test.tsxfail onmain.Verification
Every
ci.ymljob passes on this head merged intomain:yarn testat 2209 passed on Ubuntu with Node 22.18.0, and the iOS dev build on Xcode 27.1, where CI pins 26.6.Device evidence, before and after: #425 (comment)
Known limits
container-type, a ratio among them, as it answersheightonmain: aninline-sizeornormalcontainer matches(aspect-ratio > 1)where Chromium, Firefox and WebKit select no container, or the nearest one that takes both axes. That is a change of its own.calc(NaN)as an overflowing number (parcel-bundler/lightningcss#1405), so acalc(NaN)ratio component compiles as infinite, where the three engines read it as 0.device-aspect-ratiocompiles, and is unknown until the runtime measures the screen, whichdevice-widthanddevice-heightwait on too, a change of its own.Related
This builds on #426, and its diff includes #426's commit until #426 lands; this PR's change is the last commit. Range pairs moved to #426, and logical container sizes to #480.