Skip to content
D
Documentation

Diagram Kit

reference
9 min readUpdated

Import these from @grafloria/element.

On their own pages

  • JoinColumn: Join guidance — live "which column should I join to?" scoring + tinting for

Functions

addColumnAt

Append a column (immutably) — returns the new column array.

ts
function addColumnAt(columns: ErColumn[], column: ErColumn, at = columns.length): ErColumn[]

assignTiers

Tier every candidate score at once: the FIRST candidate holding the maximum score is top when that maximum is >= 2 — exactly one gold row per drag.

ts
function assignTiers(scores: number[]): MatchTier[]

bindCardEditing

Wire the editing gestures onto a kit container. Idempotent per container (a re-bind disposes the previous one), matching bindRowInteractions.

ts
function bindCardEditing(api: EditApi): CardEditingHandle

bindJoinGuidance

Bind live join guidance to a diagram instance. Listens to the engine's connection lifecycle; while a drag is live, every kit-card row on every other table wears its tier class (and the best one its "★ BEST" chip), and the matching port glyphs glow via a generated stylesheet. Everything clears when the drag ends, however it ends.

ts
function bindJoinGuidance(api: JoinGuidanceApi, options: JoinGuidanceOptions = {}): JoinGuidanceHandle

ensureDiagramKitStyles

Inject the kit stylesheet once. Safe to call repeatedly and in SSR.

ts
function ensureDiagramKitStyles(doc: Document | undefined = typeof document !== 'undefined' ? document : undefined): void

ensureJoinGuidanceStyles

Inject the guidance tint stylesheet once. Safe to call repeatedly / in SSR.

ts
function ensureJoinGuidanceStyles(
  doc: Document | undefined = typeof document !== 'undefined' ? document : undefined
): void

erDiagram

Build a render() spec for an ER diagram.

Entities become HTML table cards; relationships become orthogonal edges with crow's-foot cardinality. A TABLE.column end pins the edge to that row via an absolute-layout port (the FK→PK look). Two edges landing on the same column are automatically spread apart.

ts
function erDiagram(options: ErDiagramOptions): {
  nodes: Array<Record<string, unknown>>;
  edges: Array<Record<string, unknown>>;
  finalize: (api: unknown) => void;
}

erRowCenterY

Node-local y of a row's centre (the +1 offsets past the card's top border).

ts
function erRowCenterY(rowIndex: number): number

erTable

Typed handle for a kit ER table. Throws for unknown ids / non-kit nodes.

ts
function erTable(api: HandleApi, id: string): ErTable

erTables

Every kit ER table in the diagram, as handles.

ts
function erTables(api: HandleApi): ErTable[]

matchColumns

Match old columns to new columns for port reconciliation. Returns a map from OLD index → NEW index; an old index absent from the map is a REMOVED column (its pinned ports and their edges are dropped).

Three passes, most-certain first, so a single edit is always read the way a human means it:

  1. object identity — reorder / add / remove that preserved references;
  2. name — a retype (same name, new object) or a rebuilt array;
  3. positional remainder — the leftovers matched in order, which is exactly what a rename is (one old + one new in the same slot). Without pass 3 a rename reads as remove+add and the edge is severed.
ts
function matchColumns(oldCols: ErColumn[], newCols: ErColumn[]): Map<number, number>

matchTier

Per-score tier (rank-free half; assignTiers promotes the single best to top).

ts
function matchTier(score: number): Exclude<MatchTier, 'top'>

removeColumnAt

Remove the column at index — returns the new column array.

ts
function removeColumnAt(columns: ErColumn[], index: number): ErColumn[]

renameColumnAt

Rename the column at index — returns a new array with a NEW object there.

ts
function renameColumnAt(columns: ErColumn[], index: number, name: string): ErColumn[]

rowIndexFromY

Inverse of {@link erRowCenterY}: which column row a pinned port sits on.

ts
function rowIndexFromY(y: number): number

scoreMatch

Score a candidate join between two columns. Pure; the exact production logic. Higher is better; 0 means "no reason to join these".

ts
function scoreMatch(a: JoinEnd, b: JoinEnd): 0 | 1 | 2 | 3

singularize

Naive English singular — enough for schema names (orders, categories, statuses).

ts
function singularize(word: string): string

umlClass

Typed handle for a kit UML class. Throws for unknown ids / non-kit nodes.

