Mob.Composite (mob v0.7.0)

Copy Markdown View Source

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

expand(tree, screen_pid)

@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()

@spec expanders() :: %{required(atom()) => {module(), atom()}}

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

register(atom, arg)

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

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