Props tools framework
Paths are relative to apps/frontend/src/app/(dashboard)/editor/[projectId]/[pageId]/Components/PropsController/ unless they start with @/.
Files
| File | Role |
|---|---|
PropsPanel.tsx | The 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.tsx | AttrToolContext, useAttrTool, useAttrWriter, AttrValues. |
AttrRow.tsx | One labelled attribute control; sharedValue, isMixed. |
AttrField.tsx | Renders an AttrFieldSpec. |
attrToolDefinitions.tsx | ATTR_TOOL_GROUPS registry. |
attrOptions.ts | Value lists (languages, input types, ARIA roles, referrer policies, …) and seg(). |
structure.ts | addElement, removeElement, setElementText and payload builders. |
tools/*.tsx | Custom tools (below). |
tools/head.ts | findByTag, elementChildren, findHeadEntry. |
@/Utils/HTML/htmlValidation.ts | Validation engine — see HTML validation. |
@/Utils/JHTML.ts | indexJHTML, 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
| Prop | Type |
|---|---|
selectedEID | string[] |
iframeDoc | Document (only used by the reused Classes component) |
htmlPageContentJSON | JHTML |
cssPagesContentJSON | JCSSSheets |
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
| Field | Type | Meaning |
|---|---|---|
selectedEID, iframeDoc | As passed in. | |
nodes | JHTML[] | Selected nodes found in the tree. |
tag | string | null | Shared tag, or null for mixed tags / no selection. |
attributes | Record<string, string> | Attributes present with the same value on every node. |
mixed | Set<string> | Attributes present on some nodes or with differing values. |
tree | JHTML | Whole page. |
page | PageContext | From buildPageContext. |
idOptions | IdOption[] | Every id on the page, #id <tag> labels, sorted. |
classNames | string[] | 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
nodesif given (head tags, list items), else the selection. - One
ATTRIBUTE-UPDATEper 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
};| Layer | Handling |
|---|---|
types/Action.ts applyOne | removeAttribute on the iframe element and delete el.attributes[attr] in JHTML. |
apps/core-service/internal/service/action.go | delete(attrs, attr) when p["remove"] is true. |
RightPanel history | Shows <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
| Prop | Type | Meaning |
|---|---|---|
label | string | Monospace label. |
attributes | string[] | Attributes the row covers; × removes all of them. |
nodes | JHTML[] | Target override. |
stacked | boolean | Input under the label. |
hint | ReactNode | Caption 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[].
kind | Input | Extra fields | Writes |
|---|---|---|---|
text | TextInput | placeholder, suggestions (array or (ctx) => string[]), multiline, emptyToggle | Text; "" removes. emptyToggle adds a toggle that writes "" (present, empty) — used for alt. |
number | NumberInput | min, max, step, integer | Number; "" removes. |
boolean | BooleanInput | — | "" on, remove off. |
select | SelectInput | options, allowCustom, emptyAs | Value; "" removes. emptyAs maps a present-but-empty attribute to that option and back (hidden→"hidden", popover→"auto", crossorigin→"anonymous"). |
segmented | SegmentedInput | options, multiple, emptyAs | As select. multiple writes the token list as-is ("" keeps the attribute, e.g. sandbox). |
idref | IdRefsInput | multiple, filter(node) | Id(s), leading # stripped; "" removes. filter limits suggestions (e.g. datalist, form, labelable controls). |
color | ColorInput + 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
| Tool | Behavior |
|---|---|
IdClassTool | id 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). |
DataAttributesTool | data-* via KeyValueListInput with prefix data-; data-eid is reserved. Rename = remove + set in one action. |
CustomAttributesTool | Every attribute except data-eid and style; names must match /^[a-zA-Z_:][-a-zA-Z0-9_:.]*$/. |
LinkTool | Detects 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. |
EmbedPresetsTool | Builds 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). |
IssueList | Renders HtmlIssue[] with level icons; optional describe(eid) prefix. |
HeadField | One head setting (see below). |
TitleField | <title> text: add, setElementText, or remove when cleared. |
HeadListTool | Lists page nodes matching filter; per-item AttrFields with nodes={[node]}; add from templates (appended to <head>), remove via removeElement. |
StructuredDataTool | script[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
| Export | Payload |
|---|---|
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, removeElement | Execute 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]. |
Related
- Explanation: Style and props tools architecture
- How-to: Add an HTML attribute tool
- Reference: HTML validation