Releases: benface/monowind
Releases · benface/monowind
Release list
v0.3.3
monowind 0.3.3 is faster, blends opacity and translucent colors the way the browser does, and fixes a long list of layout and interaction bugs. The core package has less code than 0.3.2, and its CDN bundle is 7.5% smaller.
Faster
Measured against 0.3.2 in Chromium (medians, ms):
| measure | 0.3.2 | 0.3.3 |
|---|---|---|
| prose page (300 paragraphs), until interactive | 306 | 182 |
| boxes page (300 boxes), until interactive | 226 | 173 |
| faded boxes page, until interactive | 259 | 192 |
| prose relayout (CPU) | 88 | 65 |
| prose hover (CPU) | 97 | 74 |
| popover fading over a page, CPU per frame | 75–88 | 12–16 |
| the same, frames in a 1 s fade | 15–17 | 62–66 |
| 150 boxes fading at once, CPU per frame | 143 | 52 |
- A page load runs one layout instead of two.
- Style recalculation takes about half the time: only elements with a transition get the settling pass, and two style rules that kept Chromium from sharing styles between elements are gone.
- An
opacityor border-color transition repaints each frame instead of laying the page out again, and a frame lays out at most once. - Shade glyphs (
░▒▓) no longer defeat Chromium's style cache: pages full of them restyle a third faster.
Opacity and translucent colors
- Translucent colors (
bg-white/10,text-white/60) andopacity-*blend into the cells beneath them, as the browser composites them. Abg-white/10card on abg-slate-800panel now tints the slate; in 0.3.2 it showed white over the host's background. - An
opacity-*element fades as a whole, as in a browser: on a white page, abg-blue-600 text-white opacity-50button shows white text on a light blue fill. - A background of any alpha hides the glyph beneath it, and a color at zero alpha paints nothing.
- Where nothing opaque lies beneath, translucent colors and faded elements keep their alpha, and the browser composites them over whatever is behind the host: an image, a gradient, the page.
- Color emoji fade with their element, in Safari too.
- Every CSS color space blends:
lab(),lch(),oklab(),oklch(), andcolor()'sdisplay-p3,a98-rgb,prophoto-rgb,rec2020andxyz(0.3.2 misreaddisplay-p3as sRGB).
Fixes
- Shade glyphs (
░▒▓, in scrollbar tracks, shadows and the blocks theme) draw once, at the density the font draws them. They were two to three times darker and banded row by row. - Sticky: a sticky element inside a fixed element, a popover or a dialog sticks to the right scroller; a sticky span inside a scrolling paragraph sticks; a sticky heading among text runs moves its native element with it.
- Flex: reversed rows and columns and
wrap-reversematch the browsers for every alignment keyword, positioned children included (startstays put whereflex-startflips). w-fitcontributes its full width: aw-fitlabel in a flex row, a float, a positioned box or a table cell no longer wraps at its longest word.- Tables: a positioned child of a table sits where a row in its place would start, margins included.
- Stacking in text mode: a
z-*on an element no longer lifts its children's native elements, so clicks and selections land on the element the grid shows. - An element that stops being a box (a responsive
block md:inline) drops its engine positioning. - An
align-middleinline-flex text box lands on its row; a multicol's overflow columns can be scrolled to; gap rules on overflowing tracks meet the border correctly. - An outer inline element's background paints under its inner inline elements; a span inside a padded inline element no longer inherits that padding.
- Text ending on an inline box that doesn't fit its line no longer gains an empty last line (or crashes beside a float).
- The
*-safealignment classes (justify-center-safe,items-end-safe, …) align as their keyword; they used to align to the start. - Transitions: easing comes from the browser itself (
linear(),steps(…, jump-start)and delays included); removing an element mid-transition no longer keeps the host laying out every frame; the host's theme colors follow a background transition to its end. - An inline viewport-unit
min-/max-size wins over a class'scalc()value, as the cascade has it. - A host inside
display: nonewaits instead of laying out with zero-width cells; a removed host stops listening to window events and listens again when put back.
Vite plugin
0.3.2 pre-bundled a linked monowind (from a workspace or npm link), so the dev server kept serving the engine from before an edit. A linked engine is now served as is; an installed one is still pre-bundled.
Components
- Item parts (
Item,ItemText,ItemIndicator,ItemGroup,ItemGroupLabel) read the nearest listbox, select or combobox root in React, Vue and Svelte alike, so aSelectIteminside aListboxRootworks. - The error for an item part outside a list root, or for an item's text or indicator outside its
Item, names where the part belongs. - Vue: a bare boolean attribute sets its prop, as in HTML:
<MenuTrigger as-child>,<MenuItem disabled>,<SelectRoot multiple>(in 0.3.2 they read as false). Every part's type carries its declared props and its element's attributes. - React:
ItemTextandItemIndicatorare typed as thespans they render, and every part's type carries itsdisplayName.
Dependencies
@sveltejs/vite-plugin-svelte7.3.1 (a dev dependency of@monowind/ui-svelteand the Svelte example).- The StyleX example moves to
@stylexswc/unplugin0.19.0.
Upgrading from 0.3.2
- Shades look lighter. Scrollbar tracks, box-shadow rings and
░▒▓borders show the font's own density, no longer the doubled one. - Reversed flex layouts may move where they used
start/end(place-*utilities) orbaseline: they now sit where the browsers put them. - Vue types: a part rejects a prop name that is neither declared nor an attribute of its element (0.3.2 accepted any name).
- Wide-gamut screens: a blend clips each color to sRGB first, as browsers do on an sRGB screen, so on a wide-gamut display a translucent color outside sRGB comes out a little duller than the browser's own blend.
- 〰 ㊗ 〽 🈂 take two cells, on the grid and in
clusterWidthandtextCells, as the East Asian Wide table has them. - A selection's first frame: a selection a script makes, or one a key extends into the host, can show the browser's own highlight for one frame before the grid's.
- Fades: the main thread spends far less per frame, so fades run at two and a half to four times the frame rate. A fade over opaque cells costs the browser more paint per frame (about 17 ms against 2 when 150 boxes fade at once; under 2 ms for a popover).
- Overflowing content aligns to the start, as in 0.3.2: a flex line, grid item or positioned box larger than its container sits at the start edge whatever its alignment, where browsers center or end it past that edge.
v0.3.2
A combobox
@monowind/ui/combobox: a listbox under an input, as<mono-combobox>and as React, Vue and Svelte components. The list lines up under the box around the input and its button, not under the button alone.- Filtering is the page's. Hand it the narrowed collection and the items it drops are hidden; the keyboard walks what is left.
- Typing stays where the reader put it: the caret doesn't jump to the end as a keystroke reopens the list.
Components for markup and for every framework
<mono-*>elements.defineMonoUi()(the CDN bundle calls it) registers<mono-menu>,<mono-submenu>,<mono-listbox>,<mono-select>,<mono-combobox>,<mono-dialog>,<mono-popover>and<mono-tooltip>. The markup is the component: its parts are the marked descendants, attributes are the machine's props, callbacks are bubbling events, and the element mounts itself as its parts arrive. It is the path for htmx, Alpine, Turbo, Datastar, Solid, or plain HTML.- React, Vue and Svelte compound components for the same seven, each in its framework's idiom — props for React,
v-modelfor Vue,bind:for Svelte — withasChild(achildsnippet in Svelte) to render a part as your own element. - Forms. A select's hidden native control posts its value, follows a form's reset and a disabled fieldset, posts nothing for a value it has no option for, and is server-rendered with its options, so a form sent before the page's script runs posts the initial value.
data-selectedon an item in the markup is the initial selection, and marks the selection on every path. @monowind/ui-reactrequires React 19.
More of CSS on the grid
visibility:invisiblekeeps the box's place and paints none of it, avisibledescendant still showing.- Anchor positioning:
anchor()andanchor-size()from inline styles and arbitrary-value utilities, every fallback tried inposition-try-orderand the last one that fit kept, andposition-visibility, so a menu hides when its button scrolls out of view. pointer-events: nonepasses clicks, hover and the cursor through to what lies beneath, as it does natively — a badge over a button, a disabled link — andpointer-events-autotakes them back.- A block inside an inline element splits it, as CSS does, instead of vanishing — so a
<mono-*>element wrapped around paragraphs keeps them. - Flex and grid sizing follow the spec where they took shortcuts: an item's automatic minimum is capped by its own width and max, cross-axis margins count, a
grid-cols-Nitem keeps its track while a long word overflows, and rows under amin-heightsize as the browsers do. - Color:
opacityon an inline element fades what it holds and not the background under it; gradients of wide-gamut Tailwind colors inoklab,oklchandhslmatch Chromium and Firefox; background fades ease withcubic-bezier()(Tailwind's default easing had run linear),steps()andlinear(); a plain background honorsbackground-clip.
Faster
- A page of 300 bordered boxes reaches interactive in 231 ms against 338: a font settling no longer restyles every grid span.
- The companion stylesheet's own rules stopped making Chromium walk every element's ancestors: style work on a page of 300 paragraphs drops from 126.9 ms to 120.4.
- A pointer move over a transformed element is back to microseconds.
Examples and tooling
- Any CSS tool styles the grid: UnoCSS, Panda, vanilla-extract and StyleX examples, since the engine reads computed styles, whatever wrote them. And htmx, Alpine, Turbo and Datastar examples driving the elements with attributes alone.
@monowind/vitegives the page one engine in development too, packages built on it included.
Also
- At a raised leading, box-drawing strokes keep the weight the font draws instead of thickening with the row.
- Scrolling: a reveal clears the container's border, a key scroll is never cancelled by a relayout, and a press on a scrollbar's track pages.
- A border drawn in cells covers what scrolled under it, for clicks too.
h-screen md:h-autorespectsmd.- The playground's editor no longer zooms Safari in on a tap.
v0.3.1
Listbox and select
@monowind/ui. A listbox and a select, each a Zag.js machine wired to the grid. The items are the ones the markup marks — their values, their text and their disabled state make the collection — so a list of static items needs nothing but the mount.selectionMode: "multiple"takes several values at once, each keeping its own indicator.- A select is the anchored one. The same items and keyboard under a trigger, its value on that trigger and in a hidden native control a form posts, with a clear button beside it.
- React, Vue, and Svelte.
useListboxanduseSelect,createListboxandcreateSelect, alongside the menu, dialog, popover and tooltip hooks. - Focus lands where the reader can see. A list draws no focus ring of its own, so taking focus highlights the selection — or the first item where nothing is selected — and brings that row into view.
Borders and blocks draw cleanly
- Box drawing and block elements fit the row they are painted in. A border's joints are whole and its lines sit where the font sets them against the text, and rows of blocks no longer show a seam between them.
- A glyph a font draws past its row is clipped to it, so it no longer overlaps the rows above and below.
- A glyph the font has not got keeps to its cells. A cluster a font lacks is drawn by a substitute at that font's own advance; it is now measured on its own rather than on its range's behalf, so it can no longer carry its drift to the rest of the row.
A theme can say what its font has not got
--mw-missing-glyphslists the characters a themed font lacks, and a glyph set naming one falls back to a glyph the font draws. The period fonts behinddos,amberandbbshave no rounded corners at all, soborders-roundedon one used to draw a corner borrowed from another font, broken from the line beside it; it now draws the square corner those sets already used. The bundled themes declare their own.
Faster
- Repeats of one block glyph share a painted box. A page of QR codes or ASCII art feels it most: such a page paints 1819 grid spans where it painted 2969, and a pass over that grid — an accessibility audit, say — runs in 1.5 ms against 2.1.
- Every box shares one style rule, and its style is built once and cloned. A page of 300 bordered boxes reaches interactive in 308 ms against 406.
Also
- A dialog opens in the cells the window shows, and a pointer press lands on the cell it looks like it lands on.
- Truncation keeps its ellipsis reachable: focus can still reveal a link past the cut.
- The playground's editor keeps its text with its pane at any size.
v0.3.0
Components
@monowind/ui. A menu (with submenus), a dialog, a popover, and a tooltip, each a Zag.js state machine wired to the engine. Zag runs the roles and states, the keyboard, typeahead, focus trapping and restore, and dismissal; the engine places each floating part against its trigger in cells, in the top layer, above everything and outside any scroller it opened from. Headless: mark the parts withdata-partand mount on their root (menu(root, { id })), then style the parts with Tailwind and monowind classes.positioning.placementtakes Zag's placements (bottom-start,top,right-end, …) and flips when the part would overflow the host;positioning.gutterandpositioning.offsetcount cells, and a margin utility on the positioner does the same (mt-1under a trigger is a gap,-mt-1on a submenu levels its first item with the item that opened it).- React, Vue, and Svelte.
@monowind/ui-react(useMenu,useDialog,usePopover,useTooltip),@monowind/ui-vue(composables), and@monowind/ui-svelte(runes) fold Zag's adapter in, one function per component with the same props everywhere. They need React 18, Vue 3.5, or Svelte 5.20 and up. Examples for all three, plus a Vue example app, and the playground sample now opens a menu and a dialog. - Surfaces. A popover or dialog takes the theme's text and background colors, as the browser gives it the canvas's.
bg-clearon a menu's or popover's content sees through to whatever sits behind the host.
Top layer
- Popovers and modal dialogs paint last. An open
[popover]or modal<dialog>is laid out as a fixed box of the host and painted above everything, outside its ancestors' clips, scrolling, and transforms, in the order they opened.::backdropis drawn beneath it:backdrop:bg-black/50dims the page,backdrop:backdrop-blur-smblurs it. Afixedbox paints and hit-tests from the host's origin too, where it used to vanish inside a scroller. - Under a modal dialog the page is blocked, grid included, so grid mode falls back to the dialog's own pointer and selection.
- Exits. A popover's exit through
transition-discreteplays to the end before it leaves the top layer.
Anchor positioning
position-area. An out-of-flow box with aposition-areais placed in a cell of its anchor's 3×3 grid and aligned toward it; anchors are named byanchor-name, or implied by a popover's invoker (popovertarget,commandfor).position-try-fallbacksflips the box where it would overflow, and its margins flip with it as in CSS;anchor-centercenters on the anchor;position-anchorreadsnormal,auto,none, andmatch-parent. The area taken is written asdata-mw-area, and a box follows its anchor through scrolling.
Theming
- Derived tokens.
--mw-fgand--mw-bgnow derive from the host's owncolorandbackground-color(a transparent host reads the background behind it, the canvas past the page), sotext-*andbg-*on<mono-wind>drive the focus invert and everything that reads the tokens. A change to an ancestor's class or style, or to the color scheme, applies at once. Migration: the built-in themes setcolorandbackground-colorinstead of the two tokens; a stylesheet still declaring--mw-fg/--mw-bgkeeps its values.
Engine
align-middleon inline boxes lands the line's text on the box's middle row, where it used to behave as top.- Painting over glyphs. A glyph painted over another keeps only the fill beneath it: a heading stuck over an italic run no longer turns italic, and a plain run over colored or underlined text stays plain.
- Box model. A box sized below its borders and padding (Tailwind's
<hr>:height: 0plus a border) keeps its border row, in block and flex layout alike. - Aligned lines. A button or other inline box on a centered or right-aligned line sits where the line's text is.
- Focus and pointer. A menu, listbox, tree, or grid container leaves the focus invert to its items; a press over a focusable target focuses it; a grid drag can start from a menu's border cell;
summaryand[contenteditable]count as interactive. - Locks in a cascade layer. The companion's engine rules moved into
@layer theme, so a!utility (bg-red-500!,w-40!,text-2xl!) or an unlayered!importantrule no longer paints the element's own box over the grid or pushes it off its cells; the value still reaches the grid's paint.pointer-events-auto!keeps working as the hit-target escape hatch. - Attributes. The host follows the attributes that render (class, style, HTML rendering attributes, ARIA states,
data-*) instead of every attribute. Migration:observedAttributesis gone fromregisterLeafRenderer; the host follows every attribute of a leaf, so drop the field.
Full changelog: v0.2.14...v0.3.0
v0.2.14
Transforms and filters
- Layers.
rotate-*,scale-*,translate-*,skew-*,transform, and the filters (blur-*,grayscale,brightness-*,hue-rotate-*, …, andbackdrop-blur-*) work: the element's cells paint into a box of their own that the browser transforms or filters at pixel precision, while the layout stays on the grid, as a transform never moves a box's layout in CSS.origin-*and percentage translates resolve against the element's box, nested transforms compose, and an element resting at an identity (scale-100,rotate-0) returns to the grid. - The pointer follows. Text selection, word and paragraph gestures, and hover land on the glyph under the pointer through the transform, and a button inside a scaled dialog is clickable where it shows. Selection and copy read the grid, so copied text is the text as laid out.
- Stacking and covering. Layers stack in paint order with
z-*, and whatever paints later covers a layer's cells, so a modal's overlay covers a tilted sticker as it covers everything else. - Clipping after the transform. An
overflow-hiddenor scrolling ancestor clips the transformed result, as CSS does: a card scaled on hover in a scrolling list is cut at the list's edge, and a drawer sliding in from a shell's edge stays hidden past it. - Smooth transitions.
transition-transform(and transitions oftranslate,rotate,scale,filter) follow the browser's own easing every frame, with one layout when they settle. A transition starting from the identity — a dialog closing fromscale-100, a delayed one — animates from its first frame.
Animations
- CSS animations play.
animate-spin,animate-pulse,animate-ping,animate-bounce, and your own@keyframes(enter and exit keyframes included) run on the grid: a transform keyframe moves the element's layer every frame, a color, border-color, or opacity keyframe repaints the cells with no layout in between, and anything else re-lays-out per frame. The value is always the browser's, so timing, easing, delay, direction, fill mode, and iteration count all apply; a paused animation holds its frame, and an ending one lands exactly on its end state. - Background keyframes read as they are. A
background-colorkeyframe shows the animated color directly, with no synthesized fade on top.
Host
- A
<mono-wind>nested inside another is unsupported: the inner one warns once and is laid out as plain content of the outer, where it used to bring the page down.
Playground
- The sample's box keeps a taller minimum height.
Full changelog: v0.2.13...v0.2.14
v0.2.13
Backgrounds
- Gradients.
bg-linear-*,bg-radial, andbg-conic(withfrom-*,via-*,to-*, stop positions, and theirrepeating-forms) paint a color per cell: each cell takes the gradient's color at its center, in the same geometry a browser uses for the box, so a diagonal is a real diagonal and a radial glow a real ellipse. Stops interpolate as CSS does, in oklab by default or in/srgb,/oklch,/hsl,/srgb-linear, with the/longer,/shorter,/increasing,/decreasinghue arcs; layers stack, and a translucent stop shows the plainbg-*color through it. Tailwind's ownoklch()palette is read directly. - Gradient text.
bg-clip-text text-transparentshows the gradient through the glyphs, on a heading, an ASCII-art drop cap, or a QR code.bg-clip-paddingandbg-clip-contentfill inside the border or the padding. - Cheap on the page. A row of gradient cells is one span whose background lists the cells' colors as hard stops, so a page full of gradients resizes as smoothly as one without.
Borders
- Weight.
border-widthis a weight the glyph set draws:border-2and up draw heavy lines━ ┃ ┏ ┓in one cell, dashed and dotted included, andrule-2draws a heavy gap rule the same way. A set without heavy glyphs registers what its hardware had instead:borders-ascii,borders-single,borders-rounded, andborders-blocksdraw two rings,borders-cp437draws double lines. Custom sets register their own weight bands, glyphs and thickness per width. - Collapsed tables at any thickness. A wider cell border wins a shared edge, as CSS collapses, and two-cell lines meet in properly joined junctions instead of repeated corner glyphs.
- Inset shadows.
shadow-innerandshadow-[inset_…]shade the inside of the padding box, fading inward through the same ramp as outer shadows.
Playground
- The sample opens with a gradient drop cap and a gradient QR code.
Full changelog: v0.2.12...v0.2.13
v0.2.12
Borders
- Rounded corners.
rounded-*picks each corner's glyph: the defaults draw the arcs╭ ╮ ╰ ╯on solid, dashed, and dotted borders fromrounded-xsup, per corner, with the inner ring of a thick border rounding one cell less, as CSS's inner edge does. Double borders stay square, and a glyph set that has no arcs, like the DOS themes' code page or the teletype's ASCII, keeps its own corners at any radius. A custom glyph set can register corner glyphs by radius; the corner draws the registration nearest its radius on the character grid. - Box shadows.
shadow-*paints a box's silhouette behind it in shade glyphs: the classic DOS shadow withshadow-[4px_4px_0_0_…], or Tailwind's presets, which sit a row down and soften outward through▓ ▒ ░, blur stepping through the shades and fading in ink. A translucent shadow color reads as a lighter shade leaning on the text color, so the default shadows show on a dark theme too. Themes choose the shade ramp the way they choose border glyphs. - Outlines stay native.
outline-*draws the browser's own outline around the box, and afocus-visible:outline-*focus ring draws over the focus invert, in all three engines.
Positioning
calc()insets with a percentage.top-[calc(100%-(--spacing(2)))]and its kind now work on sticky, relative, and absolute boxes, and on margins: the percentage resolves where it should and the rest adds in cells. A sticky box can hold two rows above its scroll container's bottom, or stay hidden until scrolling reaches it. Firefox, which resolves such values before the engine can read them, reads the utility from the class list instead.
Rendering
- Translucent borders join cleanly. A box-drawing glyph under
opacity-*is clipped to its cell, so the overshoot that joins rows never composites twice and darkens the joins.
Storybook
- The Backgrounds toggle lists the
@monowind/themesthemes beside light and dark, and applies one to every story at once.
Full changelog: v0.2.11...v0.2.12
v0.2.11
Floats
- Text wraps around floated boxes.
float-left,float-right, and theclear-*utilities work in cells: a drop cap, a banner, a QR code, or a bordered pull quote sits beside the text, and each line wraps around it, as in print. The browser floats the same boxes natively, so the text stays selectable, copies, and its links click. <mono-ascii trim>drops the blank rows and columns around the art, so a drop cap sits tight against its text.- Limits, for now: a container beside a float steps aside as a whole rather than letting its own lines wrap around the float; a float placed directly in a multi-column container is ignored with a warning;
shape-outsideis ignored.
Sticky positioning
stickyworks, in cells. Section headings hold at the top of a scrolling list until the next one takes over, a footer stays at the bottom, a column sticks in a horizontally scrolling box, sticky boxes nest, and a sticky span inside a paragraph holds its place. A sticky box lays out where flow puts it and shifts with the scroll on cell boundaries; the browser's own box follows, so focus, selection, and clicks land where the grid shows.- Table headers and columns stick too, borders included. A sticky
<thead>keeps its lines and joins the column lines running on below it, a sticky first column keeps its edge, and the corner cell sticks both ways.
Forms
- Selecting text in an input or textarea inverts the control's own colors, as the grid does for any selected cell, instead of the browser's highlight, which Chromium and Safari had been painting invisible since 0.2.8.
- A focused select keeps its inverted look while another select's menu opens. Its text and background used to fall out of step for as long as the menu stayed open.
Scrolling
- A scroll landing right after another settles where it landed. The engine's settle could snap it back to the previous position.
Full changelog: v0.2.10...v0.2.11
v0.2.10
Text beside block children lays out
- Mixed content renders. A container holding text next to block children —
<div>foo<div>bar</div></div>, the host's own text beside a block, a paragraph with a block span in it — used to drop the text and warn. Each run of text between the blocks is now laid out on its own rows, like CSS's anonymous block boxes, in the container's color, weight, style, leading, and tracking. The block children stay in the browser's own flow so the text's native lines sit exactly on its grid rows: it is selectable, copies, its links are clickable, and find-in-page lands on it. - Odd markup stays hidden. Stray text inside a hand-rolled div-table still hides and warns, as any misparented table content does.
Selection
- Drags auto-scroll, like a native drag. Hold a text-mode drag, or a double- or triple-click drag in either mode, past the edge of the scroll container the press started in, and it scrolls toward the pointer, whole cells at a time, with the selection following the content that scrolls under the pointer. Past the edge of the page, the page scrolls. A wheel mid-drag extends the selection too.
- Drags reach the nearest text over a gap. A word or paragraph drag over blank cells — the gap between paragraphs, the space past a line's end — extends to the nearest paragraph, as a browser's does across a margin, instead of freezing where the last text ended.
- A double- or triple-click keeps its selection on release. Chromium and WebKit collapse a selection the press landed in when the button comes up; the engine now owns the release too.
- Hit-testing stops at a scroll container's edge. A press on a container's border or scrollbar no longer reaches a line scrolled invisibly beneath it, and an unwrapped line running past its box is selectable to its end.
Themes
- The c64 theme ships Pet Me 64, Kreative Korp's free Commodore font, at the machine's 8×8 cell, so its cells are square and its descenders draw whole.
- Shades stay seamless under a selection. A shade's lattice phase now travels through the line box rather than padding, so a grid-mode highlight covers a scrollbar track without hairlines in all three engines.
Full changelog: v0.2.9...v0.2.10
v0.2.9
QR codes: @monowind/qr-code
<mono-qr>renders a scannable QR code on the grid. The element's text is the value; the code is packed into the font's real cell proportions — half blocks where a cell is twice as tall as wide, so a version-1 code is 21 columns by 11 rows — and the light DOM keeps the value for screen readers.levelpicks the error-correction level (the renderer raises it further when the chosen size has room),scalegrows the symbol uniformly, andaspectoverrides the measured cell ratio. The quiet zone a reader needs is ordinary padding, opt-in like every utility:px-4 py-2is the standard four modules.- Themes restyle a code through their glyph set (
qrFull,qrUpper,qrLower); a set with only a full block renders two cells per module, so an ASCII-only theme still scans. - Scannability is tested end to end: every packing is decoded from a raster in Node, and screenshots of the painted code are decoded in Chromium, Firefox, and WebKit.
Borders, blocks, and shades tile in any font
- Box-drawing and block glyphs fit their row. Fonts that draw them shorter than the line (Menlo and SF Mono do, and any font under a taller
leading-*) used to leave a hairline between rows of borders, scrollbar thumbs, and QR codes. Each such range is now scaled a pixel and a half past the row and pinned to it, one transform per range so junctions and halves keep meeting, checked against the engine's own baseline. - Shades
░ ▒ ▓stay crisp. A shade's lattice is locked to whole device pixels, so scaling never resamples it into moiré, and its pattern carries on from row to row instead of restarting at every line, so a scrollbar track reads as one continuous texture. - Cells are a whole number of layout units. The cell width rounds up to 1/64 px, the unit Chromium and WebKit snap boxes to, so a row of fitted glyphs ends exactly where a row of text does; and inline backgrounds now reach a row's edges in fonts whose content area is shorter than the line box (SF Mono in Safari), so rows of
bg-*no longer show a seam. size-*utilities are read in Firefox's class-scan fallback likew-*andh-*.
Scrolling
- Arrow keys scroll a focused container reliably in Firefox. A key press no longer triggers a relayout, which cut Firefox's smooth keyboard scroll short after a few pixels; relayouts now happen only for Enter and Space (Space on a scroll container pages it instead). A layout pass also writes a container's scroll position back only when the pass moved it, since any write cancels a native scroll in flight.
- Keyboard scrolls settle on a cell in Safari, which fires no
scrollendafter them: the settle now also runs after a short pause following a key press.
Selection
- Text-mode drags inside a banner select by character, and the painted highlight and the copy follow the range; a drag across the banner still takes it whole. A double-click on a custom leaf selects the art's line under the pointer and a triple-click the whole leaf, in both modes.
<mono-ascii>folds only collapsible whitespace, so a run of widens a gap, and a no-break space draws as the space glyph.
Playground
- The default sample ends with a QR code in a promo box, alongside the story's other glyphs.
Full changelog: v0.2.8...v0.2.9