• v0.28.0 d2514eaddd

    v0.28.0 Stable

    pienter-release-bot released this 2026-09-19 22:44:35 +00:00 | 64 commits to main since this release

    Breaking

    • RichText drops blockquotes from its formatting set, which is now
      paragraphs, headings of level 2 and 3, bold, italic, bullet and numbered
      lists and links. The quote toggle and labels.blockquote are gone, and an input
      rule, a paste or the source view can no longer create a blockquote: the
      text survives as a paragraph. Stored HTML that holds a <blockquote> loads
      as paragraphs and is written back that way on the next edit; take
      blockquote out of a backend allow-list.
    • The quote icon is removed.
    Downloads
  • v0.27.0 6178ea8cb2

    v0.27.0 Stable

    pienter-release-bot released this 2026-09-19 22:41:06 +00:00 | 69 commits to main since this release

    Added

    • RichText gains an HTML source view. The toolbar's last button
      (labels.source, "Source") swaps the editor for a monospace textarea with
      the HTML, one block per line. Edits are applied on blur and on toggling
      back, always through the editor, so the model only ever receives HTML
      within the formatting set; opening and closing the source view without an
      edit emits nothing.
    • A code icon.
    Downloads
  • v0.26.0 0d6cd04a3e

    v0.26.0 Stable

    pienter-release-bot released this 2026-09-19 22:36:29 +00:00 | 73 commits to main since this release

    Added

    • RecordForm and RecordFields gain a { type: 'richtext', placeholder? }
      field type that renders RichText with the same label, hint, required,
      busy, disabled, readonly and issue wiring as the other built-ins,
      block fields at ['sections', index, …] paths included. The editor is
      loaded on demand with a Skeleton in its place, so a form without a
      richtext field never fetches Tiptap; an app that renders one bundles
      the Tiptap dependencies, as one importing RichText does. View mode
      shows the formatted content without a toolbar. In a Panel a rich text
      field spans the row, as a textarea does.
    Downloads
  • v0.25.0 efe6adb46c

    v0.25.0 Stable

    pienter-release-bot released this 2026-09-19 22:28:15 +00:00 | 83 commits to main since this release

    Breaking

    • BlockEditor is now a list/detail split, so a page of 20
      blocks stays a list instead of a wall of open forms. The list is compact
      rows and the one open block's #block form shows in a sticky detail pane
      beside it, with Previous, Next and Close in the pane head and a prompt
      (promptText) while nothing is open; in a narrow container the pane stacks
      under the list. v-model:collapsed (a list of ids) is replaced by
      v-model:open (one block id or null, local when uncontrolled, starting
      closed): a page that removed invalid ids from collapsed now sets open
      to the invalid block's id. #summary is no longer a collapsed-only line
      under the toggle: it always renders in the row, each element child a
      stacked line, clamped at three lines. New #thumbnail slot
      ({ block, index }) fills a fixed 4:3 box at the row start. #block keeps
      its context but only the open block is mounted, keyed by id, so field-local
      state no longer survives closing a block. The row toggle is named by its
      visible label and type instead of Expand …/Collapse …. Adding a block
      opens it. No compatibility layer.
    Downloads
  • v0.24.0 c2cfd669d6

    v0.24.0 Stable

    pienter-release-bot released this 2026-09-16 03:26:36 +00:00 | 86 commits to main since this release

    Added

    • RecordForm and RecordFields gain two built-in field types, so a price
      or a sale window no longer needs a #field:name slot repeating label,
      hint, error and id wiring by hand. { type: 'number', min?, max?, step?, placeholder?, steppers? } renders NumberField; only finite numbers
      display, and clearing the input writes null into the record rather than
      NaN, since the payload is JSON. { type: 'date' | 'datetime-local' | 'time', min?, max?, step? } renders DateInput with the ISO string shapes
      it documents. Both honour busy, disabled, required and readonly
      like every other built-in and can still be replaced through their slot.
    • shopping-cart icon, so a commerce module or webshop entry no longer has
      to borrow layers.
    Downloads
  • v0.23.0 2dc31877a9

    v0.23.0 Stable

    pienter-release-bot released this 2026-09-16 01:25:35 +00:00 | 90 commits to main since this release

    Added

    • SelectButton (@[secure]/ui/components/SelectButton.vue), a native
      <select> dressed as a Button for chrome such as a header toolbar: label
      (required, becomes the aria-label and is never rendered, as on
      IconButton), options, modelValue with update:modelValue, name,
      disabled, and Button's own variant and size unions and defaults, so
      a sm ghost picker sits beside a sm ghost Button at the same height,
      padding, colours and focus ring. The root is a .pui-btn mix and the
      select lies invisibly over it, so the whole button opens the native picker
      and keyboard and screen-reader behaviour stay the browser's. The form
      Select is deliberately unchanged: it keeps no size prop and no hidden
      label, so forms stay one consistent scaffold.
    Downloads
  • v0.22.0 1036703584

    v0.22.0 Stable

    pienter-release-bot released this 2026-09-16 01:19:41 +00:00 | 95 commits to main since this release

    Added

    • RecordForm tracks whether the record has unsaved edits. baseline is
      the last saved state; without one the first modelValue the form sees
      stands in, so a create form is clean until the first edit. dirty is a
      structural comparison of modelValue against that baseline
      (isEqualRecord in utils/cms, so key order does not matter), always
      false in readonly mode, emitted as update:dirty for v-model:dirty
      and exposed on the component instance. Consumers set baseline to a clone
      of the model after load and after a successful save, before navigating.
    • useUnsavedChanges(dirty) (@[secure]/ui/composables/useUnsavedChanges)
      turns that flag into a leave guard: while dirty, beforeunload has the
      browser confirm a reload or tab close, and a vue-router onBeforeRouteLeave
      guard asks through confirmLeave(), which resolves true at once when
      clean and otherwise raises pending until resolve(leave) answers it. The
      consumer binds pending to an AlertDialog and answers with
      resolve(true) on confirm, resolve(false) on cancel; a second question
      while one is pending shares its promise. Requires the optional vue-router
      peer dependency, like useUrlTab.
    Downloads
  • v0.21.1 01d23c41c3

    v0.21.1 Stable

    pienter-release-bot released this 2026-09-16 01:14:49 +00:00 | 100 commits to main since this release

    Fixed

    • Sidebar marks one entry aria-current="page" instead of every entry on
      the current path. On /pages/create the "All pages" link (/pages), its
      group row and "New page" (/pages/create) all reported themselves as the
      current page, so a screen reader heard three current pages and both links
      wore the brand wash. The hierarchical match is unchanged — it still
      drives a group's data-active and expand-on-mount — but aria-current now
      goes only to the longest matching href, and when a group row and one of its
      child links share that href the link wins.
    Downloads
  • v0.21.0 0b4b5b4012

    v0.21.0 Stable

    pienter-release-bot released this 2026-09-16 01:10:15 +00:00 | 103 commits to main since this release

    Added

    • BlockEditor's drag handle is keyboard-operable and back in the tab
      order. Space or Enter grabs a block (aria-pressed, data-state="grabbed"),
      ArrowUp and ArrowDown move it one step through the same reorder the move
      buttons use, Space or Enter drops it and Escape returns it to where it was
      grabbed; blur while grabbed cancels too. Every step emits
      update:modelValue, so the page sees the block move as it does with the
      move buttons, and each grab, step, drop and cancel is announced through the
      shared live region. The default hint now describes the keyboard pattern.
      Pointer dragging is unchanged.
    Downloads
  • v0.20.1 f1205868c6

    v0.20.1 Stable

    pienter-release-bot released this 2026-09-16 01:02:03 +00:00 | 106 commits to main since this release

    Fixed

    • Select shows its empty option again after the model clears from a value
      no option carries. A single-value FilterSelect fed
      filter[status]=draft,published selected nothing, and when Clear all set
      the model to '' the control stayed blank: a <select> with no selection
      already reads '', so Vue saw nothing to write. The control now syncs the
      native value itself after every model change. An unknown value still
      selects nothing; only the way back is fixed.
    Downloads