Skip to content

Component Library: The Part System

NEO 1984, Component Library v1. This covers one thing only: what a "part" is, why the catalog needs it, and how it attaches to the existing schema.

Naming

Stu is currently calling this "custom animations/transitions/effects." That name describes one consequence of the mechanism, not the mechanism itself. Every animation, transition, and effect in the schema is already custom, that part isn't new. What's actually new is that a component can expose named, independently addressable sub elements, and each of those can carry its own props, animations, effects, and transitions rather than sharing one set at the whole layer level. The right name for that is a part. Call the field parts, not customAnimations or similar, since props and effects live on it too, not just animation.

What a part is

  • Fixed per component, declared by the author. A component's parts are authored once, in code, the same way its properties are. An end user never invents a new part.
  • A part is effectively a mini layer, scoped inside a component. It gets tagged with the same type discriminant real layers already use (boxText, pointText, image, shape, and so on). A "text part" is a boxText/pointText layer living inside a component instead of inside a scene. This isn't a new taxonomy, it's the existing layer type system applied one level deeper, which means the inspector's existing per type field rendering already knows how to render a part's fields, it just needs pointing at the part's data instead of the layer's.
  • Every part carries the full set: props, animations, effects, and transitions. Not a subset. See the concrete example below for why this isn't optional.

Why this can't be whole layer only

Take the Betta storyboard frame "Black Friday: Sale On Now."

Part Needs independently
"Black Friday" panel Colour, pulse on or off
"Up To 50% Off" panel Colour, pulse on or off, separate from the panel above
"Sale On Now" text Editable text, its own fade and slide entrance, no pulse at all

The brief for this frame explicitly asks to "turn the animations on or off for each panel if they wish." If animation lived at the whole layer level, the two panels would have to pulse together or not at all. There is no way to give the user independent control over one without parts.

Schema shape

Additive, optional sibling field on the existing componentLayerSchema. Nothing else changes shape.

componentLayerSchema {
  ...
  parts?: Record<partId, {
    props?: ...
    animations?: AnimationsSchema
    effects?: EffectsSchema
    transitions?: TransitionsSchema
  }>
}

Checked this against how the schema actually works before calling it safe:

  • binding-paths.ts walks a plain string[] through nested objects with no assumption about depth or shape. A path like ['parts', 'title', 'props', 'text'] resolves through the exact same function as ['props', 'text'] does today. Every existing binding, none of which ever has a parts segment, is untouched.
  • animations, effects, and transitions are already optional sibling fields on every layer via commonLayerFields. Adding parts as one more optional sibling is the same pattern used to add those fields in the first place. Old documents with no parts key validate and render exactly as they do now.
  • The transition and animation resolution functions are pure, keyed only on what's passed to them. Existing call sites keep passing layer level data unchanged. A part scoped call site is a new caller, not a rewrite of the old one.

Net effect: no existing component, template, or manifest binding needs to change. Only a component being newly built, or deliberately upgraded, ever touches parts.

One geometry problem worth flagging now

When two parts need to pulse from one shared point (the visual centre of the whole component) rather than each part's own centre, that's the standard "scale around an external pivot" problem: set each part's transform-origin to the component's centre expressed as an offset from that part's own top left corner, rather than letting it default to the part's own middle. This stays entirely inside a component's own render code. The shared parts schema above doesn't need to know pivots exist at all.

Properties panel, briefly

Once a component has parts, the inspector needs to know which part it's currently showing fields for. v1 answer: inline, collapsible sections per part in the existing panel, with the component author declaring which fields are standard (main panel) versus advanced (tucked away), reusing the per layer type field rendering that already exists. A fully immersive "open this component" editing mode, where the canvas dims and you click directly into a part, is a later phase, not v1.

Open question for Stu

rsts-changes-for-stu.md already proposes grouping and pins, "these move together," "pin this layer to that layer's edge." That's the layout relationship half of what multi part components need. The two Black Friday panels almost certainly need to stay aligned as they resize, which is a pins problem, not an animation problem. Worth deciding whether pins are a property of a part (defined here) or a separate mechanism, before both get built without talking to each other.