forked from vanjs-org/van
-
Notifications
You must be signed in to change notification settings - Fork 0
/
van-0.11.2.js
88 lines (73 loc) · 2.76 KB
/
van-0.11.2.js
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
// This file consistently uses `let` keyword instead of `const` for reducing the bundle size.
// Aliasing some builtin symbols to reduce the bundle size.
let Obj = Object, Null = null, protoOf = Object.getPrototypeOf, objProto = protoOf({})
let addAndScheduleOnFirst = (set, s, func, waitMs) =>
(set ?? (setTimeout(func, waitMs), new Set)).add(s)
let changedStates
let stateProto = {
get "val"() { return this._val },
set "val"(v) {
// Aliasing `this` to reduce the bundle size.
let s = this, curV = s._val
if (v !== curV) {
if (s.oldVal === curV)
changedStates = addAndScheduleOnFirst(changedStates, s, updateDoms)
else if (v === s.oldVal)
changedStates.delete(s)
s._val = v
s.listeners.forEach(l => l(v, curV))
}
},
"onnew"(l) { this.listeners.push(l) },
}
let state = initVal => ({
__proto__: stateProto,
_val: initVal,
oldVal: initVal,
bindings: [],
listeners: [],
})
let toDom = v => v.nodeType ? v : new Text(v)
let add = (dom, ...children) =>
children.flat(Infinity).forEach(child => dom.appendChild(
protoOf(child) === stateProto ? bind(child, v => v) : toDom(child)))
let tags = new Proxy((name, ...args) => {
let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]
let dom = document.createElement(name)
Obj.entries(props).forEach(([k, v]) => {
let setter = dom[k] !== undefined ? v => dom[k] = v : v => dom.setAttribute(k, v)
if (protoOf(v) === stateProto) bind(v, v => (setter(v), dom))
else if (protoOf(v) === objProto) bind(...v["deps"], (...deps) => (setter(v["f"](...deps)), dom))
else setter(v)
})
add(dom, ...children)
return dom
}, {get: (tag, name) => tag.bind(Null, name)})
let filterBindings = s => s.bindings = s.bindings.filter(b => b.dom?.isConnected)
let updateDoms = () => {
let changedStatesArray = [...changedStates]
changedStates = Null
new Set(changedStatesArray.flatMap(filterBindings)).forEach(b => {
let {_deps, dom, func} = b
let newDom = func(..._deps.map(d => d._val), dom, ..._deps.map(d => d.oldVal))
if (newDom !== dom)
if (newDom !== Null) dom.replaceWith(b.dom = toDom(newDom)); else dom.remove(), b.dom = Null
})
changedStatesArray.forEach(s => s.oldVal = s._val)
}
let bindingGcCycleInMs = 1000
let statesToGc
let bind = (...args) => {
let deps = args.slice(0, -1), func = args[args.length - 1]
let result = func(...deps.map(d => d._val))
if (result === Null) return []
let binding = {_deps: deps, dom: toDom(result), func}
deps.forEach(s => {
statesToGc = addAndScheduleOnFirst(statesToGc, s,
() => (statesToGc.forEach(filterBindings), statesToGc = Null),
bindingGcCycleInMs)
s.bindings.push(binding)
})
return binding.dom
}
export default {add, tags, state, bind}