Annotation overview

Type-safe annotation system for attaching metadata to boxes.

Annotations allow boxes to carry arbitrary typed data (colors, styles, semantic tags) that renderers can interpret. The annotation type parameter A on Box<A> flows through all composition operations.

Mental model

  • Annotation<A> — a wrapper holding metadata of type A
  • Annotations are structural — they implement Equal and Hash
  • They compose via combineAnnotations for layered styling

Common tasks

  • Create an annotation: createAnnotation, empty
  • Inspect: isAnnotation, getAnnotationData
  • Transform: mapAnnotationData, combineAnnotations

combinators

combineAnnotations

Combines two annotations by merging their data.

Merges annotation data using a custom merger function. Essential for combining multiple styling annotations or accumulating annotation properties across box compositions.

Signature

export declare const combineAnnotations: <A>(
  first: Annotation<A>,
  second: Annotation<A>,
  merger: (a: A, b: A) => A
) => Annotation<A>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const redColor = Ansi.red
const boldStyle = Ansi.bold
 
const combined = Annotation.combineAnnotations(redColor, boldStyle, (first, second) => [...first, ...second])
 
const allCommands = Annotation.getAnnotationData(combined)
// Contains both color and bold commands

constructors

createAnnotation

Creates a new Annotation with the provided data.

The fundamental constructor for creating typed annotations. Wraps arbitrary data in the Annotation abstraction for use with the Box system.

Signature

export declare const createAnnotation: <A>(data: A) => Annotation<A>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Box from "effect-boxes/Box"
 
// Create custom annotation types
type TooltipData = { text: string; position: "top" | "bottom" }
const tooltip = Annotation.createAnnotation<TooltipData>({
  text: "Click for more info",
  position: "top"
})
 
// Apply to box
const buttonWithTooltip = Box.text("Click me").pipe(Box.annotate(tooltip))

createAnnotations

Creates an array of annotations from an array of data.

Efficiently wraps multiple data items in annotations. Useful for batch processing of styling data or creating multiple annotations with similar structure.

Signature

export declare const createAnnotations: <A>(dataArray: readonly A[]) => Annotation<A>[]

Example

import * as Annotation from "effect-boxes/Annotation"
 
type ColorData = { name: string; hex: string }
const colorData: ColorData[] = [
  { name: "red", hex: "#ff0000" },
  { name: "green", hex: "#00ff00" },
  { name: "blue", hex: "#0000ff" }
]
 
const colorAnnotations = Annotation.createAnnotations(colorData)
// Array of 3 Annotation<ColorData> instances

empty

Predefined empty annotation for use as a default or placeholder.

Provides a safe default annotation that contains no data. Useful for initializing annotation systems, providing fallback values, or creating placeholder annotations that can be replaced later.

Signature

export declare const empty: Annotation<never>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
import * as Box from "effect-boxes/Box"
 
const getStatusStyle = (isError: boolean) => (isError ? Ansi.red : Annotation.empty)
 
const statusBox = Box.text("Status: OK").pipe(Box.annotate(getStatusStyle(false)))
// Box with no styling applied due to empty annotation

guards

isAnnotation

Type guard to check if a value is an Annotation.

Signature

export declare const isAnnotation: (value: unknown) => value is Annotation<unknown>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const style = Ansi.red
if (Annotation.isAnnotation(style)) {
  console.log("This is an annotation")
  // TypeScript now knows style is Annotation<unknown>
}

isAnnotationWithData

Type guard to check if a value is an Annotation with specific data type.

Provides type-safe checking for annotations containing specific data types, useful when working with different annotation systems.

Signature

export declare const isAnnotationWithData: <A>(
  value: unknown,
  dataGuard: (data: unknown) => data is A
) => value is Annotation<A>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const isStringAnnotation = (data: unknown): data is string => typeof data === "string"
 
const annotation = Annotation.createAnnotation("some text")
if (Annotation.isAnnotationWithData(annotation, isStringAnnotation)) {
  // TypeScript knows annotation is Annotation<string>
  const text = Annotation.getAnnotationData(annotation) // string
}

models

Annotation (interface)

Generic annotation wrapper with branded type safety.

