-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathvan.debug.js
More file actions
127 lines (107 loc) · 4.58 KB
/
van.debug.js
File metadata and controls
127 lines (107 loc) · 4.58 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
import van from "./van.js"
// If this variable is set to an Array, we will push the error message into the array instead of
// throwing an error. This is useful in testing, to capture the error occurred asynchronous to the initiating
// callstack. e.g.: a state change can trigger a dom update processing when idle (i.e.: dom update
// processing is set via setTimeout function, which is asynchronous to the initiating callstack).
let capturedErrors
const startCapturingErrors = () => capturedErrors = []
const stopCapturingErrors = () => capturedErrors = null
const expect = (cond, msg) => {
if (!cond) {
if (capturedErrors) capturedErrors.push(msg); else throw new Error(msg)
return false
}
return true
}
const protoOf = Object.getPrototypeOf
const stateProto = protoOf(van.state())
const isState = s => protoOf(s ?? 0) === stateProto
const checkStateValValid = v => {
expect(!isState(v), "State couldn't have value to other state")
expect(!(v instanceof Node), "DOM Node is not valid value for state")
return v
}
const state = initVal => {
const proxy = new Proxy(van.state(checkStateValValid(initVal)), {
set: (s, prop, val) => {
prop === "val" && checkStateValValid(val)
return Reflect.set(s, prop, val, proxy)
},
})
return proxy
}
const derive = f => {
expect(typeof(f) === "function", "Must pass-in a function to `van.derive`")
return van.derive(f)
}
const isValidPrimitive = v =>
typeof(v) === "string" ||
typeof(v) === "number" ||
typeof(v) === "boolean" ||
typeof(v) === "bigint"
const isDomOrPrimitive = v => v instanceof Node || isValidPrimitive(v)
const validateChild = child => {
expect(
isDomOrPrimitive(child) || child === null || child === undefined,
"Only DOM Node, string, number, boolean, bigint, null, undefined are valid child of a DOM Element",
)
return child
}
const withResultValidation = f => dom => {
const r = validateChild(f(dom))
if (r !== dom && r instanceof Node)
expect(!r.isConnected,
"If the result of complex binding function is not the same as previous one, it shouldn't be already connected to document")
return r
}
const checkChildren = children => children.flat(Infinity).map(c => {
if (isState(c)) return withResultValidation(() => c.val)
if (typeof c === "function") return withResultValidation(c)
expect(!c?.isConnected, "You can't add a DOM Node that is already connected to document")
return validateChild(c)
})
const add = (dom, ...children) => {
expect(dom instanceof Element || dom instanceof DocumentFragment,
"1st argument of `van.add` function must be a DOM Element object")
return van.add(dom, ...checkChildren(children))
}
const debugHandler = {
get: (vanTags, name) => {
const vanTag = vanTags[name]
return (...args) => {
const [props, ...children] = protoOf(args[0] ?? 0) === Object.prototype ? args : [{}, ...args]
const debugProps = {}
for (const [k, v] of Object.entries(props)) {
const validatePropValue = k.startsWith("on") ?
(k.toLowerCase() === k ?
v => (expect(typeof v === "function" || v === null,
`Invalid property value for ${k}: Only functions and null are allowed for ${k} property`), v) :
v => (expect(typeof v === "string",
`Invalid property value for ${k}: Only strings are allowed for ${k} attribute`), v)
) :
v => (expect(isValidPrimitive(v) || v === null,
`Invalid property value for ${k}: Only string, number, boolean, bigint and null are valid prop value types`), v)
if (isState(v))
debugProps[k] = van.derive(() => validatePropValue(v.val))
else if (typeof v === "function" && (!k.startsWith("on") || v._isBindingFunc))
debugProps[k] = van.derive(() => validatePropValue(v()))
else
debugProps[k] = validatePropValue(v)
}
return vanTag(debugProps, ...checkChildren(children))
}
},
}
const _tagsNS = ns => new Proxy(van.tags(ns), debugHandler)
const tagsNS = ns => {
expect(typeof ns === "string", "Must provide a string for parameter `ns` in `van.tags`")
return _tagsNS(ns)
}
const _tags = _tagsNS("")
const tags = new Proxy(tagsNS, {get: (_, name) => _tags[name]})
const hydrate = (dom, f) => {
expect(dom instanceof Node, "1st argument of `van.hydrate` function must be a DOM Node object")
expect(typeof(f) === "function", "2nd argument of `van.hydrate` function must be a function")
return van.hydrate(dom, withResultValidation(f))
}
export default {add, tags, state, derive, hydrate, startCapturingErrors, stopCapturingErrors, get capturedErrors() { return capturedErrors }}