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

The plain HTML an author reaches for constantly — a `section`, a column grid, a heading, a list.

## Why this exists

The editor had two ways to put something on a page and neither of them was "an empty box". The
palette lists a UI kit's components; `MishkaCmsCore.Runtime.PageBuilder.Blocks` lists whole
finished sections. To get a `<section>` of your own you had to drop a Hero and delete everything
inside it, or open the Code window and type. Every other builder — Elementor's Layout group,
Gantry's structural particles — puts the primitives first, because they are what a page is
actually made of; the finished sections are the shortcut, not the foundation.

## What an element is

The same `%{id, title, category, description, template}` shape a block has, so one panel lists
both and one search covers both. The difference is intent: a block arrives finished and is edited
down, an element arrives empty and is built up.

Plain HTML and Tailwind utilities only, for the same reason blocks are: an element that reached
for a kit component would fail on an install without that kit, and every node here has to be
something the Inspector already knows how to style. `<.form>` and `<.input>` are inside that rule
rather than outside it: they are `Phoenix.Component.form/1` and
`MishkaCmsCore.CoreComponents.input/1`, imported into every compiled page and resolved by the
editing canvas, so they are there on every install — the standing `<.link>` already has.

## What the Forms elements are

A form is a real Phoenix form: `for={assigns[:form_form]}` names the page assign that holds what
the visitor typed, `as={:form}` scopes the params, and the two `phx-*` names are the handlers
`MishkaCmsCore.Runtime.Compilers.LiveViewCompiler` generates from the markup. All five names come
from one seed, minted and renumbered by
`MishkaCmsCore.Runtime.PageBuilder.PageForms.Naming`, so a second form on the page cannot share the
first one's assign. `el-form` therefore ships with a field and a button in it rather than empty:
the five attributes are the point of it, and an empty one teaches none of them.

The bare controls — `el-input`, `el-textarea`, `el-select`, `el-checkbox`, `el-radio-group` — keep
a literal `name`, because a palette card and a hover preview render them on their own, outside any
form. `MishkaCmsCore.Runtime.PageBuilder.PageForms.FormPlacement` rewrites that name into a binding
on the way in, and wraps the control in a form first when it is dropped where there is none.

## Two rules that decide whether these are usable at all

**Containers ship empty, and the canvas draws them anyway.** An empty `<section>` is zero pixels
tall, so without help it cannot be clicked, cannot be dropped into, and looks like the drop
failed. `MishkaCmsCoreWeb.Live.Admin.Builder.CanvasLive` gives any childless container a dashed
outline and a minimum height while editing — never in preview, and never for a void tag like
`<img>`, which is childless on purpose.

**Text elements ship with words in them.** An empty `<p>` has the same problem and no good answer:
a paragraph is its text. They arrive saying what they are, and the first thing an author does is
type over it — which is exactly what the inline text editor is for.

# `element`

```elixir
@type element() :: %{
  id: String.t(),
  title: String.t(),
  category: String.t(),
  description: String.t(),
  template: String.t()
}
```

# `all`

```elixir
@spec all() :: [element()]
```

Every shipped element, in the order the panel lists them.

The whole catalogue, whatever section it belongs to — which is what a check over "every element"
has to mean. Use `for_section/1` to ask what a particular document may place.

# `categories`

```elixir
@spec categories(String.t() | nil) :: [String.t()]
```

The categories the section's elements fall into, in order.

# `for_section`

```elixir
@spec for_section(String.t()) :: [element()]
```

Every element the given section can use, in the order the panel lists them.

MOST ELEMENTS BELONG EVERYWHERE and say nothing; a `<section>` is a `<section>` in all four
documents. The ones that declare `sections` are the exception, and they are exceptions for a
reason a reader can check: `{@inner_content}` names an assign only a LAYOUT is handed, so offering
it on a page would be offering a document that will not compile.

A section has to be NAMED. It used to be an optional argument defaulting to `nil`, and `nil` meant
"only the ones that belong everywhere" — so the catalogue-wide checks, which pass no section,
quietly stopped covering the three elements that declare one.

# `get`

```elixir
@spec get(String.t()) :: element() | nil
```

One element by id, or `nil`.

Unfiltered on purpose: this answers "what is `el-inner-content`", which a drop that has already
happened needs regardless of which panel offered it.

# `search`

```elixir
@spec search(String.t() | nil, String.t() | nil) :: [element()]
```

Elements matching a search string, `nil` or `""` meaning all of them.

Title and description are both searched, so "column" finds the grids and "sidebar" finds `aside`
— an author looking for a thing rarely knows what the tag is called.

---

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