# `Xenia.Component`
[🔗](https://github.com/uzaydurdu/xenia/blob/v0.1.1/lib/xenia/core/component.ex#L1)

Renders the LiveView-owned container for a Xenia adapter.

The JavaScript controller creates an adapter-owned child element
inside this container.

The component manages:

  * The LiveView hook binding, using the registered adapter name.
  * The LiveView update binding, set to "ignore".
  * `data-xenia-props`, containing JSON-encoded adapter props.
  * `data-xenia-events`, mapping adapter events to LiveView events.
  * `data-xenia-shortcuts`, mapping key combinations to LiveView events.
  * For form fields, an invisible input that carries the field's value.
    The adapter keeps it up to date with `ctx.setFormValue/2`.

Additional HTML attributes such as class, style, aria-*,
data-*, and phx-click can be passed through.

## Binding prefix

Xenia defaults to the LiveView binding prefix "phx-".

To use a custom prefix, configure:

    config :xenia, binding_prefix: "pop-"

This must match the `bindingPrefix` option passed to LiveSocket
on the JavaScript side.

## Examples

    import Xenia.Component

    <.xenia
      id="editor"
      adapter="Monaco"
      props={%{language: "elixir", value: %{value: @code, version: @version}}}
      events={%{change: "code_changed"}}
      class="editor"
      style="height: 400px"
    />

As a form field, the id and the initial value come from the field:

    <.form for={@form} phx-change="validate" phx-submit="save">
      <.xenia field={@form[:code]} adapter="Monaco" props={...} />
    </.form>

Keyboard shortcuts, while focus is inside the component, send LiveView
events (see `Xenia.Shortcuts`):

    <.xenia id="editor" adapter="Monaco" shortcuts={%{"mod+s" => "save"}} />

A co-located adapter, defined in the same module, is named with
`colocated/1` (see its docs):

    <.xenia id="sales" adapter={colocated(".Chart")} props={...} />

## Loading and errors

`<:loading>` is shown until the island's first mount finishes, for
example while a large library downloads. The server renders it, so it is
there from the first paint, before any JavaScript runs. Give the
component a height (`style` or `class`), and the placeholder fills it.

`<:error>` is shown instead of the island if its adapter throws: in
`mount`, a prop handler, a command or a remount. An element inside it
with `data-xenia-retry` starts the island again. `on_crash` names a
LiveView event that receives `%{"id" => id, "phase" => phase,
"message" => message}`, where phase is `"mount"`, `"update"`,
`"command"` or `"remount"`.

    <.xenia id="editor" adapter="Monaco" props={...} on_crash="editor_crashed" style="height: 400px">
      <:loading>Loading the editor…</:loading>
      <:error>
        The editor couldn't start. <button type="button" data-xenia-retry>Try again</button>
      </:error>
    </.xenia>

Neither needs any adapter code.

# `binding_prefix`

# `colocated`
*macro* 

The adapter name of a co-located adapter: one defined in this module, in
a `<script :type={Phoenix.LiveView.ColocatedHook} name=".Chart">` that
exports `adapterHook({...})` from `"xenia"`.

    <.xenia id="sales" adapter={colocated(".Chart")} props={%{data: @data}} />

    <script :type={Phoenix.LiveView.ColocatedHook} name=".Chart">
      import Chart from "chart.js/auto"
      import { adapterHook } from "xenia"

      export default adapterHook({
        mount(el, props) {
          const canvas = document.createElement("canvas")
          el.appendChild(canvas)
          return new Chart(canvas, { type: "bar", data: props.data })
        },
        destroy(chart) {
          chart.destroy()
        },
      })
    </script>

It expands at compile time to the module-qualified name LiveView gives
that hook, `"MyAppWeb.SalesPage.Chart"`, so two modules can each define
`.Chart`. `<.xenia>` can't resolve `".Chart"` itself: it runs at render
time and never learns which module called it. So `colocated/1` and the
`<script>` must be in the same module.

The app needs no change per adapter. It calls `createHooks` once, and
passes the hooks LiveView extracts (`import { hooks } from
"phoenix-colocated/my_app"`) to its LiveSocket, as Phoenix's generated
`app.js` does.

# `xenia`

Renders a container for a registered Xenia adapter.

The container remains owned by LiveView. The JavaScript
controller manages the child element and adapter lifecycle.

Raises ArgumentError for invalid attributes or values
that cannot be JSON-encoded.

## Attributes

* `adapter` (`:string`) (required) - Adapter name registered in JavaScript createHooks(), or colocated(".Name").
* `id` (`:string`) - Stable DOM id used for commands and adapter instances; defaults to the field's id. Defaults to `nil`.
* `field` (`Phoenix.HTML.FormField`) - Phoenix form field, for example @form[:code]. Defaults to `nil`.
* `props` (`:map`) - JSON-encodable properties passed to the adapter. Defaults to `%{}`.
* `events` (`:map`) - Adapter event name => LiveView event name. Defaults to `%{}`.
* `shortcuts` (`:map`) - Key combination => LiveView event, while focus is inside, e.g. %{"mod+s" => "save"}. Defaults to `%{}`.
* `on_crash` (`:string`) - LiveView event sent when the adapter throws, with id, phase and message. Defaults to `nil`.
* Global attributes are accepted. Additional HTML attributes not managed by Xenia.
## Slots

* `loading` - Shown until the island's first mount finishes.
* `error` - Shown if the adapter throws; an element with data-xenia-retry starts it again.

---

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