Skip to content

chore: release v0.25.0 - #141

Merged
gkurt merged 1 commit into
mainfrom
tegami/version-packages
Sep 15, 2026
Merged

gkurt merged 1 commit into
mainfrom
tegami/version-packages

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

All bumped packages.

Package From To
@reactunity/create 0.24.0 0.25.0
@reactunity/material 0.24.0 0.25.0
@reactunity/renderer 0.24.0 0.25.0
@reactunity/scripts 0.24.0 0.25.0

Changelogs

2026-09-12-logical-borders-and-sizing.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

Logical borders and sizing

The painted border properties have their logical spellings — border-inline-color,
border-start-start-radius and the rest — so direction: rtl turns a coloured edge or a rounded corner
around, and Tailwind's border-s-* and rounded-s-* do something. inline-size, block-size and their
min-/max- forms are aliases for the width and height properties.

2026-09-12-scroll-padding-margin-stop.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

scroll-padding, scroll-margin and scroll-snap-stop

The three scroll-snap properties that were missing are in: scroll-padding insets the box a snap target
lines up against, scroll-margin outsets the target itself, and scroll-snap-stop: always keeps a fling
from carrying past a target on its way to one further along.

2026-09-13-background-clip.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

background-clip

background-clip takes one value per background layer, and all four of CSS's values now do what they say.
text cuts a layer down to the element's own glyphs — a gradient or an image painted through the text,
which with color: transparent is the gradient-text effect. The glyphs are rasterised into a coverage
texture by a command buffer and only when they move, so an idle element costs nothing.

The three boxes used to be one box. Every background was masked at the padding edge, so a background never
reached under its own border: a dashed, dotted or semi-transparent border showed the page through its gaps
rather than the element, and a rounded one took the border's inner radius instead of its own. The mask is the border box now, which is both
what border-box means and what CSS makes the default; padding-box and content-box are cut per layer
in the fragment shader, with the radii that box has left, and cost nothing when there is no border or
padding to separate them.

Moving that mask corrected an inset box-shadow with it. One is cast by the padding edge, which is where
it is now drawn and rounded — it used to be cast by the border box and then trimmed back to the padding
box by the mask, so on a bordered element it sat a border-width out from where CSS puts it.

2026-09-13-calc-percentage.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer patch

calc() keeps a percentage

A calc() that works out to a percentage is one now, on every property Yoga can hold a percentage for:
width: calc(1/2 * 100%) is 50%, and so w-1/2, left-1/2, -translate-x-1/2 and basis-1/3 — which
is how a CSS framework spells every fraction it has — lay out instead of dropping. The percentage is
carried through the arithmetic rather than resolved on sight, which is what used to throw the answer away:
the parent it was resolved against had not been laid out yet, so the declaration came out as nothing at
all.

It survives a sub-expression too, parenthesised or a nested calc()calc(calc(1 / 2 * 100%) * -1),
which is what a framework emits for every negative fraction utility. A group is worked out before the
calculation around it, and the length or percentage it came out as is an operand again exactly as it
stands, rather than something the arithmetic around it could not read.

On a property Yoga does not hold as a percentage -- border-left-width, scroll-margin-left -- a
calculated percentage now resolves to the same number the percentage written out gets, rather than
reaching the property as a value of the wrong type.

A calculation mixing a percentage with a length, calc(100% - 2rem), is still dropped. Yoga has one unit
per value and no calc of its own, so there is nothing to hand it.

infinity is a number as CSS Values 4 asks — the largest length the implementation supports, not a real
infinity. border-radius: calc(infinity * 1px), which is what a rounded-full utility compiles to, used
to come out of the corner reduction as NaN and leave the element painting nothing.

Under REACT_UNITY_DEVELOPER, a declaration that resolves to nothing now says so once in the console,
naming the property and the value. A dropped declaration was silent before, which is the part of this that
cost the most to find.

2026-09-13-overscroll-behavior.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

overscroll-behavior

Scrolling now chains. A <scroll> that has reached its end hands the rest of the gesture to the
nearest scroll box above it, as a page does on the web. Until now the innermost box swallowed every
tick and every drag it was given, so a short list inside a long page was a dead spot: reaching its
end stopped the scroll rather than carrying on with the page behind it.

overscroll-behavior is how a box keeps a scroll to itself instead. contain stops the handover,
none stops it and drops the local overscroll effect with it -- which here is <scroll>'s
elasticity prop, so none on an elastic box takes its bounce away where contain leaves it. Both
axes are separate: the shorthand takes one value or two with x first, overscroll-behavior-x and
-y set them individually, and overscroll-behavior-inline and -block are the same two under
their logical names. The axis a gesture runs along is the one that decides, so a contained y does
not trap a sideways scroll.

A gesture stays with whichever box took it, rather than being handed over the moment that one
reaches its own end: dragging a list down to its end and on past it does not then start dragging the
page. That is the latching a browser does, and it is measured from where the drag began, so a slow
one is not mistaken for a stuck one.

