# `Mob.Composite`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/composite.ex#L1)

Pure-Elixir composite components: the third expansion pass.

A composite is a TAG that expands to a built-in widget tree — no native
code. UI-kit authors register an expander per tag atom and users write
`<MishkaCombobox … />` in `~MOB`; this pass replaces the node with the
expander's output before `Mob.List.expand` / `Mob.Component.expand` run
(so a composite may itself emit `<List>` or `Mob.UI.native_view`).

## Registering

Via a plugin manifest (the `expand:` ui_components form, MOB_PLUGINS.md):

    ui_components: [
      %{tag: "MishkaCombobox", atom: :mishka_combobox,
        expand: {Mishka.Combobox, :expand}}
    ]

…registered automatically at boot. Or at runtime (plain Hex UI kits with
no manifest — call from the host's `on_start/0`):

    Mob.Composite.register(:mishka_combobox, {Mishka.Combobox, :expand})

## The expander contract

    def expand(props, children, ctx)

`props` are the node's props with EVENT TARGETS AUTO-INJECTED: any `on_*`
prop written as a bare atom or string (`on_select="combo_select"`) arrives
as `{screen_pid, :combo_select}` — no `self()` threading. `children` are
the (already composite-expanded) child nodes; `ctx` is
`%{screen: pid, platform: platform}`. Return a node map or a list of nodes
(the `~MOB` sigil output). Output is re-expanded to a fixpoint (composites
can build on composites) with a depth guard of 20.

Composites are stateless by design — state lives in the screen (or in a
`Mob.Component` if a part of the tree needs its own process). Hot-pushable:
pure Elixir, same rule as any screen module.

# `expand`

```elixir
@spec expand(map() | [map()], pid()) :: map() | [map()]
```

The expansion pass. Walks the tree; nodes whose `:type` has a registered
expander are replaced by the expander output (recursively, to a fixpoint).
A crashing expander logs and renders nothing (an empty Column) rather than
taking the screen down.

# `expanders`

```elixir
@spec expanders() :: %{required(atom()) =&gt; {module(), atom()}}
```

The registered expanders (`%{atom => {module, function}}`).

# `register`

```elixir
@spec register(
  atom(),
  {module(), atom()}
) :: :ok
```

Registers an expander for a composite tag atom. Overwrites any existing
registration for `atom`.

---

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