Pure helpers for manipulating a template's canvas JSON. The editor LV
calls these on the in-memory canvas map; persistence is a separate
Templates.update/2 step.
Canvas shape:
%{
"width" => 1200,
"height" => 630,
"background" => %{"type" => "color", "value" => "#0b1220"},
"elements" => [%{"id" => "abc", "type" => "text", …}, …]
}Element ids are short random strings — stable across reorders/edits so the editor can refer to them, but ephemeral (regenerated on duplication).
Summary
Functions
Adds a new element at the end of the z-stack (rendered on top).
The canonical empty canvas. Used when a new template is created.
Moves the given element to the top of the z-stack.
Builds a fresh element struct for the given insert kind. Kinds
Removes the given element ids.
Reads the elements list, defaulting to [] for legacy canvases.
Looks up a single element by id.
Bulk-applies {x, y} deltas to a list of element ids — fast path for
drag operations that move several elements at once.
Finds an unused slot name with the given prefix. Returns the bare
prefix if it's free, otherwise appends the lowest unused numeric
suffix — so a fresh canvas gets Text, a second Text insert gets
Text2, and so on.
Replaces the elements list.
Substitutes {{slot}} tokens in a text/stamp element. Unknown slots
pass through unchanged ({{name}} stays visible), matching the
workspace convention.
Moves the given element to the bottom of the z-stack.
Updates a top-level canvas field (width, height, background). Values are coerced to their expected shape.
Updates one field on a single element. Coordinate + size fields
(x/y/width/height) are coerced to numbers and clamped inside
the canvas; everything else passes through.
Functions
Adds a new element at the end of the z-stack (rendered on top).
Returns {updated_canvas, new_element} so the caller can immediately
select the new id without recomputing it.
@spec blank() :: map()
The canonical empty canvas. Used when a new template is created.
Moves the given element to the top of the z-stack.
Builds a fresh element struct for the given insert kind. Kinds:
"text"/"text_var"— text element, static content vs. an auto-named{{TextN}}slot placeholder."image"/"image_var"— image element, empty src vs.{{ImageN}}."rect"— rectangle.
The caller passes the current canvas so the helper can pick a slot name that doesn't collide with existing slots.
Removes the given element ids.
Reads the elements list, defaulting to [] for legacy canvases.
Looks up a single element by id.
Bulk-applies {x, y} deltas to a list of element ids — fast path for
drag operations that move several elements at once.
Finds an unused slot name with the given prefix. Returns the bare
prefix if it's free, otherwise appends the lowest unused numeric
suffix — so a fresh canvas gets Text, a second Text insert gets
Text2, and so on.
Names stay descriptive rather than always carrying a numeric suffix, which reads better in the "Wire slots" list on the Assignments page.
Replaces the elements list.
Substitutes {{slot}} tokens in a text/stamp element. Unknown slots
pass through unchanged ({{name}} stays visible), matching the
workspace convention.
Used by the editor preview; the SVG renderer calls Slots.substitute
directly.
Moves the given element to the bottom of the z-stack.
Updates a top-level canvas field (width, height, background). Values are coerced to their expected shape.
Updates one field on a single element. Coordinate + size fields
(x/y/width/height) are coerced to numbers and clamped inside
the canvas; everything else passes through.