Recent Commits to wunderbaum:main (4)

  • 27/09/2026 6:16

    Add ARIA tree grid semantics

    Add ARIA tree grid semantics
    
    Wunderbaum renders no ARIA roles or states, so screen readers see a flat
    list of generic elements with no tree structure, no expansion state and no
    focus position (DOM focus stays on the container).
    
    - container: role=treegrid, aria-rowcount (rows are virtualized, so the
      count of all visible rows is announced, plus the header row if shown),
      aria-multiselectable for (non-radio) checkbox trees with selectMode
      multi/hier
    - header: role=rowgroup/row/columnheader with aria-colindex, and aria-sort
      for sortable columns
    - node list: role=rowgroup; rows: role=row with a stable id, aria-level,
      aria-setsize, aria-posinset (status nodes excluded; computed once per
      parent and render pass), aria-expanded, aria-selected (only if nodes can
      be selected), aria-busy and aria-rowindex; cells: role=gridcell with
      aria-colindex (aria-colspan for colspan nodes)
    - decorative icons (indent, expander, checkbox, column buttons, resizers)
      are aria-hidden
    - the container's aria-activedescendant follows the focus node, and is
      removed while the focused row is not rendered
    
    Adds unit tests and a short Accessibility section to the keyboard tutorial.
  • 27/09/2026 6:15

    Add paging nodes ("More...") for long child lists

    Add paging nodes ("More...") for long child lists
    
    NodeStatusType.paging and isPagingNode() exist, but there is no way to add a
    paging node and the keyboard handler for it is commented out. Long (lazy)
    child lists, for example thousands of children under one node, therefore
    need a custom node type and click/keyboard handling in every application.
    
    - node.addPagingNode(titleOrData, options) appends a colspan paging status
      node after the children, replacing an existing one
    - click, Enter and Space (row and cell mode) on a paging node fire the new
      clickPaging event instead of activating it; the handler loads the next
      page and calls addChildren() / addPagingNode(). If the handler returns a
      promise, the node shows its loading state and ignores further clicks
      until it settles
    - strings.moreItems (default "More...") and a link-like default style
    - sort() keeps paging nodes at the end
    - node.remove() resets tree.activeNode / tree.focusNode if they are
      removed (as removeChildren() already does for the focus node), so
      removing a clicked paging node does not leave a dangling focus node for
      the next key press
    
    Adds unit tests and documents the clickPaging event.
  • 27/09/2026 6:13

    Theme: derive colors from the base variables at runtime (color-mix)

    Theme: derive colors from the base variables at runtime (color-mix)
    
    The derived colors (hover, active, active cell, drop, dim, grayscale, ...)
    are computed by Sass at build time and declared on :root. Setting
    --wb-background-color or --wb-bg-highlight-color on a tree (for example for
    a dark mode) therefore leaves every derived color at its light default, and
    a theme has to restate about 20 variables.
    
    Derived variables are no longer declared on :root. Rules use
    var(--wb-<name>, color-mix(...)) based on the base variables that apply to
    the tree element (Sass helper derived-color()), so a theme sets the base
    colors only. The mix ratios approximate the previous defaults (within a few
    RGB units). Pages that set a derived variable themselves keep working,
    because an explicit value still wins over the fallback.
    
    Browsers without color-mix() support (before 2023) get the previous static
    values on :root via @supports not (...).
  • 27/09/2026 6:11

    Avoid a forced layout for every rendered row

    Avoid a forced layout for every rendered row
    
    _render_data() reads tree.element.scrollTop before it writes the title, for
    every row it renders. After the first DOM write each read forces a
    synchronous layout, so re-rendering the viewport (after expand, scroll or
    filter) costs one layout per rendered row.
    
    The value is only needed when preventScroll is set, so read it only then.

Log in