tuix
Version:
A performant TUI framework for Bun with JSX and reactive state management
152 lines (126 loc) • 3.37 kB
text/typescript
/**
* Simplified runes implementation without infinite recursion issues
*/
/**
* Base interface for all runes
*/
export interface Rune<T> {
(): T
readonly $type: string
}
/**
* State rune for reactive values
*/
export interface StateRune<T> extends Rune<T> {
readonly $type: 'state'
$set(value: T): void
$update(fn: (current: T) => T): void
}
/**
* Bindable rune for two-way data binding
*/
export interface BindableRune<T> extends StateRune<T> {
readonly $type: 'bindable'
readonly $bindable: true
$validate?: (value: T) => boolean | string
$transform?: (value: T) => T
}
/**
* Derived rune for computed values
*/
export interface DerivedRune<T> extends Rune<T> {
readonly $type: 'derived'
}
/**
* Options for creating bindable runes
*/
export interface BindableOptions<T> {
validate?: (value: T) => boolean | string
transform?: (value: T) => T
}
/**
* Creates a reactive state value
*/
export function $state<T>(initial: T): StateRune<T> {
let value = initial
const rune = (() => {
return value
}) as StateRune<T>
rune.$type = 'state' as const
rune.$set = (newValue: T) => {
if (value !== newValue) {
value = newValue
}
}
rune.$update = (fn: (current: T) => T) => {
rune.$set(fn(value))
}
return rune
}
/**
* Creates a bindable value with optional validation and transformation
*/
export function $bindable<T>(initial: T, options: BindableOptions<T> = {}): BindableRune<T> {
let value = initial
const rune = (() => {
return value
}) as BindableRune<T>
rune.$type = 'bindable' as const
rune.$bindable = true
rune.$validate = options.validate
rune.$transform = options.transform
rune.$set = (newValue: T) => {
let finalValue = newValue
// Apply transformation
if (options.transform) {
finalValue = options.transform(finalValue)
}
// Validate
if (options.validate) {
const result = options.validate(finalValue)
if (result === false) {
return // Reject the change silently
}
if (typeof result === 'string') {
console.error(`Validation error: ${result}`)
return // Reject with error message
}
}
if (value !== finalValue) {
value = finalValue
}
}
rune.$update = (fn: (current: T) => T) => {
rune.$set(fn(value))
}
return rune
}
/**
* Creates a simple derived value that recalculates on each access
* This avoids the complexity of subscription management and infinite loops
*/
export function $derived<T>(fn: () => T): DerivedRune<T> {
const rune = (() => {
return fn()
}) as DerivedRune<T>
rune.$type = 'derived' as const
return rune
}
/**
* Type guard to check if a value is a state rune
*/
export function isStateRune<T>(value: any): value is StateRune<T> {
return value && typeof value === 'function' && value.$type === 'state'
}
/**
* Type guard to check if a value is a bindable rune
*/
export function isBindableRune<T>(value: any): value is BindableRune<T> {
return value && typeof value === 'function' && value.$type === 'bindable' && value.$bindable === true
}
/**
* Type guard to check if a value is a derived rune
*/
export function isDerivedRune<T>(value: any): value is DerivedRune<T> {
return value && typeof value === 'function' && value.$type === 'derived'
}