Releases: Todari/react-pixel-ui
Release list
@react-pixel-ui/react@2.2.0
Minor Changes
-
#6
911fbb0Thanks @Todari! - React layer fixes: stable DOM across re-measures, resize awareness, StrictMode-safe observers, working responsive mode, and API hardening.<Pixel>- The drop-shadow wrapper no longer remounts the child DOM node on every re-measure (theme toggle, style change). Once a shadow wrapper exists it stays in the tree (
display: contentswhile measuring), so uncontrolled<input>values, focus, and selection survive. - A
ResizeObservernow watches the child: responsive/percentage-width elements re-measure when their size changes (stale absolute-px clip-paths are regenerated), and elements that mount at zero size (insidedisplay: nonetabs/accordions) get pixel art as soon as they become visible — previously they never did. - Backgrounds the engine can't pixelate (
url()images,conic-gradient()) are left untouched instead of being replaced withbackground: none+ a transparent PNG (elements no longer go invisible or flash solid red). - Author
box-shadowis only cleared when it is actually replaced by a pixel shadow — inset-only shadows are preserved.
usePixelRef- The style observer is created inside an effect instead of the ref callback. Under React 18 StrictMode (the Vite/Next dev default) the simulated remount previously disconnected the observer permanently, freezing all hover/focus/resize updates in development.
- Borderless elements with a
box-shadowget their pixelated drop-shadow again — the hook only readwrapperStyle.filter, which the composer only sets when a border wrapper is needed, so shadows were silently deleted. When the element has no clip-path the filter now applies to the element itself instead of its parent. - Same graceful-degradation behavior as
<Pixel>for unparseable backgrounds.
<PixelBox>responsivemode actually works now: the component no longer pins its own measured fallback (200×100) as inline px width/height, which made the ResizeObserver only ever read back the forced size. Size the box with your own CSS (style={{ width: '100%', height: 120 }}) and the pixel art follows it. Measurement uses the border box.className,style, and other HTML props now consistently land on the root element (previouslystylelanded on the inner content div when a border was used, somarginetc. silently did nothing).
<PixelButton>- Unknown
variantvalues fall back toprimarywith a dev warning instead of crashing with aTypeError. - The rendered
<button>defaults totype="button"so dropping it into a form no longer submits the form; an explicittypeprop still wins.
Misc
PixelConfigProviderno longer crashes when rendered without aconfigprop (it is now optional).usePixelArtmemoizes by the radius values, so inline per-corner arrays (borderRadius={[8,8,0,0]}) no longer regenerate the art on every parent re-render; it also skips generating the unused composite PNG.PixelBox/PixelButtonexpose properdisplayNames in React DevTools (no morePixelBox2).- ESM consumers under
moduleResolution: node16/nodenextget the ESM declaration file via per-conditionexportstypes, and the"use client"banner no longer shifts source maps by one line.
- The drop-shadow wrapper no longer remounts the child DOM node on every re-measure (theme toggle, style change). Once a shadow wrapper exists it stays in the tree (
Patch Changes
- Updated dependencies [
911fbb0]:- @react-pixel-ui/core@2.2.0
@react-pixel-ui/core@2.2.0
Minor Changes
-
#6
911fbb0Thanks @Todari! - Core engine overhaul: real PNG compression, full CSS color support, gradient correctness, and graceful degradation.Performance
- The PNG encoder now uses a built-in dependency-free DEFLATE (fixed Huffman + LZ77) instead of uncompressed stored blocks — generated data URLs are 30–100× smaller (e.g. a 600×400 card went from ~78 KB to ~2.4 KB), with a stored-block fallback for incompressible data.
generatePixelArtacceptsoptions.want: 'styles' | 'composite' | 'both'so consumers skip generating the PNG they discard (~2× faster for gradient cases). Default'both'keeps backward compatibility.generatePixelArtresults are cached in a small LRU keyed by inputs — hover/theme/re-render churn no longer regenerates identical art. Treat returned objects as immutable.
Color parsing
- All 148 CSS named colors are now supported (previously 14 —
lime,navy,teal,tomato,gold,rebeccapurple, … no longer fail to parse). - New:
color(srgb … ),color(srgb-linear …), andcolor(display-p3 …)— Chromium serializes computedcolor-mix()results ascolor(srgb …), socolor-mix()now works end-to-end through<Pixel>/usePixelRef. rgb()/rgba()patterns are anchored, so colors embedded in unsupported values (e.g.conic-gradient(rgb(255,0,0), …)) are no longer misparsed as a solid color.
Gradients
- Tailwind v4 interpolation hints are handled:
linear-gradient(to right in oklab, …)no longer silently falls back toto bottom. - Angle units
turn,rad, andgradare converted correctly (previously onlydegworked). - Corner keywords (
to top right, …) now resolve to the aspect-ratio-dependent "magic corner" angle per CSS spec via the newresolveGradientAngle(gradient, width, height)export, instead of a fixed 45° multiple. - Double-position stops (
red 0% 50%) expand into two stops, preserving hard color edges. - Stops with
pxpositions or one unparseable stop no longer corrupt the whole gradient (the-1position sentinel can no longer leak into sampling).
Correctness & safety
generatePixelShadowno longer forces zero offsets topixelSize—box-shadow: 0 4px 0stays a straight shadow instead of turning diagonal. Offsets snap to the nearest grid line; all-zero shadows are skipped entirely.generateCompositePixelImagekeeps the interior transparent for border-only elements (previously it filled the content area with the border color), and returnsnullwhen a background is present but unparseable (url(),conic-gradient(), …) so consumers can leave the original styling untouched.parseComputedStylesresolves percentageborder-radius(e.g.border-radius: 50%avatars) against the element size via a new optionalsizeparameter, and handles elliptical two-value radii.parseBoxShadowdetects theinsetkeyword anywhere in the serialization — real browsers put it at the END in computed styles, so inset shadows were previously converted into outer drop-shadows. Multiple shadows are split correctly and the first non-inset one is used.- Every exported generator sanitizes
pixelSize—pixelSize: 0no longer hangs the tab (infinite loop) or throwsRangeError.
Packaging
- ESM consumers under
moduleResolution: node16/nodenextnow get the ESM declaration file (index.d.mts) via per-conditionexportstypes — fixes the attw "Masquerading as CJS" failure and wrong CJS-interop type errors.
@react-pixel-ui/react@2.1.1
Patch Changes
-
d6078ef: fix(react): shadow wrapper collapses width when child uses
min(Xpx, 100%)When a child element had both
box-shadowand a responsive width
(e.g.,width: min(260px, 100%)), the pixel art would collapse to a
tiny, nearly-zero width. Moving the shadow slider in the demo
Playground was the easiest reproduction.Root cause:
<Pixel>wraps the child in an extra
display: inline-blockdiv carrying thefilter: drop-shadow(...)so
the shadow is not clipped by the siblingclip-path. That wrapper
has no explicit width, making it shrink-to-fit. The child's100%
then resolved against a shrink-to-fit parent, which under CSS rules
can collapse to zero, creating a circular size-resolution loop where
both wrapper and child ended up near-zero width.Fix: the shadow wrapper now pins its own width / height to the
child's measured dimensions (fromartState) and adds
max-width: 100%so it still shrinks on narrow viewports. This
breaks the circular resolution while preserving the
shrink-to-container-width behavior on mobile.Added a regression test asserting the wrapper's
width,height,
andmax-widthinline style.
@react-pixel-ui/react@2.1.0
Minor Changes
-
5732058: Robustness, correctness, and modern CSS support pass
@react-pixel-ui/reactFixes
- Next.js App Router: the published bundle now starts with
"use client", so importing<Pixel>/usePixelReffrom a server component works out of the box. Previously the package would throw"You're importing a component that needs useState...". - Dynamic style changes are now reflected:
<Pixel>re-measures when the child'sclassNameorstyleprop changes, so state-driven color/radius/border updates actually repaint the pixel art (instead of being frozen at the first measurement). - Tailwind dark mode:
<Pixel>observesclass/data-themetoggles on<html>and<body>, re-rendering pixel art with the new computed colors automatically. - No more content-box layout shift: instead of stripping the border (
border: none), we now preserve the border width and setborder-color: transparent+background-origin: border-box. The element's outer box stays the same regardless ofbox-sizing. usePixelRefparent filter leak: whenusePixelRefwritesfilter: drop-shadow(...)to the parent element (to escape clip-path), the original value is snapshotted and restored on ref detach / unmount / option change. Previously the parent stayed permanently modified.usePixelRefstyle observer: inlinestyleattribute mutations are now observed too. Apause()primitive on the observer prevents self-mutation loops.- Function-component child warning:
<Pixel>logs a dev-only warning when its child doesn't accept a ref (e.g., a function component withoutforwardRef), instead of silently doing nothing. - React 19 ref access: ref-merging now reads
element.props.reffirst (React 19) and falls back toelement.ref(React 18).peerDependenciesnow acceptsreact ^19.
Performance
- BMP memoization:
<Pixel>wrapsgeneratePixelArt()inuseMemo, eliminating the per-frame image regeneration that happened on unrelated parent re-renders. PixelConfigProvider: context value is memoized so consumers don't re-render on every provider render.
Packaging
"exports"map,"sideEffects": false, and proper dual ESM/CJS entry points.peerDependencies:react ^18 || ^19.
@react-pixel-ui/coreFixes
- Alpha channel preservation: the internal image encoder switched from 24-bit BMP to 32-bit RGBA PNG.
rgba(),transparentgradient stops, and translucent backgrounds now render correctly instead of being flattened to opaque (or worse, solid black). - Correct outside-shape pixels: pixels outside the staircase clip are now fully transparent instead of black, eliminating edge bleed at non-integer pixel alignments.
parseColor("#xyz")fix: invalid hex strings now returnnullinstead of{r: NaN, g: NaN, b: NaN, a: 1}.
Features
hsl()/hsla()parsing: comma, space, and slash syntax;deg/rad/grad/turnhue units; negative hue wrap-around.oklch()/oklab()parsing: full CSS Color 4 perceptual color space support with the standard oklab → linear sRGB → sRGB gamma-corrected pipeline. Out-of-gamut values are clamped.hsl()/oklch()/oklab()gradient stops: all accepted inlinear-gradient,radial-gradient, and therepeating-*variants.
Packaging
"exports"map,"sideEffects": false, proper dual ESM/CJS entry points.
Test coverage
- New vitest test suite: 122 tests across core (98) and react (24).
- Covers PNG encoder round-trip via Node
zlib, gradient parsing, color parsing (including hsl/oklch), composer,<Pixel>/usePixelRefbehavior, and the new fixes above.
CI
- New
.github/workflows/ci.ymlruns lint, type-check, test, and build on every PR and push tomain, on Node 20 and 22.
- Next.js App Router: the published bundle now starts with
Patch Changes
- Updated dependencies [5732058]
- @react-pixel-ui/core@2.1.0
@react-pixel-ui/core@2.1.0
Minor Changes
-
5732058: Robustness, correctness, and modern CSS support pass
@react-pixel-ui/reactFixes
- Next.js App Router: the published bundle now starts with
"use client", so importing<Pixel>/usePixelReffrom a server component works out of the box. Previously the package would throw"You're importing a component that needs useState...". - Dynamic style changes are now reflected:
<Pixel>re-measures when the child'sclassNameorstyleprop changes, so state-driven color/radius/border updates actually repaint the pixel art (instead of being frozen at the first measurement). - Tailwind dark mode:
<Pixel>observesclass/data-themetoggles on<html>and<body>, re-rendering pixel art with the new computed colors automatically. - No more content-box layout shift: instead of stripping the border (
border: none), we now preserve the border width and setborder-color: transparent+background-origin: border-box. The element's outer box stays the same regardless ofbox-sizing. usePixelRefparent filter leak: whenusePixelRefwritesfilter: drop-shadow(...)to the parent element (to escape clip-path), the original value is snapshotted and restored on ref detach / unmount / option change. Previously the parent stayed permanently modified.usePixelRefstyle observer: inlinestyleattribute mutations are now observed too. Apause()primitive on the observer prevents self-mutation loops.- Function-component child warning:
<Pixel>logs a dev-only warning when its child doesn't accept a ref (e.g., a function component withoutforwardRef), instead of silently doing nothing. - React 19 ref access: ref-merging now reads
element.props.reffirst (React 19) and falls back toelement.ref(React 18).peerDependenciesnow acceptsreact ^19.
Performance
- BMP memoization:
<Pixel>wrapsgeneratePixelArt()inuseMemo, eliminating the per-frame image regeneration that happened on unrelated parent re-renders. PixelConfigProvider: context value is memoized so consumers don't re-render on every provider render.
Packaging
"exports"map,"sideEffects": false, and proper dual ESM/CJS entry points.peerDependencies:react ^18 || ^19.
@react-pixel-ui/coreFixes
- Alpha channel preservation: the internal image encoder switched from 24-bit BMP to 32-bit RGBA PNG.
rgba(),transparentgradient stops, and translucent backgrounds now render correctly instead of being flattened to opaque (or worse, solid black). - Correct outside-shape pixels: pixels outside the staircase clip are now fully transparent instead of black, eliminating edge bleed at non-integer pixel alignments.
parseColor("#xyz")fix: invalid hex strings now returnnullinstead of{r: NaN, g: NaN, b: NaN, a: 1}.
Features
hsl()/hsla()parsing: comma, space, and slash syntax;deg/rad/grad/turnhue units; negative hue wrap-around.oklch()/oklab()parsing: full CSS Color 4 perceptual color space support with the standard oklab → linear sRGB → sRGB gamma-corrected pipeline. Out-of-gamut values are clamped.hsl()/oklch()/oklab()gradient stops: all accepted inlinear-gradient,radial-gradient, and therepeating-*variants.
Packaging
"exports"map,"sideEffects": false, proper dual ESM/CJS entry points.
Test coverage
- New vitest test suite: 122 tests across core (98) and react (24).
- Covers PNG encoder round-trip via Node
zlib, gradient parsing, color parsing (including hsl/oklch), composer,<Pixel>/usePixelRefbehavior, and the new fixes above.
CI
- New
.github/workflows/ci.ymlruns lint, type-check, test, and build on every PR and push tomain, on Node 20 and 22.
- Next.js App Router: the published bundle now starts with