Behaviour for consumer-supplied :custom field modules.
A :custom field declared in Athanor.Component.fields/0 points at a
module implementing this behaviour:
def fields, do: [
{"image", :custom, label: "Image", module: MyApp.PageBuilder.Fields.MediaPicker}
]The module IS a Phoenix.LiveComponent. Athanor mounts it inside the
auto-generated configure panel with these assigns:
:value— current value of the prop (props[key]), ornilif unset:on_change— 1-arity function. Call it with the new value andAthanor plumbs `:update_component_props` back to the host LiveView. Wholesale replace of `props[key]`.:ctx— the fullAthanor.Ctx. Use for passthrough context like`account_id`, `user_id`, `api_token`, etc.:label— optional, declared vialabel:in the field opts.
The custom LC owns its own UI completely. It can mount more LCs inside, hit the DB, render whatever HTML it wants. Athanor stays out of the way.
Example
defmodule MyApp.PageBuilder.Fields.MediaPicker do
use Phoenix.LiveComponent
@behaviour Athanor.Field
@impl true
def update(assigns, socket) do
{:ok, assign(socket, assigns)}
end
@impl true
def render(assigns) do
~H"""
<div>
<img :if={@value} src={@value} class="..." />
<button phx-click="pick" phx-target={@myself}>Pick image</button>
</div>
"""
end
@impl true
def handle_event("pick", _params, socket) do
# Open media picker, get url back, then:
socket.assigns.on_change.(url)
{:noreply, socket}
end
endWhy a LiveComponent and not a function component
Picker flows commonly need their own state (file uploads, async loads,
multi-step modals). A Phoenix.LiveComponent lets the custom field
own that state without leaking into the host LiveView.
Summary
Functions
Returns true when module implements the Athanor.Field behaviour
(or at minimum exports the required LiveComponent callbacks).
Functions
Returns true when module implements the Athanor.Field behaviour
(or at minimum exports the required LiveComponent callbacks).