Initial iris repository structure
Some checks failed
CI / build (push) Failing after 36s

WASM reactive UI framework for Go:
- reactive/ - Signal[T], Effect, Runtime
- ui/ - Button, Text, Input, View, Canvas, SVG components
- navigation/ - Router, guards, history management
- auth/ - OIDC client for WASM applications
- host/ - Static file server

Extracted from arcadia as open-source component.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-08 19:23:49 +01:00
commit 00d98879d3
36 changed files with 4181 additions and 0 deletions

18
reactive/effect.go Normal file
View File

@@ -0,0 +1,18 @@
package reactive
type EffectId int
func NewEffect(f func()) {
rt := GetRuntime()
effectId := EffectId(len(rt.effects))
rt.effects = append(rt.effects, f)
runEffect(rt, effectId)
}
func NewEffectWithRuntime(rt *Runtime, f func()) {
effectId := EffectId(len(rt.effects))
rt.effects = append(rt.effects, f)
runEffect(rt, effectId)
}

39
reactive/runtime.go Normal file
View File

@@ -0,0 +1,39 @@
package reactive
import "sync"
type Runtime struct {
signalValues []any
runningEffect *EffectId
signalSubscribers map[SignalId][]EffectId
effects []func()
}
var lock = &sync.Mutex{}
var runtimeInstance *Runtime
func GetRuntime() *Runtime {
if runtimeInstance == nil {
lock.Lock()
defer lock.Unlock()
if runtimeInstance == nil {
runtimeInstance = newRuntime()
}
}
return runtimeInstance
}
func newRuntime() *Runtime {
return &Runtime{
signalValues: []any{},
signalSubscribers: map[SignalId][]EffectId{},
effects: []func(){},
}
}
func runEffect(rt *Runtime, id EffectId) {
previous := rt.runningEffect
rt.runningEffect = &id
rt.effects[id]()
rt.runningEffect = previous
}

52
reactive/signal.go Normal file
View File

@@ -0,0 +1,52 @@
package reactive
type SignalId int
type Signal[T any] struct {
*Runtime
SignalId
}
func NewSignal[T any](initial T) Signal[T] {
rt := GetRuntime()
id := SignalId(len(rt.signalValues))
rt.signalValues = append(rt.signalValues, initial)
return Signal[T]{rt, id}
}
func NewSignalWithRuntime[T any](rt *Runtime, initial T) Signal[T] {
id := SignalId(len(rt.signalValues))
rt.signalValues = append(rt.signalValues, initial)
return Signal[T]{rt, id}
}
func (s *Signal[T]) Get() T {
val := s.Runtime.signalValues[s.SignalId]
if rt := s.Runtime; rt.runningEffect != nil {
// Check if this effect is already subscribed to this signal
subscribers := rt.signalSubscribers[s.SignalId]
alreadySubscribed := false
for _, effectId := range subscribers {
if effectId == *rt.runningEffect {
alreadySubscribed = true
break
}
}
if !alreadySubscribed {
rt.signalSubscribers[s.SignalId] = append(rt.signalSubscribers[s.SignalId], *rt.runningEffect)
}
}
return val.(T)
}
func (s *Signal[T]) Set(v T) {
s.Runtime.signalValues[s.SignalId] = v
if subscribers, ok := s.Runtime.signalSubscribers[s.SignalId]; ok {
for _, effectId := range subscribers {
runEffect(s.Runtime, effectId)
}
}
}