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:
18
reactive/effect.go
Normal file
18
reactive/effect.go
Normal 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
39
reactive/runtime.go
Normal 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
52
reactive/signal.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user