# `MishkaCmsCore.Runtime.PageBuilder.Hooks`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/page_builder/hooks.ex#L5)

The runtime JS hooks an element can be attached to, and the two attributes that attach it.

## Why a picker and not a code box

`MishkaCmsCore.Runtime.PageBuilder.Interactions` covers what a page usually wants — toggle a class,
show something, hide something — with no code at any point. What it cannot do is anything else, and
the answer to "anything else" is a hook: real JavaScript, written on the JS hooks page, compiled
into the site's bundle and registered with LiveView.

Writing one stays there. This only *selects* from what is already in the bundle, which is the whole
reason it needs no gate of its own: it grants nothing the actor did not already have, it removes
the step where they hand-type `phx-hook` into Advanced and get the capitalisation wrong.

## The two attributes

`phx-hook` names the hook, and LiveView **requires a unique DOM id** on any element carrying one —
a hook on an element with no id silently never mounts, which is a bad afternoon. So attaching one
mints an id when the element has none, and the picker says so rather than leaving it to be
discovered.

The name written is `JavaScriptCompiler.browser_name/2`'s, not the row's: a global hook called
`collapse` registers as `GlobalCollapse` and a site's own as `<Site>Collapse`. Asking the compiler
is what keeps the two from drifting.

# `available`

```elixir
@spec available(map() | nil, String.t() | nil, String.t() | nil) :: [map()]
```

Every hook this site can attach, its own and the globals, as `%{name:, hook:, description:}`.

`name` is what goes in `phx-hook`; `hook` is the row's own name, which is what the JS hooks page
calls it and therefore what a reader is looking for.

# `known?`

```elixir
@spec known?(map() | nil, String.t() | nil, String.t() | nil, String.t()) :: boolean()
```

Whether `name` is a hook the picker is entitled to write.

The picker writes the name into the document, so a stale option — a hook archived in another tab —
must not be written as though it existed.

# `registry`

```elixir
@spec registry(map() | nil, String.t() | nil, String.t() | nil) :: [map()]
```

Every hook the picker offers: this site's, the globals, and whatever a plugin contributes through
`builder:js_hooks`.

ONE LIST FOR THE PICKER AND FOR `known?/4`, which is the whole point of naming it — the same
`Interactions.registry/0` and `Conditions.registry/0` are. The picker read the appended list and
`known?/4` read `available/3`, so every hook an extension contributed was offered and then refused
on Apply.

---

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