Components API

Reusable building blocks with typed inputs, computed outputs, and encapsulated logic.

Try it

title: "Component instance"
description: "Two button instances with different inputs"
nodes:
  - type: RECT, name: "Btn1Bg", x: 120, y: 200, w: 120, h: 40, fill: "#3498DB", cornerRadius: 6
  - type: RECT, name: "Btn2Bg", x: 280, y: 200, w: 120, h: 40, fill: "#E74C3C", cornerRadius: 6
events:
  - node: "Btn1Bg", event: "click", code: "this.opacity = this.opacity === 1 ? 0.5 : 1"
  - node: "Btn2Bg", event: "click", code: "this.opacity = this.opacity === 1 ? 0.5 : 1"
tools_visible: [select, play]
ui_visible: [properties, formulas, layers]

Component definition

ComponentDef {
  id: string                    // unique identifier
  name: string                  // display name ("Button", "Card")
  inputs: ComponentProperty[]   // parameters from outside
  outputs: ComponentProperty[]  // computed values exposed outside
  nodes: VNode[]                // internal structure
  formulas: FormulaMap          // internal reactive bindings
}

ComponentProperty {
  name: string                  // property name
  type: "number" | "string" | "boolean" | "color" | "point"
  defaultValue: any             // value when not overridden
  description?: string          // tooltip text
}

Inputs

Parameters that control the component from outside. Set by the parent scene or wired with formulas.

Component "ProgressBar"
Inputs:
value: number = 0 ← current progress (0-100)
color: Color = "#3498DB" ← bar color
height: number = 8 ← bar height

Inside the component, reference inputs with $input:

Bar.width = $input.value / 100 * Track.width
Bar.fills[0].color = $input.color
Bar.height = $input.height

Outputs

Computed values available to the parent. Read-only from outside.

Outputs:
isComplete: boolean ← true when value >= 100
percentage: number ← value / 100

Parent formulas:

DoneText.visible = ProgressBar.isComplete
Label.text = ProgressBar.percentage

Instances

Place a component on the canvas → creates an instance.

  • Internal nodes are cloned from the definition
  • Each instance has independent state
  • Change the definition → all instances update
  • Override an input on one instance → only that instance changes
ComponentInstance {
  defId: string                                  // which definition
  inputValues: { [name: string]: any | string }  // static values or formulas
}

Creating components

  1. Select a group of nodes
  2. "Create Component" action
  3. Define which properties become inputs
  4. Define which computed values become outputs
  5. Internal formulas reference $input.propertyName

Non-visual components

Components that don't draw to the canvas. Logic-only entities with reactive interfaces.

Timer

Inputs: interval: number = 1000, active: boolean = false
Outputs: elapsed: number, tick: event

API Connection

Inputs: url: string, method: string = "GET", body: object
Outputs: data: any, loading: boolean, error: string
Actions: fetch()

State

Inputs: initialValue: any
Outputs: value: any
Actions: set(val), reset()

Non-visual components appear in the scene tree with a distinct icon. Their outputs wire to visual components via formulas:

Spinner.visible = api.loading
DataTable.data = api.data

Slots

Typed inputs that accept VNodes (not values) from the parent scene.

ComponentSlot {
  name: string            // "leftFootTarget", "lookAtTarget"
  type: string            // expected node type or "any"
  placeholder?: VNode     // debug-time stand-in
}

During authoring: each slot creates a placeholder node for testing. At runtime: placeholders replaced by actual scene nodes.

Formula integration:

EyeRotation = atan2($lookAtTarget.y - this.y, $lookAtTarget.x - this.x)

Properties panel shows drop zones for slots — drag a node from the tree to bind it.

Custom editors

Build custom inspector panels for your component types.

Editor for "DataChart"
├── Dropdown → $target.input.chartType
├── ColorInput → $target.input.primaryColor
├── Slider → $target.input.barWidth
└── Button → resetDefaults($target)

When a user selects an instance, the custom editor replaces the default properties panel. Custom editors are themselves components.

Editor widgets

WidgetPurpose
PropertyFieldAuto-generates input for a property type
PropertyGroupCollapsible section header
NodePickerSelect a node from document
FormulaInputFormula field with syntax highlighting
PreviewCanvasMini canvas preview
ActionButtonTriggers a script/action

Nesting

Components contain other component instances. Inputs wire through:

Page component
├── Input: theme.primaryColor
└── Button instance
└── color = $input.theme.primaryColor

Change the page theme → all buttons update.

Export

Component typeReact export
VisualReact component with props
Non-visualCustom hook
SlotsContext or ref bindings
Custom editor(editor-only, not exported)
// Visual component
function Button({ label, color, disabled }) {
  return <button style={{ backgroundColor: color }}>{label}</button>
}

// Non-visual component
function useTimer(interval, active) {
  const [elapsed, setElapsed] = useState(0)
  useEffect(() => { ... }, [interval, active])
  return { elapsed }
}

Serialization

{
  "type": "COMPONENT_INSTANCE",
  "defId": "button-v1",
  "inputValues": {
    "label": "Save",
    "color": "#2ECC71",
    "disabled": false
  }
}

Definitions serialize separately from instances. On load, instances resolve their definition by ID and clone internal nodes.


NodesPropertiesFormulas APIEvents API

← НазадEvents APIДальше →Animation API