MishkaCmsCore.Runtime.PageBuilder.Tree.Shadow (Mishka CMS Core v0.0.1)

Copy Markdown View Source

A box shadow as the five things an author is actually choosing, and back again.

box-shadow is one string holding four lengths, a colour and a keyword, and a control that offers it as one string offers nothing: 0 10px 24px -4px rgba(15,23,42,.18) is not a value anyone reads or edits, it is a value they paste. So the panel takes the shadow apart — how far right, how far down, how soft, how much bigger, what colour, and whether it falls inside the box — and puts it back together on every change. That is the same decomposition Chrome's own shadow editor makes, and it is what turns "which side is the shadow on" into two numbers you can nudge.

What survives

Only the shape the panel can express: [inset] <x> <y> <blur> <spread> <colour>. A shadow with a second layer, or a colour this cannot recognise, parse/1 refuses rather than half-reads — the author keeps the string they wrote, in the element's own CSS, instead of watching a control silently throw half of it away.

Anything omitted comes back as its CSS default: a missing blur is 0, a missing colour is currentColor's stand-in here, rgba(0,0,0,.2), chosen because a shadow with no colour at all is the one thing nobody means.

Summary

Functions

The parts a control starts from when the element has no shadow yet.

The parts a shadow is made of, or :error when this cannot hold the whole of it.

The named shadows a row of one-click presets offers.

One part changed, as the whole shadow.

The shadow a set of parts means, or nil when they mean no shadow at all.

Types

t()

@type t() :: %{
  x: String.t(),
  y: String.t(),
  blur: String.t(),
  spread: String.t(),
  color: String.t(),
  inset: boolean()
}

Functions

default()

@spec default() :: t()

The parts a control starts from when the element has no shadow yet.

parse(value)

@spec parse(String.t() | nil) :: {:ok, t()} | :error

The parts a shadow is made of, or :error when this cannot hold the whole of it.

A blank string is :error too: nothing to take apart is not a shadow with default parts, and offering one would make an unset control look set.

presets()

@spec presets() :: [{String.t(), String.t()}]

The named shadows a row of one-click presets offers.

Elevation is a scale, not a number people invent: "none, small, medium, large" is how every design system already talks about it, and the fields underneath still take anything.

put(value, field, part)

@spec put(String.t() | nil, atom(), String.t() | boolean()) :: String.t() | nil

One part changed, as the whole shadow.

The panel edits a single number at a time and the property holds all of them, so every control reads the current value, replaces its own part and writes the string back.

to_value(parts)

@spec to_value(map()) :: String.t() | nil

The shadow a set of parts means, or nil when they mean no shadow at all.

nil rather than "none": the panel clears an override by writing an empty value, and a control that wrote box-shadow: none instead would leave a real declaration behind for the next reader to wonder about.