# Select, Edit, Route, Label, And Hit-Test Connectors

Graph editing is more than creating a connector.

Users need to select connectors, inspect them, remove them, reroute them, label
them, click them reliably, and edit bends. LivePanels renders connector items in
the world SVG item projection and exposes command helpers for the surrounding
application controls.

## Connector Selection

The built-in connector path and hit path include ordinary item selection attrs.
Clicking a rendered connector selects the connector item through the canvas
selection command protocol.

External controls that target one connector item can use the ordinary selection
helpers:

```heex
<button {select_item_attrs(connector)}>Select</button>
<button {include_item_attrs(connector)}>Add to selection</button>
<button {toggle_item_selection_attrs(connector)}>Toggle</button>
```

Graph-scoped controls can still use graph selection helpers:

```heex
<button {graph_select_connector_attrs(connector)}>Select</button>
<button {graph_include_connector_attrs(connector)}>Add to selection</button>
<button {graph_toggle_connector_selection_attrs(connector)}>Toggle</button>
```

Select every visible graph item and connector:

```heex
<button {graph_select_all_attrs()}>Select graph</button>
```

Server equivalent:

```elixir
socket = LivePanels.Graph.select_all!(socket)
```

Graph selection shares the same actor-scoped selection model as item selection.

## Style Selection State

Rendered connector groups expose:

- `data-livepanels-selected="true"` when the connector item is selected;
- `data-livepanels-connector-selection="selected"` when the connector itself is
  selected;
- `data-livepanels-connector-selection="related"` when a selected graph item is
  one of the connector endpoints;
- `data-livepanels-connector-selection="unrelated"` when another graph item is
  selected.

Style those attrs in application CSS:

```css
.graph-canvas
  [data-livepanels-connector-selection="related"]
  .livepanels-connector-path {
  stroke-width: 4px;
}

.graph-canvas
  [data-livepanels-connector-selection="unrelated"]
  .livepanels-connector-path {
  opacity: 0.28;
}
```

## Hit Targets

The renderer emits an invisible hit path after the visible path:

```html
<path
  class="livepanels-connector-hit-path"
  data-livepanels-connector-hit-target="true"
>
```

Style it only when you need to inspect or debug hit geometry. The runtime keeps
the hit path in the connector item stream so replacement and shared updates do
not require host code.

## Labels

If labels are connector presentation, put them on the connector:

```elixir
connector =
  LivePanels.Item.Connector.connection(
    from: LivePanels.Item.Connector.endpoint("extract", "out"),
    to: LivePanels.Item.Connector.endpoint("load", "in"),
    label: "rows",
    role: :data
  )
```

Update labels through the graph command path:

```elixir
socket =
  LivePanels.Graph.edit!(socket, connector_id,
    label: "customer_id",
    role: :foreign_key
  )
```

If labels come from product data, resolve them before creating or updating the
connector and store the value in connector presentation.

## Routes

Supported connector routes:

- `:bezier`;
- `:orthogonal`;
- `:straight`.

Store route presentation on the connector:

```elixir
socket =
  LivePanels.Graph.edit!(socket, connector_id,
    route: :orthogonal,
    direction: :horizontal
  )
```

`LivePanels.Graph.connect!/2` accepts route options for new connectors:

```elixir
socket =
  LivePanels.Graph.connect!(socket,
    from: {"extract", "out"},
    to: {"load", "in"},
    route: :orthogonal,
    direction: :horizontal
  )
```

DOM connection controls can pass route fields through `complete_connector_attrs/3`:

```heex
<button
  {complete_connector_attrs(context.item, port,
    "phx-value-route": "orthogonal",
    "phx-value-direction": "horizontal"
  )}
>
  {port.label}
</button>
```

Route choice should live on the connector item when it should persist, stream,
or be shared with other participants.

## Connector Bends

Connectors can carry bend presentation:

```elixir
connector =
  LivePanels.Item.Connector.connection(
    from: LivePanels.Item.Connector.endpoint("source", "out"),
    to: LivePanels.Item.Connector.endpoint("target", "in"),
    bends: [%{x: 120, y: 80}]
  )
```

When a connector item is selected, the renderer emits bend handles for its bend
points. Browser bend updates use the graph `update_connector_bends` command and
stream back through the connector item.

## Connector Inspectors

A connector inspector reads selected item ids and looks up connector items:

```elixir
selection = LivePanels.Canvas.Read.selection(socket)
selected_item_ids = LivePanels.Canvas.Selection.selected_item_ids(selection)
state = LivePanels.Canvas.Read.state(socket)

selected_connectors =
  Enum.filter(LivePanels.Graph.connectors(state), fn connector ->
    MapSet.member?(selected_item_ids, connector.id)
  end)
```

Use the inspector for product fields. Use graph commands for runtime changes.

## Removing Selected Graph Elements

The ordinary remove-selected command can remove selected items and connectors:

```heex
<button {remove_selected_attrs()}>Remove</button>
```

Or remove one connector:

```heex
<button {disconnect_attrs(connector)}>Remove connector</button>
```

When an item is removed, graph pruning removes or rejects connectors that can no
longer resolve.
