Repository navigation
WebFluent v2.1.0
WebFluent v2.1 Release Notes
A release driven by building a real application. The Halyard demo
(https://github.com/monzeromer-lab/Halyard) — a nineteen-screen product design
implemented entirely in .wf — found every place where the language did less
than its documentation promised, and a handful of things a design system needs
that it had no way to say. Each is fixed or added here, with a test. Nothing in
this release is breaking.
Added
State and media in style { }
A style block may now hold blocks for how an element looks while hovered,
focused, active, disabled, or in an ARIA state, beside its @media blocks:
Button("Save") {
style {
background: "var(--brand)"
hover { background: "var(--brand-hover)" }
focus { outline: "2px solid var(--focus-ring)" }
pressed { color: "var(--text-accent)" }
@media (max-width: 768px) { width: "100%" }
}
}
States: hover focus active disabled placeholder focus-within current pressed selected checked expanded invalid. The ARIA ones key off the element's own
aria-* attribute, so state and paint cannot disagree. These compile into
styles.css under a content-hashed class — one rule per distinct block, shared
by every element that writes it, and nothing injected at run time (CSP-safe).
@media blocks, which used to become a run-time <style>, take the same path.
Attributes by name
A hyphenated named argument is an attribute: Trow(aria-selected: isSelected),
Badge("Ready", data-tone: "success"). A value that reads state follows it, and
an aria-* value of false is kept as the string "false", which is a real
ARIA state. A keyword after the hyphen is fine (data-state:).
Custom properties and reactive style values
style { --edge: color } sets a custom property. Any style value that reads
state is now kept in step with it, so style { height: "{pct}%" } moves.
Components
- A component's block reaches it as
children, in both the SPA and SSG
backends.AppShell(crumb: "Logs") { … }is one component. - Props are live:
Chip(pressed: showErrors)repaints whenshowErrors
changes. Declared defaults reach the SPA. - A handler written on a component call — the click shorthand or an explicit
on:…block — attaches to the component's root element.
Vocabulary and layout
Row/Stack/Gridgap: xs|sm|md|lg|xl,align:andjustify:(six
values each) are real utility classes in both stylesheets.- Modifiers
fluid circle multiple ordered xs sm md lg xl header:
List(ordered)is an<ol>,Tcell("Build", header)is a<th scope=col>
wherever it is written. Table(caption: "…")gives the table a visually hidden caption; aTcell
insideTheadis a<th>.Link(to: "/docs", active: "prefix"): links get.activeand
aria-current="page"from the router, on the static paint too.Option("value", "Label")submits the value and shows the label.- Theme token and property names may have digit-leading segments
(viz-1,radius-2xl).
Config
meta.fonts and meta.stylesheets add <link rel="preconnect"> and
<link rel="stylesheet"> to both HTML shells; the CSP policy widens for those
origins.
Language
Two-parameter lambdas (a, b) => expr; a keyword after a dot (Array.from);
Number, String, Boolean, Map as globals; a map literal as an arrow body;
store derived values may read other derived values and actions.
Fixed
Icon("home")rendered the word "home"; onlyicon:drew the glyph.IconButtondropped every named argument buticonandlabel, and the
static backends painted the label as visible text next to the glyph. It is
the accessible name (aria-label,title) only, and the glyph is drawn once.Progress(value: expr)was painted once and never moved.- Lambda parameters inside pages compiled as signal reads (
_x()). - An action parameter named like another action read the store member
(move(step)besidestep). placeholder,disabled,srcand the other recognised attribute names
were set once even when bound to state.- A
<select>bound to state always showed its first option: the value was
applied before the options existed (runtime), and a slot inside aSelect
component was appended after the value (codegen). - An
else ifchain with a finalelseskipped every branch. - A
SliderorInputwith bothbind:andon:inputkept only one. - A
Button's block that mixed content with an action ran the action at
render time; the action statements are its click handler now. - A sub-component dropped its style block, attributes and handlers in the SPA.
createStorebound derived values before actions, so a derived value that
called an action threw.- The router never set
document.title. wf builddid not run the V01/V02 vocabulary lint or the semantic check;
it does, with per-file locations.- The catch-all page was written to a directory literally named
*. It is
404.htmlat the root, and it addresses its assets from the site root, so a
dynamic route under a static build hydrates. wf servedid not serve a pre-rendered<route>/index.html.- The heading-outline lints (A11, A12) could not see headings inside
components; A03 did not acceptaria-labelledby; A13 warned about a white
label on--color-primaryeven when noprimaryvariant was used. - In structural mode the sheet stacked every
Rowunder 768px; it no longer
paints layout decisions the author did not make.
Install: cargo install webfluent (crates.io), or the .deb / .msi / archives below.