Skip to content

Releases: bikeshaving/termdom

TermDOM 0.1.5

Choose a tag to compare

@brainkim brainkim released this 22 Aug 21:08

0.1.5

Added

  • CSS Grid: track sizing, placement and alignment in the layout engine; grid shorthand parsing and serialization; used track sizes resolved through the CSSOM.
  • Element scrolling: overflow containers clip, scrollTop/scrollLeft clamp against content extents and repaint, wheel events chain through nested scrollers, and scrollWidth/scrollHeight read off the layout tree.
  • The clipboard: ClipboardEvent and DataTransfer, a paste event with clipboard items and permissions, transfers emptied when their dispatch ends, and the clipboard gated on user activation.
  • Selection.modify(), with character, word and line granularities.
  • Sequential focus navigation follows HTML's scope model. The document, each shadow root and each slot form their own focus scope; a scope sorts by tabindex within itself and sits at its owner's position in the parent scope. delegatesFocus, slot fallback content, and negative-tabindex scopes (unreachable by Tab, navigable once focus is scripted inside) behave as specified. Tab past the last focusable rests on nothing, as past a browser's page.
  • user-select: none — the mouse cannot anchor or extend a selection into it, Selection.modify walks past it, and the painter lays no highlight over it. user-select: text re-enables selection inside a none ancestor.
  • aspect-ratio, with the ratio counted in cells.
  • The CSS system colors, mapped onto the terminal palette.
  • <details> gets a user-agent shadow tree: a closed details hides its body, bare text children included.
  • Number inputs edit like numbers: explicit grammar, input filtering, arrow stepping, and the valueAsNumber family.
  • Node.moveBefore — the atomic move primitive, with its validations, live-range and iterator bookkeeping, slot reassignment, and connectedMoveCallback (falling back to the disconnected/connected pair).
  • New event interfaces: MessageEvent, HashChangeEvent, StorageEvent, DragEvent, TextEvent, and a createEvent table covering the spec's list. The sensor and touch names throw, because they name hardware a terminal does not have.
  • Geometry and hit-testing surface: document.elementsFromPoint, checkVisibility, Range.createContextualFragment, Document.getElementsByName, and MouseEvent's page, offset and movement coordinate spaces.
  • IME composition: the terminal passes through the keys a composition is built from, shows the syllable while it is being composed, and delivers it whole.

Changed

  • <kbd> renders bold and underlined, the way TUIs have marked accelerators, replacing the bracketed rendering.
  • :focus, :focus-within and :host(:focus) are shadow-aware. The focused element and the shadow hosts above it match :focus; :focus-within climbs the ancestor-and-host chain; a focus change restyles that chain.
  • The width tables are generated from the Unicode Character Database.
  • event.isTrusted is honest: true for events the engine dispatched from real input, false for an author's.
  • initial in author styles resolves to the spec's initial value rather than the element default.
  • Documents without an engine behave per spec. A document from createHTMLDocument or DOMParser answers with zero geometry, state-only focus and rejecting fullscreen instead of throwing.

Fixed

  • A widget-heavy initial render dropped from 43 seconds to 374 milliseconds: a form widget's shadow <style> was rebuilding the document's whole cascade several times over. A shadow sheet now refreshes its own root, and initial render is linear in document size again.
  • focus() works inside shadow trees, document.activeElement retargets to the host chain, and ShadowRoot.activeElement answers with its own descendant.
  • Focus in fullscreen: size reveals by the screen, and blur runs before Escape exits.
  • requestAnimationFrame chains tick: a callback scheduling the next frame runs on that next frame, and awaiting a frame still means the frame that includes your pending mutations has landed.
  • A pseudo-element's used box metrics resolve from its own layout box.
  • grid-template-areas serializes as its cells, a single space apart.
  • An app that closes right after a frame no longer leaks cursor-position reports into the shell that inherits the terminal.
  • A document closing while fullscreen leaves no trace, the way an alt-screen program does: the screen restores to what stood before entry, with the shell continuing below it. Exiting fullscreen before closing still pays the final flow state out to scrollback.

Conformance

The WPT DOM suites run against the engine itself: each test file mounts as the initial document of a real TermDOM over a mock transport, with a testdriver backend that turns WebDriver key and click actions into the bytes a terminal sends. 95,064 subtests pass and 1,389 fail, with each excluded file naming its reason — see docs/dom-conformance.md and docs/cssom-conformance.md.


Release notes drafted by Claude (Fable 5).

TermDOM 0.1.4

Choose a tag to compare

@brainkim brainkim released this 20 Aug 16:15

