ReferenceProps tools framework

Props tools framework

Paths are relative to apps/frontend/src/app/(dashboard)/editor/[projectId]/[pageId]/Components/PropsController/ unless they start with @/.

Files

FileRole
PropsPanel.tsxThe Props tab. Builds the context from the JHTML tree, filters tools by appliesTo and search, renders groups. Mounted by RightPanel for the properties tab.
AttrToolContext.tsxAttrToolContext, useAttrTool, useAttrWriter, AttrValues.
AttrRow.tsxOne labelled attribute control; sharedValue, isMixed.
AttrField.tsxRenders an AttrFieldSpec.
attrToolDefinitions.tsxATTR_TOOL_GROUPS registry.
attrOptions.tsValue lists (languages, input types, ARIA roles, referrer policies, …) and seg().
structure.tsaddElement, removeElement, setElementText and payload builders.
tools/*.tsxCustom tools (below).
tools/head.tsfindByTag, elementChildren, findHeadEntry.
@/Utils/HTML/htmlValidation.tsValidation engine — see HTML validation.
@/Utils/JHTML.tsindexJHTML, textContentOf (new).

Data source

Tools read attributes from the JHTML tree (htmlPageContentJSON), not the iframe DOM. PropsPanel memoizes buildPageContext(tree) (index, id counts, label targets, map names, headings) and the class names collected from cssPagesContentJSON style-rule selectors.

PropsPanel

PropType
selectedEIDstring[]
iframeDocDocument (only used by the reused Classes component)
htmlPageContentJSONJHTML
cssPagesContentJSONJCSSSheets

Element groups (scope: "element") render only when at least one selected EID resolves to a node; page groups (scope: "page") always render. A tool with appliesTo renders only when it returns true. Badge: tool.count(ctx) if defined, else the number of tool.attributes that are shared or mixed on the selection.

AttrToolContextValue

FieldTypeMeaning
selectedEID, iframeDocAs passed in.
nodesJHTML[]Selected nodes found in the tree.
tagstring | nullShared tag, or null for mixed tags / no selection.
attributesRecord<string, string>Attributes present with the same value on every node.
mixedSet<string>Attributes present on some nodes or with differing values.
treeJHTMLWhole page.
pagePageContextFrom buildPageContext.
idOptionsIdOption[]Every id on the page, #id <tag> labels, sorted.
classNamesstring[]Class names from stylesheet selectors.

useAttrWriter()

Returns setAttributes(values: AttrValues, { live?, nodes? } = {}) where AttrValues = Record<string, string | null> — a string sets the attribute, null removes it.

  • Targets nodes if given (head tags, list items), else the selection.
  • One ATTRIBUTE-UPDATE per node × attribute. Reverse: the previous value, or { remove: true } if the attribute was absent.
  • No-ops are skipped (setting the same value, removing an absent attribute), except while a live gesture is open.
  • Live gestures work as in the style writer: same key (EIDs|attributes) ⇒ action.update(...).

ATTRIBUTE-UPDATE remove flag

type AttributeUpdateActionPayload = {
    type: "ATTRIBUTE-UPDATE";
    EID: string;
    attribute: string;
    value: string;
    remove?: boolean; // remove the attribute; value ignored
};
LayerHandling
types/Action.ts applyOneremoveAttribute on the iframe element and delete el.attributes[attr] in JHTML.
apps/core-service/internal/service/action.godelete(attrs, attr) when p["remove"] is true.
RightPanel historyShows <attr> removed on <EID>.

Boolean attributes are written as "" (rendered bare by JHTMLToHTML for names in BOOLEAN_ATTRS) and turned off with remove. See ADR-0003.

AttrRow

PropTypeMeaning
labelstringMonospace label.
attributesstring[]Attributes the row covers; × removes all of them.
nodesJHTML[]Target override.
stackedbooleanInput under the label.
hintReactNodeCaption under the row.

Shows mixed when more than one node is targeted and any attribute differs.

AttrFieldSpec

All kinds take attr, optional label (default: the attribute name) and hint. AttrField also takes nodes?: JHTML[].

kindInputExtra fieldsWrites
textTextInputplaceholder, suggestions (array or (ctx) => string[]), multiline, emptyToggleText; "" removes. emptyToggle adds a toggle that writes "" (present, empty) — used for alt.
numberNumberInputmin, max, step, integerNumber; "" removes.
booleanBooleanInput—"" on, remove off.
selectSelectInputoptions, allowCustom, emptyAsValue; "" removes. emptyAs maps a present-but-empty attribute to that option and back (hidden→"hidden", popover→"auto", crossorigin→"anonymous").
segmentedSegmentedInputoptions, multiple, emptyAsAs select. multiple writes the token list as-is ("" keeps the attribute, e.g. sandbox).
idrefIdRefsInputmultiple, filter(node)Id(s), leading # stripped; "" removes. filter limits suggestions (e.g. datalist, form, labelable controls).
colorColorInput + value text—Color; "" removes.

Registry

type AttrToolDef = {
    id: string;                                   // prefix "html-"
    title: string;
    attributes: string[];                         // badge + search
    appliesTo?: (ctx: AttrToolContextValue) => boolean;
    fields?: AttrFieldSpec[];
    render?: (ctx: AttrToolContextValue) => ReactNode;
    count?: (ctx: AttrToolContextValue) => number;
};
type AttrToolGroupDef = {
    id: string;
    title: string;
    scope: "element" | "page";
    tools: AttrToolDef[];
};
export const ATTR_TOOL_GROUPS: AttrToolGroupDef[];

Helpers inside attrToolDefinitions.tsx: tags(...t), inputTypes(...types) (input type defaults to text), fieldTool(id, title, fields, appliesTo?), headFieldTool(id, title, specs), meta(label, name, extra?), og(label, property, extra?), link(label, rel, placeholder), and the field shorthands BOOL, TEXT, NUM.

Groups: Checks, Global attributes, Text & semantics, Links, Images & media, Forms, Embeds, Interactive elements, Tables, Accessibility (ARIA), Advanced (element scope); Page settings (page scope). The designer props panel reference lists every tool, tag and attribute.

Custom tools

ToolBehavior
IdClassToolid editable for a single node only (spaces → -, # stripped), warns on duplicates. Embeds StyleController/Classes with suggestions={classNames} (new optional prop, datalist on the add input).
DataAttributesTooldata-* via KeyValueListInput with prefix data-; data-eid is reserved. Rename = remove + set in one action.
CustomAttributesToolEvery attribute except data-eid and style; names must match /^[a-zA-Z_:][-a-zA-Z0-9_:.]*$/.
LinkToolDetects the destination kind from href (mailto:, tel:, #, file extension or download, ///http(s), else page) and edits it per kind; switching kind writes a default href and sets/removes download.
EmbedPresetsToolBuilds iframe AttrValues for YouTube (youtube-nocookie.com/embed/<id> from watch/shorts/live/embed/youtu.be URLs), Vimeo (player.vimeo.com/video/<id>) and Google Maps (google.com/maps?q=…&output=embed).
IssueListRenders HtmlIssue[] with level icons; optional describe(eid) prefix.
HeadFieldOne head setting (see below).
TitleField<title> text: add, setElementText, or remove when cleared.
HeadListToolLists page nodes matching filter; per-item AttrFields with nodes={[node]}; add from templates (appended to <head>), remove via removeElement.
StructuredDataToolscript[type="application/ld+json"] blocks; JSON edited locally, saved with setElementText only when valid and changed; schema templates.

HeadFieldSpec

type HeadFieldSpec = {
    label: string;
    tag: "meta" | "link" | "base";
    match: Record<string, string>; // identifies the element, e.g. { name: "description" }
    attr: string;                  // holds the value, e.g. "content" | "href" | "charset"
    kind?: "text" | "select" | "color" | "multiline";
    options?: readonly SelectOption[];
    placeholder?: string;
};

findHeadEntry matches a direct child of <head> by tag and case-insensitive attribute values. Writing: no element + value ⇒ addElement(head, { tag, attributes: { ...match, [attr]: value } }); element + value ⇒ attribute update; element + empty ⇒ removeElement if the element holds only match keys and attr, else remove just attr. ColorInput live updates are ignored; only the final value is written.

structure.ts

ExportPayload
addElementPayload(parent, element)STRUCTURE-UPDATE ADD appending at parent.children.length, EID: "". The action generates the local EID and the reverse REMOVE.
removeElementPayloads(node, parent)REMOVE [EID] + reverse ADD of a structuredClone at its JHTML index, keepEID: true (same pattern as useMarqueeSelection.deleteSelectedElements).
addElement, removeElementExecute the above.
setElementText(node, parent, text)TEXT-UPDATE on the first string child; if the node has no text child, one action that removes the node and adds a copy with children: [text].