-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdom.js
162 lines (143 loc) · 4.34 KB
/
dom.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
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
152
153
154
155
156
157
158
159
160
161
162
'use strict'
const secret = {
template: Symbol('content template'),
firstNodes: Symbol('first nodes')
}
let cloneId = 0
module.exports = {
extractContent,
normalizeContent,
insertContent,
moveContent,
removeContent,
clearContent,
mutateContext,
findAncestor,
findAncestorProp
}
function extractContent (elem) {
const template = document.createDocumentFragment()
let node = elem.firstChild
while (node) {
template.appendChild(node)
node = elem.firstChild
}
elem[secret.template] = template
elem[secret.firstNodes] = []
return template
}
function normalizeContent (node) {
if (node.nodeType === 1) {
node.setAttribute('clone-id', `content-${cloneId++}`)
const childNodes = node.childNodes
let i = childNodes.length
while (i--) {
normalizeContent(childNodes[i])
}
} else if (node.nodeType === 3) {
if (!node.nodeValue.trim()) node.remove()
} else {
node.remove()
}
}
function insertContent (elem, index, contextState) {
if (index !== undefined && typeof index !== 'number') {
throw new TypeError('Second argument must be a number or undefined.')
}
if (contextState !== undefined && typeof contextState !== 'object') {
throw new TypeError('Third argument must be an object or undefined.')
}
if (!elem[secret.template]) {
throw new Error('you must extract a template with $extractContent before inserting')
}
const content = elem[secret.template].cloneNode(true)
const firstNodes = elem[secret.firstNodes]
const firstNode = content.firstChild
const beforeNode = firstNodes[index]
if (contextState) {
contextState = Object.assign(Object.create(elem.$state), contextState)
let node = firstNode
while (node) {
node.$contextState = contextState
node = node.nextSibling
}
}
elem.insertBefore(content, beforeNode)
if (beforeNode) firstNodes.splice(index, 0, firstNode)
else firstNodes.push(firstNode)
}
function removeContent (elem, index) {
if (index !== undefined && typeof index !== 'number') {
throw new TypeError('Second argument must be a number or undefined.')
}
const firstNodes = elem[secret.firstNodes]
index = firstNodes[index] ? index : (firstNodes.length - 1)
const firstNode = firstNodes[index]
const nextNode = firstNodes[index + 1]
let node = firstNode
let next
while (node && node !== nextNode) {
next = node.nextSibling
node.remove()
node = next
}
if (nextNode) firstNodes.splice(index, 1)
else firstNodes.pop()
}
function clearContent (elem) {
elem.innerHTML = ''
elem[secret.firstNodes] = []
}
function moveContent (elem, fromIndex, toIndex, extraContext) {
if (typeof fromIndex !== 'number' || typeof toIndex !== 'number') {
throw new Error('first and second argument must be numbers')
}
if (extraContext !== undefined && typeof extraContext !== 'object') {
throw new Error('third argument must be an object or undefined')
}
const firstNodes = elem[secret.firstNodes]
const fromNode = firstNodes[fromIndex]
const untilNode = firstNodes[fromIndex + 1]
const toNode = firstNodes[toIndex]
let node = fromNode
let next
while (node && node !== untilNode) {
next = node.nextSibling
elem.insertBefore(node, toNode)
node = next
}
firstNodes.splice(fromIndex, 1)
firstNodes.splice(toIndex, 0, fromNode)
if (extraContext && fromNode && fromNode.$contextState) {
Object.assign(fromNode.$contextState, extraContext)
}
}
function mutateContext (elem, index, extraContext) {
if (index !== undefined && typeof index !== 'number') {
throw new TypeError('first argument must be a number or undefined')
}
if (typeof extraContext !== 'object') {
throw new TypeError('second argument must be an object')
}
const startNode = elem[secret.firstNodes][index]
if (startNode && startNode.$contextState) {
Object.assign(startNode.$contextState, extraContext)
}
}
function findAncestorProp (node, prop) {
node = findAncestor(node, node => node[prop] !== undefined)
return node ? node[prop] : undefined
}
function findAncestor (node, condition) {
if (!node instanceof Node) {
throw new TypeError('first argument must be a node')
}
if (typeof condition !== 'function') {
throw new TypeError('second argument must be a function')
}
node = node.parentNode
while (node && !condition(node)) {
node = node.parentNode
}
return node
}