Reactive overview
Reactive annotations for interactive terminal UIs.
Extends the annotation system with position-aware metadata, enabling hit-testing and cursor-to-element mapping. Useful for building interactive CLI applications where boxes respond to user input.
Common tasks
- Create reactive boxes:
make,reactive - Query positions:
getPositions,cursorToReactive - Check type:
isReactive
constructors
make
Creates a ReactiveId with the specified string identifier.
Constructs a Reactive object with the proper structure and tag. Use this when you need to create reactive identifiers for manual annotation or when building custom reactive systems.
Signature
export declare const make: (id: string) => ReactiveExample
import * as Reactive from "effect-boxes/Reactive"
import * as Annotation from "effect-boxes/Annotation"
const buttonReactive = Reactive.make("submit-button")
console.log(buttonReactive)
// { _tag: "ReactiveId", id: "submit-button" }
const menuReactive = Reactive.make("navigation-menu")
const formReactive = Reactive.make("contact-form")reactive
Creates a reactive annotation with the specified string identifier.
Combines reactive ID creation with annotation wrapping in a single step. This is the preferred method for creating reactive annotations as it handles both the Reactive object creation and annotation wrapping.
Signature
export declare const reactive: (id: string) => Annotation<Reactive>Example
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
const buttonAnnotation = Reactive.reactive("save-button")
const saveButton = Box.text("Save").pipe(Box.annotate(buttonAnnotation))
const menuAnnotation = Reactive.reactive("main-menu")
const menu = Box.vcat([Box.text("File"), Box.text("Edit"), Box.text("View")], Box.left).pipe(
Box.annotate(menuAnnotation)
)guards
isReactive
Type guard to check if a value is a Reactive annotation.
Determines whether a given value conforms to the Reactive interface. Essential for type-safe processing of annotations and filtering reactive elements from other annotation types.
Signature
export declare const isReactive: (value: unknown) => value is ReactiveExample
import * as Reactive from "effect-boxes/Reactive"
console.log(Reactive.isReactive({ id: "btn", kind: "reactive" }))
// false (not a valid Reactive instance)
const r = Reactive.reactive("button-1")
import * as Annotation from "effect-boxes/Annotation"
console.log(Reactive.isReactive(Annotation.getAnnotationData(r)))
// truemodels
PositionMap (type alias)
Map of reactive IDs to their positions in the rendered output.
Contains the calculated positions and dimensions of all reactive boxes after layout processing. Essential for implementing cursor navigation, click handling, and other interactive features in terminal applications.
Signature
export type PositionMap = HashMap.HashMap<
string, // Reactive ID
{
readonly row: number // 0-based row position
readonly col: number // 0-based column position
readonly rows: number // height of the box
readonly cols: number // width of the box
}
>Example
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
import { HashMap } from "effect"
const layout = Box.vcat(
[
Reactive.makeReactive(Box.text("Header"), "header"),
Reactive.makeReactive(Box.text("Content"), "content"),
Reactive.makeReactive(Box.text("Footer"), "footer")
],
Box.left
)
const positions: Reactive.PositionMap = Reactive.getPositions(layout)
// Contains entries like:
// "header" -> { row: 0, col: 0, rows: 1, cols: 6 }
// "content" -> { row: 1, col: 0, rows: 1, cols: 7 }
// "footer" -> { row: 2, col: 0, rows: 1, cols: 6 }Reactive (type alias)
Reactive identifier type for tracking box positions.
Represents a unique identifier that can be attached to boxes to enable position tracking in rendered output. Essential for building interactive terminal applications where you need to know where specific boxes are positioned after layout calculation.
Signature
export type Reactive = {
readonly _tag: "ReactiveId"
readonly id: string
}Example
import * as Reactive from "effect-boxes/Reactive"
const buttonId: Reactive.Reactive = {
_tag: "ReactiveId",
id: "submit-button"
}
// Used to track the position of a submit button in a formReactiveAnnotation (type alias)
Convenience type for reactive annotations.
Combines the Annotation wrapper with Reactive data, providing a complete type for reactive box annotations. Used throughout the reactive system for type-safe position tracking.
Signature
export type ReactiveAnnotation = Annotation<Reactive>Example
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
const menuAnnotation: Reactive.ReactiveAnnotation = Reactive.reactive("main-menu")
const reactiveMenu = Box.text("Main Menu").pipe(Box.annotate(menuAnnotation))navigation
cursorToReactive
Creates a cursor movement command to navigate to a reactive box position.
Generates an ANSI cursor movement command to position the cursor at a specific reactive element. Returns an Option that contains the movement command if the reactive ID exists, or None if not found. Essential for implementing keyboard navigation and cursor-based interactions.
Signature
export declare const cursorToReactive: {
(key: string): (positionMap: PositionMap) => Option.Option<Box<AnsiStyle>>
(positionMap: PositionMap, key: string): Option.Option<Box<AnsiStyle>>
}Example
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
import * as Cmd from "effect-boxes/Cmd"
import { pipe, Option } from "effect"
const layout = Box.vcat(
[
Reactive.makeReactive(Box.text("Menu Item 1"), "item-1"),
Reactive.makeReactive(Box.text("Menu Item 2"), "item-2"),
Reactive.makeReactive(Box.text("Menu Item 3"), "item-3")
],
Box.left
)
const positions = Reactive.getPositions(layout)
const moveToItem2 = Reactive.cursorToReactive(positions, "item-2")
pipe(
moveToItem2,
Option.match({
onNone: () => console.log("Item not found"),
onSome: (cmd) => console.log("Moving cursor to item 2")
})
)transformations
makeReactive
Annotates a box with a reactive identifier for position tracking.
Transforms any box into a reactive box by adding position tracking capabilities. Supports both data-first and data-last calling patterns for flexible composition. The resulting box can be tracked in the position map after rendering.
Signature
export declare const makeReactive: {
(id: string): <A>(self: Box<A>) => Box<Reactive>
<A>(self: Box<A>, id: string): Box<Reactive>
}Example
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
import * as Ansi from "effect-boxes/Ansi"
const titleBox = Box.text("Application Title")
const reactiveTitle = Reactive.makeReactive(titleBox, "main-title")
const styledButton = Box.text("Click Me").pipe(Box.annotate(Ansi.bold))
const reactiveButton = Reactive.makeReactive(styledButton, "click-btn")utilities
getPositions
Collects positions of reactive annotations from a box.
Traverses a box layout and extracts the calculated positions of all reactive elements, returning a position map that can be used for cursor navigation and interactive features. Essential for implementing click handling, keyboard navigation, and dynamic updates.
Signature
export declare const getPositions: <A>(self: Box<A>) => PositionMapExample
import * as Reactive from "effect-boxes/Reactive"
import * as Box from "effect-boxes/Box"
import { HashMap } from "effect"
const layout = Box.vcat(
[
Reactive.makeReactive(Box.text("Header"), "header"),
Box.text("Static content"),
Reactive.makeReactive(Box.text("Button"), "btn"),
Reactive.makeReactive(Box.text("Footer"), "footer")
],
Box.left
)
const positions = Reactive.getPositions(layout)
const headerPos = HashMap.get(positions, "header")
// Some({ row: 0, col: 0, rows: 1, cols: 6 })