Skip to content

Releases

LayoutSans now has real text interaction: selection, copy, search & links on canvas (pure TS, no DOM layout)

Choose a tag to compare

@BaselAshraf81 BaselAshraf81 released this 04 Apr 04:15

v0.2 — Interactive Text Engine

This release introduces a robust, canvas-based interaction layer, transforming the engine from a static layout tool into a fully interactive text environment.

Core Interaction Features

  • Canvas-Based Selection: Support for native-feeling text selection including click-drag, double-click for word selection, sub-glyph precision, and shift-click to extend ranges.
  • Programmatic Control: Full API access to manage selection states and cursors.
  • System Integration: Native OS clipboard integration supporting standard shortcuts (e.g., Ctrl+C).
  • Search & Navigation: Integrated Ctrl+F full-text search featuring highlighted matches and jump-to-navigation.

Accessibility & Navigation

  • Shadow Semantic Tree: A virtualized accessibility layer using $O(\text{viewport})$ DOM nodes to ensure compatibility with screen readers like VoiceOver, NVDA, and JAWS.
  • Focus Management: Full keyboard focus support coupled with custom canvas-drawn focus rings.
  • Interactive Elements: Support for clickable hyperlinks including reactive hover states.

Performance & Architecture

  • Spatial Hit-Testing: Utilizes a packed R-Tree for spatial queries, maintaining high performance even with 100k+ interactive items.
  • Layout Core: Built upon the high-performance pure-math layout engine powered by Pretext for sub-pixel accurate text measurement.

Live Demo

Experience 100k variable-height items with full text interactivity:
https://baselashraf81.github.io/layout-sans/demo/interactive-text.html

v0.1.2 — Bug fixes and performance improvements

Choose a tag to compare

@BaselAshraf81 BaselAshraf81 released this 30 Mar 19:19

🐛 Bug Fixes

  • Fixed absolute container positioning — Absolute containers now correctly report their resolved x/y position instead of inheriting from the first child
  • Fixed crash at 65k+ nodes — Replaced spread operator patterns that hit V8's call stack limit, enabling layouts with 100k+ nodes
  • Fixed flex-shrink calculation — Now uses flexBasis instead of mainSize per CSS Flexbox spec
  • Fixed magazine column overflow — Content blocks can now flow across unlimited columns instead of being truncated after 2

⚡ Performance

  • Optimized measureNode — Added lightweight measureFlexSize() and measureGridSize() paths that avoid allocating full layout records, eliminating O(n²) work for nested flex containers
  • Added flex row fast-path — Horizontal fixed-size lists now use an O(n) fast path, matching the existing column optimization

📊 Benchmarks

Scenario LayoutSans vs DOM vs Yoga WASM
100 flex boxes 0.25ms 32× faster 3× faster
10,000 flex boxes 8.55ms 94× faster 1× (comparable)
100,000 var-height items 50.01ms ∞× (DOM crashes) 2× faster

What's Changed
Full changelog: https://github.com/BaselAshraf81/layout-sans/blob/main/CHANGELOG.md

Install

npm install layout-sans@0.1.2

v0.1.0 – Pretext-powered 2D Layout Engine (Pure TS, No DOM, No WASM)

Choose a tag to compare

@BaselAshraf81 BaselAshraf81 released this 30 Mar 02:37

🎉 LayoutSans v0.1.0 is here!

The missing layer after Pretext.

Pretext gives you perfect text measurement without DOM reflows.
LayoutSans takes those measurements and gives you full 2D layout: Flex, basic Grid, Magazine-style multi-column, Absolute positioning — all in pure TypeScript.

  • Zero DOM — never touches getBoundingClientRect or the browser layout engine
  • Zero WASM bloat — ~3.7 kB gzipped (core)
  • Universal — runs in Browser, Node, Bun, Deno, Cloudflare Workers, Edge
  • Pretext integration — accurate text measurement via peer dependency
  • Virtualization-ready — flat output perfect for 100k+ variable-height items at 120 fps

Highlights

  • Full Flexbox support (row/column, gap, justify, align, wrap, flex-grow/shrink)
  • Basic Grid & Magazine multi-column text flow
  • Absolute positioning
  • Text nodes powered by @chenglou/pretext
  • Clean 5-line API with .compute() returning positioned boxes
  • Benchmarks included (10k–100k boxes)

5-line demo

import { createLayout } from 'layout-sans'

const engine = createLayout({
  type: 'flex',
  direction: 'row',
  width: 800,
  gap: 16,
  children: [
    { type: 'box', flex: 1 },
    { type: 'box', width: 240 }
  ]
})

const boxes = engine.compute()