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<ChildRef>"]
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.