API Reference
Composable API
The composable API is the recommended way to use Flexily. It provides a high-level engine with pluggable text measurement, built on top of the low-level Node API.
createFlexily(options?)
Create a batteries-included engine with monospace text measurement.
import { createFlexily } from "flexily"
const flex = createFlexily()
const node = flex.createNode()
node.setTextContent("Hello world")
flex.calculateLayout(node, 80, 24)Options:
| Option | Type | Default | Description |
|---|---|---|---|
charWidth | number | 1 | Width of each character cell |
charHeight | number | 1 | Height of each character cell |
defaults | "yoga" | "css" | "yoga" | Style-defaults preset for created nodes — see Yoga divergences |
For terminal UIs, the default charWidth=1, charHeight=1 maps directly to terminal cells.
createBareFlexily()
Create a minimal engine with no text measurement plugin. Use pipe() to add plugins. Accepts the same optional defaults preset as createFlexily().
import { createBareFlexily, pipe, withTestMeasurer } from "flexily"
const flex = pipe(createBareFlexily(), withTestMeasurer())Calling setTextContent() on a bare engine without a text plugin throws an error.
pipe(engine, ...plugins)
Apply plugins to an engine, left to right. Returns the modified engine.
import { createBareFlexily, pipe, withMonospace, withTestMeasurer } from "flexily"
const flex = pipe(createBareFlexily(), withMonospace(1, 1))FlexilyEngine
The composable engine interface.
interface FlexilyEngine {
createNode(): FlexilyNode
calculateLayout(root: FlexilyNode, width?: number, height?: number, direction?: number): void
textLayout?: TextLayoutService
}| Method | Description |
|---|---|
createNode() | Create a new FlexilyNode (Node with text content mixin) |
calculateLayout | Calculate layout for a tree (direction defaults to LTR) |
textLayout | The active text measurement backend (if any) |
FlexilyNode
Extends Node with text content methods. Created via engine.createNode().
interface FlexilyNode extends Node {
setTextContent(text: string, style?: Partial<ResolvedTextStyle>): void
getTextContent(): string | null
}| Method | Description |
|---|---|
setTextContent() | Set text content and install an automatic measure function |
getTextContent() | Get the current text content (or null if none) |
Calling setMeasureFunc() on a FlexilyNode clears its text content. Calling setTextContent() replaces any existing measure function.
FlexilyPlugin
A function that extends or configures the engine.
type FlexilyPlugin = (engine: FlexilyEngine) => FlexilyEngineTextLayoutService
The pluggable text measurement backend interface.
interface TextLayoutService {
prepare(input: TextPrepareInput): PreparedText
}prepare() takes text and style, returning a PreparedText object that can compute intrinsic sizes and layout at any width.
Text Measurement Plugins
withMonospace(charWidth?, charHeight?)
Monospace text measurement. Each grapheme cluster = charWidth units wide, always 1 line (no wrapping). Default for createFlexily().
import { createBareFlexily, pipe, withMonospace } from "flexily"
const flex = pipe(createBareFlexily(), withMonospace(1, 1))| Parameter | Type | Default | Description |
|---|---|---|---|
charWidth | number | 1 | Width per character cell |
charHeight | number | 1 | Height per character cell |
withTestMeasurer()
Deterministic text measurement for tests and CI. Fixed grapheme width table: Latin 0.8, CJK 1.0, emoji 1.8 (relative to fontSize). Supports word wrapping.
import { createBareFlexily, pipe, withTestMeasurer } from "flexily"
const flex = pipe(createBareFlexily(), withTestMeasurer())Cross-platform deterministic -- same results on every OS and runtime.
withPretext(pretext)
Proportional font measurement via @chenglou/pretext (peer dependency). For Canvas-based text rendering.
import { createBareFlexily, pipe, withPretext } from "flexily"
import pretext from "@chenglou/pretext"
const flex = pipe(createBareFlexily(), withPretext(pretext))| Parameter | Type | Description |
|---|---|---|
pretext | PretextAPI | The pretext module (install separately) |
Node
The low-level Yoga-compatible API. All composable API nodes extend Node.
Creating Nodes
import { Node } from "flexily"
const node = Node.create()Nodes form a tree. Each node represents a flexbox container or leaf.
Tree Operations
node.insertChild(child, index) // Insert child at index
node.removeChild(child) // Remove child
node.getChildCount() // Number of children
node.getChild(index) // Get child by index
node.getParent() // Get parent node (or null)Dimension Setters
node.setWidth(100) // Fixed width
node.setHeight(50) // Fixed height
node.setWidthAuto() // Auto width
node.setHeightAuto() // Auto height
node.setWidthPercent(50) // 50% of parent
node.setHeightPercent(50) // 50% of parent
node.setMinWidth(10)
node.setMinHeight(10)
node.setMaxWidth(200)
node.setMaxHeight(200)Flex Properties
import {
FLEX_DIRECTION_ROW,
FLEX_DIRECTION_COLUMN,
FLEX_DIRECTION_ROW_REVERSE,
FLEX_DIRECTION_COLUMN_REVERSE,
WRAP_WRAP,
WRAP_NO_WRAP,
JUSTIFY_CENTER,
JUSTIFY_FLEX_START,
JUSTIFY_FLEX_END,
JUSTIFY_SPACE_BETWEEN,
JUSTIFY_SPACE_AROUND,
JUSTIFY_SPACE_EVENLY,
ALIGN_CENTER,
ALIGN_FLEX_START,
ALIGN_FLEX_END,
ALIGN_STRETCH,
ALIGN_BASELINE,
} from "flexily"
node.setFlexDirection(FLEX_DIRECTION_ROW)
node.setFlexWrap(WRAP_WRAP)
node.setFlexGrow(1)
node.setFlexShrink(0)
node.setFlexBasis(100)
node.setJustifyContent(JUSTIFY_CENTER)
node.setAlignItems(ALIGN_CENTER)
node.setAlignSelf(ALIGN_FLEX_END)
node.setAlignContent(ALIGN_CENTER)Spacing
import {
EDGE_TOP,
EDGE_RIGHT,
EDGE_BOTTOM,
EDGE_LEFT,
EDGE_ALL,
EDGE_START,
EDGE_END,
GUTTER_ALL,
GUTTER_ROW,
GUTTER_COLUMN,
} from "flexily"
// Padding
node.setPadding(EDGE_ALL, 10)
node.setPadding(EDGE_TOP, 5)
// Margin
node.setMargin(EDGE_ALL, 10)
node.setMarginAuto(EDGE_LEFT)
// Border
node.setBorder(EDGE_ALL, 1)
// Gap
node.setGap(GUTTER_ALL, 10)
node.setGap(GUTTER_ROW, 5)
node.setGap(GUTTER_COLUMN, 5)The production engine accepts number | Value in all four spacing setters. Numbers remain point lengths. Use parseLength with your adapter's ch and lh scale to construct a Value; the existing resolver evaluates math expressions.
import { parseLength } from "flexily"
const scale = { ch: 1, lh: 1 } // Terminal cells; choose your adapter's scale.
node.setPadding(EDGE_LEFT, parseLength("2ch", scale))
node.setMargin(EDGE_START, parseLength("10cqi", scale))
node.setBorder(EDGE_LEFT, parseLength("calc(1ch + 2cqi)", scale))
node.setGap(GUTTER_ROW, parseLength("1lh", scale))cqi uses the nearest ancestor query container, skipping the node itself. A query container's own padding, margin, border and gap therefore use its ancestor context; its children use its frozen inline size. The layout root uses the width passed to calculateLayout for its own CQ spacing. Resize resolves these values again during layout and intrinsic measurement.
Inline edges (left, right, start and end) and column gaps refuse lh. Block edges (top and bottom) and row gaps refuse ch and cqi. EDGE_ALL and GUTTER_ALL check both axes before changing any value. Percentage padding and margins retain their existing containing-block width basis; border widths refuse percentages, including percentages inside math expressions.
getPadding and getMargin return stored Value objects. getBorder and getGap retain numeric point results and throw LengthError for a stored non-point length; read node.style.border or node.style.gap to inspect that Value. After layout, getComputedPadding, getComputedMargin and getComputedBorder resolve edge lengths in the layout context. There is no computed gap getter. Unset logical borders still return NaN from getBorder.
The classic engine retains its numeric setters and percentage/auto behavior. It refuses CQ, adapter units and math expressions in shared spacing storage with a typed property error saying production engine only.
Positioning
import { POSITION_TYPE_ABSOLUTE, POSITION_TYPE_RELATIVE } from "flexily"
node.setPositionType(POSITION_TYPE_ABSOLUTE)
node.setPosition(EDGE_TOP, 10)
node.setPosition(EDGE_LEFT, 20)Aspect Ratio
node.setAspectRatio(16 / 9)Measure Functions
For leaf nodes that measure their own content (e.g., text):
node.setMeasureFunc((width, widthMode, height, heightMode) => {
// widthMode: MEASURE_MODE_EXACTLY | MEASURE_MODE_AT_MOST | MEASURE_MODE_UNDEFINED
return { width: computedWidth, height: computedHeight }
})Baseline Functions
node.setBaselineFunc((width, height) => {
return baselineOffset // number
})Layout Calculation
import { DIRECTION_LTR, DIRECTION_RTL } from "flexily"
node.calculateLayout(availableWidth, availableHeight, DIRECTION_LTR)Reading Computed Values
After calculateLayout():
node.getComputedLeft()
node.getComputedTop()
node.getComputedRight()
node.getComputedBottom()
node.getComputedWidth()
node.getComputedHeight()
node.getComputedPadding(EDGE_TOP)
node.getComputedMargin(EDGE_LEFT)
node.getComputedBorder(EDGE_ALL)Dirty Tracking
node.isDirty() // Has this node been modified since last layout?
node.markDirty() // Force recalculationDisposal
node.free() // Free this node and clean up referencesConstants
All constants are Yoga-compatible. Import from flexily:
| Category | Constants |
|---|---|
| Direction | DIRECTION_LTR, DIRECTION_RTL, DIRECTION_INHERIT |
| Flex Direction | FLEX_DIRECTION_ROW, FLEX_DIRECTION_COLUMN, FLEX_DIRECTION_ROW_REVERSE, FLEX_DIRECTION_COLUMN_REVERSE |
| Justify | JUSTIFY_FLEX_START, JUSTIFY_CENTER, JUSTIFY_FLEX_END, JUSTIFY_SPACE_BETWEEN, JUSTIFY_SPACE_AROUND, JUSTIFY_SPACE_EVENLY |
| Align | ALIGN_FLEX_START, ALIGN_CENTER, ALIGN_FLEX_END, ALIGN_STRETCH, ALIGN_BASELINE, ALIGN_AUTO, ALIGN_SPACE_BETWEEN, ALIGN_SPACE_AROUND, ALIGN_SPACE_EVENLY |
| Wrap | WRAP_NO_WRAP, WRAP_WRAP, WRAP_WRAP_REVERSE |
| Position | POSITION_TYPE_STATIC, POSITION_TYPE_RELATIVE, POSITION_TYPE_ABSOLUTE |
| Edge | EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_START, EDGE_END, EDGE_HORIZONTAL, EDGE_VERTICAL, EDGE_ALL |
| Gap | GUTTER_COLUMN, GUTTER_ROW, GUTTER_ALL |
| Measure Mode | MEASURE_MODE_EXACTLY, MEASURE_MODE_AT_MOST, MEASURE_MODE_UNDEFINED |
| Overflow | OVERFLOW_VISIBLE, OVERFLOW_HIDDEN, OVERFLOW_SCROLL |
| Display | DISPLAY_FLEX, DISPLAY_NONE |
Testing Utilities
Flexily exports diagnostic helpers for downstream consumers:
import {
getLayout,
formatLayout,
diffLayouts,
textMeasure,
assertLayoutSanity,
expectRelayoutMatchesFresh,
expectIdempotent,
expectResizeRoundTrip,
} from "flexily/testing"| Export | Description |
|---|---|
getLayout(node) | Recursively extract computed layout as plain objects |
formatLayout(layout) | Pretty-print a layout tree for debugging |
diffLayouts(a, b) | Node-by-node diff with NaN-safe comparison |
textMeasure(width) | Factory for wrapping text measure functions |
assertLayoutSanity(node) | Validate dimensions are finite and non-negative |
expectRelayoutMatchesFresh(buildTree, w, h) | Differential oracle: incremental must match fresh |
expectIdempotent(buildTree, w, h) | Two identical layouts must produce same result |
expectResizeRoundTrip(buildTree, widths) | Resize sequence must match fresh at final width |