ts
function umlClass(api: HandleApi, id: string): UmlClass

umlClasses

Every kit UML class in the diagram, as handles.

ts
function umlClasses(api: HandleApi): UmlClass[]

umlDiagram

Build a render() spec for a UML class diagram. Call spec.finalize(api) after render() — multiplicity chips are positioned labels, which only the live model's link.addLabel can express.

ts
function umlDiagram(options: UmlDiagramOptions): {
  nodes: Array<Record<string, unknown>>;
  edges: Array<Record<string, unknown>>;
  finalize: (api: unknown) => void;
}

updateClass

Edit a rendered UML class in place. delta may change the name, stereotype, abstract flag, attributes, methods, and width/height. One undoable step.

ts
function updateClass(api: KitApi, classId: string, delta: UmlClassDelta): Promise<boolean>

updateEntity

Edit a rendered ER table in place. delta may change the name, the column list (add / remove / reorder / rename / retype), and the width/height. Field ports and their edges are reconciled so the diagram stays correct, as ONE undoable step.

ts
function updateEntity(api: KitApi, entityId: string, delta: ErEntityDelta): Promise<boolean>

Returns whether the node existed and the edit was applied.

Classes

CardHandle

ts
abstract class CardHandle

Methods

  • constructor( protected readonly api: HandleApi, readonly id: string )
  • get exists(): boolean
  • get node(): unknown — Escape hatch to the underlying NodeModel (typed loosely on purpose).
  • get width(): number
  • get height(): number
  • abstract get name(): string
  • abstract rename(name: string): Promise<boolean> — Rename the card title — one undoable step.
  • abstract resize(size: { width?: number; height?: number }): Promise<boolean> — Resize the card — one undoable step (kit body scrolls when capped).
  • select(): void
  • async undo(): Promise<void>
  • async redo(): Promise<void>
  • onRowSelect( cb: (e: { field: ErField | null; selected: RowRef | null }) => void ): () => void — Row-selection events for THIS card only (the kit's axk:row-select, filtered). ER cards resolve the selection to a typed {@link ErField} when possible. Returns an unbind function.

ErColumnList

The columns collection of an {@link ErTable}. Iterable of {@link ErField}.

ts
class ErColumnList implements Iterable<ErField>

Methods

  • constructor(private readonly table: ErTable)
  • get length(): number
  • names(): string[]
  • at(index: number): ErField | undefined
  • get(name: string): ErField | undefined
  • [Symbol.iterator](): Iterator<ErField>
  • add(column: ErColumn, opts: { at?: number } = {}): Promise<boolean>
  • removeAt(index: number): Promise<boolean>
  • renameAt(index: number, name: string): Promise<boolean>
  • patchAt(index: number, patch: Partial<ErColumn>): Promise<boolean>
  • move(from: number, to: number): Promise<boolean>

ErField

A column of an {@link ErTable} — itself just (table, name): stateless.

ts
class ErField

Methods

  • constructor( readonly table: ErTable, private readonly columnName: string )
  • get exists(): boolean
  • get name(): string
  • get index(): number
  • get type(): string | undefined
  • get pk(): boolean
  • get fk(): boolean
  • rename(name: string): Promise<boolean> — Rename — the field port keeps its id, so attached edges stay glued.
  • setType(type: string): Promise<boolean>
  • setKeys(keys: { pk?: boolean; fk?: boolean }): Promise<boolean>
  • remove(): Promise<boolean> — Remove this column (its ports and attached edges are dropped).

ErTable

ts
class ErTable extends CardHandle

Properties

NameTypeDefaultDescription
columns

Methods

  • get spec(): ErEntitySpec — A deep COPY of the stored entity spec — never a live reference.
  • get name(): string
  • rename(name: string): Promise<boolean> — Rename the card title — one undoable step.
  • resize(size: { width?: number; height?: number }): Promise<boolean> — Resize the card — one undoable step (kit body scrolls when capped).
  • update(delta: Parameters<typeof updateEntity>[2]): Promise<boolean> — The raw delta path — everything above funnels through here.

UmlClass

ts
class UmlClass extends CardHandle

Properties

NameTypeDefaultDescription
attributes
methods

Methods

  • get spec(): UmlClassSpec — A deep COPY of the stored class spec — never a live reference.
  • get name(): string
  • get abstract(): boolean
  • get stereotype(): string | undefined
  • rename(name: string): Promise<boolean> — Rename the card title — one undoable step.
  • resize(size: { width?: number; height?: number }): Promise<boolean> — Resize the card — one undoable step (kit body scrolls when capped).
  • setAbstract(abstract: boolean): Promise<boolean>
  • setStereotype(stereotype: string | undefined): Promise<boolean>
  • update(delta: Parameters<typeof updateClass>[2]): Promise<boolean>

UmlMemberList

attributes / methods of a {@link UmlClass} (members are plain strings).

ts
class UmlMemberList

Methods

  • constructor( private readonly cls: UmlClass, private readonly kind: 'attributes' | 'methods' )
  • list(): string[]
  • get length(): number
  • at(index: number): string | undefined
  • add(member: string, opts: { at?: number } = {}): Promise<boolean>
  • removeAt(index: number): Promise<boolean>
  • renameAt(index: number, member: string): Promise<boolean>

Constants

DIAGRAM_KIT_STYLE_ID

Diagram-kit stylesheet — injected once, on first use of any kit builder.

Everything is prefixed axk- and the selection overrides are scoped with :has(...) to kit cards only, so embedding the kit can never restyle a host's own nodes. The rules encode the lessons the diagrams/* demos learned the hard way:

  • the card fills the node and draws the ONLY border (the node's own rect is hidden by the builders, and suppressed again on selection because the theme paints .selected with an accent stroke that overrides inline transparency);
  • the default .selection-highlight outline (a dashed rect a few px OUTSIDE the node) reads as a second floating box around a bordered card — the kit hides it and rings the card itself instead.
ts
const DIAGRAM_KIT_STYLE_ID: "grafloria-diagram-kit-styles"

ER_HEAD_H

ts
const ER_HEAD_H: 28

ER_ROW_H

Row height / header height of the entity card — sizing is derived from these.

ts
const ER_ROW_H: 25

JOIN_GUIDANCE_STYLE_ID

ts
const JOIN_GUIDANCE_STYLE_ID: "grafloria-join-guidance-styles"

Interfaces

CardEditingHandle

ts
interface CardEditingHandle

Members

  • dispose(): void

ErColumn

ts
interface ErColumn

Properties

NameTypeDefaultDescription
namestring
type?string
pk?boolean
fk?boolean

ErDiagramOptions

ts
interface ErDiagramOptions

Properties

NameTypeDefaultDescription
entitiesErEntitySpec[]
relationships?ErRelationshipSpec[]
rowSelection?booleanRows are selectable by default: click a column to select it (painted with .axk-row-selected; axk
/ axk
CustomEvents fire on the container). Set false to opt out.
editable?booleanIn-canvas editing (opt-in, default false — read-only diagrams are unchanged). When true the card grows editing chrome: double-click the header to rename the table, double-click a column name to rename it, an "add column" affordance and a per-row delete control. Every change routes through {@link updateEntity } as ONE undoable step.

ErEntityDelta

What an ER table edit can change. Omitted fields are left as they were.

ts
interface ErEntityDelta

Properties

NameTypeDefaultDescription
name?string
width?number
height?number
columns?ErColumn[]The NEW full column list. Survivors are matched to the old columns to move their ports (object identity → name → positional remainder), so a rename, a retype, a reorder, an insert and a delete are all understood without the caller preserving object references. See {@link matchColumns}.

ErEntitySpec

ts
interface ErEntitySpec

Properties

NameTypeDefaultDescription
idstring
name?stringHeader text. Defaults to the id.
columnsErColumn[]
position?{ x: number; y: number }
width?number
height?numberFixed card height. When smaller than the computed height the column list SCROLLS (the kit body is overflow-y
, and the canvas yields the wheel to it). Omit for auto-height from the column count.

ErRelationshipSpec

ts
interface ErRelationshipSpec

Properties

NameTypeDefaultDescription
fromstringEntity id, or ENTITY.column to attach at that column's row.
tostring
label?string
cardinality?ErCardinality | { tail: string; head: string }Named cardinality (default one-to-many) or explicit marker types.
fromSide?ErSide
toSide?ErSide
color?string
id?string

HandleApi

The slice of DiagramInstance the handles need (same shape update.ts uses).

ts
interface HandleApi

Properties

NameTypeDefaultDescription
containerHTMLElement

Members

  • getModel(): { getNode(id: string): | { getMetadata?(key: string): unknown; size: { width: number; height: number }; state?: { selected?: boolean }; } | undefined; selectNode?(node: unknown): void; }
  • getEngine?(): { undo(): Promise<void> | void; redo(): Promise<void> | void } | undefined
  • renderNow?(): void

JoinEnd

One end of a candidate join: a table (node) and one of its columns.

ts
interface JoinEnd

Properties

NameTypeDefaultDescription
tablestring
columnJoinColumn

JoinGuidanceApi

The slice of DiagramInstance the binding needs (matches HandleApi's shape).

ts
interface JoinGuidanceApi

Properties

NameTypeDefaultDescription
containerHTMLElement

Members

  • getModel(): { getNode(id: string): NodeLike | undefined; getNodes?(): NodeLike[]; }
  • getEngine?(): | { eventBus?: { on(event: string, handler: (data: unknown) => void): () => void } } | undefined

JoinGuidanceHandle

ts
interface JoinGuidanceHandle

Members

  • activeTiers(): Map<string, Map<number, MatchTier>> — nodeId → rowIndex → tier for the drag in progress (empty when idle).
  • dispose(): void

JoinGuidanceOptions

ts
interface JoinGuidanceOptions

Properties

NameTypeDefaultDescription
resolvePort?(portId: string, nodeId: string | undefined)Resolve a PORT id to its table column. The default understands both the ER kit's convention (TABLE__col__side__n) and the query-builder dot convention (table.col-in / table.col-out), checked against the node's actual kitEntity columns so an ambiguous name never mis-resolves.
chipText?stringText of the gold chip on the best row. Default '★ BEST'.
portCss?(tier: MatchTier, selector: string)Extra per-tier CSS for the target columns' PORT glyphs, injected per drag. Default paints the production glows (gold/green/blue drop-shadows).

UmlClassDelta

What a UML class edit can change.

ts
interface UmlClassDelta

Properties

NameTypeDefaultDescription
name?string
stereotype?string
abstract?boolean
width?number
height?number
attributes?string[]
methods?string[]

UmlClassSpec

ts
interface UmlClassSpec

Properties

NameTypeDefaultDescription
idstring
name?stringDisplayed name. Defaults to the id.
stereotype?stringRenders as «stereotype» above the name (e.g. 'interface', 'abstract', 'enum').
abstract?booleanItalicises the name (also set automatically for stereotype 'abstract'/'interface').
attributes?string[]
methods?string[]
position?{ x: number; y: number }
width?number
height?numberFixed card height — smaller than the content makes the compartments scroll.

UmlDiagramOptions

ts
interface UmlDiagramOptions

Properties

NameTypeDefaultDescription
classesUmlClassSpec[]
relationships?UmlRelationshipSpec[]
rowSelection?booleanMembers are selectable by default: click one to select it (painted with .axk-row-selected; axk
/ axk
CustomEvents fire on the container). Set false to opt out.
editable?booleanIn-canvas editing (opt-in, default false). When true: double-click the class name to rename it, double-click a member to rename it, an "add" affordance per compartment and a per-member delete control — all routed through {@link updateClass } as one undoable step.

UmlRelationshipSpec

ts
interface UmlRelationshipSpec

Properties

NameTypeDefaultDescription
fromstring
tostring
kind?UmlRelationKindDefault 'association'.
label?string
multiplicity?[string, string]Multiplicity / role chips at the [from, to] ends (e.g. ['0..*', '1']).
fromSide?UmlSide
toSide?UmlSide
id?string

Types

ErCardinality

ts
type ErCardinality =
  | 'one-to-many'
  | 'one-to-one'
  | 'many-to-many'
  | 'one-to-zero-or-many'
  | 'one-to-one-or-many';

ErSide

ts
type ErSide = 'left' | 'right' | 'top' | 'bottom';

MatchTier

ts
type MatchTier = 'top' | 'good' | 'ok' | 'none';

UmlRelationKind

ts
type UmlRelationKind =
  | 'inheritance'
  | 'realization'
  | 'association'
  | 'directed-association'
  | 'aggregation'
  | 'composition'
  | 'dependency';

UmlSide

ts
type UmlSide = 'left' | 'right' | 'top' | 'bottom';

Was this page helpful?