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

Wraps a field dropped with no form around it, and binds a field dropped inside one.

Prevention rather than a report, because "this field is not inside a form" is a mistake with
exactly one sensible repair and no reason to make the author read about it. One template rewrite,
so one `:insert` op and one undo step.

It is deliberately NOT a `MishkaCmsCore.Runtime.PageBuilder.Containment` refusal. Building a form
top-down — three fields first, wrapper after — is an ordinary gesture, and refusing it turns the
intermediate state of a legal workflow into an error message. `Containment` refuses only the
nesting HTML itself forbids.

## What gets a binding

A component the kit calls a field — `MishkaCmsCore.Runtime.PageBuilder.Fields.field_node?/2`, the
editor's one answer to that, which reads the kit's own declaration first — is bound where it
stands. Anything else is asked of the controls UNDER it, because a template is one field rather
than one node: `el-checkbox` is a `<label>` over a hidden input and a checkbox, `el-radio-group` a
`<fieldset>` over its options, and in both the head carries no value at all while every control
beneath it answers to one name.

Asking only the head left those two never bound. They kept their literal `name`, so the browser
posted `agree` at the event's TOP level while `MishkaCmsCore.Runtime.PageForm.scoped/2` reads only
`params["contact"]` — the consent tick was dropped from every submission, the spec claimed a key no
input carried, and nothing in the audit rail said so.

One name is one field, and several names are several fields the drop has no business choosing
between, so a head over controls with different names is left exactly as it arrived. So is a
template that brings a form of its own, and a head already carrying a binding.

The attribute the binding is written on is the kit's, not this module's: `Fields.binding_attr/1`
names the component's `Phoenix.HTML.FormField` attribute whatever the kit called it, and a kit that
takes no such struct gets the `Fields.pair_attrs/1` pair instead — `name={f[:email].name}` and
`value={f[:email].value}`. A raw `<input name="field">` from the Elements library takes the same
pair, which is what turns a palette template that has to render standalone into a real field on
the page.

## A value the author wrote is kept

`value="one"` on a radio, `value="true"` on a checkbox and the `value="false"` on the hidden input
beside it are the option's identity, not the box the visitor types in. Binding them would give
every option in a group the form's own value and leave the submission with nothing to tell them
apart, so a control that already carries a written value takes the name binding alone. A palette
card's seeded blank is not a value and is superseded as it always was.

## Where the key comes from

`MishkaCmsCore.Runtime.PageBuilder.Fields.field_label/2` — the author's own words where there are
any, asked of the kit's own attributes rather than of Phoenix's spelling — minted through
`MishkaCmsCore.Runtime.PageBuilder.PageForms.Naming.field_key/2` so it is legal and unique inside
the form it is joining. Two email fields in one form are `email` and `email_2`, never one key with
two inputs writing over each other.

What is already taken is `MishkaCmsCore.Runtime.PageBuilder.PageForms.names/2`, the text half:
every drop used to intern an atom per name already in the form, from markup an author is still
typing, only for the minting to compare them as strings.

## Where a field lands

`place/3` is what every placement asks. A field joins the form it is dropped in or against, the
form's own top or bottom edge included; anywhere else it starts a new form that comes with the
styled Send button. A field never lands after its form's first Send button: a spot past it is moved
in front of it (`landing/4`), wherever in the form the Send button sits. A kit wrapper that hands
its form to its content arrives wired on a Page (`wiring/4`).

## Moving a field between forms

`unit/3` is the whole field a node belongs to (a checkbox's label with its hidden twin, a radio
group), `move_ops/3` moves it to another form or into a new one and `merge_ops/3` moves every field
of a form into another. A form left with no fields is removed in the same step.

See `MishkaCmsCore.Runtime.PageBuilder.PageForms` for what makes a node a form in the first place.

# `outcome`

```elixir
@type outcome() :: :none | {:joined, String.t() | nil} | :started | :wired
```

What a placement did about forms.

# `arriving_field?`

```elixir
@spec arriving_field?(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()]
) :: boolean()
```

Whether a template being dragged in is a field: its head is one, or the controls under it answer
to one name.

# `as_typed_offered?`

```elixir
@spec as_typed_offered?(map(), MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil) ::
  boolean()
```

Whether the form `form` can be made to work as they type: a working form on a Page that passes
the attribute recording it (`PageForms.as_typed_attr/0`) on to what it renders. The Form panel's
How it works, the audit's "Make it work as they type" and `how_ops/3` all ask this.

# `bind`

```elixir
@spec bind(String.t(), map(), String.t() | nil) :: String.t()
```

The template with its field bound to the form it is landing in, or the template untouched.

Untouched covers every case that is not a field joining a form: a block that brings its own form,
a drop with no form above it, a field already carrying a binding, and a form whose `:let` variable
is missing — binding to a variable the form does not bind would not compile.

# `binding`

```elixir
@spec binding(
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()],
  atom(),
  atom() | String.t()
) ::
  [{:set, String.t(), {:expr | :string, String.t()}} | {:drop, String.t()}]
```

