ui

js/ui.ts

fino:ui — host-neutral component construction and rendering.

fino:ui is the portable core for JSX-style Fino interfaces. It owns VNode construction, function components, and keyed reconciliation into a host adapter. It re-exports the reactive primitives from fino:signals for compatibility, but the signal kernel itself is shared runtime infrastructure. This module does not know about the DOM, HTML, terminal cells, input devices, or styling rules. Renderers such as fino:tty/tui provide those host details.

Design

Components are plain functions that receive normalized props and a children array. State is held in explicit Signal objects rather than hook order. Hosts provide a small imperative adapter for creating nodes, moving children, updating props, and batching mutations.

/** @jsxImportSource fino:ui *\/
import { createSignal } from 'fino:ui';

const count = createSignal(0);

function Counter() {
  return <label>Count: {count.get()}</label>;
}

count.subscribe(() => {
  // Ask the selected host renderer to render Counter again.
});

Types

type Child = VNode | string | number | boolean | null | undefined | Child[]

Primitive child value accepted by h().

Numbers are stringified, nested arrays are flattened, and null, undefined, and boolean placeholders are ignored during normalization.

type Component<P = Record<string, unknown>> = (props: P & { children?: NormalizedChild[]; }) => VNode

Function component accepted by h().

Components receive normalized props plus an optional normalized children array. They return a concrete VNode; components do not keep hidden hook state.

type VNodeType = string | typeof Fragment | Component<any>

Host-neutral element or component type accepted by h().

Strings are host element names, Fragment groups children without a host node, and functions are invoked as components.

type NormalizedChild = VNode | string

Child value after normalization.

A normalized child is either a VNode or text. Empty placeholders and nested arrays have already been removed.

type Props = Record<string, unknown>

Props object stored on a VNode after key and children are removed.

Interfaces

interface VNode {

Host-neutral virtual node produced by h() and the JSX runtime.

type is either a host element name or a renderer-specific component output type. props never includes key or children; children is already flattened and does not contain null, undefined, or boolean placeholders.

Properties

type: string

Host element name, or 'fragment' for fragment VNodes.

props: Props

Host props with key and children removed.

children: NormalizedChild[]

Flattened child nodes and text.

key: string | number | null

Optional reconciliation key copied from the original props.

interface HostAdapter< Node, Root > {

Host adapter consumed by createRenderer().

Hosts own the concrete node representation. The renderer calls beginUpdate and endUpdate once around each render() call when those hooks are provided.

Contract

createNode() and createText() allocate host nodes. insertChild(), moveChild(), and removeChild() mutate child order under a parent or root. updateNode() replaces host props for an existing element, and setText() updates an existing text node.

Methods

createNode(type: string, props: Props): Node

Create a host element node for type and props.

createText(text: string): Node

Create a host text node.

updateNode(node: Node, props: Props): void

Replace or patch props on an existing host element node.

setText(node: Node, text: string): void

Update an existing host text node.

insertChild(parent: Node | Root, child: Node, index: number): void

Insert child under parent at index.

moveChild(parent: Node | Root, child: Node, index: number): void

Move an existing child under parent to index.

removeChild(parent: Node | Root, child: Node): void

Remove child from parent.

beginUpdate?(): void

Optional hook called before each render pass.

endUpdate?(): void

Optional hook called after each render pass, including failed passes.

Constants

const Fragment

Fragment marker used by JSX to group children without adding a host node.

h(Fragment, null, ...) returns the normalized children to its parent rather than creating a renderer-visible node.

Functions

function h(type: VNodeType, props: Props | null, ...children: Child[]): VNode

Construct a host-neutral VNode or invoke a function component.

key is copied out of props and stored on the VNode for reconciliation. children from props and variadic children are merged, flattened, and stripped of empty placeholders. Function components receive the normalized children as props.children.

import { h } from 'fino:ui';

const button = h('button', { key: 'save', disabled: true }, 'Save');

function createRenderer< Node, Root >(host: HostAdapter<Node, Root>)

Create a renderer for a concrete host adapter.

The renderer keeps one mounted tree per root object. Re-rendering the same root reconciles by element type and child keys, producing host updates inside one update batch.

Children without explicit keys are matched by position. Text nodes are keyed by their index, while VNodes prefer their key and fall back to index.

import { createRenderer, h } from 'fino:ui';

const renderer = createRenderer(host);
renderer.render(h('label', null, 'Ready'), root);