Skip to content

Latest commit

 

History

History
413 lines (309 loc) · 14.4 KB

reference.md

File metadata and controls

413 lines (309 loc) · 14.4 KB

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) => FlexilyEngine

TextLayoutService

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 recalculation

Disposal

node.free() // Free this node and clean up references

Constants

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