# `Athanor.Component`
[🔗](https://github.com/Arsenalist/athanor/blob/v0.1.0-beta.7/lib/athanor/component.ex#L1)

Behaviour every Athanor-aware component implements.

At v1 only `metadata/0` is required. Every other callback has a default
injected by `use Athanor.Component` that components can override at will.

## Defining a component

    defmodule MyApp.Components.Promo do
      use Athanor.Component

      @impl Athanor.Component
      def metadata, do: %{type: "promo", label: "Promo", icon: "fa-bullhorn"}

      @impl Athanor.Component
      def required_props, do: ["headline"]

      @impl Athanor.Component
      def render(:live, node, _ctx) do
        assigns = %{headline: node["props"]["headline"]}
        ~H"<h2>{@headline}</h2>"
      end
    end

## Render targets

Only `:live` is supported in v1. Future steps add `:static`, `:mjml`, `:text`
without breaking the callback signature.

## Editor configuration: two ways

Components describe how they are edited in one of two ways.

### Preferred: `fields/0` (declarative)

Return a list of field tuples describing inputs. Athanor auto-generates
the configure panel.

    def fields, do: [
      {"title", :text,     label: "Title"},
      {"level", :select,   label: "Level", options: [{"H1", "1"}, {"H2", "2"}]},
      {"image", :custom,   label: "Image", module: MyApp.MediaPicker}
    ]

Built-in types: `:text`, `:textarea`, `:number`, `:select`, `:radio`, `:color`,
`:checkbox`, `:asset`. The `:radio` type is a single-choice radio group sharing
the `:select` `options:` contract (static list or arity-1 `Ctx` function). The
`:asset` type is a host-agnostic uploaded-asset
picker (image/pdf/video/...) — it ships a paste-a-URL default and emits a
request the host fulfills (see `Athanor.Fields` and
`Athanor.Editor.AssetRequest`). The `:custom` type mounts a consumer-supplied
module implementing `Athanor.Field`. See `Athanor.Field` and `Athanor.Fields`.

### Legacy: `editor_form/0` (LiveComponent)

Return a `Phoenix.LiveComponent` module the editor mounts directly.
Use this when you need state, multi-step flows, or behaviour the
built-in field types don't cover. Return `nil` (default) for no
configuration.

### Dispatch order

When both are defined, `fields/0` (returning a non-empty list) wins.
See `Athanor.Renderer` for the full dispatch rules.

## Child zones

Container components (e.g., Columns) override `child_zones/1` to expose
their children to `Athanor.Tree` walk/find/insert/remove/move. Default
is no children.

# `field`

```elixir
@type field() :: {String.t(), field_type(), keyword()}
```

# `field_type`

```elixir
@type field_type() ::
  :text
  | :textarea
  | :number
  | :select
  | :radio
  | :color
  | :checkbox
  | :asset
  | :custom
```

# `props`

```elixir
@type props() :: map()
```

# `target`

```elixir
@type target() :: :live
```

# `tree_node`

```elixir
@type tree_node() :: %{required(String.t()) =&gt; any()}
```

# `child_zones`
*optional* 

```elixir
@callback child_zones(tree_node()) :: %{required(String.t()) =&gt; [tree_node()]}
```

# `default_props`
*optional* 

```elixir
@callback default_props() :: props()
```

# `editor_form`
*optional* 

```elixir
@callback editor_form() :: module() | nil
```

# `fields`
*optional* 

```elixir
@callback fields() :: [field()]
```

# `metadata`

```elixir
@callback metadata() :: %{
  :type =&gt; String.t(),
  :label =&gt; String.t(),
  optional(:icon) =&gt; String.t(),
  optional(:category) =&gt; atom(),
  optional(:description) =&gt; String.t(),
  optional(:placeholder) =&gt; String.t()
}
```

# `render`
*optional* 

```elixir
@callback render(target(), tree_node(), Athanor.Ctx.t()) :: any()
```

# `required_props`
*optional* 

```elixir
@callback required_props() :: [String.t()]
```

# `resolve_data`
*optional* 

```elixir
@callback resolve_data(old_props :: props(), new_props :: props()) :: props()
```

# `resolve_fields`
*optional* 

```elixir
@callback resolve_fields(props(), map()) :: [field()]
```

# `validate`
*optional* 

```elixir
@callback validate(props()) :: :ok | {:error, term()}
```

# `default_validate`

Default `validate/1` used when a component does not override.

Returns `:ok` if every key listed in `required_props/0` is present and
non-blank in `props`. Otherwise `{:error, {:missing, [missing_keys]}}`.

---

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