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.
Summary
Functions
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.
The registered expanders (%{atom => {module, function}}).
Registers an expander for a composite tag atom. Overwrites any existing
registration for atom.
Functions
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.
The registered expanders (%{atom => {module, function}}).
Registers an expander for a composite tag atom. Overwrites any existing
registration for atom.