Lynx/Modules/Rich Text/API reference
@sigx/lynx-richtext · Stable · Component library

API reference#

Exports of @sigx/lynx-richtext v0.20.0 — one component, one command object, five helper functions, and the typed document/event model.

The public surface is the RichTextInput component plus the RichTextMethods command object, a set of pure document helpers, and the supporting types:

TypeScript
import {
  RichTextInput,
  RichTextMethods,
  encodeDoc,
  decodeDoc,
  docEquals,
  normalizeDoc,
  emptyDoc,
} from '@sigx/lynx-richtext';
import type {
  RichTextInputProps,
  RichTextHandle,
  RichDoc,
  InlineSpan,
  InlineSpanType,
  BlockAttr,
  BlockAttrType,
  SelectionState,
  RichTextChangeEvent,
  RichTextSelectionEvent,
  RichTextHeightChangeEvent,
  RichTextFocusEvent,
  SigxRichTextAttributes,
} from '@sigx/lynx-richtext';

The runtime exports (RichTextInput, RichTextMethods) back the sigx-richtext element, which runs on iOS, Android and web (native elements on device, a contenteditable implementation on web in v0.19.0+). The helper functions and the types are platform-shared. Importing the package entry also registers the sigx-richtext tag in the JSX namespace.

Components#

Each component has its own page with usage, props, slots and events — see the Components catalog:

  • RichTextInput — the typed SignalX wrapper over the native sigx-richtext element, a generic attributed-text input that decodes native events into typed shapes and delivers the element handle for commands.

Their prop types and related APIs remain documented below.

RichTextInputProps#

The prop type for RichTextInput. Every prop is optional.

