Skip to content

v0.156.0

Choose a tag to compare

@tenphi tenphi released this 07 Aug 15:33
· 194 commits to main since this release
583b9d3

Minor Changes

  • #1287 946f6b85 Thanks @tenphi! - Add ColorPicker — a form-attachable color input. The field shows the current color as a swatch, accepts hex, rgb(), hsl(), okhsl(), okhst() and oklch() text, and opens a popover where the color can be tuned on three axes: HST (OKHST hue/saturation/tone), LCH (OKLCH lightness/chroma/hue) or RGB. Every conversion runs through Glaze, so the value is always a real, in-gamut color.

    formatMode controls how the text relates to the value: forced (default) rewrites the text in format, derive keeps the notation the user typed but normalizes the value, and free passes the text through verbatim after verifying it parses. Also adds a PipetteIcon.

  • #1286 272a9e9b Thanks @tenphi! - Add widget selection and rigid group movement to Board.

    Set selectionMode="single" | "multiple" and read the selection with
    selectedKeys / defaultSelectedKeys / onSelectionChange (keys are layout item
    ids, always returned in layout order).

    Pressing a widget selects it on pointer-down and arms a drag of the selection —
    selecting and grabbing are one gesture, so move the pointer and it drags, stay
    still and it was only a selection. Shift (or Cmd/
    Ctrl) toggles membership, dragging from empty canvas lassos
    (allowMarqueeSelection), Space toggles the focused widget, and
    Escape clears.

    Selection behaves like focus: it tracks what the user is working with and moves on
    as soon as they touch something else — pressing another widget makes that the
    selection, and pressing an interactive control inside a widget or moving focus off
    the board drops it entirely.

    With "multiple", dragging any selected widget moves the whole selection as a
    rigid block that reflows by the board's own rules — the same compaction a single
    widget gets, so a group can never be parked in empty space on a vertical board
    and the widgets around it close the gap in the same frame. Every widget travels by the same delta, the group clamps against the
    grid edge as a unit instead of collapsing into it, a frame that cannot be placed
    is rejected outright rather than partially applied, and the move commits through a
    single onLayoutChange. Arrow keys move the group too. BoardInteractionInfo
    gains items, oldItems and placeholders describing the whole gesture; the
    existing item / oldItem / placeholder fields are unchanged, and a board with
    no selection behaves exactly as before.

    The selectionCancel selector (board- or widget-level, defaulting to the exported
    BOARD_SELECTION_CANCEL) marks interactive descendants; [data-no-select] opts
    out a custom control. On a selectable board it also gates dragging, which fixes a
    long-standing trap: useMove's pointer-down calls preventDefault(), so without
    a dragCancel an input inside a widget could not be focused or typed into.
    Selected widgets are drawn with a #primary-border border and a #primary ring —
    an edge treatment rather than a fill, since selection reads as a focus-like state;
    outline stays reserved for the real focus ring. Widgets get a selected
    modifier you can restyle through widgetProps.styles.

    onWidgetsDelete reports a Delete/Backspace press with a
    non-empty selection. Board never mutates the layout itself, so removal stays
    yours to implement and to make undoable.

    Accessibility: widget hosts are now role="group" with an accessible name from
    the new Board.Widget aria-label prop (falling back to qa, then the layout
    id). aria-roledescription is now localized rather than hardcoded English — it
    was previously also invalid, sitting on a role-less element. Selected widgets are
    described as "Selected", and selection changes are announced through a polite live
    region.

    Widget hosts also expose data-board-widget-id and data-selected.

Patch Changes

  • #1284 fc517477 Thanks @tenphi! - Fix the types of FilterListBox.Item and CommandMenu.Item. Both were declared as React Stately's bare Item, so Item props such as icon, rightIcon, description, hotkeys and actions were rejected by TypeScript even though they worked at runtime. They now use CollectionItem, matching ListBox.Item, Menu.Item and the other collection components.