Skip to content

WebFluent v2.1.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 22:36
· 344 commits to master since this release

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 when showErrors
    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/Grid gap: xs|sm|md|lg|xl, align: and justify: (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; a Tcell
    inside Thead is a <th>.
  • Link(to: "/docs", active: "prefix"): links get .active and
    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"; only icon: drew the glyph.
  • IconButton dropped every named argument but icon and label, 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) beside step).
  • placeholder, disabled, src and 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 a Select
    component was appended after the value (codegen).
  • An else if chain with a final else skipped every branch.
  • A Slider or Input with both bind: and on:input kept 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.
  • createStore bound derived values before actions, so a derived value that
    called an action threw.
  • The router never set document.title.
  • wf build did 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.html at the root, and it addresses its assets from the site root, so a
    dynamic route under a static build hydrates.
  • wf serve did not serve a pre-rendered <route>/index.html.
  • The heading-outline lints (A11, A12) could not see headings inside
    components; A03 did not accept aria-labelledby; A13 warned about a white
    label on --color-primary even when no primary variant was used.
  • In structural mode the sheet stacked every Row under 768px; it no longer
    paints layout decisions the author did not make.

Install: cargo install webfluent (crates.io), or the .deb / .msi / archives below.