# Move, Resize, Select, And Focus Items

LivePanels item interaction has two sides.

The browser owns immediate pointer feel: drag previews, resize previews,
marquee rectangles, and local gesture state. The server owns accepted canvas
state: final placement, selection, focus, z-order, history, persistence, and
shared fanout.

This guide shows the application APIs for normal workspace interaction.

## Make An Item Draggable

Put `drag_handle_attrs/1` on the element that should start a drag:

```heex
<header {drag_handle_attrs(class: "item-titlebar")}>
  {context.item.label}
</header>
```

The application chooses the titlebar markup. The attrs tell the browser runtime that
this element is a drag handle for the containing item frame.

During a drag, the browser previews movement locally. On release, it sends a
placement command. The server validates and accepts or rejects the final frame.

## Make An Item Resizable

Use the standard resize handles:

```heex
<.resize_handles :if={context.view.resizable?} />
```

Or render one handle yourself:

```heex
<button {resize_handle_attrs(:se)} aria-label="Resize" />
```

Read `context.view.resizable?` instead of rechecking item capabilities in the
application. The render view already answers whether resize should be offered.

## Move Or Resize From Server Code

Use `LivePanels.Canvas.Item` when an application event or app process changes item
placement:

```elixir
socket = LivePanels.Canvas.Item.move_by!(socket, "note-1", {24, 0})
socket = LivePanels.Canvas.Item.move_to!(socket, "note-1", {120, 80})
socket = LivePanels.Canvas.Item.resize_by!(socket, "note-1", {40, 20})
socket = LivePanels.Canvas.Item.resize_to!(socket, "note-1", {420, 300})
```

For exact placement, use `place/3`:

```elixir
socket =
  LivePanels.Canvas.Item.place!(socket, "note-1", %{
    space: :world,
    anchor: :top_left,
    x: 120,
    y: 80,
    w: 420,
    h: 300
  })
```

Do not update item placement by assigning your own item list. Commands are what
connect validation, history, persistence, shared mode, and projection.

## Select Items

Use selection attrs for browser controls:

```heex
<button {select_item_attrs(item)}>Select</button>
<button {toggle_item_selection_attrs(item)}>Toggle</button>
<button {clear_selection_attrs()}>Clear</button>
<button {select_all_attrs()}>Select all</button>
```

Use selection commands from server code:

```elixir
socket = LivePanels.Canvas.Selection.select!(socket, "note-1")
socket = LivePanels.Canvas.Selection.include!(socket, ["note-1", "note-2"])
socket = LivePanels.Canvas.Selection.toggle!(socket, "note-1")
socket = LivePanels.Canvas.Selection.clear!(socket)
```

Selection is actor-scoped. In a shared canvas, each participant can have a
different selection unless your app chooses to coordinate selection in product
state.

The render context exposes selection for the current actor:

```heex
<section class={["item-frame", context.selected? && "is-selected"]}>
  {context.content}
</section>
```

## Use Marquee Selection

Put marquee attrs on the world or viewport region where the gesture should
start:

```heex
<div {marquee_selection_attrs(:world, class: "selection-plane")}></div>
```

The browser renders the marquee gesture. The server accepts the resulting
selection command.

Use graph-specific selection helpers when the marquee should include connectors
as well as items.

## Focus Items

Focus is the current actor's active item and usually promotes the item in the
canvas stack. It is separate from selection.

```elixir
socket = LivePanels.Canvas.Item.focus!(socket, "note-1")
```

The slot item exposes focus:

```heex
<article class={["item-frame", context.focused? && "is-focused"]}>
  {context.content}
</article>
```

Use `focus_exempt_attrs/1` on controls inside an item frame that should not
change item focus when clicked:

```heex
<button {focus_exempt_attrs()} phx-click="open-menu">Menu</button>
```

Set `interaction: %{promote_on_focus: false}` on item types or item
creation opts when focus should preserve the current stack order. The item still
becomes active, selection helpers still work, and focus-loss attachments still
dismiss; only automatic z-order promotion is skipped. Use the z-order commands
below when the user explicitly reorders layers.

## Reorder Selected Items

Use z-order commands for selected items:

```elixir
socket = LivePanels.Canvas.Selection.z_order!(socket, :front)
socket = LivePanels.Canvas.Selection.z_order!(socket, :back)
socket = LivePanels.Canvas.Selection.z_order!(socket, :forward)
socket = LivePanels.Canvas.Selection.z_order!(socket, :backward)
```

From markup:

```heex
<button {z_order_selection_attrs(:front)}>Bring front</button>
<button {z_order_selection_attrs(:back)}>Send back</button>
```

LivePanels keeps z-order in canvas state so projection, persistence, shared
mode, graph connectors, and vector items can stay consistent.

## Align, Distribute, And Layout

Selection layout helpers operate on visible world-space items:

```heex
<button {align_selection_attrs(:left)}>Align left</button>
<button {distribute_selection_attrs(:horizontal)}>Distribute</button>
<button {layout_attrs(:grid)}>Grid</button>
<button {layout_attrs(:skyline_pack)}>Compact</button>
```

