Repository navigation
Expand file tree
/
Copy pathruntime.go
More file actions
151 lines (135 loc) · 3.72 KB
/
Copy pathruntime.go
File metadata and controls
151 lines (135 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//go:build wasm
package preveltekit
import (
"syscall/js"
)
// IsBuildTime is always false in WASM - we're running in the browser.
const IsBuildTime = false
// document is a cached reference to the DOM document
var document = js.Global().Get("document")
// nodeFilterShowComment is cached for TreeWalker (NodeFilter.SHOW_COMMENT = 128)
var nodeFilterShowComment = js.ValueOf(128)
// getEl returns an element by ID
func getEl(id string) js.Value {
return document.Call("getElementById", id)
}
// exists returns true if el is a valid element
func exists(el js.Value) bool {
return !el.IsNull() && !el.IsUndefined()
}
// cleanupBag holds js.Func references and destroy callbacks for batch release.
// Use this to prevent memory leaks when components unmount or re-render.
type cleanupBag struct {
funcs []js.Func
onDestroy []func()
}
// Add registers a js.Func for later cleanup.
// Safe to call with zero-value js.Func.
func (c *cleanupBag) Add(fn js.Func) {
if fn.Value.IsUndefined() {
return
}
c.funcs = append(c.funcs, fn)
}
// AddDestroy registers a destroy callback to run on Release.
func (c *cleanupBag) AddDestroy(fn func()) {
c.onDestroy = append(c.onDestroy, fn)
}
// Release runs all destroy callbacks and frees all registered js.Func references.
// Safe to call multiple times.
func (c *cleanupBag) Release() {
for _, fn := range c.onDestroy {
fn()
}
c.onDestroy = nil
for _, fn := range c.funcs {
fn.Release()
}
c.funcs = nil
}
// findComment finds a comment node with the given marker text using TreeWalker
func findComment(marker string) js.Value {
walker := document.Call("createTreeWalker",
document.Get("body"),
nodeFilterShowComment,
js.Null(),
)
for {
node := walker.Call("nextNode")
if node.IsNull() {
return js.Null()
}
if node.Get("nodeValue").String() == marker {
return node
}
}
}
// bindInput binds a text input to a string store (two-way).
func bindInput(c *cleanupBag, id string, store *Store[string]) {
el := getEl(id)
if !exists(el) {
return
}
fn := js.FuncOf(func(this js.Value, args []js.Value) any {
store.Set(this.Get("value").String())
return nil
})
el.Call("addEventListener", "input", fn)
c.Add(fn)
c.AddDestroy(store.subscribe(func(v string) { el.Set("value", v) }))
}
// bindInputInt binds a text input to an int store (two-way).
func bindInputInt(c *cleanupBag, id string, store *Store[int]) {
el := getEl(id)
if !exists(el) {
return
}
fn := js.FuncOf(func(this js.Value, args []js.Value) any {
store.Set(atoiSafe(this.Get("value").String()))
return nil
})
el.Call("addEventListener", "input", fn)
c.Add(fn)
c.AddDestroy(store.subscribe(func(v int) { el.Set("value", itoa(v)) }))
}
// bindCheckbox binds a checkbox to a bool store (two-way).
func bindCheckbox(c *cleanupBag, id string, store *Store[bool]) {
el := getEl(id)
if !exists(el) {
return
}
fn := js.FuncOf(func(this js.Value, args []js.Value) any {
store.Set(this.Get("checked").Bool())
return nil
})
el.Call("addEventListener", "change", fn)
c.Add(fn)
c.AddDestroy(store.subscribe(func(v bool) { el.Set("checked", v) }))
el.Set("checked", store.Get())
}
// bindEvent attaches a handler to the element with the given ID, applying
// the modifiers registered via PreventDefault/StopPropagation.
func bindEvent(c *cleanupBag, id, event string, handler func()) {
el := getEl(id)
if !exists(el) {
return
}
mods := GetHandlerModifiers(id)
fn := js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) > 0 {
ev := args[0]
for _, mod := range mods {
switch mod {
case "preventDefault":
ev.Call("preventDefault")
case "stopPropagation":
ev.Call("stopPropagation")
}
}
}
handler()
return nil
})
el.Call("addEventListener", event, fn)
c.Add(fn)
}