-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.mjs
142 lines (112 loc) · 3.11 KB
/
index.mjs
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
export default v => {
// Internal
let root // Nearest ancestral mount-point
let rootVs // Root vnodes as of last check
let host // v's vnode sequence
let path // v's position within container
// Flags to qualify nature of current draw loop
let first = true
let local = false
return {
oncreate: tick,
onupdate: tick,
view,
}
// Refresh & reset references
function tick(fresh){
v = fresh
first = false
local = false
}
function view(){
return v.children[0].children({first, local, redraw})
}
// Overload redraw function
function redraw(handler){
// Manual, explicit redraw
if(typeof handler !== 'function')
render()
// Event handler wrapper: inverts Mithril auto-redraw directives:
else
return function(e){
const output = handler(...arguments)
// Unless e.redraw was set to false, redraw
if(e.redraw !== false)
render()
// Prevent global redraw
e.redraw = false
return output
}
}
function render(){
if(!v.dom || !v.dom.parentNode)
return
if(!root)
root = findRoot(v.dom)
local = true
// If a global redraw took place since last local draw,
// we might need to relocate the island's global position
if(rootVs !== root.vnodes){
({host, path} = findWithinRoot(v, root))
rootVs = root.vnodes
}
// Trace the path from our (new) local instance up to the dom-rooted host vnode
// (this could be of any length, considering fragments & DOM-less components)
// and clone each node in the path for a replacement tree.
// This ensures a 'hot path', ensuring uncloned siblings aren't redrawn.
const replacement = O(
host,
[...path].reverse().reduce(
(patch, key) => ({
[key] : O(patch)
}),
O(v, {
instance: view()
}),
)
)
// Set a local vnodes modulo to allow Mithril to skip siblings
// and diff from last global draw
v.dom.parentNode.vnodes = Array.isArray(host) ? host : [host]
// Render the patched local container + our new local draw
m.render(v.dom.parentNode, replacement)
// Persist the vnode patch to Mithril's global vtree cache
// (for global draw diffing)
host[path[0]] = replacement[path[0]]
}
}
const findRoot = element => {
while(!element.vnodes)
element = element.parentNode
return element
}
const findWithinRoot = (target, root) => {
const path = []
let host
for(const {node, key, container} of crawl({node: root.vnodes})){
if(node.dom === target.dom){
path.push(key)
if(!host)
host = container
}
if(node === target)
return {path, host}
}
}
function * crawl({key, node, container}){
yield {key, node, container}
if(Array.isArray(node))
for(var key = 0; key < node.length; key++)
yield * recurse()
else
for(var key of ['instance', 'children'])
if(node[key])
return yield * recurse()
function recurse(){
return crawl({
key,
node: node[key],
container: node,
})
}
}