Server equivalents:

```elixir
socket = LivePanels.Canvas.Selection.align!(socket, :left)
socket = LivePanels.Canvas.Selection.distribute!(socket, :horizontal)
socket = LivePanels.Canvas.Arrange.layout!(socket, :grid)
socket = LivePanels.Canvas.Arrange.layout!(socket, :skyline_pack, preserve_size: true)
```

These are still canvas commands. They do not bypass policy, history, or shared
coordination.

Canvas layout strategies are headless planning contracts. `Canvas.Arrange`
submits commands, while `Canvas.Layout` exposes planning adapters for external
engines:

```elixir
socket = LivePanels.Canvas.Arrange.layout!(socket, :masonry, scope: :visible, gap: 12, columns: :auto)
socket = LivePanels.Canvas.Arrange.layout!(socket, :master_stack, master: :active, ratio: 0.62, stack: :right)
socket = LivePanels.Canvas.Arrange.layout!(socket, :ordered_treemap, weight: :priority)

adapter = LivePanels.Canvas.Layout.adapter(state, :visible, viewport: viewport)
commands = LivePanels.Canvas.Layout.adapter_commands(state, external_result)
strategies = LivePanels.Canvas.Layout.registered_strategies(state)
```

Built-in strategy keys include `:horizontal`, `:vertical`, `:grid`, `:cascade`,
`:masonry`, `:fit_rows`, `:fit_columns`, `:skyline_pack`, `:master_stack`,
`:bsp_balance`, `:bsp_split`, and `:ordered_treemap`. Browser controls send the
registered strategy key plus params. Server code may also pass a module that
implements `LivePanels.Canvas.Layout.Strategy`; browser payloads cannot name
modules. Custom browser-visible strategy keys come from canvas extensions:

```elixir
LivePanels.Canvas.Extension.new!(:workflow,
  authorize: MyApp.CanvasAuthorize,
  apply: MyApp.CanvasApply,
  commands: %{},
  layout_strategies: %{workflow_board: MyApp.WorkflowBoardLayout}
)
```

## Keyboard Scope

Put `keyboard_scope_attrs/1` on the element that should receive canvas keyboard
behavior:

```heex
<main {keyboard_scope_attrs(class: "board-page")}>
  ...
</main>
```

Use focus exemptions for inputs, menus, and controls where ordinary browser or
app keyboard behavior should win.

## Input Policy

Use one runtime input policy when an app needs to change wheel, pointer, or
keyboard behavior:

```elixir
input = %{
  wheel: %{bare: :z, shift: :x, command: :y},
  drag: %{
    primary: :mode,
    middle: :pan,
    secondary: :host,
    space_primary: :pan,
    space_middle: :pan,
    space_secondary: :pan
  },
  keyboard: %{
    nudge: %{base: :snap_grid, large_multiplier: 4},
    overrides: %{
      "Space" => :temporary_pan,
      "+" => :zoom_in,
      "-" => :zoom_out,
      "0" => :reset_zoom
    }
  }
}

<.canvas_runtime {canvas_runtime_attrs(assigns, input: input)} />
```

For markup composition, emit the same contract as flat attrs:

```heex
<.canvas_runtime
  {canvas_runtime_attrs(assigns)}
  {input_attrs(
    wheel_bare: :z,
    wheel_shift: :x,
    wheel_command: :y,
    drag_secondary: :pan,
    keyboard_nudge_large_multiplier: 4
  )}
/>
```

Wheel dimensions are `:x`, `:y`, `:z`, `:none`, and `:host`. `:z` zooms around
the pointer, `:x` and `:y` pan the camera, `:none` consumes the gesture, and
`:host` leaves it to application code. The shipped wheel mapping is bare `:z`,
Shift `:x`, and Ctrl/Cmd `:y`.

Pointer drag actions are `:mode`, `:pan`, `:none`, and `:host`. The shipped drag
mapping is primary `:mode`, middle `:pan`, secondary `:host`, and Space plus any
of those buttons `:pan`. `:mode` follows the active cursor mode so draw, erase,
marquee, item drag, resize, and drop interactions keep priority over camera pan.

Keyboard overrides replace or add chords by public runtime action name. The
runtime ships Escape cancel, Ctrl/Cmd undo-redo-copy-cut-paste-duplicate-select,
Delete/Backspace remove, arrow-key nudging, `V/H/M` select-pan-marquee,
Space temporary pan, `+/-/0/Home/F/Shift+F` camera actions, bracket z-order
actions, and the drawing tool keys `1` through `9`, `P`, `Shift+P`, `A`, `L`,
`R`, `O`, `D`, and `E`.

The JS entrypoint exports `LIVEPANELS_INPUT_DEFAULTS` for hosts that need to
inspect the shipped `keyboard.shortcuts` and `keyboard.reservedButUnbound`
tables before applying overrides.

## What LivePanels Owns

LivePanels owns the accepted item frame, selection set, focus target, z-order,
and command validation.

The browser owns transient previews. Your application owns where handles and controls
appear and how focused or selected items look.