Annotations provide a way to attach arbitrary data to boxes without affecting their layout properties. This abstraction allows styling systems (like ANSI colors) to work with the Box layout system.

Signature

export interface Annotation<A = never> extends Pipeable, Equal.Equal, Hash.Hash {
  readonly [AnnotationTypeId]: "annotation"
  readonly data: A
}

AnnotationData (type alias)

Type-level utility to extract the data type from an Annotation type.

Signature

export type AnnotationData<T> = T extends Annotation<infer A> ? A : never

AnnotationTypeId

Signature

export declare const AnnotationTypeId: typeof AnnotationTypeId

AnnotationTypeId (type alias)

Signature

export type AnnotationTypeId = typeof AnnotationTypeId

transformations

mapAnnotationData

Maps the data of an Annotation<A> to create an Annotation<B>.

Transforms annotation data while preserving the annotation structure. Essential for converting between different annotation formats or modifying annotation data for different rendering contexts.

Signature

export declare const mapAnnotationData: <A, B>(annotation: Annotation<A>, mapper: (data: A) => B) => Annotation<B>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const redAnsi = Ansi.red
const cssAnnotation = Annotation.mapAnnotationData(redAnsi, (ansiCommands) => ({ className: "text-red-500" }))
 
const cssData = Annotation.getAnnotationData(cssAnnotation)
console.log(cssData) // { className: "text-red-500" }

utilities

cloneAnnotation

Creates a copy of an annotation with the same data.

Creates a deep copy of an annotation with identical data. Useful for creating independent annotation instances that can be modified separately or for ensuring annotation immutability in complex operations.

Signature

export declare const cloneAnnotation: <A>(annotation: Annotation<A>) => Annotation<A>

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Box from "effect-boxes/Box"
 
type Style = { color: string; weight: string }
const baseStyle = Annotation.createAnnotation<Style>({
  color: "blue",
  weight: "normal"
})
 
const clonedStyle = Annotation.cloneAnnotation(baseStyle)
 
// Both annotations have the same data but are separate instances
const box1 = Box.text("Text 1").pipe(Box.annotate(baseStyle))
const box2 = Box.text("Text 2").pipe(Box.annotate(clonedStyle))

extractAnnotationData

Extracts data from an array of annotations.

Unwraps multiple annotations to get their underlying data as an array. The inverse operation of createAnnotations, useful for batch processing annotation data or preparing data for external systems.

Signature

export declare const extractAnnotationData: <A>(annotations: readonly Annotation<A>[]) => A[]

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const annotations = [Ansi.red, Ansi.bold, Ansi.underlined]
const allCommands = Annotation.extractAnnotationData(annotations)
 
// Flatten all ANSI commands for processing
const flatCommands = allCommands.flat()
console.log(flatCommands.length) // Total number of ANSI commands

filterAnnotation

Filters annotation data based on a predicate function.

Returns the annotation if the predicate passes, otherwise undefined. Useful for conditionally applying annotations or filtering out annotations that don't meet certain criteria.

Signature

export declare const filterAnnotation: <A>(
  annotation: Annotation<A>,
  predicate: (data: A) => boolean
) => Annotation<A> | undefined

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Box from "effect-boxes/Box"
 
type ConditionalStyle = { color: string; condition: boolean }
 
const conditionalRed = Annotation.createAnnotation<ConditionalStyle>({
  color: "red",
  condition: true
})
 
const validStyle = Annotation.filterAnnotation(conditionalRed, (data) => data.condition)
 
if (validStyle) {
  // Apply the style to a box
  const styledBox = Box.text("Error!").pipe(Box.annotate(validStyle))
}

getAnnotationData

Extracts the data from an Annotation<A>.

Unwraps the annotation wrapper to access the underlying data. Essential for consuming annotation data in styling systems and custom renderers.

Signature

export declare const getAnnotationData: <A>(annotation: Annotation<A>) => A

Example

import * as Annotation from "effect-boxes/Annotation"
import * as Ansi from "effect-boxes/Ansi"
 
const redStyle = Ansi.red
const ansiCommands = Annotation.getAnnotationData(redStyle)
console.log(ansiCommands)
// [{ _tag: "ForegroundColor", name: "red", code: "31" }]