Skip to content

Releases: madnh/puredashboard

v0.5.0

Choose a tag to compare

@madnh madnh released this 29 Oct 03:27
Immutable release. Only release title and notes can be modified.

Changed — BREAKING

  • The router no longer sets aria-current on links. Until v0.4.2, after every
    navigation Router scanned EVERY a[href] in the document and set
    aria-current="page" on links whose path equalled the route exactly, removing it from
    all others. That (a) dropped the parent section on a child route (#/scans lost it on
    #/scans/5), and an app could not fix it in afterEach because the router ran after and
    again in a microtask; (b) marked content links that happen to point at the current page
    (a header status link, a link in a table); (c) fought <puredashboard-nav> /
    <puredashboard-tabs> over the same attribute and could leave their current link
    without it. Reported from ribeto-sync.

    Migrate — pick what renders your links:

    • <puredashboard-nav>: add the follow-route attribute and stop setting current from
      afterEach (or keep setting current yourself; both work).
    • <puredashboard-tabs> in link mode: add follow-route, or keep setting value.
    • Plain <a> links (e.g. the theme shell's <nav class="tabs">), which got
      aria-current from the router for free before:
      import { Router, markActive } from "LIB/router.js";
      document.addEventListener("routechange", () => markActive(document.querySelector("nav.tabs")));
      router.start();   // add the listener before start() so the first navigation is caught
    • CSS that styles [aria-current="page"] needs no change.
    • Code that relied on the exact-match rule: markActive(container, { exact: true }) or
      follow-route="exact".

Added

  • routechange event on document after every committed navigation (guards passed,
    modules loaded, before the page mounts). detail = { path, params, query, route, mode, base }.
    A navigation cancelled or redirected by a guard publishes only its final route.
  • src/active-link.js: currentRoute() (the last committed route, for code that starts
    after a navigation) and markActive(container, { exact? }), which marks the best-matching
    link INSIDE container only. Both re-exported by router.js. Matching is by path
    segments: equal, or a segment prefix (#/scans on #/scans/5, never #/scansx);
    longest match wins within the container; the root matches only itself; queries ignored;
    history mode strips base.
  • follow-route on <puredashboard-nav> and <puredashboard-tabs> (link mode): the
    component sets its own current / value from the router on connect, on every
    routechange, and when items / tabs change; follow-route="exact" turns prefix
    matching off. No match clears it (no item current). Survives detach + re-attach.

v0.4.2

Choose a tag to compare

@madnh madnh released this 28 Oct 18:18
Immutable release. Only release title and notes can be modified.

Fixed

  • A host input/change listener reads the new value. In input, textarea, number,
    select, date, time, datetime, color, slider, checkbox and switch, a listener the app added
    on the element BEFORE it connected ran ahead of the element's own value sync, so .value
    (or .checked) was one edit behind. The sync now runs in the capture phase
    (Reactive.on(type, sel, fn, { capture: true })). Reported from ribeto-sync.
  • error no longer blocks the form for good. Once set, error stayed a custom validity
    until the app cleared it, so a native <form> never fired submit again and the app had
    no event to clear it on. Input, textarea, number, select, date, time, datetime, checkbox,
    switch, radio-group and combobox now clear error when the user changes the value, before
    the app's input/change handler runs (which may set it again).
  • theme/shell.css: cards in a grid/flex line up. .card + .card added a top margin to
    every later card, on top of the container's gap. The rule is now .page > .card + .card
    (cards stacked directly in the page). Cards stacked inside another wrapper no longer get
    that margin; space them with the wrapper's gap.

[0.4.1] - 2026-10-22 - Re-attach fixes, json-view follows app theme

Choose a tag to compare

@madnh madnh released this 22 Oct 06:24
Immutable release. Only release title and notes can be modified.

Fixed

  • Detach + re-attach no longer leaves an element dead. <puredashboard-popconfirm> and
    <puredashboard-popover> stopped opening on a trigger click, <puredashboard-form> let the
    browser submit natively (navigation, no submit event), <puredashboard-splitter> gutters
    stopped resizing (pointer and keyboard), and <puredashboard-gallery> lost its /
    shortcut, once the element was removed and inserted again (a parent re-rendering by
    replacing children). disconnectedCallback removed the listeners; the run-once wiring never
    added them back. connectedCallback now re-adds them.
  • <puredashboard-json-view theme="auto"> follows the app theme. It read only
    prefers-color-scheme, so a page forced to <html data-theme="light"> on a dark OS (or the
    reverse) showed a tree in the opposite mode to everything around it. auto now uses
    <html data-theme="light|dark"> when set, else the OS, and tracks both live.

[0.4.0] - 2026-10-12 - Dialog sizes, gallery, agent API

Choose a tag to compare

@madnh madnh released this 12 Oct 03:20
Immutable release. Only release title and notes can be modified.

Added

  • dialog() / drawer() size: "sm" 400 · "md" 560 · "lg" 800 · "xl" 1140px ·
    "full" · a number (px) · a CSS length · "auto". A modal is now a FIXED width capped
    to the viewport; drawers take the same names for their width (320 / 400 / 560 / 720px)
    or, at top/bottom, height (30 / 45 / 65 / 80vh). Presets are themable
    (--puredashboard-dialog-w-<name>, --puredashboard-dialog-drawer-w-<name> / -h-<name>),
    and the height cap is --puredashboard-dialog-max-h (85vh). Gallery story "Sizes".
  • _components.jsonl lists the imperative functions — dialog, drawer, alert
    (with confirm / prompt), menu, toast — one line each with summary, params,
    returns and example (grep '"function":"dialog"'). The generator reads their
    @function JSDoc from the source; before, they were missing and agents guessed.
  • _agents.md: "Always pass size" with a content → size table.
  • <puredashboard-gallery> index page. With no component selected (the new default;
    previously the first one) the canvas lists every component as a card — name, title,
    story count, and the def's description or its first story's notes — grouped like
    the sidebar. Cards are links (?c=<tag>).
  • <puredashboard-gallery> filter. A filter box in the sidebar (filter property,
    URL q=, / focuses it) narrows the sidebar and the index by short name or title.

Changed

  • dialog() defaults to size: "md" (560px, fixed). It used to shrink to its content
    (up to 520px), so a dialog holding a form or a table came out cramped — the usual
    result when nobody set a width. Pass size: "auto" for the old behaviour.
    alert / confirm / prompt still fit their message unless given a size.
  • <puredashboard-gallery> is built from the library's own layout: a
    <puredashboard-sider> (filter in its header slot, a <puredashboard-nav> with Index /
    Overview rows and one section per title prefix; collapses off-canvas, a drawer below
    md, remembered in localStorage), a <puredashboard-header> toolbar split into start
    (sidebar toggle + title) and end (controls), and a <puredashboard-content> canvas. Nav
    rows are real links, so Cmd/Ctrl-click opens a component in a new tab. Stories are no
    longer remounted by unrelated re-renders (filtering, theme). On small screens the
    toolbar controls wrap and the per-card viewport switch is icon-only. The old
    __side / __item / __group / __main classes are gone.

Fixed

  • Theme: a forced data-theme="dark" now sets color-scheme: dark. It inherited
    light dark, which lets the browser follow the OS — so on a light-mode OS every native
    scrollbar and control of a dark page was drawn light (a white scrollbar gutter beside
    dark panels in dialogs, drawers, popovers, lists).
  • dialog() / drawer() body scrollbar matches the sider's: thin, transparent track,
    thumb while the pointer is over the dialog or focus is inside;
    --puredashboard-dialog-scrollbar sets its colour.
  • _agents.md: the prompt() example used { value }; the option is
    defaultValue.
  • reactive.js: a child position that switched template → repeat() → the same
    template rendered nothing.
    The nested instance was kept after its DOM was removed, so
    the switch back updated the detached instance. Any component rendering a list in one
    state and a fixed template in another at the same position was affected.
  • <puredashboard-sider> drawer no longer reflows the content. Closing it put the
    sider back in the flow while its width was still animating to 0, so the text beside it
    rewrapped through every step; with the icon rail below the breakpoint, opening it also
    shifted the content by the rail width. The drawer now keeps exactly its collapsed
    footprint in the flow (--pd-sider-cw, set by the element) and overlaps the rest with a
    negative end margin that animates with the width, so the content width never changes.
  • <puredashboard-sider> scrollbar. When the nav is taller than the space between the
    sticky header and footer, the scroll region showed the browser's default scrollbar (a
    light gutter in dark themes). It is now thin, on a transparent track, and its thumb only
    shows while the pointer is over the sider or focus is inside; --pd-sider-scrollbar
    sets its colour.

[0.3.2] - 2026-10-12 - Rail hides on phones

Choose a tag to compare

@madnh madnh released this 11 Oct 23:38
Immutable release. Only release title and notes can be modified.

Changed

  • <puredashboard-sider> rail hides while the sider is closed below its breakpoint.
    On a phone the 16px edge strip sat invisibly over the content's left edge and the way
    back in is the app's hamburger anyway; above the breakpoint (desktop offcanvas) the rail
    stays as the pointer handle. (Observation from model-gateway's GW-231 measurements.)

[0.3.1] - 2026-10-11 - Sidebar follow-ups

Choose a tag to compare

@madnh madnh released this 11 Oct 16:49
Immutable release. Only release title and notes can be modified.

Follow-ups to the 0.3.0 sidebar from the first app that adopted it (model-gateway):
the "icon rail on desktop, hamburger + drawer on phones" shell, and three small
contracts the app had been bridging on its side. Defaults unchanged; the one visible
change is that a sider collapsed to width 0 now really is gone (see Fixed).

Added

  • <puredashboard-sider> breakpoint-collapse="offcanvas" (property
    breakpointCollapse, default "icon" = today's behaviour): below the breakpoint the
    sider collapses to width 0 instead of the icon rail, and collapseMode reads
    "offcanvas" there; above it the collapsible mode applies. toggle() from a header
    hamburger opens the existing drawer. Follows media-query flips both ways and attribute
    changes.
  • <puredashboard-header> sections. Wrap children in puredashboard-header__start,
    puredashboard-header__center and puredashboard-header__end to split the bar into a
    left-hugging start (sider toggle, breadcrumb), a centred middle that takes the slack
    (search, tabs) and a right-hugging end (actions, user menu). Any may be omitted.
  • <puredashboard-sider> trigger="bar"|"button": the collapse trigger as today's
    full-width strip, or a small centred icon button with no strip. Unset, variant="inset"
    now uses the button (the strip read as a stray bar beside the content card); everything
    else keeps the bar.
  • <puredashboard-sider> breakpointMatches (read-only) and a breakpoint event
    { matches, collapsed, collapseMode } on every media-query flip — whether or not
    collapsed changed — so an app can show or hide desktop-only UI without its own
    matchMedia.
  • <puredashboard-sider> closed state attribute (set by the element) whenever it is
    collapsed to width 0 — collapsible="offcanvas", breakpoint-collapse="offcanvas"
    below the breakpoint, or collapsed-width="0". Its header, scroll region, footer and
    trigger are made inert and hidden, the edge border is dropped; only the rail (a
    pointer handle) and the shortcut remain as ways back in.
  • Router afterEach(ctx, hit) option — called once the guards have passed and the
    page module has loaded, i.e. the navigation is committed, just before the page mounts
    (before the view-transition swap); router.current already points at the new hit. A
    throwing hook is reported through onError (or the console) and never blocks the mount.
  • <puredashboard-gallery> viewport switch. Every story card gets an Auto · Phone ·
    Tablet · Desktop switch (a <puredashboard-segmented> with icons), and the toolbar a
    "Viewport" select (URL vp=) that frames every card at once. A framed story is staged in an <iframe> of that size (390 / 820 / 1280px
    wide, or "WxH") that loads the gallery in only mode, so the story gets a real viewport
    of its own — breakpoints, media queries and 100vh behave as on that device. A story may
    declare its default with viewport: "phone" (or { width, height }); props viewport
    / frames, .puredashboard-gallery__frame / __vp.
  • <puredashboard-layout> plain for the nested layout beside an inset sider: it stays
    on the page background, its header becomes a bare toolbar (no panel, no border) and the
    content — or the nested layout with its own header — inside is the inset card: the
    "toolbar above the card" arrangement.
  • Gallery: Nav story "Inset" now carries an app header split into the three sections (sider
    toggle + breadcrumb | search | inbox badge + user), and "Inset · top header" shows the
    same header as a bare toolbar ABOVE the inset card (plain).
  • Gallery: Nav stories "Hamburger" (breakpoint md + breakpoint-collapse="offcanvas",
    header button → toggle(), follows the window width) and "Hamburger · phone" (the same
    shell framed as a phone by default, so the drawer shows on a desktop screen), and Menu
    story "Toggle from its button" (a counter shows that the re-click closes without
    re-opening).

Changed

  • <puredashboard-sider> persist is desktop-only. Opening or closing the drawer
    below the breakpoint (trigger, rail, shortcut, toggle(), backdrop, Escape, link) no
    longer writes the stored value — a desktop "collapsed" survives a phone-width detour.
  • <puredashboard-sider> growing past its breakpoint restores the state held before it
    matched
    (the persisted choice when persist is set; otherwise the author-set or
    previous value) instead of always expanding.
  • <puredashboard-sider> drawer is modal: while it is open the sider's layout siblings
    are inert (restored on close), and an Escape that something else already handled
    (defaultPrevented — an open menu()) no longer closes the drawer as well.

Fixed

  • <puredashboard-sider> collapsed to width 0 kept its contents in the Tab order (11
    stops measured through a hidden sider), left a 1px edge line, and let the collapse
    trigger's chevron spill out
    of the 0-wide offcanvas sider. All three are the closed
    state above.
  • menu(): clicking the anchor of the open menu re-opened it. The press light-dismissed
    the menu and the same click's handler opened a fresh one. Now a press on the anchor
    closes it, the promise resolves null, and menu() called from that click's handler
    returns an already-resolved null instead of opening again — no change to the usual
    button.onclick = () => menu(button, …) wiring.

[0.3.0] - 2026-10-11 - Sidebar

Choose a tag to compare

@madnh madnh released this 11 Oct 02:16
Immutable release. Only release title and notes can be modified.

A stronger sidebar, modelled on the shadcn Sidebar and adapted to PureDashboard's
zero-dependency, light-DOM conventions. Defaults keep their look; everything new is
opt-in except two small visual corrections called out under Changed.

Added

  • <puredashboard-nav> sections. A node { heading, children, collapsible? } renders
    a titled block (small muted heading over a flat list, puredashboard-nav__section /
    __heading / __list--section); collapsible: true makes the heading a
    <button aria-expanded> and the toggle event reports it by its heading.
  • <puredashboard-nav> row actions. A node's action: { icon, label } adds a secondary
    button beside the link (puredashboard-nav__action, shown on hover / focus / on the
    active row, always on touch screens) that fires action with { item, label, href }.
  • <puredashboard-nav> icon-only attribute. The rail look on the nav itself: centred
    icons, labels clipped (still the links' names) and copied to each link's title, badges /
    chevrons / actions / sub-lists / section headings hidden (a section keeps a separator).
  • <puredashboard-nav> loading. true (5 rows) or a number renders skeleton rows and
    marks the nav aria-busy.
  • <puredashboard-nav> palette knobs --pd-nav-text / -muted / -hover / -active-bg
    / -border / -radius, so a container (the sider) can recolour a nav without touching
    the page tokens.
  • <puredashboard-sider> header and footer slots. Children with slot="header" /
    slot="footer" sit in sticky regions above / below the scrolling middle (brand or
    workspace switcher on top, user menu at the bottom); puredashboard-sider__expanded-only
    on any element inside hides it while collapsed.
  • <puredashboard-sider> variant="floating" (a rounded, bordered, shadowed card inset
    from the layout) and variant="inset" (the sider sits on the page background and the
    content beside it becomes the raised card).
  • <puredashboard-sider> collapsible="offcanvas" slides the sider out completely
    (width 0) instead of leaving the icon rail; collapseMode reads back "icon" /
    "offcanvas".
  • <puredashboard-sider> rail — a thin toggle handle along the outer edge (hover
    shows a line, click toggles; labels.rail).
  • <puredashboard-sider> shortcut — Cmd (macOS) / Ctrl + the given key toggles; the
    bare attribute means b.
  • <puredashboard-sider> persist — remembers the collapsed state in localStorage
    under puredashboard-sider:<name> and restores it on connect.
  • <puredashboard-sider> drawer below its breakpoint. Expanding a breakpoint-collapsed
    sider (trigger, rail, shortcut, toggle()) opens it over the content as a drawer
    (overlay state attribute, backdrop on the layout); backdrop click, Escape or a link
    inside closes it.
  • Theme tokens --sidebar-bg / -text / -muted / -border / -hover / -active in
    tokens.css, defaulting to the page panel tokens; the sider and the nav inside it paint
    with them, so one override gives every sidebar its own palette.
  • Gallery: Nav stories "App shell", "Icon rail", "Floating", "Inset", "Offcanvas",
    "Loading", "Sidebar palette".

Changed

  • Nav rows are square. Top-level rows were rounded (--radius, 10px) while nested rows
    were squared off next to the guide line, so the active row changed shape with its depth;
    now every row (and section heading / action button) has no radius. Opt back in with
    --pd-nav-radius.
  • A <puredashboard-sider> mirrors collapsed onto its navs as icon-only (a
    MutationObserver covers navs added later); the icon-only CSS moved from layout.css
    into nav.css under that attribute. Visually unchanged.
  • A sider's breakpoint only collapses on a match. Above the breakpoint an author-set
    or persisted collapsed now stands; it used to be forced open on connect.
  • The sider no longer clips its own overflow (the header, middle and footer regions do),
    so the edge rail can straddle the border.

[0.2.0] - 2026-10-07 - Full component set

Choose a tag to compare

@madnh madnh released this 07 Oct 07:58
Immutable release. Only release title and notes can be modified.

From three components to a full admin-UI set. 0.1.0 shipped the table, the uploader, the
Markdown viewer, the overlays, the router and the optional theme; 0.2.0 adds about sixty
components across forms, navigation, data display, feedback and layout, a desktop-native
layer, a component gallery, a landing page and machine-readable docs for AI agents — still
zero-dependency, no-build and CSP-safe. It also takes in the 19 local patches a production
consumer (model-gateway) was carrying, plus one option it asked for, and fixes the UI
defects a full review of the gallery found in both themes.

Upgrade notes (from 0.1.0)

  • Light theme colours are darker. --accent is #2563eb (hover #1d4ed8, active
    #1e40af) and --red is #dc2626 in the light palette, for readable white-on-accent
    text. Pages pinned to the old hues should override these tokens.
  • The optional theme's button / a / form-control rules have zero specificity
    (:where()). Any class in your app now beats them; if you relied on the theme winning
    over a single class of yours, raise that rule.
  • <puredashboard-table> markup: every <th> carries scope="col", and the
    rows-per-page select always lists the current pageSize. Tests that snapshot the table
    header may need updating.
  • components.css is the new one-link bundle for every component stylesheet; linking
    files one by one still works.

Added

  • Design tokens in three tiers (theme/tokens.css): primitive --pd-ref-*, semantic
    app tokens, per-component --pd-<name>-* knobs — plus focus ring, elevation, z-index,
    motion (honours reduced motion), control sizing with a compact density
    ([data-density="compact"]), a type scale and status backgrounds. Every 0.1.0 token
    name is kept, and every value except the light palette's accent and red (see the
    upgrade notes). components.css @imports every component stylesheet.

  • Forms — <puredashboard-input>, -textarea, -number (± steppers), -select,
    -combobox (searchable, APG), -checkbox, -switch, -radio-group, -slider, -date,
    -time, -datetime, -color, -rate, the <puredashboard-form> wrapper (real
    <form>, values + validity) and the <puredashboard-field> label/hint/error wrapper.
    All form-associated through ElementInternals.

  • Navigation — <puredashboard-tabs> (APG tabs, or a nav of real links),
    -breadcrumb, -pagination (windowed; cursor paging with hasMore), -steps,
    -nav (collapsible sidebar tree), -menubar (desktop application menu bar).

  • Data display — <puredashboard-card>, -descriptions, -statistic, -tag,
    -badge, -avatar, -list, -tree, -collapse, -timeline, -empty, -json-view,
    -lazy, -copy, -toggle, -toggle-group, -segmented.

  • Feedback and overlays — <puredashboard-alert>, -progress (line / circle),
    -meter, -spinner, -skeleton, -result, -tooltip, -popover, -popconfirm.

  • General and layout — <puredashboard-button> (variants, sizes, status
    success/warning/danger, shape round/circle, loading, link mode), -divider, -space,
    -flex, -row / -col grid, <puredashboard-layout> with -header / -content /
    -footer / -sider (collapsible), -splitter (resizable panes, APG keyboard).

  • Desktop-native layer — <puredashboard-titlebar> for frameless Tauri / Wails /
    Electron windows (mac / windows / linux control layouts, drag region), an opt-in macOS
    skin theme/native.css ([data-skin="macos"]: vibrancy, hairlines, compact radii) and a
    frameless-window recipe in docs/DESKTOP.md.

  • dialog() layout — a flex column whose header and new footer option stay put while
    the body scrolls; alert / confirm / prompt put their buttons in that footer.

  • <puredashboard-gallery> — a reusable, zero-dependency component explorer (sidebar,
    theme / lang / density toolbar, overview grid, URL state) with a story for every
    component (test/gallery.html), a demo dashboard (test/dashboard.html) and a desktop
    demo (test/desktop.html).

  • Landing page (index.html) for GitHub Pages, built with the library itself, with a
    live demo section.

  • Docs for AI agents that consume the library — src/_agents.md (rules, recipes,
    component index) and src/_components.jsonl (one JSON line per component, generated
    from the JSDoc by tools/api-reference).

  • <puredashboard-copy> (copy.js): a copy-to-clipboard button — one click writes a
    value to the system clipboard and the button reports the result (the Lucide copy
    icon swaps to a check, or a cross on failure, for feedback ms, while an off-screen
    live region announces it). The value does not have to be text: a string, rich
    HTML (type="html" writes text/html and a plain-text flattening), or an
    image — a URL, a Blob/File, an <img> or a <canvas>, normalised to PNG
    through a canvas because that is the one format clipboards reliably accept. The value
    comes from value (also a possibly-async function, so it stays late-bound), src
    (an image URL, fetched on click) or from (a CSS selector — an <input> contributes
    its .value, anything else its textContent). Text degrades to the legacy
    <textarea> + execCommand path when the async Clipboard API is unavailable; images
    and HTML need a secure context, and a failure is never silent — it shows the error
    state and emits copyerror. Renders a real <button> (platform keyboard, focus,
    disabled) that is named "Copy" by default, so an icon-only one needs no
    aria-label. variant="text", three sizes and the --pd-copy-* knobs match
    <puredashboard-toggle>, so they line up in one toolbar. The success event is
    copied, not copy, so it can't be confused with the platform's own bubbling
    Ctrl+C event.
    Tables paste into a spreadsheet as real cells: an element source contributes its
    outerHTML (so from="#report table" copies the grid, not a run-on string), a
    <table> is inferred as html without setting type, and the text/plain half is a
    structured flattening — TSV for tables (tab per cell, newline per row), line breaks
    for block elements and <br> — so Excel's "Paste Special → Text" and any plain-text
    field still get one cell per column instead of everything in one.

  • <puredashboard-lazy> (lazy.js): defers building expensive content until it is
    needed — <img loading="lazy">, but for components. A page holding dozens of
    <puredashboard-json-view> / <puredashboard-markdown> / tables pays for all of them
    up front; wrapping each one defers the work until it scrolls into view. Measured in
    Chromium with 200 <puredashboard-json-view>s: 1005 ms → 53 ms to build,
    13 000 → 656 DOM nodes, 200 → 4 components upgraded.
    Three content sources: a <template> child (zero JS — a template's content is
    inert, so the elements inside are never even upgraded), a render(host) function, or
    load: () => import(…) whose default export is a tag name or a mount function (the
    same contract as a router.js page). Triggers: visible (default,
    IntersectionObserver + rootMargin), idle, eager, manual + renderNow().
    While pending it shows the author's [data-lazy-fallback] child or a built-in shimmer
    of the reserved height, so nothing jumps on swap; unrender also tears content down
    when it scrolls far away (long lists). State is reflected as data-state
    (pending/rendering/rendered/error), it emits render / unrender /
    loaderror, materialises everything pending on beforeprint, and renders immediately
    where IntersectionObserver is unavailable so content is never lost.

  • <puredashboard-toggle-group> (toggle-group.js): a set of <puredashboard-toggle>
    buttons sharing one selection — text alignment, a view mode, a formatting set. The
    toggles are the host's real light-DOM children (adopted like splitter.js's panels,
    and re-synced live by a MutationObserver when they are added or removed), so any
    toggle feature — icons, labels, sizes, variant — works inside a group. Single-select
    by default (value is a string, null when empty); multiple makes it an array.
    deselectable=false keeps one always selected, attached (default) joins the buttons
    into one control, orientation="vertical" stacks them, disabled disables the set
    (and restores only what it disabled). Renders role="group" with a roving tabindex
    — the whole group is ONE tab stop and Arrow/Home/End move between the toggles
    (loop wraps). The children's own change events are swallowed; the group emits a
    single change {value}, so a caller listens in one place.

  • <puredashboard-toggle> (toggle.js): a two-state button — it stays visibly
    pressed until pressed again — for a setting that applies IMMEDIATELY (bold/italic in a
    toolbar, mute, pin, "show archived"). Renders a native <button> with aria-pressed,
    so keyboard (Space/Enter), focus and disabled come from the platform. Deliberately
    not form-associated: <puredashboard-switch> (role=switch) and
    <puredashboard-checkbox> remain the form inputs — a toggle is an action button and
    submits nothing. Props: pressed, disabled, value (its identity inside a toggle
    group), label (string or node), icon (trusted SVG), size, variant
    (default/text for toolbars), plus tabbable + focus() so a group can run a
    roving tabindex. Emits change {pressed, value} on user action only — setting
    .pressed in JS stays silent. pressed is reflected as an attribute; an icon-only
    toggle takes its name from aria-label (mirrored onto the inner button).

  • <puredashboard-meter> (meter.js): a gauge for a MEASUREMENT inside a known
    range (disk used, memory, quota, a score) — role="meter" with
    aria-valuenow/min/max + aria-valuetext,...

Read more

[0.1.0] - 2026-06-26 - First public release

Choose a tag to compare

@madnh madnh released this 26 Jun 07:55
Immutable release. Only release title and notes can be modified.

First public release — extracted into a standalone, zero-dependency, no-build library.

Added

  • Template engine (reactive.js): a lit-html-style parts engine that diffs the DOM
    in place (so <input> focus/caret/scroll survive re-renders), the repeat() keyed-list
    directive, renderResult, and the Reactive custom-element base. No eval/new Function.
  • String templating (html.js): escaped html, plus raw, icon, escapeHTML.
  • Components
    • <puredashboard-table> — sortable columns, filter, pagination + rows-per-page,
      row selection with a bulk-action bar, per-row actions.
    • <puredashboard-upload> — drag-and-drop, thumbnails, per-file progress, native
      multipart form submit; plus uploadFile().
    • <puredashboard-markdown> — XSS-safe Markdown rendered textContent-only with an
      href whitelist; plus parseMarkdown / renderMarkdown.
  • Imperative overlays — dialog / drawer / alert / confirm / prompt,
    menu(), and toast (+ .success/.error/.warn/.info), all on the native top layer.
  • Router (router.js) — hash or History API modes, params, catch-all 404,
    lazy-loaded pages, layouts, and guards.
  • Optional theme (src/theme/) — tokens.css (light/dark palette), base.css
    (form controls), shell.css (dashboard frame), and dashboard.css (all-in-one).
    Dark by default, light via prefers-color-scheme, forceable with data-theme.
  • Docs & tooling — README, docs/ARCHITECTURE.md, docs/USAGE.md,
    docs/DEVELOPMENT.md; jsdom test suite run in Docker (make -C test); a browser
    showcase/demo harness under test/.

Changed

  • Toasts now use the elevated --panel surface (consistent with menu / dialog), so
    they read as crisp cards instead of relying on a low-contrast background.