PetalComponents.Resizable (petal_components v4.15.4)

Copy Markdown View Source

Split-pane layout groups with draggable, keyboard-operable dividers.

This is the layout primitive behind docs sites with an adjustable sidebar, IDE-style workspaces and editor/preview splits. Three components compose it:

Panels are sized as percentages of the group and rendered as flex: <pct> 1 0px, so a window resize keeps the split proportional. The hook reads only its DIRECT children, which is what makes nested groups work: each group owns its own hook instance and never touches an inner group's panels.

The library stores nothing. On drag release and on keyboard commit the group dispatches a bubbling petal:resizable-resize DOM event carrying detail.sizes (percentages in panel order) and, when on_resize is set, pushes the same payload to your LiveView. Persist it wherever you like - session, URL params, localStorage.

Keyboard

With a handle focused (it is in the tab order):

  • ArrowLeft / ArrowRight - resize a vertical separator by 2 points
  • ArrowUp / ArrowDown - resize a horizontal separator by 2 points
  • hold Shift for a 10-point step
  • Home - shrink the preceding panel to its min_size (collapse it if collapsible)
  • End - grow the preceding panel to its max_size
  • Enter - toggle collapse on a collapsible preceding panel

Arrows perpendicular to the separator are a no-op, per the WAI-ARIA window splitter pattern.

Examples

A docs layout: a collapsible sidebar and a content pane.

<.resizable_group id="docs" class="h-80">
  <.resizable_panel id="docs-nav" default_size={25} min_size={15} collapsible>
    Navigation
  </.resizable_panel>
  <.resizable_handle controls="docs-nav" with_handle />
  <.resizable_panel default_size={75}>
    Content
  </.resizable_panel>
</.resizable_group>

A vertical split that reports its sizes back to the LiveView.

<.resizable_group id="editor" orientation="vertical" on_resize="split_changed" class="h-96">
  <.resizable_panel id="editor-code" default_size={70} min_size={30}>Code</.resizable_panel>
  <.resizable_handle orientation="vertical" controls="editor-code" />
  <.resizable_panel default_size={30} min_size={10}>Output</.resizable_panel>
</.resizable_group>

Nested groups - a horizontal split inside a vertical one. Each group gets its own id and its own hook.

<.resizable_group id="ide" orientation="vertical" class="h-96">
  <.resizable_panel default_size={75}>
    <.resizable_group id="ide-top" class="h-full">
      <.resizable_panel id="ide-files" default_size={20} min_size={10}>Files</.resizable_panel>
      <.resizable_handle controls="ide-files" />
      <.resizable_panel default_size={50}>Editor</.resizable_panel>
      <.resizable_handle />
      <.resizable_panel default_size={30}>Preview</.resizable_panel>
    </.resizable_group>
  </.resizable_panel>
  <.resizable_handle orientation="vertical" />
  <.resizable_panel default_size={25} min_size={10}>Terminal</.resizable_panel>
</.resizable_group>

Summary

Functions

The container. Renders a flex row (or column) and mounts the PetalResizable hook, which owns dragging, keyboard resizing, clamping and the resize events.

The separator between two panels.

One pane of a group.

Functions

resizable_group(assigns)

The container. Renders a flex row (or column) and mounts the PetalResizable hook, which owns dragging, keyboard resizing, clamping and the resize events.

Put resizable_panel/1 and resizable_handle/1 children in it, alternating, with a handle between every adjacent pair of panels. Give the group a height (class="h-80", class="h-full", ...) - a flex container has none of its own.

Attributes

  • id (:string) - auto-generated when omitted; set it so the resize events can be told apart. Defaults to nil.
  • orientation (:string) - horizontal = panels side by side (vertical dividers); vertical = panels stacked. Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • on_resize (:string) - optional LiveView event name pushed on drag release and keyboard commit with %{"sizes" => [..]} percentages. Defaults to nil.
  • class (:any) - extra classes on the group; this is where height goes. Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block (required) - resizable_panel and resizable_handle children, in order.

resizable_handle(assigns)

The separator between two panels.

It is the accessible control: role="separator", in the tab order, and carrying the live aria-valuenow for the panel before it. Note the inversion - a handle in a horizontal group (panels side by side) is a VERTICAL separator, so pass the group's orientation and the component flips it for you.

The painted line is a hairline; the hit area around it is deliberately much larger. with_handle adds the visible grip.

Server-rendered aria-valuenow/valuemin/valuemax are a starting point - the hook restamps all three (plus aria-orientation and, when the preceding panel has an id, aria-controls) from the live layout on mount and on every resize.

Attributes

  • orientation (:string) - the ORIENTATION OF THE GROUP; the separator's own aria-orientation is the inverse. Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • with_handle (:boolean) - renders the visible grip-dot affordance. Defaults to false.
  • controls (:string) - id of the preceding panel, for aria-controls; the hook fills it in when the panel has one. Defaults to nil.
  • value_now (:integer) - initial aria-valuenow (the preceding panel's size); the hook keeps it current. Defaults to 50.
  • value_min (:integer) - initial aria-valuemin for the preceding panel. Defaults to 0.
  • value_max (:integer) - initial aria-valuemax for the preceding panel. Defaults to 100.
  • label (:string) - accessible name for the separator; override per split when a page has several. Defaults to "Resize panels".
  • class (:any) - extra classes on the separator. Defaults to nil.
  • Global attributes are accepted.

resizable_panel(assigns)

One pane of a group.

default_size is a percentage of the group. Panels without one grow to share whatever is left (flex: 1 1 0px); when a group mixes sized and unsized panels the hook normalises the shares on mount so the sized ones land on their exact percentage.

Give the panel an id if a handle needs to point aria-controls at it, or if you want to read the panel out of a petal:resizable-collapse event.

Attributes

  • id (:string) - needed for aria-controls and collapse events. Defaults to nil.
  • default_size (:integer) - initial size as a percentage of the group; unsized panels share the remainder equally. Defaults to nil.
  • min_size (:integer) - smallest percentage the panel can be dragged or keyed down to. Defaults to 10.
  • max_size (:integer) - largest percentage the panel can grow to. Defaults to 100.
  • collapsible (:boolean) - when true, dragging below roughly half the min_size snaps the panel to collapsed_size and fires petal:resizable-collapse. Defaults to false.
  • collapsed_size (:integer) - the size the panel snaps to when collapsed. Defaults to 0.
  • class (:any) - extra classes on the panel. Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block (required)