• v0.36.0 c42b8acb91

    v0.36.0 Stable

    pienter-release-bot released this 2026-09-22 21:27:00 +00:00 | 3 commits to main since this release

    Added

    • TreeIndex gains search and filters that dim rather than remove (ADR 0012).
      Given queryOptions (or a state from useIndexQuery, which no longer
      needs a load), it owns the same query state as Index and provides it, so
      FilterCheckbox, FilterSelect, FilterMultiSelect, FilterRange, the
      search box, the filters slot and the active-filter chips work unchanged,
      in a page aside included, with syncQuery keeping the URL. It never reloads
      for a query: it emits query with the committed search and filters
      (TreeQuery), the project answers with matching, a set of row keys, and
      every row outside the set is dimmed (data-dimmed on the row), keeps its
      move button and stays offered as a destination, so the tree keeps its shape.
      The match count is announced through the table's live region and a dimmed
      row's header carries a visually hidden "Does not match." cue; both strings
      are matchLabels. TreeTable takes the same matching and matchLabels
      for controlled data.
    Downloads
  • v0.35.0 7242f8b32e

    v0.35.0 Stable

    pienter-release-bot released this 2026-09-22 14:34:59 +00:00 | 8 commits to main since this release

    Breaking

    • The tree leaves Index and DataTable for a component of its own,
      TreeIndex at @[secure]/ui/components/TreeIndex.vue (ADR 0012). Index
      loses loadTree, move, parentKey, canMove, canParent, rowLabel
      and moveLabels, useIndexQuery loses loadTree and tree, and
      DataTable loses parentKey, canMove, canParent, rowLabel,
      moveLabels, @move and announce(); both are the paged list and the flat
      table again. MoveLabels.childOf is replaced by level(n, parent), and
      treeItems gives each item a depth and lanes instead of a lane and
      four booleans. To migrate an index that used them, replace <Index> with
      <TreeIndex>: pass the tree loader as load (the flat load,
      queryOptions, state and syncQuery go, since a Tree Index has no
      query), keep columns with the level and move columns, parentKey,
      move, canParent, rowLabel, moveLabels, selection and row click, and
      drop sortable from the columns. The summary slot receives total only.
      The move request body is unchanged.

    Added

    • TreeIndex composes a new TreeTable
      (@[secure]/ui/components/TreeTable.vue) the way Index composes
      DataTable: the data-driven tree over Table for controlled data, taking
      rows, the same columns and predicates, v-model:placing and
      v-model:selected, and emitting move with the intent; it never reorders
      rows. TreeIndex shows the tree to any depth: every record is drawn, a row can
      be moved into or out of any parent, and the moving row's whole subtree
      travels with it. The optional maxDepth removes the destinations a move
      would exceed, so a backend that allows two levels never sees a third;
      without it any depth is offered. Each row's header cell carries a visually
      hidden "Level n, under ." prefix, so a screen reader hears depth and
      direct parent while the table stays a plain <table>. Loading, error and
      retry, the placing state, the optimistic move with rollback and
      announcement are as Index had them. Search and filters are a follow-up.

    Fixed

    • A table no longer stretches the page to its own height. Its visually hidden
      content (the caption, a typed column's header text, TreeTable's live
      region in the after slot) is absolutely positioned and was contained by
      the page rather than by the table's scroll port, so a long tree made the
      window scroll behind the app layout's own scrolling main and left a gap
      under the sticky footer. The wrap now contains it.
    Downloads
  • v0.34.0 a195e9892c

    v0.34.0 Stable

    pienter-release-bot released this 2026-09-22 11:13:39 +00:00 | 17 commits to main since this release

    Added

    • Sidebar gains a #link slot, forwarded by AppLayout, that renders every
      navigable entry — plain links, group rows with an href, the bottom nav and
      the collapsed rail's flyout — through the consumer's own element, so an
      application sets its RouterLink once and drops its click interception. It
      follows Breadcrumb's #link: the scope carries item, linkClass and, in
      addition, attrs (the ARIA state, data hooks and listeners the Sidebar's
      keyboard, flyout and drawer wiring depend on) and content (the icon, label
      and badge). Render <RouterLink :to="item.href" :class="linkClass" v-bind="attrs"><component :is="content" /></RouterLink>. Active state and
      aria-current stay the Sidebar's decision. Without the slot the output is
      unchanged. SidebarLinkSlotScope is exported from Sidebar.types.
    Downloads
  • v0.33.0 4f1f87e370

    v0.33.0 Stable

    pienter-release-bot released this 2026-09-22 11:09:05 +00:00 | 23 commits to main since this release

    Breaking

    • RecordDetails is removed, with RecordDetailsField and the
      @[secure]/ui/components/RecordDetails.vue and RecordDetails.types
      exports. A record's view page is its edit form with readonly, which
      renders the same layout, tabs and fields read-only, without an actions row
      or a submit. A related record beside a form is a read-only RecordFields
      group bound to that record: replace <RecordDetails :record :fields> with
      <RecordFields :model-value="record" :fields readonly>, retype the fields
      as RecordFormField[], and turn a #field:name slot that rendered a link
      or badge into a type: 'custom' field whose slot renders the value inside
      a .pui-field with a .pui-field__label. The description list's
      placeholder, Yes/No booleans and JSON objects have no equivalent: a
      boolean is a checkbox or switch field and an object is its own fields.
      Move an #actions slot to the page header.
    Downloads
  • v0.32.0 4eb5aaff3c

    v0.32.0 Stable

    pienter-release-bot released this 2026-09-22 10:41:49 +00:00 | 30 commits to main since this release

    Added

    • RecordForm and RecordFields accept password, url, tel, switch
      and tags field types, rendering TextInput with that input type, Switch
      and TagsInput. Each is bound to its path with the control's value type (a
      string, a boolean, a string array), takes busy, disabled and backend
      issues like the existing built-ins, and view mode renders it read-only, not
      disabled. tags forwards placeholder and maxTags. A user form no longer
      needs a #field:name slot for its password input. Relation pickers stay
      type: 'custom'.
    Downloads
  • v0.31.1 39153a723e

    v0.31.1 Stable

    pienter-release-bot released this 2026-09-22 10:30:41 +00:00 | 35 commits to main since this release

    Fixed

    • useUnsavedChanges asks before moving to another record inside the same
      component. It registered onBeforeRouteLeave only, so going from
      /blog/1/edit to /blog/2/edit with a dirty draft reused the component,
      skipped the guard and dropped the edits. It now registers
      onBeforeRouteUpdate too. A navigation that keeps the route params, such as
      a ?tab= or hash change, does not ask. Pass
      { sameRecord: (to, from) => boolean } as the second argument when the
      record is not identified by the params. Leaving the route asks as before.
    Downloads
  • v0.31.0 1358d846f7

    v0.31.0 Stable

    pienter-release-bot released this 2026-09-21 13:53:52 +00:00 | 38 commits to main since this release

    Added

    • Index and DataTable show records that carry a parent and a manual order
      as a two-level tree, and move them by click-to-place (ADR 0011). This is for
      an index that doubles as the menu editor, such as Pages.
      • The project adds two typed columns, { key, label, type: 'level' } and
        { key, label, type: 'move' }, and names the parent field with
        parentKey. The level column draws the hierarchy, so every other column
        stays aligned. Rows must arrive in manual order; nothing orders them.
      • A row's move button opens a placing state in which every legal
        destination is a button that says where it leads ("Top level",
        "Inside About"). Hovering or focusing one previews the row in that
        place. There is no drag and drop. Escape, Cancel or the move button
        again leaves the state.
      • Index takes loadTree({ signal }), returning { data } with every
        record, and uses it instead of load while no search, filter or sort is
        active; pagination hides in that view. Every other view is the flat
        paginated list without the two typed columns. useIndexQuery takes the
        same option and exposes tree.
      • Index takes move({ id, parentId, index, siblingIds }), an async
        function. The move is applied at once, rolled back with the rejection's
        message shown and announced if it rejects, and the tree is reloaded if
        it resolves. DataTable emits the same intent as move and never
        reorders rows; applyMove from @[secure]/ui/utils applies one.
      • canMove(row) and canParent(row) remove move buttons and "inside"
        destinations a backend would refuse. The package itself enforces two
        levels. rowLabel(row) names a row and moveLabels overrides any of
        the English strings; they are functions so a translation owns its word
        order.
      • Table gains an after slot, rendered inside the wrap after the
        <table>.
      • The HTTP side is in docs/cms/backend-contract.md under "Manual order
        and moves".
    Downloads
  • v0.30.0 a2f10602a7

    v0.30.0 Stable

    pienter-release-bot released this 2026-09-19 22:59:21 +00:00 | 47 commits to main since this release

    Changed

    • BlockEditor edits the open block in a Sheet when its container is
      narrower than 44rem, instead of stacking the detail pane under the list:
      label, type and position in the sheet's head, Previous, Next and Close in
      its footer, no prompt. Closing the sheet sets open to null and returns
      focus to the row.
    Downloads
  • v0.29.1 ca5733f980

    v0.29.1 Stable

    pienter-release-bot released this 2026-09-19 22:55:01 +00:00 | 52 commits to main since this release

    Fixed

    • Index validates its sortable columns against queryOptions.sorts before
      it creates its own query state. The state loads at once, so a column the
      vocabulary does not allow fired one request before the configuration error
      was thrown.
    Downloads
  • v0.29.0 69743341c8

    v0.29.0 Stable

    pienter-release-bot released this 2026-09-19 22:49:51 +00:00 | 58 commits to main since this release

    Breaking

    • BlockEditor replaces the row of per-type add buttons with one Add block
      button that opens a type picker: a DropdownMenu, or a searchable
      Command list when searchable is set or, left unset, above 8 block
      types (searchPlaceholder, searchEmptyText). Each row gains an insert
      button above it, shown on row hover or keyboard focus without moving the
      rows, that opens the same picker and inserts before that row. addLabel
      is now a string (default Add block) instead of (typeLabel) => string:
      a page that passed a function passes the single button's label. The add
      announcement names the position (Text added at position 2 of 4.).

    Added

    • DropdownMenu takes anchor, the trigger to position against when
      several popovertarget triggers share one menu; the default stays the
      first matching trigger.

    Fixed

    • DropdownMenu no longer stays visible while closed. Its display: flex
      applied in every state and overrode the browser rule that hides a closed
      popover; it now applies under :popover-open only.
    • Sheet now spans the full edge it is anchored to. The browser's
      fit-content size and max-height/max-width for <dialog> won over the
      top/bottom (or left/right) pair, so a short sheet stopped under its
      footer; each side now resets them on its stretched axis.
    Downloads