# `Xenia.Adapters.Monaco`
[🔗](https://github.com/uzaydurdu/xenia/blob/v0.1.1/lib/xenia/adapters/monaco.ex#L1)

Phoenix function component for Xenia's Monaco adapter.

Wraps the editor value into the versioned payload expected by the
JavaScript Monaco adapter.

## Examples

    import Xenia.Adapters.Monaco

    <.monaco
      id="editor"
      value={@code}
      version={@version}
      language="elixir"
      on_change="code_changed"
      style="height: 400px"
    />

As a form field, the id, the input name and the initial value come from
the field, and the editor's text is submitted with the form:

    <.form for={@form} phx-change="validate" phx-submit="save">
      <.monaco field={@form[:code]} language="elixir" />
    </.form>

The Monaco JavaScript adapter must be registered with `createHooks()`
under the name given in `adapter` (default `"Monaco"`). It comes from its
own entry point, and your app passes the Monaco import:

    import { createMonaco } from "xenia/monaco"

    createHooks({ Monaco: createMonaco({ load: () => import("monaco-editor") }) })

## Setup

Monaco is the heaviest library to set up, so here is all of it.

**1. Install it:** `npm install monaco-editor@0.57.0 --save-exact --prefix assets`

**2. Create the adapter** in `assets/js/monaco.js`. Monaco finds its web
workers with `new URL(..., import.meta.url)`, which esbuild doesn't
bundle, so tell it where they are. The `import("monaco-editor")` is
written in your app, so esbuild finds it in your `assets/node_modules`:

    import { createMonaco } from "xenia/monaco"

    const WORKERS = {
      json: "json", css: "css", scss: "css", less: "css",
      html: "html", handlebars: "html", razor: "html",
      typescript: "ts", javascript: "ts",
    }

    self.MonacoEnvironment = {
      getWorker: (_id, label) => new Worker(`/assets/js/monaco/${WORKERS[label] ?? "editor"}.worker.js`),
    }

    export const monaco = createMonaco({ load: () => import("monaco-editor") })

Register it next to your other adapters: `createHooks({ Monaco: monaco })`.
To add completion providers or commands, pass
`setup: (monaco, editor) => disposable` to `createMonaco`.

**3. Build the workers** in `config/config.exs`: give `app.js` an entry
name, add the five worker files, and a loader for Monaco's icon font:

    config :esbuild,
      my_app: [
        args: ~w(app=js/app.js
                 monaco/editor.worker=node_modules/monaco-editor/esm/vs/editor/editor.worker.js
                 monaco/json.worker=node_modules/monaco-editor/esm/vs/language/json/json.worker.js
                 monaco/css.worker=node_modules/monaco-editor/esm/vs/language/css/css.worker.js
                 monaco/html.worker=node_modules/monaco-editor/esm/vs/language/html/html.worker.js
                 monaco/ts.worker=node_modules/monaco-editor/esm/vs/language/typescript/ts.worker.js
                 --bundle --target=es2022 --outdir=../priv/static/assets/js
                 --external:/fonts/* --external:/images/* --alias:@=. --loader:.ttf=file),
        # cd and env as Phoenix generated them
      ]

Restart `mix phx.server` after changing `config.exs`.

**4. Load Monaco's CSS.** esbuild writes the CSS Monaco imports next to
`app.js`. Add a second stylesheet to `root.html.heex`:

    <link phx-track-static rel="stylesheet" href={~p"/assets/js/app.css"} />

**5. Use it,** with `import Xenia.Adapters.Monaco` in `html_helpers`, and
echo the client's `version` back from your change handler. That's how the
editor tells your echo of its own typing apart from a real update:

    def handle_event("code_changed", %{"value" => code, "version" => version}, socket) do
      {:noreply, assign(socket, code: code, version: version)}
    end

Pass `version={nil}` for an authoritative update. Commands:
`Xenia.push_to(socket, "editor", "focus")`, and `"insert"` with
`%{text: "..."}`.

## Theme

The editor follows the app's `theme` provider (light → `vs`, dark →
`vs-dark`). `themes` replaces those defaults with your own, which can be
theme maps. `theme` pins one theme and ignores the app's:

    <.monaco id="editor" themes={%{dark: %{name: "midnight", base: "vs-dark",
                                          colors: %{"editor.background" => "#0b1020"}}}} />
    <.monaco id="editor" theme="hc-black" />

Monaco themes are global: the last theme applied wins for every editor on
the page.

## Forcing the server's value

Props are only sent to the client when they change. If the server rejects
an edit and re-renders the same value it rendered before, nothing is sent
and the editor keeps the user's text. Increment `revision` whenever the
server's value must replace the editor's content, for example when a form
is reset after Save.

# `monaco`

Renders a Monaco editor through Xenia.
## Attributes

* `id` (`:string`) - stable DOM id; commands use it. Defaults to the field's id. Defaults to `nil`.
* `field` (`Phoenix.HTML.FormField`) - Phoenix form field, for example @form[:code]; provides id and value. Defaults to `nil`.
* `adapter` (`:string`) - adapter name registered in createHooks(). Defaults to `"Monaco"`.
* `value` (`:string`) - editor content when no field is given; nil is treated as "". Defaults to `nil`.
* `version` (`:integer`) - version echoed from a change event; nil means the value is authoritative. Defaults to `nil`.
* `revision` (`:integer`) - increment to force the server's value, even if it equals the last one rendered. Defaults to `0`.
* `language` (`:string`) - Monaco language id; omitted if nil. Defaults to `nil`.
* `theme` (`:any`) - explicit Monaco theme override (a name or a theme map); beats the app's theme. Omitted if nil. Monaco themes are global, so this affects every editor on the page. Defaults to `nil`.
* `themes` (`:map`) - the Monaco theme for each app theme, e.g. %{light: "vs", dark: %{name: "midnight", base: "vs-dark", colors: %{...}}}. App themes it leaves out use the defaults (light → vs, dark → vs-dark). Omitted if nil. Defaults to `nil`.
* `options` (`:map`) - additional Monaco editor options; must not contain value, language or theme. Defaults to `%{}`.
* `on_change` (`:string`) - LiveView event pushed when the content changes. Defaults to `nil`.
* `shortcuts` (`:map`) - Key combination => LiveView event, while the editor has focus (see Xenia.Shortcuts). Defaults to `%{}`.
* `on_crash` (`:string`) - LiveView event sent if the editor throws (see Xenia.Component). Defaults to `nil`.
* Global attributes are accepted.
## Slots

* `loading` - Shown until the editor has loaded, e.g. "Loading the editor…".
* `error` - Shown if the editor fails; data-xenia-retry starts it again.

---

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