Examples

Real-world examples of Inval in action.

Basic Calculator

A simple dependency chain: two inputs, one computed result.

calculator.ts TYPESCRIPT
import { input, node } from '@blu3ph4ntom/inval'

const a = input(10)
const b = input(20)

const sum = node({
  dependsOn: { a, b },
  compute: ({ a, b }) => a + b
})

const product = node({
  dependsOn: { a, b },
  compute: ({ a, b }) => a * b
})

const average = node({
  dependsOn: { sum, count: input(2) },
  compute: ({ sum, count }) => sum / count
})

sum.get()      // 30
product.get()  // 200
average.get()  // 15

a.set(50)
sum.get()      // 70 (recomputed)
product.get()  // 1000 (recomputed)
average.get()  // 35 (recomputed)

Variable-Height Virtualized List

Full virtualized list with dynamic row heights. Only visible range recomputes on scroll.

virtualized-list.ts TYPESCRIPT
import { input, node } from '@blu3ph4ntom/inval'

const viewportWidth = input(800)
const scrollTop = input(0)
const items = input(generateItems(1000))

const rowHeights = node({
  dependsOn: { width: viewportWidth, items },
  compute: ({ width, items }) => items.map(item => {
    const charsPerLine = Math.floor(width / 8)
    const lines = Math.ceil(item.text.length / charsPerLine)
    return lines * 20 + 16
  })
})

const totalHeight = node({
  dependsOn: { heights: rowHeights },
  compute: ({ heights }) => heights.reduce((a, b) => a + b, 0)
})

const visibleRange = node({
  dependsOn: { scroll: scrollTop, heights: rowHeights },
  compute: ({ scroll, heights }) => {
    const viewportHeight = 600
    let offset = 0, start = 0
    while (start < heights.length && offset + heights[start] < scroll) {
      offset += heights[start]
      start++
    }
    let end = start, visibleOffset = offset
    while (end < heights.length && visibleOffset < scroll + viewportHeight) {
      visibleOffset += heights[end]
      end++
    }
    return { start, end }
  }
})

// Scroll: only visibleRange dirty
scrollTop.set(500)
rowHeights.isDirty()   // false
visibleRange.isDirty() // true

// Resize: everything dirty
viewportWidth.set(400)
rowHeights.isDirty()   // true
totalHeight.isDirty()  // true
visibleRange.isDirty() // true

Dashboard with Widgets

Multi-widget layout with dependent dimensions. Sidebar resize cascades intelligently.

dashboard.ts TYPESCRIPT
import { input, node, batch } from '@blu3ph4ntom/inval'

const dashboardWidth = input(1200)
const sidebarWidth = input(300)
const zoomLevel = input(1)

const contentWidth = node({
  dependsOn: { dash: dashboardWidth, sidebar: sidebarWidth },
  compute: ({ dash, sidebar }) => dash - sidebar
})

const chartWidth = node({
  dependsOn: { content: contentWidth },
  compute: ({ content }) => content * 0.6
})

const chartHeight = node({
  dependsOn: { zoom: zoomLevel },
  compute: ({ zoom }) => 400 * zoom
})

const tableWidth = node({
  dependsOn: { content: contentWidth },
  compute: ({ content }) => content * 0.4
})

const tableRowHeight = node({
  dependsOn: { zoom: zoomLevel },
  compute: ({ zoom }) => 40 * zoom
})

// Initial
chartWidth.get()     // 540
tableWidth.get()     // 360
chartHeight.get()    // 400
tableRowHeight.get() // 40

// Sidebar resize
sidebarWidth.set(400)
chartWidth.get()     // 480 (dirty)
tableWidth.get()     // 320 (dirty)
chartHeight.isDirty()    // false
tableRowHeight.isDirty() // false

// Zoom change
zoomLevel.set(1.5)
chartHeight.get()    // 600 (dirty)
tableRowHeight.get() // 60 (dirty)
chartWidth.isDirty()     // false
tableWidth.isDirty()     // false

Kanban Board

Column widths, card heights, and scroll positions tracked as a dependency graph.

kanban.ts TYPESCRIPT
import { input, node, batch } from '@blu3ph4ntom/inval'

const boardWidth = input(1400)
const columnCount = input(4)

const columnWidth = node({
  dependsOn: { board: boardWidth, count: columnCount },
  compute: ({ board, count }) => (board - (count - 1) * 16) / count
})

const cardHeight = node({
  dependsOn: { colWidth: columnWidth },
  compute: ({ colWidth }) => {
    // Cards scale proportionally to column width
    return Math.max(80, colWidth * 0.3)
  }
})

const columnHeight = node({
  dependsOn: { cardH: cardHeight, cardCount: input(5) },
  compute: ({ cardH, cardCount }) => cardH * cardCount + 40
})

// Resize board
boardWidth.set(1200)
columnWidth.get()  // 288 (recomputed)
cardHeight.get()   // 86.4 (recomputed)
columnHeight.get() // 472 (recomputed)

// Add a column
batch(() => {
  columnCount.set(5)
  boardWidth.set(1600)
})
columnWidth.get()  // 304 (single recomputation)