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

What a document's minted element classes have to become when its markup lands in ANOTHER document.

The Visual Editor names an element by a `pb-xxxxxxxx` class
(`MishkaCmsCore.Runtime.PageBuilder.Tree.scope/2`), and that class is both the key
`MishkaCmsCore.Runtime.PageBuilder.ElementCss` files the element's CSS under and the `to:` of an
interaction aimed at it. The rules live in one stylesheet row per document — `owner/2` names it —
and EVERY row is compiled into the one site stylesheet, so markup that keeps the classes it was
written under is a second element wearing somebody else's rule:

  * it is styled by the other document's row while its own CSS box reads empty;
  * CSS written on it is a second rule for the same class, and restyles the other document;
  * the builder pruning that element from the other document deletes the rule it was relying on.

`rescope/1` mints one fresh class per class the markup holds and swaps them; `carry_css/5` writes
the source's rules for those classes under the copy's own owner, inside the create that writes the
copy — so a copy whose CSS could not be written is not written either.

## What does NOT come through here, and why

A DRAFT IS THE SAME DOCUMENT. A draft row, a fork of one and the publish that writes it back all
end as the record they were taken from, under the owner that record already has: rescoping one
would leave every rule the live document is wearing pointing at nothing. Copy, paste and Duplicate
inside one editing session are the same story — two elements of one document may share a rule, and
the class is how they do it.

COPYING AN ELEMENT AND COPYING A LOOK ARE NOT THE SAME GESTURE, which is why `drop_scopes/1` lives
here beside all that. Copy and Duplicate carry the element, and the name it answers to comes with
it on purpose. Copy styles carries only the look onto an element that already has a name of its
own, and a name arriving with it is a second element keyed to the first one's rule — so that is
the one gesture inside a session the name is taken off for.

What comes through here is markup that ends up in a DIFFERENT document: another component
(`MishkaCmsCore.Runtime.ComponentCopy`), another page
(`MishkaCmsCore.Runtime.PageEditions.translate/4`), or a snippet, an example or a wizard's
composition placed into the document someone is editing — which is TWO doors, not one, because a
card can be clicked (`MishkaCmsCore.Runtime.PageBuilder.EditorSession.place_template/3`) or
dragged (`MishkaCmsCore.Runtime.PageBuilder.EditorSession.apply_drop/2`, where the markup arrives
in the payload instead), and one gesture told two ways must not give the author two answers.

# `carry_css`

```elixir
@spec carry_css(
  Ash.Changeset.t(),
  String.t() | atom() | nil,
  struct(),
  %{required(String.t()) =&gt; String.t()},
  map() | nil
) :: Ash.Changeset.t()
```

Copies the source document's rules for `scopes` into the copy's own row, as the create's
after-action.

AFTER THE ROW, INSIDE ITS TRANSACTION: the row is named after the copy's id, and a refusal here
takes the copy back out with it. A rule whose class is not in the copied text is one the builder
has not pruned yet, and it is left behind rather than filed under nothing.

# `drop_expr_scopes`

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

A `class={[…]}` expression's source with the minted classes taken out of its literal elements, and
`nil` when they were the whole of it.

AN EXPRESSION HOLDS THE NAME TOO. `MishkaCmsCore.Runtime.PageBuilder.Tree.ensure_scope/2` splices
the minted class into the list rather than flattening `class={@variant}` into the literal text of
the expression, so a look copied off such an element carries the name exactly the way a string
class did — and the target, wearing the source's name ahead of its own, is styled by the source's
rule while its own is pruned by the next save.

An expression that is not a list comes back as it went in: there is nowhere in it a class can be
read, so there is nothing there to take out.

# `drop_scopes`

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

`class` — a whole `class` attribute's text — with the minted classes taken out.

A COPIED LOOK IS NOT THE ELEMENT IT WAS COPIED FROM. The minted class names one element to its own
CSS rule and is the `to:` of every interaction aimed at it, so carrying it onto a second element
keys both to one rule: `Tree.scope/2` answers with the first class it finds, the second element's
own rule stops being live and the next save prunes it away.

# `owner`

```elixir
@spec owner(String.t() | atom() | nil, String.t()) :: String.t()
```

The row a document's element CSS is filed under: its builder section and its id.

One definition, because the builder writing `page-<id>` while a copy reads `page_<id>` is a copy
styled by nothing and a row nobody prunes.

# `rescope`

```elixir
@spec rescope(term()) :: {term(), %{required(String.t()) =&gt; String.t()}}
```

`value` with a fresh class in place of every scope class it holds, and the `%{held => fresh}` map
that says which became which — what `carry_css/5` needs to bring the source's rules over.

`value` is anything a copy is made of: a string, a list, a map of them.

# `rewrite`

```elixir
@spec rewrite(term(), (String.t() -&gt; String.t())) :: term()
```

`fun` over every string inside `value`, however deep it sits.

The walker `rescope/1` swaps classes with, public because a copy usually has one more thing to
rewrite in the same text and it has to happen FIRST: a component may itself be called
`pb-deadbeef`, and its `component_name="pb-deadbeef"` is a name, not an element.

It rewrites strings, lists and plain maps. A STRUCT IS LEFT AS WRITTEN, which is what a page's
`helpers` are — hand-written code, stored as an embedded resource. The builder mints a class onto
an element of the TEMPLATE and keys that element's rule on it, so a class a helper holds is one no
rule is filed under and one no copy can carry; a helper aiming an interaction at `.pb-…` by hand
is the case this leaves pointing where it pointed, rather than rebuilding every struct a copy
might be carrying to chase it.

# `scope?`

```elixir
@spec scope?(term()) :: boolean()
```

Whether `class` — ONE class, not a whole `class` attribute — is a minted one.

THE ONE ANSWER TO THAT QUESTION. `MishkaCmsCore.Runtime.PageBuilder.Tree.scope/2` finds an
element's own class by it and `MishkaCmsCore.Runtime.PageBuilder.ElementCss` refuses to file a
rule under anything else, so a second spelling of the format is a rule keyed on a class the other
spelling does not read back.

It is asked by matching the class against the pattern the rest of this module scans text with,
and checking that the match is the WHOLE of it: `hover:pb-0a1b2c3d` and `pb-0a1b2c3d-lg` are
classes of their own, and the lookarounds already say so.

---

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