TypeScript
type RichTextInputProps =
  & Define.Prop<'value', RichDoc | string, false>
  & Define.Prop<'placeholder', string, false>
  & Define.Prop<'editable', boolean, false>
  & Define.Prop<'minHeight', number, false>
  & Define.Prop<'maxHeight', number, false>
  & Define.Prop<'fontSize', number, false>
  & Define.Prop<'textColor', string, false>
  & Define.Prop<'accentColor', string, false>
  & Define.Prop<'placeholderColor', string, false>
  & Define.Prop<'confirmType', 'send' | 'search' | 'next' | 'go' | 'done', false>
  & Define.Prop<'autoFocus', boolean, false>
  & Define.Prop<'class', string, false>
  & Define.Prop<'style', string | Record<string, string | number>, false>
  & Define.Prop<'onElement', (el: RichTextHandle) => void, false>
  & Define.Prop<'onChange', (doc: RichDoc, isComposing: boolean) => void, false>
  & Define.Prop<'onSelection', (sel: SelectionState) => void, false>
  & Define.Prop<'onHeightChange', (height: number, lines: number) => void, false>
  & Define.Prop<'onFocus', () => void, false>
  & Define.Prop<'onBlur', () => void, false>;
  • value — a RichDoc or a JSON string. Initial-only: once the user has edited (or after any applied setDocument), value is ignored — use RichTextMethods.setDocument for later programmatic replacement. A RichDoc is JSON-stringified for you.
  • placeholder — placeholder text shown when the field is empty.
  • editable — whether the field accepts edits. Defaults to true. Pass an explicit boolean; editable={undefined} historically coerced to false on Android.
  • minHeight / maxHeight — px bounds for auto-grow. maxHeight clamps the frame and enables internal scrolling past the ceiling.
  • fontSize — base font size in px; headings scale from it.
  • textColor / accentColor / placeholderColor — hex color strings (#RGB, #RRGGBB, or #RRGGBBAA; leading # optional). accentColor is the caret tint and the link color.
  • confirmType — the keyboard return-key style: 'send', 'search', 'next', 'go', or 'done'.
  • autoFocus — raises the keyboard on mount.
  • class / style — standard styling props.
  • onElement — receives the RichTextHandle for command calls.
  • onChange — receives a decoded RichDoc plus isComposing. Do not echo writes while isComposing is true.
  • onSelection — receives a SelectionState; drives toolbar active state and popup anchoring.
  • onHeightChange — receives (height, lines) for auto-grow.
  • onFocus / onBlur — fire on focus / blur.

Platform notes: shared type. The runtime behavior backing it runs on iOS, Android and web.

Command object#

RichTextMethods#

The background-thread, fire-and-forget command surface for the field. Each method rides the INVOKE_UI_METHOD op (background → main) and returns void — state reconciles through the field's bindchange / bindselection events, which are the editor's single source of truth. A null/undefined handle makes any command a silent no-op.

TypeScript
const RichTextMethods: {
  setDocument(el: RichTextHandle, doc: RichDoc): void;
  toggleFormat(el: RichTextHandle, type: InlineSpanType): void;
  setBlockType(el: RichTextHandle, type: BlockAttrType, level?: number, checked?: boolean): void;
  applyFormat(el: RichTextHandle, type: 'link', start: number, end: number, attrs?: { href?: string }): void;
  insertText(el: RichTextHandle, text: string): void;
  setSelectionRange(el: RichTextHandle, start: number, end: number): void;
  insertChip(el: RichTextHandle, chip: { id: string; label: string; kind?: string }, replace?: { from: number; to: number }): void;
  focus(el: RichTextHandle): void;
  blur(el: RichTextHandle): void;
};

The first argument of every method is a RichTextHandle. All methods are iOS + Android native UI methods. See each method below.

RichTextMethods.setDocument#

Replace the entire document.

TypeScript
setDocument(el: RichTextHandle, doc: RichDoc): void
  • doc — the new RichDoc. Its v (version) is carried for stale-write protection.
  • Drops a write whose v is older than the field's current version; no-ops on structurally-identical content; drops writes during IME composition; locks out the initial value prop. Returns nothing — the new state re-emits through onChange.

RichTextMethods.toggleFormat#

Toggle an inline format over the current selection.

TypeScript
toggleFormat(el: RichTextHandle, type: InlineSpanType): void
  • type — an InlineSpanType. A collapsed selection flips the typing attributes instead.
  • Note: 'code' is terminal — it excludes bold/italic/strike, which cannot be enabled inside a code run. Links and mentions are not toggled here (use applyFormat / insertChip).

RichTextMethods.setBlockType#

Set the block type of the paragraph(s) covered by the selection.

TypeScript
setBlockType(el: RichTextHandle, type: BlockAttrType, level?: number, checked?: boolean): void
  • type — a BlockAttrType.
  • level — optional heading level (1–6) when type is 'heading'.
  • checked — optional initial checkbox state when type is 'task'.

RichTextMethods.applyFormat#

Apply a payload-carrying inline format over an explicit character range. Only 'link' is supported.

TypeScript
applyFormat(el: RichTextHandle, type: 'link', start: number, end: number, attrs?: { href?: string }): void
  • type — must be 'link'.
  • start / end — the range in UTF-16 code units (inclusive start, exclusive end).
  • attrs.href — a non-empty href links the range; an empty or missing href unlinks it. The command is refused inside a codeBlock (it would not round-trip).

RichTextMethods.insertText#

Insert text at the caret.

TypeScript
insertText(el: RichTextHandle, text: string): void
  • text — the string to insert. It inherits the current typing attributes.

RichTextMethods.setSelectionRange#

Move or extend the caret/selection.

TypeScript
setSelectionRange(el: RichTextHandle, start: number, end: number): void
  • start / end — the new selection bounds in UTF-16 code units. A collapsed range (start === end) places the caret.

RichTextMethods.insertChip#

Insert an atomic mention chip — one U+FFFC code unit carrying the mention attributes.

TypeScript
insertChip(el: RichTextHandle, chip: { id: string; label: string; kind?: string }, replace?: { from: number; to: number }): void
  • chip.id / chip.label — both required and non-empty; otherwise the command is refused. The visible label lives in the attributes, not in the covered text.
  • chip.kind — optional classifier.
  • replace — when both from and to are given (UTF-16 code units), the range [from, to) is removed first; otherwise the chip replaces the live selection. The chip never inherits or leaks typing attributes.

RichTextMethods.focus#

Focus the field (shows the soft keyboard).

TypeScript
focus(el: RichTextHandle): void

RichTextMethods.blur#

Blur the field (dismisses the keyboard).

TypeScript
blur(el: RichTextHandle): void

Functions#

These are pure, platform-shared helpers for working with the document model and the JSON wire form.

encodeDoc#

Serialize a RichDoc to the single JSON wire schema.

TypeScript
function encodeDoc(doc: RichDoc): string
  • doc — the document to serialize.
  • Returns — the JSON string (JSON.stringify). This is the one encode point used by the value prop and the setDocument command, opposite decodeDoc.

Platform: shared.

decodeDoc#

Parse a JSON RichDoc defensively.

TypeScript
function decodeDoc(raw: string | null | undefined): RichDoc
  • raw — a JSON string (or null/undefined).
  • Returns — a sanitized RichDoc. Malformed or empty input degrades to emptyDoc() instead of throwing (native events must never crash the background thread). It clamps span/block offsets to [0, text.length], drops unknown types and empty/inverted ranges, clamps heading level to 1–6, drops an ordered start of 1, keeps checked only for task and lang only when non-empty, and coerces v to a finite number (else 0). Used internally by RichTextInput's onChange.

Platform: shared.

docEquals#

Structural equality ignoring the version field v — the echo-suppression comparison.

TypeScript
function docEquals(a: RichDoc, b: RichDoc): boolean
  • a / b — the documents to compare.
  • Returnstrue when text, span/block counts, and each span/block's fields match (deep attrs equality for spans; level/checked/lang for blocks). The comparison ignores v.
  • Note: order-sensitive — run normalizeDoc on both inputs first if producer ordering may differ.

Platform: shared.

normalizeDoc#

Return a copy with spans and blocks sorted into a canonical order.

TypeScript
function normalizeDoc(doc: RichDoc): RichDoc
  • doc — the document to normalize.
  • Returns — a copy with spans sorted by (start, end, type) and blocks sorted by start, so structurally-identical content compares equal under docEquals regardless of producer ordering. text and v are preserved.

Platform: shared.

emptyDoc#

Construct an empty document.

TypeScript
function emptyDoc(v?: number): RichDoc
  • v — optional starting version (default 0).
  • Returns{ text: '', spans: [], blocks: [], v }. This is the fallback that decodeDoc returns on bad input.

Platform: shared.

Types#

RichTextHandle#

The minimal structural handle to the native element — the background ShadowElement delivered by a callback ref (or via RichTextInput's onElement prop).

TypeScript
type RichTextHandle = { id: number } | null | undefined;

It is the first argument to every RichTextMethods command; a null/undefined handle makes a command a no-op. Platform: shared.

RichDoc#

The flat, span-based document model that crosses the JS ↔ native bridge.

TypeScript
interface RichDoc {
  text: string;
  spans: InlineSpan[];
  blocks: BlockAttr[];
  /** Monotonic document version (see module docs). */
  v: number;
}
  • text — the flat text; \n separates paragraphs.
  • spans — inline character-range formats (InlineSpan).
  • blocks — paragraph-range attributes (BlockAttr).
  • v — a monotonic version bumped on every native user edit (used for echo/stale-write ordering).

Platform notes: maps 1:1 onto NSAttributedString (iOS) / Spannable (Android), with UTF-16 code-unit offsets everywhere — surrogate pairs count as 2. Native never parses markdown; it reads the model back out of its own text storage after each edit.

InlineSpan#

An inline character-range format.

TypeScript
interface InlineSpan {
  /** Inclusive start, UTF-16 code units. */
  start: number;
  /** Exclusive end, UTF-16 code units. */
  end: number;
  type: InlineSpanType;
  /** Type-specific payload (`href` for links, `id`/`label` for mentions). */
  attrs?: Record<string, string>;
}
  • start / end — the range (start inclusive, end exclusive) in UTF-16 code units.
  • type — an InlineSpanType.
  • attrs — type-specific payload: href for link; id / label (+ optional kind) for mention. A mention span covers exactly one U+FFFC code unit; its visible label lives only in attrs.label. Platform: shared.

InlineSpanType#

The fixed v1 vocabulary of inline formats.

TypeScript
type InlineSpanType = 'bold' | 'italic' | 'strike' | 'code' | 'link' | 'mention';
  • bold / italic / strike / code are toggleable via toggleFormat.
  • link is applied via applyFormat with attrs.href.
  • mention is an atomic chip inserted via insertChip.
  • 'code' is terminal — it excludes bold/italic/strike. Platform: shared.

BlockAttr#

A paragraph-level block attribute over a line-aligned range.

TypeScript
interface BlockAttr {
  /** Inclusive start of the paragraph's char range (line boundary). */
  start: number;
  /** Exclusive end (line boundary / end of text). */
  end: number;
  type: BlockAttrType;
  /** Heading level 1–6, or an ordered run's start number (omitted when 1). */
  level?: number;
  /** Task checkbox state (task only). */
  checked?: boolean;
  /** Code fence info string (codeBlock only) — carried opaquely by native. */
  lang?: string;
}
  • start / end — the line-aligned range (UTF-16 code units).
  • type — a BlockAttrType.
  • level — heading level (1–6) or an ordered run's start number (omitted when 1; ordered numbering itself is derived from position at draw time).
  • checked — task checkbox state (task only).
  • lang — code-fence info string (codeBlock only).

Platform notes: markers (bullet/ordered/task) are draw-only and never present in text. Producers normalize ranges to line boundaries; native re-snaps defensively. Platform: shared.

BlockAttrType#

Paragraph-level block types.

TypeScript
type BlockAttrType =
  | 'paragraph'
  | 'heading'
  | 'bullet'
  | 'ordered'
  | 'task'
  | 'blockquote'
  | 'codeBlock'
  | 'raw';

All render in-field natively (headings; bullet/ordered/task lists; blockquote with a leading bar + inset; codeBlock with mono text + full-width background). raw is a consumer escape hatch — rendered as a plain paragraph with the attribute round-tripped untouched. Platform: shared.

SelectionState#

The parsed form of the selection event, delivered to RichTextInput's onSelection.

TypeScript
interface SelectionState {
  start: number;
  end: number;
  activeFormats: InlineSpanType[];
  activeBlock: BlockAttrType;
  headingLevel?: number;
  caretRect: { x: number; y: number; height: number };
}
  • start / end — the selection bounds in UTF-16 code units.
  • activeFormats — inline formats covering the selection, or the typing attributes when collapsed.
  • activeBlock — the caret paragraph's block type (defaults to 'paragraph').
  • headingLevel — present only when activeBlock === 'heading'.
  • caretRect — the caret rectangle in the element's own coordinate space, for popup anchoring (e.g. mention/link popups). Platform: shared.

Event types#

These are the raw native payloads on the sigx-richtext tag. RichTextInput decodes them for you into its typed callbacks — you only need these when wiring the raw element directly.

RichTextChangeEvent#

Raw bindchange payload — fired after every user edit and after applied programmatic mutations.

TypeScript
interface RichTextChangeEvent {
  type: 'change';
  detail: {
    /** JSON-encoded RichDoc (decode with decodeDoc). */
    doc: string;
    /** True while an IME composition session is active — do NOT echo writes back. */
    isComposing: boolean;
  };
}
  • detail.doc — a JSON-encoded RichDoc; decode with decodeDoc.
  • detail.isComposingtrue during an active IME composition; callers must not echo writes mid-composition. RichTextInput decodes this into onChange(doc, isComposing). Platform: shared (event type).

RichTextSelectionEvent#

Raw bindselection payload — fired when the caret/selection moves.

TypeScript
interface RichTextSelectionEvent {
  type: 'selection';
  detail: {
    start: number;
    end: number;
    /** Comma-separated inline formats, e.g. "bold,italic". */
    activeFormats: string;
    /** Block type of the caret's paragraph. */
    activeBlock: string;
    /** Heading level when activeBlock === 'heading'. */
    headingLevel?: number;
    /** Caret rectangle in the element's own coordinate space. */
    caretX: number;
    caretY: number;
    caretHeight: number;
  };
}

activeFormats is a comma-separated string; RichTextInput parses it and reshapes caretX/caretY/caretHeight into caretRect to produce a SelectionState for onSelection. Platform: shared (event type).

RichTextHeightChangeEvent#

Raw bindheightchange payload — fired when the intrinsic content height changes (auto-grow).

TypeScript
interface RichTextHeightChangeEvent {
  type: 'heightchange';
  detail: {
    /** Content height in px (may exceed the clamped frame height). */
    height: number;
    /** Line count. */
    lines: number;
  };
}
  • detail.height — px (already clamped to min/max in native; emitted only when it moves by at least 0.5px).
  • detail.lines — line count. RichTextInput forwards this to onHeightChange(height, lines). Platform: shared (event type).

RichTextFocusEvent#

Raw bindfocus / bindblur payload (empty detail).

TypeScript
interface RichTextFocusEvent {
  type: 'focus' | 'blur';
  detail: Record<string, never>;
}

RichTextInput forwards these to onFocus() / onBlur(). Platform: shared (event type).

JSX element#

SigxRichTextAttributes#

The JSX intrinsic attribute/event surface for the raw native sigx-richtext tag (kebab-case props, bind* event handlers). Importing the package entry registers the sigx-richtext tag in the JSX namespace.

TypeScript
interface SigxRichTextAttributes extends LynxCommonAttributes {
  value?: string;
  placeholder?: string;
  editable?: boolean;
  'min-height'?: number;
  'max-height'?: number;
  'font-size'?: number;
  'text-color'?: string;
  'accent-color'?: string;
  'placeholder-color'?: string;
  'confirm-type'?: 'send' | 'search' | 'next' | 'go' | 'done';
  'auto-focus'?: boolean;
  bindchange?: LynxEventHandler<RichTextChangeEvent>;
  bindselection?: LynxEventHandler<RichTextSelectionEvent>;
  bindheightchange?: LynxEventHandler<RichTextHeightChangeEvent>;
  bindfocus?: LynxEventHandler<RichTextFocusEvent>;
  bindblur?: LynxEventHandler<RichTextFocusEvent>;
}
  • value — a JSON-encoded RichDoc (use encodeDoc). Initial-only once the user has edited — use setDocument afterward.
  • The remaining attributes mirror the component props in kebab-case.
  • The bind* handlers receive the raw event types above.

Platform notes: the sigx-richtext element is native on iOS/Android and a contenteditable field on web. Most consumers use the typed RichTextInput component instead of this raw tag. LynxCommonAttributes and LynxEventHandler come from @sigx/lynx-runtime.