# `Drafter.WidgetHierarchy`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/widget_hierarchy.ex#L1)

The mounted widget tree of a running application.

One of these is built from the element tree `render/1` returns and is what
routes events, resolves selectors and remembers where each widget was drawn.
`Drafter.Test.get_widget_hierarchy/1` hands the whole struct to a test; the
functions here are otherwise called by the framework rather than by an
application.

The fields a test reads most:

  * `:widgets` - every mounted widget by id, each carrying its `:module`,
    `:state`, `:parent`, `:children` and `:pid`
  * `:widget_rects` - the rect a widget was last drawn into, by id, as
    `%{x:, y:, width:, height:}` in zero-based screen cells
  * `:focused_widget` - the id holding keyboard focus, or `nil`
  * `:hover_widget` - the id under the pointer, or `nil`
  * `:root` - the id of the outermost widget

# `rect`

```elixir
@type rect() :: %{x: integer(), y: integer(), width: integer(), height: integer()}
```

# `scroll_info`

```elixir
@type scroll_info() :: %{
  viewport_rect: rect(),
  content_height: integer(),
  content_width: integer(),
  click_to_scroll: boolean(),
  scroll_exceptions: MapSet.t()
}
```

# `t`

```elixir
@type t() :: %Drafter.WidgetHierarchy{
  drag_capture_widget: term(),
  event_consumed: term(),
  focus_cleared: term(),
  focused_widget: widget_id() | nil,
  hidden_widgets: term(),
  hover_widget: widget_id() | nil,
  pending_focus: term(),
  preferred_sizes: term(),
  root: widget_id() | nil,
  scroll_containers: %{required(widget_id()) =&gt; scroll_info()},
  widget_counter: integer(),
  widget_overflow: term(),
  widget_rects: %{required(widget_id()) =&gt; rect()},
  widget_scroll_parents: %{required(widget_id()) =&gt; widget_id()},
  widgets: %{
    required(widget_id()) =&gt; %{
      module: module(),
      state: map(),
      parent: widget_id() | nil,
      children: [widget_id()],
      pid: pid() | nil,
      order: integer()
    }
  }
}
```

# `widget_id`

```elixir
@type widget_id() :: atom() | String.t()
```

# `add_widget`

```elixir
@spec add_widget(
  t(),
  widget_id(),
  module(),
  map(),
  widget_id() | nil,
  rect(),
  keyword()
) :: t()
```

# `clear_consumed`

```elixir
@spec clear_consumed(t()) :: t()
```

Clear the per-dispatch consumption flag before routing a new event.

# `collect_session_pdict`

```elixir
@spec collect_session_pdict() :: %{required(atom()) =&gt; pid()}
```

Snapshot the calling process's session context, as `Drafter.Session.Context.capture/0`.

# `get_children`

```elixir
@spec get_children(t(), widget_id()) :: [widget_id()]
```

# `get_parent`

```elixir
@spec get_parent(t(), widget_id()) :: widget_id() | nil
```

# `get_preferred_size`

```elixir
@spec get_preferred_size(t(), widget_id()) :: integer() | nil
```

# `get_widget_info`

```elixir
@spec get_widget_info(t(), widget_id()) :: map() | nil
```

# `get_widget_state`

```elixir
@spec get_widget_state(t(), widget_id()) :: map() | nil
```

# `live_widget_state`

```elixir
@spec live_widget_state(map()) :: term()
```

The freshest state for a widget info map.

Asks the widget's server when it has one, falling back to the cached state if the
server cannot answer. Returns the cached state directly for an inline widget.

# `mark_consumed`

```elixir
@spec mark_consumed(t()) :: t()
```

Record that a widget handled the event currently being routed.

Consumption comes from the handler's own verdict, not from whether its state
changed. Cleared before each dispatch by `clear_consumed/1`.

# `new`

```elixir
@spec new(keyword()) :: t()
```

# `put_widget`

```elixir
@spec put_widget(t(), widget_id(), map()) :: t()
```

Store `widget_info` under `widget_id`, replacing whatever was there.

# `remove_widget`

```elixir
@spec remove_widget(t(), widget_id()) :: t()
```

# `set_widget_overflow`

```elixir
@spec set_widget_overflow(t(), widget_id(), :clip | :ellipsis) :: t()
```

Record how a widget should mark content that does not fit its width.

`:clip` is the default and is not stored. Read back with `widget_overflow/2`.

# `set_widget_state`

```elixir
@spec set_widget_state(t(), widget_id(), map()) :: t()
```

# `stop_all_servers`

```elixir
@spec stop_all_servers(t() | nil) :: :ok
```

Stop every widget server the hierarchy holds.

Widgets held inline and servers that have already exited are skipped, and a server
that exits while being stopped is ignored. Always returns `:ok`, including for a
`nil` hierarchy.

# `update_preferred_size`

```elixir
@spec update_preferred_size(t(), widget_id(), integer()) :: t()
```

# `update_widget`

```elixir
@spec update_widget(t(), widget_id(), map()) :: t()
```

Push new props into a widget.

A widget backed by a live server has them sent to it and the hierarchy comes back
unchanged; a widget held inline has them merged into its cached state. A widget the
hierarchy does not hold is ignored.

# `update_widget_info`

```elixir
@spec update_widget_info(t(), widget_id(), (map() -&gt; map())) :: t()
```

Replace a widget's info map with `fun.(info)`.

A widget the hierarchy does not hold is left alone and `fun` is not called.

# `update_widget_parent`

```elixir
@spec update_widget_parent(t(), widget_id(), widget_id() | nil) :: t()
```

Reparent a widget. A widget the hierarchy does not hold is left alone.

# `update_widget_rect`

```elixir
@spec update_widget_rect(t(), widget_id(), rect()) :: t()
```

Record a widget's rect and re-render it.

The widget is re-rendered on every call, including when the rect is unchanged,
so its strips reflect any state that moved since the last pass.

# `update_widget_state`

```elixir
@spec update_widget_state(t(), widget_id(), map()) :: t()
```

# `update_widget_state_in_hierarchy`

```elixir
@spec update_widget_state_in_hierarchy(t(), widget_id(), term()) :: t()
```

Replace a widget's whole state.

A widget backed by a live server has the state set on it and the hierarchy comes
back unchanged; a widget held inline has its cached state replaced. A widget the
hierarchy does not hold is ignored.

# `widget_overflow`

```elixir
@spec widget_overflow(t(), widget_id()) :: :clip | :ellipsis
```

How a widget marks content that does not fit, defaulting to clipping.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