How binding `node` to `key` changes it: what to set, and what that supersedes.

`{:set, name, source}` and `{:drop, name}`, in the order they must be applied. The caller renders
them in whatever shape it applies ops in — this module writes straight to a tree, the audit's Fix
builds `MishkaCmsCore.Runtime.PageBuilder.Op` structs — but WHICH attributes move is decided once,
here, because it was decided twice and the two answers drifted.

## Why anything is dropped

A palette card is seeded `name=""` and `value=""` so it renders standing alone on a canvas, and a
kit derives both from the form field with `assign_new/3`, which fills only an ABSENT key. Leave the
empty pair beside the binding that replaces it and the empty pair wins: the input renders `name=""`
and submits nothing at all, while the page validates a key no input carries.

## Why the value is sometimes left

`node` rather than its palette entry, because the entry cannot answer this: a control the author
already gave a written value is a radio option, a checkbox's `true` or the hidden `false` beside
it, and the value there is the option's identity. It keeps it and takes the name binding alone.

# `field?`

```elixir
@spec field?(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()]
) :: boolean()
```

Whether this node in the document is part of a field (`unit/3` answers for it).

# `filed`

```elixir
@spec filed(MishkaCmsCore.Runtime.PageBuilder.Tree.t(), String.t() | nil, [map()]) ::
  MishkaCmsCore.Runtime.PageBuilder.Tree.t()
```

`tree` with `multipart` turned on (`files_ops/3`) for every form the node `node_id` sits in, is or
holds: what a node put or moved there asks of the form it lands in.

# `files_ops`

```elixir
@spec files_ops(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()]
) :: [map()]
```

The op that turns `multipart` on for `form` when a file field sits inside it in `tree`, so the
files chosen there are sent with it; `[]` when there is none, the form already carries it, or it
does not pass `multipart` on.

# `form_name`

```elixir
@spec form_name(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil) ::
  String.t() | nil
```

A working form's people name (`Naming.people_name/1` of its seed), or `nil`.

# `form_of`

```elixir
@spec form_of(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil,
  [map()]
) :: MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil
```

The form `node` sits in, or `nil`.

# `forms`

```elixir
@spec forms(MishkaCmsCore.Runtime.PageBuilder.Tree.t()) :: [{String.t(), String.t()}]
```

Every working form in `tree` a field can join, as `{node id, people name}` in document order.

# `how_ops`

```elixir
@spec how_ops(map(), String.t(), :typing | :send) ::
  {:ok, [map()], String.t()} | {:error, String.t()}
```

The ops that switch how the working form `form_id` sends, as one undo step, and the sentence the
author is told.

  * `:typing` removes every Send button inside the form and records the choice on it
    (`PageForms.as_typed_attr/0`). Its `phx-submit` stays, so Enter never reloads the page. A
    form `as_typed_offered?/2` refuses is refused with a sentence.
  * `:send` removes the record and, when no Send button is left, adds the styled one
    (`send_button/0`) at the end of the form's content. The form stays selected.

# `landing`

```elixir
@spec landing(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()],
  keyword()
) :: {String.t(), non_neg_integer()}
```

Where a field added at the end of `form` lands, as `{parent id, index}`: in front of the form's
first Send button, wherever inside the form it sits, or at the end when there is none.

A Send button inside one of the form's own slots (a kit wrapper's `<:actions>`) is outside the
form's `:let`, so the field lands at the end of the form's content instead, after its last child
that is not a slot.

## Options

  * `:dragged` - the node, or nodes, arriving, so a spot whose parent refuses any of them is
    passed over for the next one out.
  * `:dragged_tree` - the tree `:dragged` belongs to.

# `marks`

```elixir
@spec marks(MishkaCmsCore.Runtime.PageBuilder.Tree.t(), [map()], map()) ::
  {%{forms: map(), landings: MapSet.t()}, map()}
```

What the canvas marks while a field is dragged: `forms` maps every form's node id to its people
name, "" for a form that does not work yet, and `landings` holds each form's first Send button,
which a field dropped at the form's end lands in front of (`landing/4`).

`memo` is the roles already worked out, keyed by component name; the answer carries it grown.

# `merge_ops`

```elixir
@spec merge_ops(map(), String.t(), String.t()) ::
  {:ok, [map()], String.t()} | {:error, String.t()}
```

The ops that move every field of the form `from_id` into the form `into_id`, in order and in front
of its first Send button, then remove `from_id`; one undo step, and the sentence the author is
told.

# `move_ops`

```elixir
@spec move_ops(map(), String.t(), String.t()) ::
  {:ok, [map()], String.t()} | {:error, String.t()}
```

The ops that move the field `node_id` belongs to (`unit/3`) into another form, as one undo step,
and the sentence the author is told.

`target` is a working form's node id, or `new_target/0` for a new form with a Send button placed
after the form the field leaves. Every control of the field is bound to the form it joins, under a
key that form is not using. A form left with no fields is removed in the same step.

