All checks were successful
CI / build (pull_request) Successful in 28s
Replace direct internal/element usage with public ui package components to ensure examples only use the public API: - Add ui.RawCheckbox for plain checkbox without label wrapper - Add ui.Span for span elements with text content - Add View.TextDecoration modifier for strikethrough styling - Update todo example to use these public components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
228 lines
5.8 KiB
Go
228 lines
5.8 KiB
Go
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}
|
|
}
|
|
// RawCheckbox creates a plain checkbox input without a label wrapper.
|
|
// The onChange callback is called with the new checked state when the user clicks.
|
|
func RawCheckbox(checked bool, onChange func(bool)) View {
|
|
checkbox := element.NewElement("input")
|
|
checkbox.Attr("type", "checkbox")
|
|
|
|
// Set initial state
|
|
if checked {
|
|
checkbox.Set("checked", true)
|
|
}
|
|
|
|
// Call onChange when user clicks
|
|
checkbox.On("change", func() {
|
|
newValue := checkbox.Get("checked").Bool()
|
|
onChange(newValue)
|
|
})
|
|
|
|
return View{checkbox}
|
|
}
|