# `Plushie.Widget.Handler`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.6.0/lib/plushie/widget/handler.ex#L1)

Runtime support for stateful widgets.

Canvas widgets are pure-Elixir widgets that render via canvas shapes,
manage internal state (hover, focus, animation), and transform raw
canvas events into semantic widget events via `handle_event/2`.

## Widget lifecycle

Canvas widgets follow the standard Widget protocol pipeline:

1. `new/2` returns a struct (like all other widgets)
2. `Widget.to_node/1` produces a placeholder node tagged with the
   module and props as metadata
3. During `Tree.normalize`, the placeholder is detected and rendered
   with the best available state (stored from a previous cycle, or
   initial defaults for new widgets)
4. The rendered output is normalized in place -- no post-processing

The tree carries widget state in `:meta` (`__widget_state__`),
making it the single source of truth. The runtime derives a registry
from the tree after each render for O(1) event dispatch lookups.

## Event dispatch (captured/ignored model)

`invoke_handler/4` is called by the runtime when an event arrives
for a widget inside a widget's scope. It calls the module's
`handle_event/2` and interprets the return value using iced's
captured/ignored model:

- `{:emit, family, data}` -- captured, emit semantic event
- `{:emit, family, data, new_state}` -- captured, emit + update state
- `{:update_state, new_state}` -- captured, internal state change only
- `:consumed` -- captured, suppress event
- `:ignored` -- not captured, continue to next handler in scope chain

Events are dispatched through the scope chain (innermost to outermost).
`:ignored` continues to the next widget handler in the chain. Captured
events stop propagation (or, for `:emit`, replace the event and
continue). If no handler captures, the event reaches `app.update/2`.

# `handle_event_result`

```elixir
@type handle_event_result() ::
  {:emit, atom(), term()}
  | {:emit, atom(), term(), map()}
  | {:update_state, map()}
  | :consumed
  | :ignored
```

Valid return values from `handle_event/2`.

# `handle_event`

```elixir
@callback handle_event(event :: struct(), state :: map()) :: handle_event_result()
```

Transforms a raw event into a semantic widget event (or ignores it).

# `subscribe`
*optional* 

```elixir
@callback subscribe(props :: map(), state :: map()) :: [Plushie.Subscription.t()]
```

Returns subscription specs for this widget (optional).

# `view`

```elixir
@callback view(id :: String.t(), props :: map(), state :: map()) :: map()
```

Returns the widget tree for this widget given its id, resolved props, and internal state.

# `invoke_handler`

```elixir
@spec invoke_handler(
  module :: module(),
  event :: struct(),
  state :: map(),
  widget_id :: String.t(),
  window_id :: String.t() | nil
) :: {{:emit, struct()} | :consumed | :ignored, map()}
```

Invokes a widget's handle_event/2 and interprets the result.

Returns `{action, new_state}` where action is one of:
- `{:emit, %WidgetEvent{}}` -- captured with transformed event
- `:consumed` -- captured, no output
- `:ignored` -- not captured, continue to next handler

# `widget_states_key`

```elixir
@spec widget_states_key() :: atom()
```

Process dictionary key used to pass canvas widget states during normalization.

---

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