Rendering and layout:

  • Fire the resize event on window.
  • Fix borders not joining for some parent/child layouts.
  • Add rounded corners when border-radius is set.
  • Remove whitespace-only text between flex items regardless of what the neighboring items display as (css-flexbox-1 §4).
  • Stop collapsing spaces under white-space: break-spaces.

DOM and CSSOM:

  • Fix serialization, the dir attribute, and event dispatch, including retargeting across shadow trees.
  • Write user edits to a field's value slot instead of calling its value setter (this broke React)
  • Track live ranges and node iterators per tree and release them with it.
  • Define indexed access on collections and computed styles as accessor properties.
  • Invalidate styles on every attribute writer (classList, className, toggleAttribute, the parser) and register declaratively parsed shadow roots with the cascade.

Width measurement:

  • Regenerate the character width tables from the Unicode Character Database at 17.0.0. Trigrams, hexagrams, counting rods, and recent emoji now measure two cells.
  • Measure clusters that only appear against the right margin, where a reply is ambiguous, at the left edge of a row the frame repaints anyway.

Examples:

  • Add hello-world examples for React, Vue, Svelte, and Crank, each running under Node with no build step.

Internal:

  • Adopt @b9g/eslint-config across the codebase; remove WeakRef, most proxies, and the compile-cache call from the entry module.

TermDOM 0.1.3

Choose a tag to compare

@brainkim brainkim released this 14 Aug 18:40

