containers and hierarchy

Containers organize objects into layers and groups, forming a tree hierarchy with ordered children.

Container Types

Code Name Purpose
L Layer Top-level organizational container (like Photoshop layers). Typically added to root children.
G Group User-created group of objects. Can nest inside layers or other groups.

StoredContainer Fields

Field Type Default Description
t 'L' | 'G' (required) Container type
p string Parent ContainerId (omitted if root-level)
n string User-assigned name (e.g., "Layer 1", "Header Group")
xy [number, number] (required) Position [x, y]
r number 0 Rotation in degrees
sc [number, number] Scale [scaleX, scaleY]
o number 1 Opacity (0–1)
bm BlendModeCode 'N' Blend mode
v false true Visible — only stored when false
k true false Locked — only stored when true
x boolean Expanded in the UI layer panel

Hierarchy Model

The document hierarchy is a tree rooted at the r (root children) array:

graph TB
    Root["r (Root Children)<br/>Y.Array&lt;ChildRef&gt;"]

    Root -->|"[1, id1]"| L1["Layer 1<br/>(Container, t='L')"]
    Root -->|"[1, id2]"| L2["Layer 2<br/>(Container, t='L')"]

    L1 -->|"[0, id3]"| R1["Rectangle<br/>(Object, t='R')"]
    L1 -->|"[1, id4]"| G1["Group<br/>(Container, t='G')"]
    L1 -->|"[0, id5]"| T1["Text Box<br/>(Object, t='T')"]

    G1 -->|"[0, id6]"| E1["Ellipse<br/>(Object, t='E')"]
    G1 -->|"[0, id7]"| I1["Image<br/>(Object, t='I')"]

    L2 -->|"[0, id8]"| C1["Connector<br/>(Object, t='C')"]

ChildRef System

Children are referenced using ChildRef tuples that discriminate between objects and containers:

type ChildRef = [0 | 1, string]
Tuple Meaning Look Up In
[0, objectId] References an object o map
[1, containerId] References a container c map

This allows a single ordered array to contain a mix of direct objects and nested containers.

Children Arrays

Container children are stored in the ch map, not embedded in the container data:

// ch: Y.Map<Y.Array<ChildRef>>
// Key: ContainerId
// Value: Y.Array of ChildRef tuples

// Example: get children of a layer
const layerChildren = doc.ch.get(layerId)  // Y.Array<ChildRef>

// Iterate children
for (const [type, id] of layerChildren) {
  if (type === 0) {
    const obj = doc.o.get(id)      // StoredObject
  } else {
    const container = doc.c.get(id) // StoredContainer
  }
}
Children are separate from container data

The ch map stores Y.Array<ChildRef> entries — these are Yjs shared types that enable collaborative reordering. They are not embedded in the StoredContainer JSON in the c map. Always look up children via doc.ch.get(containerId), never by reading a children property from the container object.

Z-Ordering

The position of a ChildRef within its parent’s children array determines draw order:

  • Index 0 is drawn first (bottom/back)
  • Last index is drawn last (top/front)

Reordering is done by moving entries within the Y.Array, which Yjs handles correctly for concurrent edits.

Parent References

Objects and containers store a p (parent) field pointing to their containing ContainerId. This enables:

  • Quick lookup of an item’s parent without scanning children arrays
  • Efficient tree traversal in both directions

When an item is at root level (directly in r), the p field is omitted.