Chaining walks the element tree rather than the transform hierarchy, so a filter or a
perspective in between -- both of which reparent their subtree onto an offscreen canvas -- does
not break it.

2026-09-13-perspective-and-backface.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

perspective, perspective-origin and backface-visibility

3D transforms have always composed here — rotateX and rotateY set a real rotation, and children keep
their own depth the way transform-style: preserve-3d describes — but nothing projected them, so a
rotation only ever foreshortened linearly and never converged anywhere.

perspective on a parent now does. Its subtree is captured through a camera of its own, the same offscreen
machinery filter uses, with an off-axis frustum standing the viewer wherever perspective-origin puts
them; the capture grows to hold whatever the projection throws outside the element's box, so a card leaning
out of its stage is not clipped. backface-visibility: hidden is separate and needs no perspective: it
watches the element's winding — which catches a mirror as well as a rotation, as CSS does — and takes the
element out of both drawing and hit testing while its back is turned.

There is still no transform-style. Its preserve-3d is what everything already does, and its default
flat would need a shear the transform model has nowhere to put.

A transform whose functions run together with no space between them -- rotateX(35deg)rotateY(-30deg),
which is what every CSS minifier emits -- now parses. It was being read as one token naming no function,
so a built stylesheet silently lost the whole declaration while the same rule worked in source.

translate-z has changed sign. A positive value now comes towards the viewer, as CSS says and as
rotateX/rotateY already did. Nothing could show the difference under an orthographic screen-space
canvas, which is why it went unnoticed; a perspective shows it immediately. Code that leaned on the old
direction — most likely ordering elements in a world-space canvas — wants the opposite sign now.

2026-09-14-lightningcss-logical-properties.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer patch

Logical border radii survive the Vite build

The preset names an old CSS target so that Lightning CSS lowers what ReactUnity's CSS subset has no
parser for. border-start-start-radius and its three siblings were caught by that too, and their
lowered form is a pair of rules selected by :-webkit-any(:lang(ae),:lang(ar),...) naming every
right-to-left language -- pseudo-classes ReactUnity reads as custom states, which never match, so the
radius was dropped and the console carried a warning for each. Lowering is excluded for the logical
properties now, the way it already was for light-dark(): both are things the renderer resolves
itself, and against the element's own direction rather than the document's language.

2026-09-14-relative-color-syntax.md

Show Bumped Packages (1)
Package Bump
npm:@reactunity/renderer minor

Relative color syntax

Every color function takes a from <color> origin, which is CSS Color 5's relative color syntax:
rgb(), hsl(), hsv(), lab(), lch(), oklab() and oklch(). The origin is converted into the
function's own space and its channels become keywords the channel slots can use, on their own or inside
calc() — so rgb(from var(--brand) r g b / 50%) is a brand color at half opacity without repeating it,
and oklch(from currentcolor calc(l * 0.8) c h) is a hover shade of whatever the element's own color
turns out to be. A keyword may appear in any slot, so rgb(from red b g r) is blue.

Each keyword carries the range CSS gives it — r is 0255, hsl()'s s is 0100, oklch()'s
l is 01, a hue is degrees, alpha is 01 — so a literal mixes with one exactly as it would in
a browser. An omitted alpha is the origin's own rather than 1, so rgb(from <color> r g b) is that
color unchanged.

The origin stays lazy, so it may be a var(), currentColor, a color-mix() or another relative color,
and one parsed rule resolves against each element it lands on. A literal origin is decomposed while
parsing instead, so the whole function still folds to a constant.

Two things the color functions have always got wrong were in the way, and are fixed:

hsl() and hsv() read saturation and lightness on CSS's 0100 scale rather than 01, so the
% may be left off as every framework that emits hsl(152 37 59) expects. Written with percentages
they are unchanged; written as bare fractions — hsl(152, 0.37, 0.59), which was the only spelling that
used to work — they now mean what CSS says they mean, which is very nearly black.

Out-of-range components are clamped rather than carried through the conversion, which is what CSS
Color 4 asks of a specified color and what this arithmetic reaches constantly: rgb(300 0 0) is red,
hsl(0 150 50) is a saturation of 100%, and calc(l * 4) is a lightness of 1 rather than a color
that paints nothing. A saturation past 100% used to come out of the conversion as a different hue
entirely. Alpha is clamped everywhere, hue wraps, and a channel written as none is 0 in rgb(),
hsl() and hsv() as it already was in the Lab family.

Publish

The following packages will be published if merged:

Package Version Registry
@reactunity/create 0.25.0 npm
@reactunity/material 0.25.0 npm
@reactunity/renderer 0.25.0 npm
@reactunity/scripts 0.25.0 npm

@github-actions
github-actions Bot force-pushed the tegami/version-packages branch from 50419bf to 5f61f97 Compare September 14, 2026 02:34
@github-actions
github-actions Bot force-pushed the tegami/version-packages branch from 5f61f97 to fac5ce4 Compare September 14, 2026 16:21
@gkurt
gkurt merged commit f02ba22 into main Sep 15, 2026
@gkurt
gkurt deleted the tegami/version-packages branch September 15, 2026 09:47
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