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

The `~MOB` sigil for declarative native UI.

Compiles a tag template to a `Mob.Renderer`-compatible node map **at compile
time** using NimbleParsec. Expressions in `{...}` are evaluated in the
caller's scope at runtime.

Use `~MOB(...)` for single nodes or `~MOB"""..."""` for nested layouts.

## Examples

    import Mob.Sigil

    # Self-closing
    ~MOB(<Text text="Hello" />)
    #=> %{type: :text, props: %{text: "Hello"}, children: []}

    # Nested layout
    ~MOB"""
    <Column padding={:space_md}>
      <Text text="Title" text_size={:xl} />
      <Button text="OK" on_tap={{self(), :ok}} />
    </Column>
    """

    # Expression child — inject any node map or list of maps
    ~MOB"""
    <Column>
      {Enum.map(items, fn i -> ~MOB(<Text text={i} />) end)}
    </Column>
    """

## Assigns shorthand

Inside a `{...}` expression, `@foo` rewrites to `assigns.foo` (matching
Phoenix HEEx), so a `render(assigns)` body reads cleanly:

    ~MOB(<Text text={@title} />)   # same as text={assigns.title}

`@foo` only works where a variable named `assigns` is in scope — i.e. a
screen's or component's `render(assigns)`. Reusable helper functions take
positional arguments instead, so use the argument directly there:

    # Screen render — assigns is in scope, @ works:
    def render(assigns), do: ~MOB(<Text text={@title} />)

    # Helper — no assigns; interpolate the argument, don't use @:
    def label(title), do: ~MOB(<Text text={title} />)

Using `@foo` where no `assigns` exists raises a `CompileError` naming the
fix, rather than a cryptic "undefined variable assigns".

## Control attributes — `:if` and `:for`

Two LiveView-style directives wrap an element without extra ceremony.
Both take a `{expr}` value and may read `@assigns`.

    # Conditional — omitted entirely when the expression is falsy
    ~MOB(<Badge text="New" :if={@unread > 0} />)

    # Comprehension — one element per item; splices into the parent
    ~MOB"""
    <Column>
      <Row :for={user <- @users}>
        <Text text={user.name} />
      </Row>
    </Column>
    """

Combine them — `:if` then acts as a comprehension filter (an element is
produced only for items where the condition holds):

    <Text text={n} :for={n <- @nums} :if={rem(n, 2) == 0} />

## Tag whitelist

Tags are validated against `priv/tags/ios.txt` and `priv/tags/android.txt` at
compile time. Unknown tags emit a warning but still pass through — the type
atom is derived by converting PascalCase to snake_case (e.g. `TabBar` →
`:tab_bar`). This allows new native tags to be used before the whitelist is
updated.

Composite tags an app registers at boot (`Mob.Composite.register/2`, or a UI
kit's own registry) are invisible to that compile-time check, so declare them
either at the app level or, for a Hex-shipped kit, at the plugin level:

    # app-level: config/config.exs
    config :mob, :extra_tags, ~w(MishkaChip MishkaDrawer)   # or [:mishka_chip, ...]

    # plugin-level: priv/mob_plugin.exs
    %{
      name: :mob_mishka,
      mob_version: "~> 0.8",
      plugin_spec_version: 1,
      tags: ~w(MishkaChip MishkaDrawer)                # flat list
      # or per-platform:
      # tags: %{ios: ~w(FooIOS), android: ~w(FooAndroid), both: ~w(FooShared)}
    }

Both surfaces are read at each `~MOB` call site's compile time, so tags a Hex
plugin ships flow into the whitelist automatically — no user-side config edit
and no writing into mob's `priv/tags` files (which `mix deps.get` would undo).
Mix does not track the reads, so after changing the config or updating a
plugin run `mix compile --force` (or touch the screens) for already-compiled
modules to pick it up.

# `brace_content`

```elixir
@spec brace_content(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()
```

Parses the given `binary` as brace_content.

Returns `{:ok, [token], rest, context, position, byte_offset}` or
`{:error, reason, rest, context, line, byte_offset}` where `position`
describes the location of the brace_content (start position) as `{line, offset_to_start_of_line}`.

To column where the error occurred can be inferred from `byte_offset - offset_to_start_of_line`.

## Options

  * `:byte_offset` - the byte offset for the whole binary, defaults to 0
  * `:line` - the line and the byte offset into that line, defaults to `{1, byte_offset}`
  * `:context` - the initial context value. It will be converted to a map

# `node`

```elixir
@spec node(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()
```

Parses the given `binary` as node.

Returns `{:ok, [token], rest, context, position, byte_offset}` or
`{:error, reason, rest, context, line, byte_offset}` where `position`
describes the location of the node (start position) as `{line, offset_to_start_of_line}`.

To column where the error occurred can be inferred from `byte_offset - offset_to_start_of_line`.

## Options

  * `:byte_offset` - the byte offset for the whole binary, defaults to 0
  * `:line` - the line and the byte offset into that line, defaults to `{1, byte_offset}`
  * `:context` - the initial context value. It will be converted to a map

# `parse_template`

```elixir
@spec parse_template(binary(), keyword()) ::
  {:ok, [term()], rest, context, line, byte_offset}
  | {:error, reason, rest, context, line, byte_offset}
when line: {pos_integer(), byte_offset},
     byte_offset: non_neg_integer(),
     rest: binary(),
     reason: String.t(),
     context: map()
```

Parses the given `binary` as parse_template.

Returns `{:ok, [token], rest, context, position, byte_offset}` or
`{:error, reason, rest, context, line, byte_offset}` where `position`
describes the location of the parse_template (start position) as `{line, offset_to_start_of_line}`.

To column where the error occurred can be inferred from `byte_offset - offset_to_start_of_line`.

## Options

  * `:byte_offset` - the byte offset for the whole binary, defaults to 0
  * `:line` - the line and the byte offset into that line, defaults to `{1, byte_offset}`
  * `:context` - the initial context value. It will be converted to a map

# `sigil_MOB`
*macro* 

Compiles a `~MOB(...)` or `~MOB"""..."""` template into a native UI node map.
Parsed at compile time; `{expr}` values evaluated at runtime in the caller's scope.

# `wrap_child`

```elixir
@spec wrap_child(list() | map() | nil) :: list()
```

Normalizes a child's value to a list of UI-node maps for the
surrounding sigil. Single nodes wrap into a one-element list; lists
pass through; `nil` (a `:if` that didn't render) drops to `[]`.
Public so the sigil-generated AST can call it by FQ name; not part
of the application API.

---

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