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:
208
ui/input.go
Normal file
208
ui/input.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.flowmade.one/flowmade-one/iris/internal/element"
|
||||
"git.flowmade.one/flowmade-one/iris/reactive"
|
||||
)
|
||||
|
||||
// TextInput creates a reactive text input field
|
||||
func TextInput(value *reactive.Signal[string], placeholder string) View {
|
||||
input := element.NewElement("input")
|
||||
input.Attr("type", "text")
|
||||
input.Attr("placeholder", placeholder)
|
||||
|
||||
// Set initial value
|
||||
input.Set("value", value.Get())
|
||||
|
||||
// Update input when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
currentValue := value.Get()
|
||||
if input.Get("value").String() != currentValue {
|
||||
input.Set("value", currentValue)
|
||||
}
|
||||
})
|
||||
|
||||
// Update signal when user types
|
||||
input.On("input", func() {
|
||||
newValue := input.Get("value").String()
|
||||
if value.Get() != newValue {
|
||||
value.Set(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
return View{input}
|
||||
}
|
||||
|
||||
// TextArea creates a multi-line text input
|
||||
func TextArea(value *reactive.Signal[string], placeholder string, rows int) View {
|
||||
textarea := element.NewElement("textarea")
|
||||
textarea.Attr("placeholder", placeholder)
|
||||
textarea.Attr("rows", string(rune(rows)))
|
||||
|
||||
// Set initial value
|
||||
textarea.Set("value", value.Get())
|
||||
|
||||
// Update textarea when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
currentValue := value.Get()
|
||||
if textarea.Get("value").String() != currentValue {
|
||||
textarea.Set("value", currentValue)
|
||||
}
|
||||
})
|
||||
|
||||
// Update signal when user types
|
||||
textarea.On("input", func() {
|
||||
newValue := textarea.Get("value").String()
|
||||
if value.Get() != newValue {
|
||||
value.Set(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
return View{textarea}
|
||||
}
|
||||
|
||||
// Checkbox creates a reactive checkbox input
|
||||
func Checkbox(checked *reactive.Signal[bool], label string) View {
|
||||
container := element.NewElement("label")
|
||||
container.Get("style").Call("setProperty", "display", "flex")
|
||||
container.Get("style").Call("setProperty", "align-items", "center")
|
||||
container.Get("style").Call("setProperty", "gap", "8px")
|
||||
container.Get("style").Call("setProperty", "cursor", "pointer")
|
||||
|
||||
checkbox := element.NewElement("input")
|
||||
checkbox.Attr("type", "checkbox")
|
||||
|
||||
// Set initial state
|
||||
if checked.Get() {
|
||||
checkbox.Set("checked", true)
|
||||
}
|
||||
|
||||
// Update checkbox when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
isChecked := checked.Get()
|
||||
checkbox.Set("checked", isChecked)
|
||||
})
|
||||
|
||||
// Update signal when user clicks
|
||||
checkbox.On("change", func() {
|
||||
newValue := checkbox.Get("checked").Bool()
|
||||
checked.Set(newValue)
|
||||
})
|
||||
|
||||
// Add label text
|
||||
labelText := element.NewTextNode(label)
|
||||
|
||||
container.Child(checkbox)
|
||||
container.Child(labelText)
|
||||
|
||||
return View{container}
|
||||
}
|
||||
|
||||
// Select creates a dropdown selection component
|
||||
func Select(selected *reactive.Signal[string], options []string, placeholder string) View {
|
||||
selectElem := element.NewElement("select")
|
||||
|
||||
// Add placeholder option if provided
|
||||
if placeholder != "" {
|
||||
placeholderOption := element.NewElement("option")
|
||||
placeholderOption.Set("value", "")
|
||||
placeholderOption.Set("disabled", true)
|
||||
placeholderOption.Set("selected", true)
|
||||
placeholderOption.Set("textContent", placeholder)
|
||||
selectElem.Child(placeholderOption)
|
||||
}
|
||||
|
||||
// Add options
|
||||
for _, option := range options {
|
||||
optionElem := element.NewElement("option")
|
||||
optionElem.Set("value", option)
|
||||
optionElem.Set("textContent", option)
|
||||
selectElem.Child(optionElem)
|
||||
}
|
||||
|
||||
// Set initial value
|
||||
selectElem.Set("value", selected.Get())
|
||||
|
||||
// Update select when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
currentValue := selected.Get()
|
||||
selectElem.Set("value", currentValue)
|
||||
})
|
||||
|
||||
// Update signal when user selects
|
||||
selectElem.On("change", func() {
|
||||
newValue := selectElem.Get("value").String()
|
||||
selected.Set(newValue)
|
||||
})
|
||||
|
||||
return View{selectElem}
|
||||
}
|
||||
|
||||
// Slider creates a range input for numeric values
|
||||
func Slider(value *reactive.Signal[int], min, max int) View {
|
||||
slider := element.NewElement("input")
|
||||
slider.Attr("type", "range")
|
||||
slider.Attr("min", strconv.Itoa(min))
|
||||
slider.Attr("max", strconv.Itoa(max))
|
||||
|
||||
// Set initial value
|
||||
slider.Set("value", strconv.Itoa(value.Get()))
|
||||
|
||||
// Update slider when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
currentValue := value.Get()
|
||||
slider.Set("value", strconv.Itoa(currentValue))
|
||||
})
|
||||
|
||||
// Update signal when user drags
|
||||
slider.On("input", func() {
|
||||
newValueStr := slider.Get("value").String()
|
||||
if newValue, err := strconv.Atoi(newValueStr); err == nil {
|
||||
value.Set(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
return View{slider}
|
||||
}
|
||||
|
||||
// NumberInput creates a reactive numeric input field for float64 values
|
||||
func NumberInput(value *reactive.Signal[float64], placeholder string) View {
|
||||
input := element.NewElement("input")
|
||||
input.Attr("type", "number")
|
||||
input.Attr("step", "0.01")
|
||||
input.Attr("min", "0")
|
||||
input.Attr("placeholder", placeholder)
|
||||
|
||||
// Set initial value
|
||||
if value.Get() != 0 {
|
||||
input.Set("value", strconv.FormatFloat(value.Get(), 'f', -1, 64))
|
||||
}
|
||||
|
||||
// Update input when signal changes
|
||||
reactive.NewEffect(func() {
|
||||
currentValue := value.Get()
|
||||
var displayValue string
|
||||
if currentValue == 0 {
|
||||
displayValue = ""
|
||||
} else {
|
||||
displayValue = strconv.FormatFloat(currentValue, 'f', -1, 64)
|
||||
}
|
||||
if input.Get("value").String() != displayValue {
|
||||
input.Set("value", displayValue)
|
||||
}
|
||||
})
|
||||
|
||||
// Update signal when user types
|
||||
input.On("input", func() {
|
||||
newValueStr := input.Get("value").String()
|
||||
if newValueStr == "" {
|
||||
value.Set(0.0)
|
||||
} else if newValue, err := strconv.ParseFloat(newValueStr, 64); err == nil {
|
||||
value.Set(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
return View{input}
|
||||
}
|
||||
Reference in New Issue
Block a user