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)