pseudo-dom 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +468 -237
- package/browser/pseudo-dom.js +986 -563
- package/browser/pseudo-dom.min.js +1 -1
- package/dist/classes/PseudoHTMLDocument.d.ts +17 -43
- package/dist/classes/PseudoHTMLDocument.js +24 -94
- package/dist/classes/PseudoHTMLDocument.min.js +1 -1
- package/dist/factories/createDataset.d.ts +10 -0
- package/dist/factories/createDataset.js +78 -0
- package/dist/factories/createDataset.min.js +1 -0
- package/dist/factories/createStyleDeclaration.d.ts +30 -0
- package/dist/factories/createStyleDeclaration.js +64 -0
- package/dist/factories/createStyleDeclaration.min.js +1 -0
- package/dist/factories/cssSelectAdapter.d.ts +23 -23
- package/dist/factories/cssSelectAdapter.js +87 -87
- package/dist/factories/query.d.ts +33 -33
- package/dist/factories/query.js +66 -66
- package/dist/functions/getElementById.d.ts +13 -0
- package/dist/functions/getElementById.js +33 -0
- package/dist/functions/getElementById.min.js +1 -0
- package/dist/interfaces/PseudoCSSStyleDeclaration.d.ts +14 -0
- package/dist/interfaces/PseudoCSSStyleDeclaration.js +5 -0
- package/dist/interfaces/PseudoCSSStyleDeclaration.min.js +1 -0
- package/dist/interfaces/PseudoDocument.d.ts +35 -10
- package/dist/interfaces/PseudoDocumentFragment.d.ts +7 -0
- package/dist/interfaces/PseudoElement.d.ts +702 -702
- package/dist/interfaces/PseudoHTMLElement.d.ts +33 -22
- package/dist/services/CSSStyleDeclarationService.d.ts +73 -0
- package/dist/services/CSSStyleDeclarationService.js +156 -0
- package/dist/services/CSSStyleDeclarationService.min.js +1 -0
- package/dist/services/DocumentFragmentService.d.ts +8 -0
- package/dist/services/DocumentFragmentService.js +18 -0
- package/dist/services/DocumentFragmentService.min.js +1 -1
- package/dist/services/DocumentService.d.ts +46 -19
- package/dist/services/DocumentService.js +95 -46
- package/dist/services/DocumentService.min.js +1 -1
- package/dist/services/ElementService.d.ts +191 -191
- package/dist/services/ElementService.js +426 -426
- package/dist/services/HTMLCollectionService.d.ts +54 -54
- package/dist/services/HTMLCollectionService.js +92 -92
- package/dist/services/HTMLElementService.d.ts +87 -57
- package/dist/services/HTMLElementService.js +202 -163
- package/dist/services/HTMLElementService.min.js +1 -1
- package/dist/services/NodeService.d.ts +297 -297
- package/dist/services/NodeService.js +721 -721
- package/package.json +2 -2
- package/dist/classes.d.ts +0 -20
- package/dist/classes.js +0 -31
- package/dist/classes.min.js +0 -1
- package/dist/factories/generateNode.d.ts +0 -32
- package/dist/factories/generateNode.js +0 -85
- package/dist/factories/generateNode.min.js +0 -1
- package/dist/factories.d.ts +0 -11
- package/dist/factories.js +0 -27
- package/dist/factories.min.js +0 -1
- package/dist/functions.d.ts +0 -5
- package/dist/functions.js +0 -23
- package/dist/functions.min.js +0 -1
|
@@ -1,721 +1,721 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
require('core-js/modules/esnext.iterator.constructor.js')
|
|
4
|
-
require('core-js/modules/esnext.iterator.every.js')
|
|
5
|
-
require('core-js/modules/esnext.iterator.filter.js')
|
|
6
|
-
require('core-js/modules/esnext.iterator.for-each.js')
|
|
7
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
8
|
-
return mod && mod.__esModule
|
|
9
|
-
? mod
|
|
10
|
-
: {
|
|
11
|
-
default: mod
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
Object.defineProperty(exports, '__esModule', {
|
|
15
|
-
value: true
|
|
16
|
-
})
|
|
17
|
-
exports.CommentService = exports.TextService = exports.NodeService = void 0
|
|
18
|
-
/**
|
|
19
|
-
* @file Substitute for the DOM Node Class.
|
|
20
|
-
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
21
|
-
* @version 1.0.0
|
|
22
|
-
*/
|
|
23
|
-
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
24
|
-
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
25
|
-
const EventTargetService_1 = __importDefault(require('./EventTargetService'))
|
|
26
|
-
const HTMLCollectionService_1 = require('./HTMLCollectionService')
|
|
27
|
-
const query_1 = require('../factories/query')
|
|
28
|
-
/**
|
|
29
|
-
* Simulate the behaviour of the Node Class when there is no DOM available.
|
|
30
|
-
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
31
|
-
* @class
|
|
32
|
-
* @augments PseudoEventTarget
|
|
33
|
-
* @property {string} name
|
|
34
|
-
* @property {function} appendChild
|
|
35
|
-
* @property {function} removeChild
|
|
36
|
-
*/
|
|
37
|
-
class NodeService extends EventTargetService_1.default {
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
* @constructor
|
|
41
|
-
*/
|
|
42
|
-
constructor () {
|
|
43
|
-
super()
|
|
44
|
-
this.nodeValueStore = null
|
|
45
|
-
this.ownerDocumentStore = null
|
|
46
|
-
this.nodeId = NodeService.nextNodeId++
|
|
47
|
-
this.nodeNameValue = ''
|
|
48
|
-
this.childList = (0, generateNodeList_1.default)()
|
|
49
|
-
this.parent = null
|
|
50
|
-
this.listLinker = null
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get baseURI () {
|
|
54
|
-
return window.location || '/'
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get childNodes () {
|
|
58
|
-
return this.childList
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get firstChild () {
|
|
62
|
-
return this.childList.first ? this.childList.first.data : null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get isConnected () {
|
|
66
|
-
// Connected means the tree the node is in has a document at the top
|
|
67
|
-
return this.getRootNode().nodeType === NodeService.DOCUMENT_NODE
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get lastChild () {
|
|
71
|
-
return this.childList.last ? this.childList.last.data : null
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get nextSibling () {
|
|
75
|
-
return this.listLinker && this.listLinker.next ? this.listLinker.next.data : null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
get nodeName () {
|
|
79
|
-
return this.nodeNameValue || ''
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
get nodeType () {
|
|
83
|
-
return NodeService.DEFAULT_NODE
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
get nodeValue () {
|
|
87
|
-
return this.nodeValueStore
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
set nodeValue (value) {
|
|
91
|
-
this.nodeValueStore = value
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
get ownerDocument () {
|
|
95
|
-
if (this.nodeType === NodeService.DOCUMENT_NODE) {
|
|
96
|
-
return null
|
|
97
|
-
}
|
|
98
|
-
const root = this.getRootNode()
|
|
99
|
-
return root.nodeType === NodeService.DOCUMENT_NODE ? root : this.ownerDocumentStore
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get parentNode () {
|
|
103
|
-
return this.parent
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get parentElement () {
|
|
107
|
-
return this.parent && this.parent.nodeType === NodeService.ELEMENT_NODE ? this.parent : null
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
get previousSibling () {
|
|
111
|
-
return this.listLinker && this.listLinker.prev ? this.listLinker.prev.data : null
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
get textContent () {
|
|
115
|
-
if (this.nodeType === NodeService.DOCUMENT_NODE || this.nodeType === NodeService.DOCUMENT_TYPE_NODE) {
|
|
116
|
-
return null
|
|
117
|
-
}
|
|
118
|
-
// The text of everything below, in order (comments and the like do not count)
|
|
119
|
-
let text = ''
|
|
120
|
-
Array.from(this.childNodes).forEach(child => {
|
|
121
|
-
if (child.nodeType === NodeService.TEXT_NODE) {
|
|
122
|
-
text += child.nodeValue
|
|
123
|
-
} else if (child.nodeType !== NodeService.COMMENT_NODE) {
|
|
124
|
-
text += child.textContent
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
return text
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
set textContent (text) {
|
|
131
|
-
if (this.nodeType === NodeService.DOCUMENT_NODE || this.nodeType === NodeService.DOCUMENT_TYPE_NODE) {
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
// All the children are replaced by a single text node (or none for an empty text)
|
|
135
|
-
while (this.firstChild) {
|
|
136
|
-
this.removeChild(this.firstChild)
|
|
137
|
-
}
|
|
138
|
-
if (text !== null && typeof text !== 'undefined' && String(text) !== '') {
|
|
139
|
-
const textNode = new TextService(String(text))
|
|
140
|
-
textNode.ownerDocumentStore = this.ownerDocument
|
|
141
|
-
this.appendChild(textNode)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
147
|
-
* @param {PseudoNode} childNode The node to add
|
|
148
|
-
* @returns {PseudoNode} The added node
|
|
149
|
-
*/
|
|
150
|
-
appendChild (childNode) {
|
|
151
|
-
return this.insertBefore(childNode, null)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Whether this kind of node can have children (text, comments and attributes cannot).
|
|
156
|
-
* @returns {boolean}
|
|
157
|
-
*/
|
|
158
|
-
get acceptsChildren () {
|
|
159
|
-
return true
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Make a copy of this node without its children, its parent or its listeners, which is what cloneNode starts from.
|
|
164
|
-
* Kinds of node which are made with arguments override this to give them.
|
|
165
|
-
* @returns {NodeService}
|
|
166
|
-
*/
|
|
167
|
-
cloneShallow () {
|
|
168
|
-
const copy = new this.constructor()
|
|
169
|
-
copy.nodeValue = this.nodeValue
|
|
170
|
-
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
171
|
-
return copy
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Whether another node of the same type is equal to this one apart from its children, which isEqualNode compares
|
|
176
|
-
* afterwards. Kinds of node with more to compare (an element has attributes) override this.
|
|
177
|
-
* @param {NodeService} other The node to compare with
|
|
178
|
-
* @returns {boolean}
|
|
179
|
-
*/
|
|
180
|
-
equalsShallow (other) {
|
|
181
|
-
return this.nodeName === other.nodeName && this.nodeValue === other.nodeValue
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Add nodes (strings become text nodes) as the last children of this node, in the order given.
|
|
186
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
187
|
-
* @throws {Error} When this kind of node cannot have children
|
|
188
|
-
*/
|
|
189
|
-
append (...nodes) {
|
|
190
|
-
nodes.forEach(node => this.appendChild(this.toChildNode(node)))
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Add nodes (strings become text nodes) as the first children of this node, in the order given.
|
|
195
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
196
|
-
* @throws {Error} When this kind of node cannot have children
|
|
197
|
-
*/
|
|
198
|
-
prepend (...nodes) {
|
|
199
|
-
const reference = this.firstChild
|
|
200
|
-
nodes.forEach(node => this.insertBefore(this.toChildNode(node), reference))
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Remove every child of this node and put the given nodes (strings become text nodes) in their place, in order.
|
|
205
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
206
|
-
* @throws {Error} When this kind of node cannot have children
|
|
207
|
-
*/
|
|
208
|
-
replaceChildren (...nodes) {
|
|
209
|
-
while (this.firstChild) {
|
|
210
|
-
this.removeChild(this.firstChild)
|
|
211
|
-
}
|
|
212
|
-
this.append(...nodes)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Add nodes (strings become text nodes) as this node's previous siblings, in order. Does nothing when this node has
|
|
217
|
-
* no parent.
|
|
218
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
219
|
-
*/
|
|
220
|
-
before (...nodes) {
|
|
221
|
-
const parent = this.parentNode
|
|
222
|
-
if (!parent) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), this))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Add nodes (strings become text nodes) as this node's next siblings, in order. Does nothing when this node has no
|
|
230
|
-
* parent.
|
|
231
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
232
|
-
*/
|
|
233
|
-
after (...nodes) {
|
|
234
|
-
const parent = this.parentNode
|
|
235
|
-
if (!parent) {
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
const reference = this.nextSibling
|
|
239
|
-
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), reference))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Put the given nodes (strings become text nodes) where this node is, in order, then remove this node. Does nothing
|
|
244
|
-
* when this node has no parent.
|
|
245
|
-
* @param {...(PseudoNode|string)} nodes The nodes (or text) to put in this node's place
|
|
246
|
-
*/
|
|
247
|
-
replaceWith (...nodes) {
|
|
248
|
-
const parent = this.parentNode
|
|
249
|
-
if (!parent) {
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), this))
|
|
253
|
-
parent.removeChild(this)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Remove this node from its parent. Does nothing when it has no parent.
|
|
258
|
-
*/
|
|
259
|
-
remove () {
|
|
260
|
-
if (this.parentNode) {
|
|
261
|
-
this.parentNode.removeChild(this)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Turn a value given to append / prepend / before / after / replaceWith / replaceChildren into a node: a string
|
|
267
|
-
* becomes a text node belonging to this node's document, anything else is returned as it is.
|
|
268
|
-
* @param {PseudoNode|string} value The value to add
|
|
269
|
-
* @returns {PseudoNode}
|
|
270
|
-
*/
|
|
271
|
-
toChildNode (value) {
|
|
272
|
-
if (typeof value !== 'string') {
|
|
273
|
-
return value
|
|
274
|
-
}
|
|
275
|
-
const text = new TextService(value)
|
|
276
|
-
text.ownerDocumentStore = this.ownerDocument
|
|
277
|
-
return text
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Every element below this node with the given tag name (or every element when tagName is *), live.
|
|
282
|
-
* @param {string} tagName
|
|
283
|
-
* @returns {PseudoHTMLCollection}
|
|
284
|
-
*/
|
|
285
|
-
getElementsByTagName (tagName) {
|
|
286
|
-
const matchesTag = tagName === '*' ? () => true : element => element.tagName === tagName
|
|
287
|
-
return new HTMLCollectionService_1.HTMLCollectionService(this, matchesTag, true)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Every element below this node which has all of the given (space separated) classes, live.
|
|
292
|
-
* @param {string} className
|
|
293
|
-
* @returns {PseudoHTMLCollection}
|
|
294
|
-
*/
|
|
295
|
-
getElementsByClassName (className) {
|
|
296
|
-
const names = className.trim().split(/\s+/).filter(Boolean)
|
|
297
|
-
return new HTMLCollectionService_1.HTMLCollectionService(this, element => names.every(name => element.classList.contains(name)), true)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* The first element below this node which matches the CSS selector, in tree order, or null when there is none.
|
|
302
|
-
* @param {string} selectors A CSS selector
|
|
303
|
-
* @returns {PseudoElement|null}
|
|
304
|
-
*/
|
|
305
|
-
querySelector (selectors) {
|
|
306
|
-
return (0, query_1.querySelector)(selectors, this)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Every element below this node which matches the CSS selector, in tree order. A plain array (not a live
|
|
311
|
-
* collection): like the DOM's querySelectorAll, it is a snapshot taken when it is called.
|
|
312
|
-
* @param {string} selectors A CSS selector
|
|
313
|
-
* @returns {Array<PseudoElement>}
|
|
314
|
-
*/
|
|
315
|
-
querySelectorAll (selectors) {
|
|
316
|
-
return (0, query_1.querySelectorAll)(selectors, this)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
321
|
-
* (for example elements applying default events) can do so.
|
|
322
|
-
* @param {NodeService} child The node which was inserted
|
|
323
|
-
*/
|
|
324
|
-
childInserted (child) {}
|
|
325
|
-
/**
|
|
326
|
-
* Make a copy of this node (without its parent, and without its event listeners). With deep the children are copied
|
|
327
|
-
* too, all the way down.
|
|
328
|
-
* @param {boolean} [deep=false] Copy the children as well
|
|
329
|
-
* @returns {PseudoNode}
|
|
330
|
-
*/
|
|
331
|
-
cloneNode (deep = false) {
|
|
332
|
-
const copy = this.cloneShallow()
|
|
333
|
-
if (deep) {
|
|
334
|
-
Array.from(this.childNodes).forEach(child => copy.appendChild(child.cloneNode(true)))
|
|
335
|
-
}
|
|
336
|
-
return copy
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Say where another node is in relation to this one, as the bits of NodeService.DOCUMENT_POSITION_*: 0 for this node
|
|
341
|
-
* itself, DISCONNECTED (with IMPLEMENTATION_SPECIFIC and a consistent PRECEDING or FOLLOWING) for a node in another tree,
|
|
342
|
-
* CONTAINS + PRECEDING when the other node is an ancestor, CONTAINED_BY + FOLLOWING when it is a descendant,
|
|
343
|
-
* otherwise PRECEDING or FOLLOWING by their order in the tree.
|
|
344
|
-
* @param {PseudoNode} otherNode The node to locate
|
|
345
|
-
* @returns {number}
|
|
346
|
-
*/
|
|
347
|
-
compareDocumentPosition (otherNode) {
|
|
348
|
-
if (otherNode === this) {
|
|
349
|
-
return 0
|
|
350
|
-
}
|
|
351
|
-
const pathFromRoot = node => {
|
|
352
|
-
const path = []
|
|
353
|
-
for (let current = node; current; current = current.parentNode) {
|
|
354
|
-
path.unshift(current)
|
|
355
|
-
}
|
|
356
|
-
return path
|
|
357
|
-
}
|
|
358
|
-
const mine = pathFromRoot(this)
|
|
359
|
-
const theirs = pathFromRoot(otherNode)
|
|
360
|
-
if (mine[0] !== theirs[0]) {
|
|
361
|
-
// Not in the same tree, so there is no real order: use a consistent one (the order the nodes were made in)
|
|
362
|
-
const before = otherNode.nodeId < this.nodeId ? NodeService.DOCUMENT_POSITION_PRECEDING : NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
363
|
-
return NodeService.DOCUMENT_POSITION_DISCONNECTED | NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | before
|
|
364
|
-
}
|
|
365
|
-
if (theirs.length < mine.length && theirs.every((node, index) => mine[index] === node)) {
|
|
366
|
-
return NodeService.DOCUMENT_POSITION_CONTAINS | NodeService.DOCUMENT_POSITION_PRECEDING
|
|
367
|
-
}
|
|
368
|
-
if (mine.length < theirs.length && mine.every((node, index) => theirs[index] === node)) {
|
|
369
|
-
return NodeService.DOCUMENT_POSITION_CONTAINED_BY | NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
370
|
-
}
|
|
371
|
-
// The paths part ways at siblings: whichever comes first among them is first in the tree
|
|
372
|
-
let depth = 0
|
|
373
|
-
while (mine[depth] === theirs[depth]) {
|
|
374
|
-
++depth
|
|
375
|
-
}
|
|
376
|
-
for (let sibling = mine[depth].nextSibling; sibling; sibling = sibling.nextSibling) {
|
|
377
|
-
if (sibling === theirs[depth]) {
|
|
378
|
-
return NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return NodeService.DOCUMENT_POSITION_PRECEDING
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Check whether a node is this node or one of its descendants.
|
|
386
|
-
* @param {PseudoNode|null} otherNode The node to look for
|
|
387
|
-
* @returns {boolean}
|
|
388
|
-
*/
|
|
389
|
-
contains (otherNode) {
|
|
390
|
-
let current = otherNode
|
|
391
|
-
while (current) {
|
|
392
|
-
if (current === this) {
|
|
393
|
-
return true
|
|
394
|
-
}
|
|
395
|
-
current = current.parentNode
|
|
396
|
-
}
|
|
397
|
-
return false
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
getRootNode (options = {
|
|
401
|
-
composed: false
|
|
402
|
-
}) {
|
|
403
|
-
return this.parent ? this.parent.getRootNode(options) : this
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
hasChildNodes () {
|
|
407
|
-
return this.childList.length > 0
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Insert a node as a child of this node, before the given child (or at the end when there is none). A node which is
|
|
412
|
-
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
413
|
-
* @param {PseudoNode} newNode The node to insert
|
|
414
|
-
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
415
|
-
* @returns {PseudoNode} The inserted node
|
|
416
|
-
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
417
|
-
*/
|
|
418
|
-
insertBefore (newNode, referenceNode = null) {
|
|
419
|
-
if (!this.acceptsChildren) {
|
|
420
|
-
throw new Error('This kind of node cannot have children.')
|
|
421
|
-
}
|
|
422
|
-
if (referenceNode !== null && referenceNode.parentNode !== this) {
|
|
423
|
-
throw new Error('The node before which the new node is to be inserted is not a child of this node.')
|
|
424
|
-
}
|
|
425
|
-
if (newNode === referenceNode) {
|
|
426
|
-
// Inserting a node before itself leaves it where it is
|
|
427
|
-
return newNode
|
|
428
|
-
}
|
|
429
|
-
if (typeof newNode.contains === 'function' && newNode.contains(this)) {
|
|
430
|
-
throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
|
|
431
|
-
}
|
|
432
|
-
if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
|
|
433
|
-
// The children of a fragment are inserted (moved) in order, and the fragment is left empty
|
|
434
|
-
while (newNode.firstChild) {
|
|
435
|
-
this.insertBefore(newNode.firstChild, referenceNode)
|
|
436
|
-
}
|
|
437
|
-
return newNode
|
|
438
|
-
}
|
|
439
|
-
if (newNode.parentNode) {
|
|
440
|
-
// A node can only be in one place, so it is moved from where it was
|
|
441
|
-
newNode.parentNode.removeChild(newNode)
|
|
442
|
-
}
|
|
443
|
-
const linker = new TreeLinker_1.TreeLinker({
|
|
444
|
-
data: newNode
|
|
445
|
-
})
|
|
446
|
-
this.childList.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
|
|
447
|
-
const inserted = newNode
|
|
448
|
-
inserted.parent = this
|
|
449
|
-
inserted.listLinker = linker
|
|
450
|
-
this.childInserted(inserted)
|
|
451
|
-
return newNode
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
isDefaultNamespace (namespaceURI) {
|
|
455
|
-
return namespaceURI === null
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Whether another node is the same as this one, by what they hold: the same type, name and value (an element also
|
|
460
|
-
* needs the same attributes), and children which are equal in the same order.
|
|
461
|
-
* @param {PseudoNode|null} otherNode The node to compare with
|
|
462
|
-
* @returns {boolean}
|
|
463
|
-
*/
|
|
464
|
-
isEqualNode (otherNode) {
|
|
465
|
-
if (!otherNode || otherNode.nodeType !== this.nodeType || !this.equalsShallow(otherNode)) {
|
|
466
|
-
return false
|
|
467
|
-
}
|
|
468
|
-
const mine = Array.from(this.childNodes)
|
|
469
|
-
const theirs = Array.from(otherNode.childNodes)
|
|
470
|
-
return mine.length === theirs.length && mine.every((child, index) => child.isEqualNode(theirs[index]))
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
isSameNode (otherNode) {
|
|
474
|
-
return this === otherNode
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
lookupPrefix (namespace) {
|
|
478
|
-
return null
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
lookupNamespaceURI (prefix) {
|
|
482
|
-
return null
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Tidy the text below this node: neighbouring text nodes are joined into one and empty text nodes are removed.
|
|
487
|
-
*/
|
|
488
|
-
normalize () {
|
|
489
|
-
let child = this.firstChild
|
|
490
|
-
while (child) {
|
|
491
|
-
if (child.nodeType === NodeService.TEXT_NODE) {
|
|
492
|
-
// Join the text nodes which follow into this one, and drop it when it is empty
|
|
493
|
-
let text = child.nodeValue
|
|
494
|
-
let following = child.nextSibling
|
|
495
|
-
while (following && following.nodeType === NodeService.TEXT_NODE) {
|
|
496
|
-
text += following.nodeValue
|
|
497
|
-
const after = following.nextSibling
|
|
498
|
-
this.removeChild(following)
|
|
499
|
-
following = after
|
|
500
|
-
}
|
|
501
|
-
child.nodeValue = text
|
|
502
|
-
if (text === '') {
|
|
503
|
-
this.removeChild(child)
|
|
504
|
-
}
|
|
505
|
-
child = following
|
|
506
|
-
} else {
|
|
507
|
-
child.normalize()
|
|
508
|
-
child = child.nextSibling
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
515
|
-
* @param {PseudoNode} childElement The child node to remove
|
|
516
|
-
* @returns {PseudoNode} The removed node
|
|
517
|
-
* @throws {Error} When the node is not a child of this node
|
|
518
|
-
*/
|
|
519
|
-
removeChild (childElement) {
|
|
520
|
-
if (!childElement || childElement.parentNode !== this) {
|
|
521
|
-
throw new Error('The node to be removed is not a child of this node.')
|
|
522
|
-
}
|
|
523
|
-
const removed = childElement
|
|
524
|
-
this.childList.remove(removed.listLinker)
|
|
525
|
-
removed.parent = null
|
|
526
|
-
removed.listLinker = null
|
|
527
|
-
return childElement
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
532
|
-
* @param {PseudoNode} newChild The node which takes the place
|
|
533
|
-
* @param {PseudoNode} oldChild The child of this node to replace
|
|
534
|
-
* @returns {PseudoNode} The replaced node
|
|
535
|
-
* @throws {Error} When the old node is not a child of this node
|
|
536
|
-
*/
|
|
537
|
-
replaceChild (newChild, oldChild) {
|
|
538
|
-
if (!oldChild || oldChild.parentNode !== this) {
|
|
539
|
-
throw new Error('The node to be replaced is not a child of this node.')
|
|
540
|
-
}
|
|
541
|
-
if (newChild === oldChild) {
|
|
542
|
-
return oldChild
|
|
543
|
-
}
|
|
544
|
-
// The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
|
|
545
|
-
let reference = oldChild.nextSibling
|
|
546
|
-
if (reference === newChild) {
|
|
547
|
-
reference = newChild.nextSibling
|
|
548
|
-
}
|
|
549
|
-
this.removeChild(oldChild)
|
|
550
|
-
this.insertBefore(newChild, reference)
|
|
551
|
-
return oldChild
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
exports.NodeService = NodeService
|
|
555
|
-
NodeService.DEFAULT_NODE = 0
|
|
556
|
-
NodeService.ELEMENT_NODE = 1
|
|
557
|
-
NodeService.ATTRIBUTE_NODE = 2
|
|
558
|
-
NodeService.TEXT_NODE = 3
|
|
559
|
-
NodeService.CDATA_SECTION_NODE = 4
|
|
560
|
-
NodeService.ENTITY_REFERENCE_NODE = 5
|
|
561
|
-
NodeService.ENTITY_NODE = 6
|
|
562
|
-
NodeService.PROCESSING_INSTRUCTION_NODE = 7
|
|
563
|
-
NodeService.COMMENT_NODE = 8
|
|
564
|
-
NodeService.DOCUMENT_NODE = 9
|
|
565
|
-
NodeService.DOCUMENT_TYPE_NODE = 10
|
|
566
|
-
NodeService.DOCUMENT_FRAGMENT_NODE = 11
|
|
567
|
-
NodeService.NOTATION_NODE = 12
|
|
568
|
-
NodeService.DOCUMENT_POSITION_DISCONNECTED = 1
|
|
569
|
-
NodeService.DOCUMENT_POSITION_PRECEDING = 2
|
|
570
|
-
NodeService.DOCUMENT_POSITION_FOLLOWING = 4
|
|
571
|
-
NodeService.DOCUMENT_POSITION_CONTAINS = 8
|
|
572
|
-
NodeService.DOCUMENT_POSITION_CONTAINED_BY = 16
|
|
573
|
-
NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32
|
|
574
|
-
NodeService.nextNodeId = 1
|
|
575
|
-
/**
|
|
576
|
-
* Simulate the behaviour of the Text Class when there is no DOM available: the text in an element.
|
|
577
|
-
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
578
|
-
* @class
|
|
579
|
-
* @augments NodeService
|
|
580
|
-
* @property {string} data - The text
|
|
581
|
-
* @property {number} length - How many characters there are
|
|
582
|
-
* @property {string} wholeText - The text of this node and of the text nodes next to it
|
|
583
|
-
*/
|
|
584
|
-
class TextService extends NodeService {
|
|
585
|
-
/**
|
|
586
|
-
* @param {string} [data=''] The text
|
|
587
|
-
* @constructor
|
|
588
|
-
*/
|
|
589
|
-
constructor (data = '') {
|
|
590
|
-
super()
|
|
591
|
-
this.nodeValue = String(data)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
get acceptsChildren () {
|
|
595
|
-
return false
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
get nodeName () {
|
|
599
|
-
return '#text'
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
get nodeType () {
|
|
603
|
-
return NodeService.TEXT_NODE
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
get data () {
|
|
607
|
-
return this.nodeValue
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
set data (data) {
|
|
611
|
-
this.nodeValue = String(data)
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
get length () {
|
|
615
|
-
return this.data.length
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
get textContent () {
|
|
619
|
-
return this.data
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
set textContent (text) {
|
|
623
|
-
this.data = text === null ? '' : String(text)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
get wholeText () {
|
|
627
|
-
let first = this
|
|
628
|
-
while (first.previousSibling && first.previousSibling.nodeType === NodeService.TEXT_NODE) {
|
|
629
|
-
first = first.previousSibling
|
|
630
|
-
}
|
|
631
|
-
let text = ''
|
|
632
|
-
for (let current = first; current && current.nodeType === NodeService.TEXT_NODE; current = current.nextSibling) {
|
|
633
|
-
text += current.nodeValue
|
|
634
|
-
}
|
|
635
|
-
return text
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Break this text node in two at a position: this node keeps the text before it and a new node with the rest is put
|
|
640
|
-
* after this one.
|
|
641
|
-
* @param {number} offset How many characters stay in this node
|
|
642
|
-
* @returns {TextService} The new node
|
|
643
|
-
* @throws {Error} When the offset is beyond the end of the text
|
|
644
|
-
*/
|
|
645
|
-
splitText (offset) {
|
|
646
|
-
if (offset < 0 || offset > this.length) {
|
|
647
|
-
throw new Error('The offset is beyond the end of the text.')
|
|
648
|
-
}
|
|
649
|
-
const rest = new TextService(this.data.slice(offset))
|
|
650
|
-
rest.ownerDocumentStore = this.ownerDocumentStore
|
|
651
|
-
this.data = this.data.slice(0, offset)
|
|
652
|
-
if (this.parentNode) {
|
|
653
|
-
this.parentNode.insertBefore(rest, this.nextSibling)
|
|
654
|
-
}
|
|
655
|
-
return rest
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
cloneShallow () {
|
|
659
|
-
const copy = new TextService(this.data)
|
|
660
|
-
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
661
|
-
return copy
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
exports.TextService = TextService
|
|
665
|
-
/**
|
|
666
|
-
* Simulate the behaviour of the Comment Class when there is no DOM available: a note in the markup which is not shown.
|
|
667
|
-
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
668
|
-
* @class
|
|
669
|
-
* @augments NodeService
|
|
670
|
-
* @property {string} data - The comment
|
|
671
|
-
* @property {number} length - How many characters there are
|
|
672
|
-
*/
|
|
673
|
-
class CommentService extends NodeService {
|
|
674
|
-
/**
|
|
675
|
-
* @param {string} [data=''] The comment
|
|
676
|
-
* @constructor
|
|
677
|
-
*/
|
|
678
|
-
constructor (data = '') {
|
|
679
|
-
super()
|
|
680
|
-
this.nodeValue = String(data)
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
get acceptsChildren () {
|
|
684
|
-
return false
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
get nodeName () {
|
|
688
|
-
return '#comment'
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
get nodeType () {
|
|
692
|
-
return NodeService.COMMENT_NODE
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
get data () {
|
|
696
|
-
return this.nodeValue
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
set data (data) {
|
|
700
|
-
this.nodeValue = String(data)
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
get length () {
|
|
704
|
-
return this.data.length
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
get textContent () {
|
|
708
|
-
return this.data
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
set textContent (text) {
|
|
712
|
-
this.data = text === null ? '' : String(text)
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
cloneShallow () {
|
|
716
|
-
const copy = new CommentService(this.data)
|
|
717
|
-
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
718
|
-
return copy
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
exports.CommentService = CommentService
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
4
|
+
require('core-js/modules/esnext.iterator.every.js')
|
|
5
|
+
require('core-js/modules/esnext.iterator.filter.js')
|
|
6
|
+
require('core-js/modules/esnext.iterator.for-each.js')
|
|
7
|
+
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
8
|
+
return mod && mod.__esModule
|
|
9
|
+
? mod
|
|
10
|
+
: {
|
|
11
|
+
default: mod
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(exports, '__esModule', {
|
|
15
|
+
value: true
|
|
16
|
+
})
|
|
17
|
+
exports.CommentService = exports.TextService = exports.NodeService = void 0
|
|
18
|
+
/**
|
|
19
|
+
* @file Substitute for the DOM Node Class.
|
|
20
|
+
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
21
|
+
* @version 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
24
|
+
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
25
|
+
const EventTargetService_1 = __importDefault(require('./EventTargetService'))
|
|
26
|
+
const HTMLCollectionService_1 = require('./HTMLCollectionService')
|
|
27
|
+
const query_1 = require('../factories/query')
|
|
28
|
+
/**
|
|
29
|
+
* Simulate the behaviour of the Node Class when there is no DOM available.
|
|
30
|
+
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
31
|
+
* @class
|
|
32
|
+
* @augments PseudoEventTarget
|
|
33
|
+
* @property {string} name
|
|
34
|
+
* @property {function} appendChild
|
|
35
|
+
* @property {function} removeChild
|
|
36
|
+
*/
|
|
37
|
+
class NodeService extends EventTargetService_1.default {
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @constructor
|
|
41
|
+
*/
|
|
42
|
+
constructor () {
|
|
43
|
+
super()
|
|
44
|
+
this.nodeValueStore = null
|
|
45
|
+
this.ownerDocumentStore = null
|
|
46
|
+
this.nodeId = NodeService.nextNodeId++
|
|
47
|
+
this.nodeNameValue = ''
|
|
48
|
+
this.childList = (0, generateNodeList_1.default)()
|
|
49
|
+
this.parent = null
|
|
50
|
+
this.listLinker = null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get baseURI () {
|
|
54
|
+
return window.location || '/'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get childNodes () {
|
|
58
|
+
return this.childList
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get firstChild () {
|
|
62
|
+
return this.childList.first ? this.childList.first.data : null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get isConnected () {
|
|
66
|
+
// Connected means the tree the node is in has a document at the top
|
|
67
|
+
return this.getRootNode().nodeType === NodeService.DOCUMENT_NODE
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get lastChild () {
|
|
71
|
+
return this.childList.last ? this.childList.last.data : null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get nextSibling () {
|
|
75
|
+
return this.listLinker && this.listLinker.next ? this.listLinker.next.data : null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get nodeName () {
|
|
79
|
+
return this.nodeNameValue || ''
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get nodeType () {
|
|
83
|
+
return NodeService.DEFAULT_NODE
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get nodeValue () {
|
|
87
|
+
return this.nodeValueStore
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set nodeValue (value) {
|
|
91
|
+
this.nodeValueStore = value
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get ownerDocument () {
|
|
95
|
+
if (this.nodeType === NodeService.DOCUMENT_NODE) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
const root = this.getRootNode()
|
|
99
|
+
return root.nodeType === NodeService.DOCUMENT_NODE ? root : this.ownerDocumentStore
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get parentNode () {
|
|
103
|
+
return this.parent
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get parentElement () {
|
|
107
|
+
return this.parent && this.parent.nodeType === NodeService.ELEMENT_NODE ? this.parent : null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get previousSibling () {
|
|
111
|
+
return this.listLinker && this.listLinker.prev ? this.listLinker.prev.data : null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get textContent () {
|
|
115
|
+
if (this.nodeType === NodeService.DOCUMENT_NODE || this.nodeType === NodeService.DOCUMENT_TYPE_NODE) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
// The text of everything below, in order (comments and the like do not count)
|
|
119
|
+
let text = ''
|
|
120
|
+
Array.from(this.childNodes).forEach(child => {
|
|
121
|
+
if (child.nodeType === NodeService.TEXT_NODE) {
|
|
122
|
+
text += child.nodeValue
|
|
123
|
+
} else if (child.nodeType !== NodeService.COMMENT_NODE) {
|
|
124
|
+
text += child.textContent
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
return text
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
set textContent (text) {
|
|
131
|
+
if (this.nodeType === NodeService.DOCUMENT_NODE || this.nodeType === NodeService.DOCUMENT_TYPE_NODE) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
// All the children are replaced by a single text node (or none for an empty text)
|
|
135
|
+
while (this.firstChild) {
|
|
136
|
+
this.removeChild(this.firstChild)
|
|
137
|
+
}
|
|
138
|
+
if (text !== null && typeof text !== 'undefined' && String(text) !== '') {
|
|
139
|
+
const textNode = new TextService(String(text))
|
|
140
|
+
textNode.ownerDocumentStore = this.ownerDocument
|
|
141
|
+
this.appendChild(textNode)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
147
|
+
* @param {PseudoNode} childNode The node to add
|
|
148
|
+
* @returns {PseudoNode} The added node
|
|
149
|
+
*/
|
|
150
|
+
appendChild (childNode) {
|
|
151
|
+
return this.insertBefore(childNode, null)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Whether this kind of node can have children (text, comments and attributes cannot).
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
*/
|
|
158
|
+
get acceptsChildren () {
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Make a copy of this node without its children, its parent or its listeners, which is what cloneNode starts from.
|
|
164
|
+
* Kinds of node which are made with arguments override this to give them.
|
|
165
|
+
* @returns {NodeService}
|
|
166
|
+
*/
|
|
167
|
+
cloneShallow () {
|
|
168
|
+
const copy = new this.constructor()
|
|
169
|
+
copy.nodeValue = this.nodeValue
|
|
170
|
+
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
171
|
+
return copy
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Whether another node of the same type is equal to this one apart from its children, which isEqualNode compares
|
|
176
|
+
* afterwards. Kinds of node with more to compare (an element has attributes) override this.
|
|
177
|
+
* @param {NodeService} other The node to compare with
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
equalsShallow (other) {
|
|
181
|
+
return this.nodeName === other.nodeName && this.nodeValue === other.nodeValue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Add nodes (strings become text nodes) as the last children of this node, in the order given.
|
|
186
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
187
|
+
* @throws {Error} When this kind of node cannot have children
|
|
188
|
+
*/
|
|
189
|
+
append (...nodes) {
|
|
190
|
+
nodes.forEach(node => this.appendChild(this.toChildNode(node)))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add nodes (strings become text nodes) as the first children of this node, in the order given.
|
|
195
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
196
|
+
* @throws {Error} When this kind of node cannot have children
|
|
197
|
+
*/
|
|
198
|
+
prepend (...nodes) {
|
|
199
|
+
const reference = this.firstChild
|
|
200
|
+
nodes.forEach(node => this.insertBefore(this.toChildNode(node), reference))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove every child of this node and put the given nodes (strings become text nodes) in their place, in order.
|
|
205
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
206
|
+
* @throws {Error} When this kind of node cannot have children
|
|
207
|
+
*/
|
|
208
|
+
replaceChildren (...nodes) {
|
|
209
|
+
while (this.firstChild) {
|
|
210
|
+
this.removeChild(this.firstChild)
|
|
211
|
+
}
|
|
212
|
+
this.append(...nodes)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Add nodes (strings become text nodes) as this node's previous siblings, in order. Does nothing when this node has
|
|
217
|
+
* no parent.
|
|
218
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
219
|
+
*/
|
|
220
|
+
before (...nodes) {
|
|
221
|
+
const parent = this.parentNode
|
|
222
|
+
if (!parent) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), this))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add nodes (strings become text nodes) as this node's next siblings, in order. Does nothing when this node has no
|
|
230
|
+
* parent.
|
|
231
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to add
|
|
232
|
+
*/
|
|
233
|
+
after (...nodes) {
|
|
234
|
+
const parent = this.parentNode
|
|
235
|
+
if (!parent) {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
const reference = this.nextSibling
|
|
239
|
+
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), reference))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Put the given nodes (strings become text nodes) where this node is, in order, then remove this node. Does nothing
|
|
244
|
+
* when this node has no parent.
|
|
245
|
+
* @param {...(PseudoNode|string)} nodes The nodes (or text) to put in this node's place
|
|
246
|
+
*/
|
|
247
|
+
replaceWith (...nodes) {
|
|
248
|
+
const parent = this.parentNode
|
|
249
|
+
if (!parent) {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
nodes.forEach(node => parent.insertBefore(this.toChildNode(node), this))
|
|
253
|
+
parent.removeChild(this)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Remove this node from its parent. Does nothing when it has no parent.
|
|
258
|
+
*/
|
|
259
|
+
remove () {
|
|
260
|
+
if (this.parentNode) {
|
|
261
|
+
this.parentNode.removeChild(this)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Turn a value given to append / prepend / before / after / replaceWith / replaceChildren into a node: a string
|
|
267
|
+
* becomes a text node belonging to this node's document, anything else is returned as it is.
|
|
268
|
+
* @param {PseudoNode|string} value The value to add
|
|
269
|
+
* @returns {PseudoNode}
|
|
270
|
+
*/
|
|
271
|
+
toChildNode (value) {
|
|
272
|
+
if (typeof value !== 'string') {
|
|
273
|
+
return value
|
|
274
|
+
}
|
|
275
|
+
const text = new TextService(value)
|
|
276
|
+
text.ownerDocumentStore = this.ownerDocument
|
|
277
|
+
return text
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Every element below this node with the given tag name (or every element when tagName is *), live.
|
|
282
|
+
* @param {string} tagName
|
|
283
|
+
* @returns {PseudoHTMLCollection}
|
|
284
|
+
*/
|
|
285
|
+
getElementsByTagName (tagName) {
|
|
286
|
+
const matchesTag = tagName === '*' ? () => true : element => element.tagName === tagName
|
|
287
|
+
return new HTMLCollectionService_1.HTMLCollectionService(this, matchesTag, true)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Every element below this node which has all of the given (space separated) classes, live.
|
|
292
|
+
* @param {string} className
|
|
293
|
+
* @returns {PseudoHTMLCollection}
|
|
294
|
+
*/
|
|
295
|
+
getElementsByClassName (className) {
|
|
296
|
+
const names = className.trim().split(/\s+/).filter(Boolean)
|
|
297
|
+
return new HTMLCollectionService_1.HTMLCollectionService(this, element => names.every(name => element.classList.contains(name)), true)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The first element below this node which matches the CSS selector, in tree order, or null when there is none.
|
|
302
|
+
* @param {string} selectors A CSS selector
|
|
303
|
+
* @returns {PseudoElement|null}
|
|
304
|
+
*/
|
|
305
|
+
querySelector (selectors) {
|
|
306
|
+
return (0, query_1.querySelector)(selectors, this)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Every element below this node which matches the CSS selector, in tree order. A plain array (not a live
|
|
311
|
+
* collection): like the DOM's querySelectorAll, it is a snapshot taken when it is called.
|
|
312
|
+
* @param {string} selectors A CSS selector
|
|
313
|
+
* @returns {Array<PseudoElement>}
|
|
314
|
+
*/
|
|
315
|
+
querySelectorAll (selectors) {
|
|
316
|
+
return (0, query_1.querySelectorAll)(selectors, this)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
321
|
+
* (for example elements applying default events) can do so.
|
|
322
|
+
* @param {NodeService} child The node which was inserted
|
|
323
|
+
*/
|
|
324
|
+
childInserted (child) {}
|
|
325
|
+
/**
|
|
326
|
+
* Make a copy of this node (without its parent, and without its event listeners). With deep the children are copied
|
|
327
|
+
* too, all the way down.
|
|
328
|
+
* @param {boolean} [deep=false] Copy the children as well
|
|
329
|
+
* @returns {PseudoNode}
|
|
330
|
+
*/
|
|
331
|
+
cloneNode (deep = false) {
|
|
332
|
+
const copy = this.cloneShallow()
|
|
333
|
+
if (deep) {
|
|
334
|
+
Array.from(this.childNodes).forEach(child => copy.appendChild(child.cloneNode(true)))
|
|
335
|
+
}
|
|
336
|
+
return copy
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Say where another node is in relation to this one, as the bits of NodeService.DOCUMENT_POSITION_*: 0 for this node
|
|
341
|
+
* itself, DISCONNECTED (with IMPLEMENTATION_SPECIFIC and a consistent PRECEDING or FOLLOWING) for a node in another tree,
|
|
342
|
+
* CONTAINS + PRECEDING when the other node is an ancestor, CONTAINED_BY + FOLLOWING when it is a descendant,
|
|
343
|
+
* otherwise PRECEDING or FOLLOWING by their order in the tree.
|
|
344
|
+
* @param {PseudoNode} otherNode The node to locate
|
|
345
|
+
* @returns {number}
|
|
346
|
+
*/
|
|
347
|
+
compareDocumentPosition (otherNode) {
|
|
348
|
+
if (otherNode === this) {
|
|
349
|
+
return 0
|
|
350
|
+
}
|
|
351
|
+
const pathFromRoot = node => {
|
|
352
|
+
const path = []
|
|
353
|
+
for (let current = node; current; current = current.parentNode) {
|
|
354
|
+
path.unshift(current)
|
|
355
|
+
}
|
|
356
|
+
return path
|
|
357
|
+
}
|
|
358
|
+
const mine = pathFromRoot(this)
|
|
359
|
+
const theirs = pathFromRoot(otherNode)
|
|
360
|
+
if (mine[0] !== theirs[0]) {
|
|
361
|
+
// Not in the same tree, so there is no real order: use a consistent one (the order the nodes were made in)
|
|
362
|
+
const before = otherNode.nodeId < this.nodeId ? NodeService.DOCUMENT_POSITION_PRECEDING : NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
363
|
+
return NodeService.DOCUMENT_POSITION_DISCONNECTED | NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | before
|
|
364
|
+
}
|
|
365
|
+
if (theirs.length < mine.length && theirs.every((node, index) => mine[index] === node)) {
|
|
366
|
+
return NodeService.DOCUMENT_POSITION_CONTAINS | NodeService.DOCUMENT_POSITION_PRECEDING
|
|
367
|
+
}
|
|
368
|
+
if (mine.length < theirs.length && mine.every((node, index) => theirs[index] === node)) {
|
|
369
|
+
return NodeService.DOCUMENT_POSITION_CONTAINED_BY | NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
370
|
+
}
|
|
371
|
+
// The paths part ways at siblings: whichever comes first among them is first in the tree
|
|
372
|
+
let depth = 0
|
|
373
|
+
while (mine[depth] === theirs[depth]) {
|
|
374
|
+
++depth
|
|
375
|
+
}
|
|
376
|
+
for (let sibling = mine[depth].nextSibling; sibling; sibling = sibling.nextSibling) {
|
|
377
|
+
if (sibling === theirs[depth]) {
|
|
378
|
+
return NodeService.DOCUMENT_POSITION_FOLLOWING
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return NodeService.DOCUMENT_POSITION_PRECEDING
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check whether a node is this node or one of its descendants.
|
|
386
|
+
* @param {PseudoNode|null} otherNode The node to look for
|
|
387
|
+
* @returns {boolean}
|
|
388
|
+
*/
|
|
389
|
+
contains (otherNode) {
|
|
390
|
+
let current = otherNode
|
|
391
|
+
while (current) {
|
|
392
|
+
if (current === this) {
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
current = current.parentNode
|
|
396
|
+
}
|
|
397
|
+
return false
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
getRootNode (options = {
|
|
401
|
+
composed: false
|
|
402
|
+
}) {
|
|
403
|
+
return this.parent ? this.parent.getRootNode(options) : this
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
hasChildNodes () {
|
|
407
|
+
return this.childList.length > 0
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Insert a node as a child of this node, before the given child (or at the end when there is none). A node which is
|
|
412
|
+
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
413
|
+
* @param {PseudoNode} newNode The node to insert
|
|
414
|
+
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
415
|
+
* @returns {PseudoNode} The inserted node
|
|
416
|
+
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
417
|
+
*/
|
|
418
|
+
insertBefore (newNode, referenceNode = null) {
|
|
419
|
+
if (!this.acceptsChildren) {
|
|
420
|
+
throw new Error('This kind of node cannot have children.')
|
|
421
|
+
}
|
|
422
|
+
if (referenceNode !== null && referenceNode.parentNode !== this) {
|
|
423
|
+
throw new Error('The node before which the new node is to be inserted is not a child of this node.')
|
|
424
|
+
}
|
|
425
|
+
if (newNode === referenceNode) {
|
|
426
|
+
// Inserting a node before itself leaves it where it is
|
|
427
|
+
return newNode
|
|
428
|
+
}
|
|
429
|
+
if (typeof newNode.contains === 'function' && newNode.contains(this)) {
|
|
430
|
+
throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
|
|
431
|
+
}
|
|
432
|
+
if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
|
|
433
|
+
// The children of a fragment are inserted (moved) in order, and the fragment is left empty
|
|
434
|
+
while (newNode.firstChild) {
|
|
435
|
+
this.insertBefore(newNode.firstChild, referenceNode)
|
|
436
|
+
}
|
|
437
|
+
return newNode
|
|
438
|
+
}
|
|
439
|
+
if (newNode.parentNode) {
|
|
440
|
+
// A node can only be in one place, so it is moved from where it was
|
|
441
|
+
newNode.parentNode.removeChild(newNode)
|
|
442
|
+
}
|
|
443
|
+
const linker = new TreeLinker_1.TreeLinker({
|
|
444
|
+
data: newNode
|
|
445
|
+
})
|
|
446
|
+
this.childList.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
|
|
447
|
+
const inserted = newNode
|
|
448
|
+
inserted.parent = this
|
|
449
|
+
inserted.listLinker = linker
|
|
450
|
+
this.childInserted(inserted)
|
|
451
|
+
return newNode
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
isDefaultNamespace (namespaceURI) {
|
|
455
|
+
return namespaceURI === null
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Whether another node is the same as this one, by what they hold: the same type, name and value (an element also
|
|
460
|
+
* needs the same attributes), and children which are equal in the same order.
|
|
461
|
+
* @param {PseudoNode|null} otherNode The node to compare with
|
|
462
|
+
* @returns {boolean}
|
|
463
|
+
*/
|
|
464
|
+
isEqualNode (otherNode) {
|
|
465
|
+
if (!otherNode || otherNode.nodeType !== this.nodeType || !this.equalsShallow(otherNode)) {
|
|
466
|
+
return false
|
|
467
|
+
}
|
|
468
|
+
const mine = Array.from(this.childNodes)
|
|
469
|
+
const theirs = Array.from(otherNode.childNodes)
|
|
470
|
+
return mine.length === theirs.length && mine.every((child, index) => child.isEqualNode(theirs[index]))
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
isSameNode (otherNode) {
|
|
474
|
+
return this === otherNode
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
lookupPrefix (namespace) {
|
|
478
|
+
return null
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
lookupNamespaceURI (prefix) {
|
|
482
|
+
return null
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Tidy the text below this node: neighbouring text nodes are joined into one and empty text nodes are removed.
|
|
487
|
+
*/
|
|
488
|
+
normalize () {
|
|
489
|
+
let child = this.firstChild
|
|
490
|
+
while (child) {
|
|
491
|
+
if (child.nodeType === NodeService.TEXT_NODE) {
|
|
492
|
+
// Join the text nodes which follow into this one, and drop it when it is empty
|
|
493
|
+
let text = child.nodeValue
|
|
494
|
+
let following = child.nextSibling
|
|
495
|
+
while (following && following.nodeType === NodeService.TEXT_NODE) {
|
|
496
|
+
text += following.nodeValue
|
|
497
|
+
const after = following.nextSibling
|
|
498
|
+
this.removeChild(following)
|
|
499
|
+
following = after
|
|
500
|
+
}
|
|
501
|
+
child.nodeValue = text
|
|
502
|
+
if (text === '') {
|
|
503
|
+
this.removeChild(child)
|
|
504
|
+
}
|
|
505
|
+
child = following
|
|
506
|
+
} else {
|
|
507
|
+
child.normalize()
|
|
508
|
+
child = child.nextSibling
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
515
|
+
* @param {PseudoNode} childElement The child node to remove
|
|
516
|
+
* @returns {PseudoNode} The removed node
|
|
517
|
+
* @throws {Error} When the node is not a child of this node
|
|
518
|
+
*/
|
|
519
|
+
removeChild (childElement) {
|
|
520
|
+
if (!childElement || childElement.parentNode !== this) {
|
|
521
|
+
throw new Error('The node to be removed is not a child of this node.')
|
|
522
|
+
}
|
|
523
|
+
const removed = childElement
|
|
524
|
+
this.childList.remove(removed.listLinker)
|
|
525
|
+
removed.parent = null
|
|
526
|
+
removed.listLinker = null
|
|
527
|
+
return childElement
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
532
|
+
* @param {PseudoNode} newChild The node which takes the place
|
|
533
|
+
* @param {PseudoNode} oldChild The child of this node to replace
|
|
534
|
+
* @returns {PseudoNode} The replaced node
|
|
535
|
+
* @throws {Error} When the old node is not a child of this node
|
|
536
|
+
*/
|
|
537
|
+
replaceChild (newChild, oldChild) {
|
|
538
|
+
if (!oldChild || oldChild.parentNode !== this) {
|
|
539
|
+
throw new Error('The node to be replaced is not a child of this node.')
|
|
540
|
+
}
|
|
541
|
+
if (newChild === oldChild) {
|
|
542
|
+
return oldChild
|
|
543
|
+
}
|
|
544
|
+
// The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
|
|
545
|
+
let reference = oldChild.nextSibling
|
|
546
|
+
if (reference === newChild) {
|
|
547
|
+
reference = newChild.nextSibling
|
|
548
|
+
}
|
|
549
|
+
this.removeChild(oldChild)
|
|
550
|
+
this.insertBefore(newChild, reference)
|
|
551
|
+
return oldChild
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
exports.NodeService = NodeService
|
|
555
|
+
NodeService.DEFAULT_NODE = 0
|
|
556
|
+
NodeService.ELEMENT_NODE = 1
|
|
557
|
+
NodeService.ATTRIBUTE_NODE = 2
|
|
558
|
+
NodeService.TEXT_NODE = 3
|
|
559
|
+
NodeService.CDATA_SECTION_NODE = 4
|
|
560
|
+
NodeService.ENTITY_REFERENCE_NODE = 5
|
|
561
|
+
NodeService.ENTITY_NODE = 6
|
|
562
|
+
NodeService.PROCESSING_INSTRUCTION_NODE = 7
|
|
563
|
+
NodeService.COMMENT_NODE = 8
|
|
564
|
+
NodeService.DOCUMENT_NODE = 9
|
|
565
|
+
NodeService.DOCUMENT_TYPE_NODE = 10
|
|
566
|
+
NodeService.DOCUMENT_FRAGMENT_NODE = 11
|
|
567
|
+
NodeService.NOTATION_NODE = 12
|
|
568
|
+
NodeService.DOCUMENT_POSITION_DISCONNECTED = 1
|
|
569
|
+
NodeService.DOCUMENT_POSITION_PRECEDING = 2
|
|
570
|
+
NodeService.DOCUMENT_POSITION_FOLLOWING = 4
|
|
571
|
+
NodeService.DOCUMENT_POSITION_CONTAINS = 8
|
|
572
|
+
NodeService.DOCUMENT_POSITION_CONTAINED_BY = 16
|
|
573
|
+
NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32
|
|
574
|
+
NodeService.nextNodeId = 1
|
|
575
|
+
/**
|
|
576
|
+
* Simulate the behaviour of the Text Class when there is no DOM available: the text in an element.
|
|
577
|
+
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
578
|
+
* @class
|
|
579
|
+
* @augments NodeService
|
|
580
|
+
* @property {string} data - The text
|
|
581
|
+
* @property {number} length - How many characters there are
|
|
582
|
+
* @property {string} wholeText - The text of this node and of the text nodes next to it
|
|
583
|
+
*/
|
|
584
|
+
class TextService extends NodeService {
|
|
585
|
+
/**
|
|
586
|
+
* @param {string} [data=''] The text
|
|
587
|
+
* @constructor
|
|
588
|
+
*/
|
|
589
|
+
constructor (data = '') {
|
|
590
|
+
super()
|
|
591
|
+
this.nodeValue = String(data)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
get acceptsChildren () {
|
|
595
|
+
return false
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
get nodeName () {
|
|
599
|
+
return '#text'
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
get nodeType () {
|
|
603
|
+
return NodeService.TEXT_NODE
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
get data () {
|
|
607
|
+
return this.nodeValue
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
set data (data) {
|
|
611
|
+
this.nodeValue = String(data)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
get length () {
|
|
615
|
+
return this.data.length
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
get textContent () {
|
|
619
|
+
return this.data
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
set textContent (text) {
|
|
623
|
+
this.data = text === null ? '' : String(text)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
get wholeText () {
|
|
627
|
+
let first = this
|
|
628
|
+
while (first.previousSibling && first.previousSibling.nodeType === NodeService.TEXT_NODE) {
|
|
629
|
+
first = first.previousSibling
|
|
630
|
+
}
|
|
631
|
+
let text = ''
|
|
632
|
+
for (let current = first; current && current.nodeType === NodeService.TEXT_NODE; current = current.nextSibling) {
|
|
633
|
+
text += current.nodeValue
|
|
634
|
+
}
|
|
635
|
+
return text
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Break this text node in two at a position: this node keeps the text before it and a new node with the rest is put
|
|
640
|
+
* after this one.
|
|
641
|
+
* @param {number} offset How many characters stay in this node
|
|
642
|
+
* @returns {TextService} The new node
|
|
643
|
+
* @throws {Error} When the offset is beyond the end of the text
|
|
644
|
+
*/
|
|
645
|
+
splitText (offset) {
|
|
646
|
+
if (offset < 0 || offset > this.length) {
|
|
647
|
+
throw new Error('The offset is beyond the end of the text.')
|
|
648
|
+
}
|
|
649
|
+
const rest = new TextService(this.data.slice(offset))
|
|
650
|
+
rest.ownerDocumentStore = this.ownerDocumentStore
|
|
651
|
+
this.data = this.data.slice(0, offset)
|
|
652
|
+
if (this.parentNode) {
|
|
653
|
+
this.parentNode.insertBefore(rest, this.nextSibling)
|
|
654
|
+
}
|
|
655
|
+
return rest
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
cloneShallow () {
|
|
659
|
+
const copy = new TextService(this.data)
|
|
660
|
+
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
661
|
+
return copy
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
exports.TextService = TextService
|
|
665
|
+
/**
|
|
666
|
+
* Simulate the behaviour of the Comment Class when there is no DOM available: a note in the markup which is not shown.
|
|
667
|
+
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
668
|
+
* @class
|
|
669
|
+
* @augments NodeService
|
|
670
|
+
* @property {string} data - The comment
|
|
671
|
+
* @property {number} length - How many characters there are
|
|
672
|
+
*/
|
|
673
|
+
class CommentService extends NodeService {
|
|
674
|
+
/**
|
|
675
|
+
* @param {string} [data=''] The comment
|
|
676
|
+
* @constructor
|
|
677
|
+
*/
|
|
678
|
+
constructor (data = '') {
|
|
679
|
+
super()
|
|
680
|
+
this.nodeValue = String(data)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
get acceptsChildren () {
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
get nodeName () {
|
|
688
|
+
return '#comment'
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
get nodeType () {
|
|
692
|
+
return NodeService.COMMENT_NODE
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
get data () {
|
|
696
|
+
return this.nodeValue
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
set data (data) {
|
|
700
|
+
this.nodeValue = String(data)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
get length () {
|
|
704
|
+
return this.data.length
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
get textContent () {
|
|
708
|
+
return this.data
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
set textContent (text) {
|
|
712
|
+
this.data = text === null ? '' : String(text)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
cloneShallow () {
|
|
716
|
+
const copy = new CommentService(this.data)
|
|
717
|
+
copy.ownerDocumentStore = this.ownerDocumentStore
|
|
718
|
+
return copy
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
exports.CommentService = CommentService
|