# `moving`

```elixir
@spec moving(map(), map()) :: {:plain, map()} | {:run, [map()], String.t()}
```

How a drag of the existing node `params["node_id"]` lands, when it is a field on a Page.

`params["index"]` is counted as `Tree.move/4` counts it, with the dragged node already out of its
row.

`{:plain, params}` is an ordinary move, its spot kept in front of its form's Send button. A field
dragged into another form or out of every form answers `{:run, ops, sentence}`: its whole field
(`unit/3`) moved and rebound into the other form, or a new form with a Send button around it, as
one undo step. A form that binds no variable takes the field as it is.

# `new_target`

```elixir
@spec new_target() :: String.t()
```

The `target` that asks `move_ops/3` for a new form.

# `page_forms?`

```elixir
@spec page_forms?(map()) :: boolean()
```

Whether this section's compiler emits `__page_forms__/0` — whether a form written here could ever
reach a destination.

Total: anything without a section is not a page.

# `phrase`

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

A form as a sentence names it: "Form 2", "the Contact form", or "the form" when it has no name.

# `place`

```elixir
@spec place(String.t(), map(), map()) :: {String.t(), map(), outcome()}
```

The template to insert, the spot it lands in and what was done about forms.

`params` carries `"parent_id"` and `"index"`, and `"anchor_id"`/`"anchor_before"` when the drop
was aimed beside a node. On a Page a field joins the form it lands in or is dropped against (the
answer's params then name the spot inside that form), and anywhere else it starts a new form with
a Send button; a kit wrapper that hands its form to its content arrives wired, unless it already
names its own `phx-submit`. Everywhere else a field is only bound to a form the author wrote
around it.

# `replace_ops`

```elixir
@spec replace_ops(map(), String.t(), keyword()) ::
  {:ok, [map()]} | {:error, String.t()}
```

The ops that replace the raw `<form>` `node_id` with a working `<.form>` in one undo step: the
same attributes and children, then `wiring/4`. The form keeps its own `id`, so what already points
at it still does. A form that posts to its own `action` is a developer's and is refused.

## Options

  * `:events` - the page's Events-tab entries. Defaults to `[]`.

# `replaceable?`

```elixir
@spec replaceable?(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()) :: boolean()
```

Whether `replace_ops/3` can make the raw `<form>` `node` a working form.

# `send_button`

```elixir
@spec send_button() :: String.t()
```

The styled Send button a form is given: the Elements palette's `el-submit`, read by id so every
place that adds one inserts the same markup.

# `told`

```elixir
@spec told(outcome()) :: String.t() | nil
```

What the author is told after a placement, or `nil` when there is nothing to say.

# `unit`

```elixir
@spec unit(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()]
) :: MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil
```

The whole field `node` belongs to, or `nil` when it is not part of one: the smallest element
holding every control of its key in its form (a checkbox's label with the hidden twin, a radio
group), widened inside a form to the highest element below the form that holds nothing of another
field (its label, its column), and outside every form only to a `<label>` or `<fieldset>` around
it. Never the form itself, never a slot, and never anything that also holds another field or a
Send button.

# `units`

```elixir
@spec units(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()]
) :: [MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()]
```

Every field of `form` as its `unit/3`, outermost first, in document order.

# `wirable?`

```elixir
@spec wirable?(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil, [map()]) ::
  boolean()
```

Whether Make it a working form can make the idle form `node` one: it posts to no web address of
its own, and it hands its form to its content (`wiring/4`) or is a plain HTML form
(`replace_ops/3`).

# `wiring`

```elixir
@spec wiring(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(),
  [map()],
  keyword()
) :: {:ok, [map()]} | {:error, String.t()}
```

The ops that make an idle form-lending node (`Fields.hands_form?/2`) a working form: `:let`, the
five names with a fresh token, and a binding for every field inside it, ready for
`MishkaCmsCore.Runtime.PageBuilder.EditorSession.apply_ops/4`.

`tree` is the page the names must be unique in. A seed the node already carries in `as` is kept.
A `phx-submit` the page's code already answers (`:events`) is the developer's: then only `:let`,
`for` and `as` are written, and its fields keep the names that code reads. A form that posts to
its own `action` is a developer's and is refused.

## Options

  * `:within` - the tree `node` sits in, when it is not `tree` (a template on its way in).
  * `:events` - the page's Events-tab entries. Defaults to `[]`.
  * `:seed` - the name to start from when the node carries no `as`. Defaults to `"form"`.
  * `:keep_id` - `true` keeps an `id` the node already has. Defaults to `false`.

# `wrap`

```elixir
@spec wrap(String.t(), map(), String.t() | nil) :: {String.t(), boolean()}
```

The template to insert, and whether a form was put around it.

`parent_id` is where the drop is about to land, so the question "is there a form here" is asked of
the page rather than of the template being dropped. A form put around a field comes with the
styled Send button.

---

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