Two patches.

  • The mode probes sent at attach (ESC[8$p, ESC[?2027$p) left a stray p visible on terminals without DECRQM, Terminal.app included. An erase after the probes cleans up whatever the terminal echoed. (#41)
  • @layer and @scope rules now participate in the cascade — previously they parsed into the CSSOM and styled nothing. Full css-cascade-5 layer ordering (including the !important reversal), @scope with proximity, selector specificity computed from the selector AST per selectors-4 (:is()/:not() take their heaviest argument, :where() weighs zero), and the flex-flow/place-content/place-items/place-self shorthands expand. word-break and overflow-wrap were always implemented; the compatibility probes now show it. 170 CSS properties supported. (#42)

Full suite: node 1247, bun 1272.

TermDOM 0.1.2

Choose a tag to compare

@brainkim brainkim released this 14 Aug 05:28

63 commits since 0.1.1.

New

  • dialog.showModal(): the top layer, ::backdrop, :modal, focus containment, and Escape firing cancel. Modal dialogs paint over the page and block interaction behind them. (#32)
  • The Popover API: popover attribute states, showPopover()/hidePopover()/togglePopover(), popovertarget invokers, beforetoggle/toggle events, light dismiss, stacking, and :popover-open. (#38)
  • border-radius: the shorthand and all four longhands, including slash syntax. A corner with a nonzero radius renders with the rounded box-drawing glyph. (#31)
  • CSS logical properties: the margin/padding/inset/border block and inline families, block-size/inline-size with min/max variants, and text-align: start/end, resolved against direction by cascade order. (#33)
  • beforeunload: window.close() and Ctrl-C dispatch a cancelable BeforeUnloadEvent, so an app can ask before quitting. (#29)
  • <kbd> renders as a bold bracketed keycap. (#28)
  • Terminal width measurement: where a terminal doesn't negotiate mode 2027, the engine measures ambiguous glyph advances (VS16 emoji, East Asian Ambiguous, Arabic presentation forms) with in-frame cursor queries and corrects itself, fixing emoji misalignment in Terminal.app. (#37)
  • window.scrollX and pageXOffset read 0.

Fixed

  • Preserved spaces no longer collapse at inline element boundaries under white-space: pre. (#36)
  • Attribute values containing selector metacharacters parse correctly — thanks @yowainwright for the first outside contribution. (#39)
  • text-align: end maps to the left edge under direction: rtl.
  • Absolutely positioned boxes with both insets and margin: auto center per CSS 2.1 instead of stretching.
  • Enter activates checkboxes, as Space does.
  • The engine no longer uses Buffer; browser bundles need no polyfill, and ProcessLike accepts Uint8Array/ArrayBuffer chunks.

Examples

  • weather.ts: an Open-Meteo forecast with hourly charts drawn in eighth-blocks.
  • popover.ts: a menu bar where every menu is a declarative popover.

Docs

  • The README explains how the engine works; COMPATIBILITY.md classifies every CSS property as measured, not applicable to a character grid, or not yet implemented; the conformance docs are regenerated (94,816 DOM subtests passing, 3,124 CSSOM).

TermDOM 0.1.1

Choose a tag to compare

@brainkim brainkim released this 11 Aug 18:00

TermDOM now ships its own DOM and CSSOM

jsdom and cssstyle are gone. The DOM tree, mutation algorithms, events with shadow retargeting, live collections, ranges and selection, custom elements, and the full CSSOM — stylesheets, rules, declarations, computed and resolved values — are TermDOM's own, written against the specs with web-platform-tests as the referee (94,800+ DOM subtests passing at the swap, more fixed since). Parsing is parse5 and css-tree; selectors are nwsapi. Author-visible APIs follow one rule: spec-complete or absent. getComputedStyle resolved values now read real layout, styleEl.sheet returns the element's actual stylesheet, and ResizeObserver honors options.box.

Rendering changes to know about

  • Margin collapsing is now fully per CSS 2.2 §8.3.1 — including self-collapsing empty blocks and negative margins, which previously didn't parse. If a layout leaned on the old behavior, vertical gaps can shift. One knowing deviation: the body's edges stop the collapse.
  • Block containers are laid out by a real block algorithm instead of a flex emulation: gap and order no longer misapply to blocks, a wrapping flex container keeps its later lines inside its own box, and inline-flex establishes a formatting context.

Fixes

Around thirty rendering bugs found by a new differential fuzzer and property suite, among them: hidden (display: none) subtrees kept reporting geometry; content vanishing or misordering around blocks inside inlines and display: contents elements; nested inline-block content never laid out after edits; whitespace-only blocks under white-space: pre collapsing to nothing; hit-testing missing ::before content and dissolved boxes; a stray <svg> crashing style resolution; counter(x, lower-alpha) wrong past 26; vw/vh values stale across terminal resizes; multi-line pastes into a <textarea> arriving as one line (terminals send pasted newlines as CR); mouse coordinates disagreeing with painted cells in fixed and scrolled content.

Performance

Invalidation is targeted instead of global: a class flip on one row of a long list re-renders that row, not the document. Live-collection updates during bulk insertion went from quadratic to linear (a 5,000-item filter list: 1,243ms → 162ms per keystroke), and a heap leak that pinned removed nodes for the life of the process is fixed.

Examples and docs

examples/solitaire.ts is new and leads the README: full-keyboard Klondike (cursor, Tab, Enter), one- and three-card draw from a <dialog> menu, responsive card sizes via @media, fullscreen, a speedrun clock with per-deal bests — deals are seeded, and node examples/solitaire.ts 13 replays the demo GIF's game. The getting-started guide gains verified framework recipes: React 19, Vue 3.5, Svelte 5, and Crank each mount with a few lines of globals.

For contributors

npm test now carries a differential fuzzer regression net (incremental rendering must equal fresh rendering, byte for byte, over 31 historical seeds) and six fast-check properties covering rendering, computed styles, geometry/hit-testing agreement, resize round-trips, serialization fixpoints, and field editing against a reference model.

TermDOM 0.1.0

Choose a tag to compare

@brainkim brainkim released this 08 Aug 05:36

The first release.

TermDOM is a JavaScript library that displays HTML and CSS in the terminal. It draws actual DOM nodes to terminal output and redraws the screen when they mutate, so TUIs and interactive CLIs can be written with vanilla JavaScript or any frontend web framework.

npm install @b9g/termdom
import {TermDOM} from "@b9g/termdom";

const term = new TermDOM();
term.attach();
term.document.body.innerHTML = `<div style="color: green; border: 1px solid">hello</div>`;

What's in it:

  • A real DOM document with a CSS cascade and a cell-based layout engine: box model, flexbox, tables, and inline layout. One cell is 1ch wide and 1px tall.
  • Stylesheets from <style> elements and style attributes, translated to ANSI color and text decoration.
  • Keyboard, mouse, focus, and paste events fired on elements, the document, and the window.
  • <input>, <textarea>, <select>, checkboxes, and radios as shadow-tree widgets with terminal-native looks, restylable with CSS. The caret is the real terminal cursor, so IMEs compose in the field.
  • CJK, emoji, and combining characters at their correct widths; Hebrew and Arabic in visual order with contextual shaping.
  • Scrolling with window.scrollTo() and element.scrollIntoView(); the alternate screen via element.requestFullscreen().
  • Web Components: customElements.define(), attachShadow(), <slot>, :host.
  • Drag selection styled with ::selection; navigator.clipboard over OSC 52.

Documentation: https://termdom.org
Guides: https://termdom.org/guides/getting-started/
Compatibility matrix: https://termdom.org/compatibility/
Examples: https://github.com/bikeshaving/termdom/tree/main/examples