# GroupModel

Import it from `@grafloria/engine`.

Also has every member of `DiagramEntity`, listed on its own entry.

```ts
class GroupModel extends DiagramEntity
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  |  |
| `members` | `Set<string>` |  |  |
| `isCollapsed` | `boolean` | `false` |  |
| `bounds?` | `{ x: number; y: number; width: number; height: number }` |  |  |
| `layoutType` | `LayoutType` | `'none'` |  |
| `layoutConfig?` | `LayoutConfig` |  |  |
| `position` | `{ x: number; y: number }` |  |  |
| `size?` | `{ width: number; height: number; depth: number }` |  |  |
| `parentGroupId?` | `string` |  |  |
| `memberValidation?` | `MemberValidation` |  |  |
| `isHovered` | `boolean` | `false` |  |
| `padding?` | `GroupPadding` |  |  |
| `headerHeight` |  |  |  |
| `zIndex` |  | `0` |  |
| `fitMode` | `GroupFitMode` | `'exact'` |  |
| `constrainChildren` |  | `false` |  |
| `collapsedState?` | `CollapsedState` |  |  |
| `subgraphLayout?` | `SubgraphGroupConfig` |  |  |
| `laneConfig?` | `LaneConfig` |  |  |
| `membershipRule?` | `MembershipRule` |  |  |
| `capacity?` | `number` |  |  |

**Methods**

- `constructor(config: { id?: string; name: string })`
- `canAddMember(candidateId: string, diagram?: DiagramModel): boolean` — Whether `candidateId` may legally join this group. Rejects self-membership, ancestor cycles (adding an ancestor group as a
member would create a containment loop), and candidates failing the
per-group `memberValidation` predicate. Node candidates only run the
predicate check (nodes can't form group cycles).
- `getWipState(): { count: number; capacity?: number; state: 'unlimited' | 'under' | 'full' | 'over' }` — WIP state for the capacity limit. 'under' = room to spare,
'full' = exactly at the limit (the visual warning threshold), 'over' = past
it (only reachable by lowering capacity below the current count). Returns
'unlimited' when no capacity is set.
- `isOverCapacity(): boolean` — True when at or beyond the capacity limit (warning state).
- `setCapacity(capacity: number | undefined): void` — Set (or clear) the capacity / WIP limit, tracked for undo/diff.
- `addMember(entityId: string, diagram?: DiagramModel): void` — Add member to group.

When the member is itself a group, this establishes containment by setting
the child's `parentGroupId` (detaching it from any previous parent), which
keeps the nesting tree consistent for getAncestors/getDescendants. Members
that would create a cycle or fail `memberValidation` are rejected (no-op).
- `removeMember(entityId: string, diagram?: DiagramModel): boolean` — Remove member from group. When the member is a group whose parent is this
group, its `parentGroupId` back-pointer is cleared so the nesting tree
stays consistent.
- `setParent(newParentId: string | undefined, diagram?: DiagramModel): boolean` — Reparent this group under `newParentId` (or detach when undefined),
keeping both the `parentGroupId` pointer and the parents' `members` sets
consistent. Rejects cycles (a group cannot be nested under one of its own
descendants) and returns false without mutating anything.
- `setHovered(hovered: boolean): void` — Set transient drag-hover highlight state and notify listeners. Renderers/canvas can subscribe to 'hover:changed' to outline a drop target.
- `expand(): void` — Expand the group
- `collapse(): void` — Collapse the group
- `setCollapsedState(state: CollapsedState | undefined): void` — Set (or clear) the reversible collapse snapshot. Tracked as a
change so the incremental diff-capture serializes it and undo/redo see it.
- `setLayout(type: 'flexbox', config: FlexboxLayoutConfig): void` — Set layout configuration
- `setLayout(type: 'grid', config: GridLayoutConfig): void` — Set layout configuration
- `setLayout(type: 'flexbox' | 'grid', config: LayoutConfig): void` — Set layout configuration
- `clearLayout(): void` — Clear layout configuration
- `getLayout(): { type: LayoutType; config?: LayoutConfig }` — Get layout configuration
- `hasLayout(): boolean` — Check if group has layout configured
- `getFlexboxLayout(): FlexboxLayoutConfig` — Get layout as flexbox config
- `getGridLayout(): GridLayoutConfig` — Get layout as grid config
- `calculateBounds(diagram: DiagramModel): void` — Calculate bounds from member nodes using global bounds
- `getPadding(): { top: number; right: number; bottom: number; left: number }` — Resolve {@link padding} to a full four-sided rectangle. `undefined` (the
author never said) resolves to {@link DEFAULT_GROUP_PADDING}; an explicit
value — including the 0 that loaders pin for legacy documents — is taken
as written.
- `getOuterBounds(): GroupRect` — The group's outer frame rectangle in world coordinates. Prefers explicit
geometry (position + size) and falls back to the derived member `bounds`,
matching how GroupMembershipService hit-tests a group.
- `getInnerBounds(): GroupRect` — The rectangle members must stay inside: the outer frame minus padding and
minus the header band (which is reserved at the top). Never returns a
negative width/height.
- `fitToContents(diagram?: DiagramModel, options?: FitToContentsOptions): void` — Auto-fit the group frame to its members' bounding box plus padding and the
header band. This is the real consumer of `padding`/`headerHeight` that
`calculateBounds` (a tight, padding-free box) never was.

Member groups contribute their own OUTER frame (so a parent fits around a
nested group's full extent, not just its raw member points). With
`deepRecursive`, descendant groups are fitted first (deepest first) so the
parent fits around already-fitted children.

The computed content rectangle is reconciled with the current frame per the
effective fit mode (grow-only / shrink-only / exact) and written to both
`position`/`size` (authoritative geometry) and `bounds` (hit-test rect). No-op with no positioned members (nothing to fit around).
- `setFrame(rect: GroupRect): void` — Write a world rectangle into the group's authoritative geometry
(position + size) and the derived hit-test `bounds`, keeping the existing
`depth` when present. Tracks a single 'bounds' change for undo/diff.
- `clampChildToExtent(nodeId: string, diagram?: DiagramModel): boolean` — Clamp a member node so it stays fully inside the group's inner extent. No-op unless `constrainChildren` is set and the node is a direct member. Returns true when the node was actually moved. Absolute-coordinate model:
this adjusts the node's world position directly.
- `restoreGeometry(geo: { position: { x: number; y: number }; size?: { width: number; height: number; depth: number }; bounds?: GroupRect; }): void` — Restore raw geometry (position + optional size + bounds)
captured before a collapse. Unlike setFrame this permits size === undefined
so a group that had no explicit frame is restored to exactly that.
- `setZIndex(z: number): void` — Set the group's stacking index (lower renders further back).
- `bringToFront(diagram?: DiagramModel): void` — Bring this group in front of every other group (highest zIndex + 1). Minimal z-order API — the renderer honors zIndex ordering.
- `sendToBack(diagram?: DiagramModel): void` — Send this group behind every other group (lowest zIndex - 1).
- `isAutoLayoutEnabled(): boolean` — Is push-driven reflow active for this container?

DEFAULT ON for any group that declares a layout — a container that says
"I am a 12-column grid" and then lets its children drift is not a layout
container. `metadata('autoLayout')` survives as the explicit override:
set it to `false` to freeze the children (useful while dragging, or for a
container whose positions are authored by hand). Setting it to `true`
remains valid and is now simply the default.
- `requestLayout(diagram?: DiagramModel): void` — THE push entry point: "something changed, reflow if you are supposed to."
Distinct from {@link applyLayout}, which is the unconditional PULL — an
explicit `applyLayout()` still works on an opted-out container.
- `applyLayout(diagram?: DiagramModel): void` — Apply layout to member nodes
Positions child nodes based on flex or grid layout configuration
- `serialize(): SerializedGroup` — Serialize to JSON
- `static fromJSON(data: SerializedGroup): GroupModel` (static) — Deserialize from JSON
