pseudo-dom 0.10.0 → 0.11.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.
@@ -1,732 +1,732 @@
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
- * Every element below this node with the given tag name, live. There is no real namespace parsing here, so this
302
- * ignores the namespace and behaves exactly like getElementsByTagName.
303
- * @param {string} namespace Ignored
304
- * @param {string} tagName
305
- * @returns {PseudoHTMLCollection}
306
- */
307
- getElementsByTagNameNS (namespace, tagName) {
308
- return this.getElementsByTagName(tagName)
309
- }
310
-
311
- /**
312
- * The first element below this node which matches the CSS selector, in tree order, or null when there is none.
313
- * @param {string} selectors A CSS selector
314
- * @returns {PseudoElement|null}
315
- */
316
- querySelector (selectors) {
317
- return (0, query_1.querySelector)(selectors, this)
318
- }
319
-
320
- /**
321
- * Every element below this node which matches the CSS selector, in tree order. A plain array (not a live
322
- * collection): like the DOM's querySelectorAll, it is a snapshot taken when it is called.
323
- * @param {string} selectors A CSS selector
324
- * @returns {Array<PseudoElement>}
325
- */
326
- querySelectorAll (selectors) {
327
- return (0, query_1.querySelectorAll)(selectors, this)
328
- }
329
-
330
- /**
331
- * Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
332
- * (for example elements applying default events) can do so.
333
- * @param {NodeService} child The node which was inserted
334
- */
335
- childInserted (child) {}
336
- /**
337
- * Make a copy of this node (without its parent, and without its event listeners). With deep the children are copied
338
- * too, all the way down.
339
- * @param {boolean} [deep=false] Copy the children as well
340
- * @returns {PseudoNode}
341
- */
342
- cloneNode (deep = false) {
343
- const copy = this.cloneShallow()
344
- if (deep) {
345
- Array.from(this.childNodes).forEach(child => copy.appendChild(child.cloneNode(true)))
346
- }
347
- return copy
348
- }
349
-
350
- /**
351
- * Say where another node is in relation to this one, as the bits of NodeService.DOCUMENT_POSITION_*: 0 for this node
352
- * itself, DISCONNECTED (with IMPLEMENTATION_SPECIFIC and a consistent PRECEDING or FOLLOWING) for a node in another tree,
353
- * CONTAINS + PRECEDING when the other node is an ancestor, CONTAINED_BY + FOLLOWING when it is a descendant,
354
- * otherwise PRECEDING or FOLLOWING by their order in the tree.
355
- * @param {PseudoNode} otherNode The node to locate
356
- * @returns {number}
357
- */
358
- compareDocumentPosition (otherNode) {
359
- if (otherNode === this) {
360
- return 0
361
- }
362
- const pathFromRoot = node => {
363
- const path = []
364
- for (let current = node; current; current = current.parentNode) {
365
- path.unshift(current)
366
- }
367
- return path
368
- }
369
- const mine = pathFromRoot(this)
370
- const theirs = pathFromRoot(otherNode)
371
- if (mine[0] !== theirs[0]) {
372
- // Not in the same tree, so there is no real order: use a consistent one (the order the nodes were made in)
373
- const before = otherNode.nodeId < this.nodeId ? NodeService.DOCUMENT_POSITION_PRECEDING : NodeService.DOCUMENT_POSITION_FOLLOWING
374
- return NodeService.DOCUMENT_POSITION_DISCONNECTED | NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | before
375
- }
376
- if (theirs.length < mine.length && theirs.every((node, index) => mine[index] === node)) {
377
- return NodeService.DOCUMENT_POSITION_CONTAINS | NodeService.DOCUMENT_POSITION_PRECEDING
378
- }
379
- if (mine.length < theirs.length && mine.every((node, index) => theirs[index] === node)) {
380
- return NodeService.DOCUMENT_POSITION_CONTAINED_BY | NodeService.DOCUMENT_POSITION_FOLLOWING
381
- }
382
- // The paths part ways at siblings: whichever comes first among them is first in the tree
383
- let depth = 0
384
- while (mine[depth] === theirs[depth]) {
385
- ++depth
386
- }
387
- for (let sibling = mine[depth].nextSibling; sibling; sibling = sibling.nextSibling) {
388
- if (sibling === theirs[depth]) {
389
- return NodeService.DOCUMENT_POSITION_FOLLOWING
390
- }
391
- }
392
- return NodeService.DOCUMENT_POSITION_PRECEDING
393
- }
394
-
395
- /**
396
- * Check whether a node is this node or one of its descendants.
397
- * @param {PseudoNode|null} otherNode The node to look for
398
- * @returns {boolean}
399
- */
400
- contains (otherNode) {
401
- let current = otherNode
402
- while (current) {
403
- if (current === this) {
404
- return true
405
- }
406
- current = current.parentNode
407
- }
408
- return false
409
- }
410
-
411
- getRootNode (options = {
412
- composed: false
413
- }) {
414
- return this.parent ? this.parent.getRootNode(options) : this
415
- }
416
-
417
- hasChildNodes () {
418
- return this.childList.length > 0
419
- }
420
-
421
- /**
422
- * 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
423
- * already in a tree is moved, and the children of a document fragment are moved in order.
424
- * @param {PseudoNode} newNode The node to insert
425
- * @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
426
- * @returns {PseudoNode} The inserted node
427
- * @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
428
- */
429
- insertBefore (newNode, referenceNode = null) {
430
- if (!this.acceptsChildren) {
431
- throw new Error('This kind of node cannot have children.')
432
- }
433
- if (referenceNode !== null && referenceNode.parentNode !== this) {
434
- throw new Error('The node before which the new node is to be inserted is not a child of this node.')
435
- }
436
- if (newNode === referenceNode) {
437
- // Inserting a node before itself leaves it where it is
438
- return newNode
439
- }
440
- if (typeof newNode.contains === 'function' && newNode.contains(this)) {
441
- throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
442
- }
443
- if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
444
- // The children of a fragment are inserted (moved) in order, and the fragment is left empty
445
- while (newNode.firstChild) {
446
- this.insertBefore(newNode.firstChild, referenceNode)
447
- }
448
- return newNode
449
- }
450
- if (newNode.parentNode) {
451
- // A node can only be in one place, so it is moved from where it was
452
- newNode.parentNode.removeChild(newNode)
453
- }
454
- const linker = new TreeLinker_1.TreeLinker({
455
- data: newNode
456
- })
457
- this.childList.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
458
- const inserted = newNode
459
- inserted.parent = this
460
- inserted.listLinker = linker
461
- this.childInserted(inserted)
462
- return newNode
463
- }
464
-
465
- isDefaultNamespace (namespaceURI) {
466
- return namespaceURI === null
467
- }
468
-
469
- /**
470
- * Whether another node is the same as this one, by what they hold: the same type, name and value (an element also
471
- * needs the same attributes), and children which are equal in the same order.
472
- * @param {PseudoNode|null} otherNode The node to compare with
473
- * @returns {boolean}
474
- */
475
- isEqualNode (otherNode) {
476
- if (!otherNode || otherNode.nodeType !== this.nodeType || !this.equalsShallow(otherNode)) {
477
- return false
478
- }
479
- const mine = Array.from(this.childNodes)
480
- const theirs = Array.from(otherNode.childNodes)
481
- return mine.length === theirs.length && mine.every((child, index) => child.isEqualNode(theirs[index]))
482
- }
483
-
484
- isSameNode (otherNode) {
485
- return this === otherNode
486
- }
487
-
488
- lookupPrefix (namespace) {
489
- return null
490
- }
491
-
492
- lookupNamespaceURI (prefix) {
493
- return null
494
- }
495
-
496
- /**
497
- * Tidy the text below this node: neighbouring text nodes are joined into one and empty text nodes are removed.
498
- */
499
- normalize () {
500
- let child = this.firstChild
501
- while (child) {
502
- if (child.nodeType === NodeService.TEXT_NODE) {
503
- // Join the text nodes which follow into this one, and drop it when it is empty
504
- let text = child.nodeValue
505
- let following = child.nextSibling
506
- while (following && following.nodeType === NodeService.TEXT_NODE) {
507
- text += following.nodeValue
508
- const after = following.nextSibling
509
- this.removeChild(following)
510
- following = after
511
- }
512
- child.nodeValue = text
513
- if (text === '') {
514
- this.removeChild(child)
515
- }
516
- child = following
517
- } else {
518
- child.normalize()
519
- child = child.nextSibling
520
- }
521
- }
522
- }
523
-
524
- /**
525
- * Remove a child from this node, it no longer has a parent or siblings afterwards.
526
- * @param {PseudoNode} childElement The child node to remove
527
- * @returns {PseudoNode} The removed node
528
- * @throws {Error} When the node is not a child of this node
529
- */
530
- removeChild (childElement) {
531
- if (!childElement || childElement.parentNode !== this) {
532
- throw new Error('The node to be removed is not a child of this node.')
533
- }
534
- const removed = childElement
535
- this.childList.remove(removed.listLinker)
536
- removed.parent = null
537
- removed.listLinker = null
538
- return childElement
539
- }
540
-
541
- /**
542
- * Replace a child of this node with another node (which is moved if it is already in a tree).
543
- * @param {PseudoNode} newChild The node which takes the place
544
- * @param {PseudoNode} oldChild The child of this node to replace
545
- * @returns {PseudoNode} The replaced node
546
- * @throws {Error} When the old node is not a child of this node
547
- */
548
- replaceChild (newChild, oldChild) {
549
- if (!oldChild || oldChild.parentNode !== this) {
550
- throw new Error('The node to be replaced is not a child of this node.')
551
- }
552
- if (newChild === oldChild) {
553
- return oldChild
554
- }
555
- // The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
556
- let reference = oldChild.nextSibling
557
- if (reference === newChild) {
558
- reference = newChild.nextSibling
559
- }
560
- this.removeChild(oldChild)
561
- this.insertBefore(newChild, reference)
562
- return oldChild
563
- }
564
- }
565
- exports.NodeService = NodeService
566
- NodeService.DEFAULT_NODE = 0
567
- NodeService.ELEMENT_NODE = 1
568
- NodeService.ATTRIBUTE_NODE = 2
569
- NodeService.TEXT_NODE = 3
570
- NodeService.CDATA_SECTION_NODE = 4
571
- NodeService.ENTITY_REFERENCE_NODE = 5
572
- NodeService.ENTITY_NODE = 6
573
- NodeService.PROCESSING_INSTRUCTION_NODE = 7
574
- NodeService.COMMENT_NODE = 8
575
- NodeService.DOCUMENT_NODE = 9
576
- NodeService.DOCUMENT_TYPE_NODE = 10
577
- NodeService.DOCUMENT_FRAGMENT_NODE = 11
578
- NodeService.NOTATION_NODE = 12
579
- NodeService.DOCUMENT_POSITION_DISCONNECTED = 1
580
- NodeService.DOCUMENT_POSITION_PRECEDING = 2
581
- NodeService.DOCUMENT_POSITION_FOLLOWING = 4
582
- NodeService.DOCUMENT_POSITION_CONTAINS = 8
583
- NodeService.DOCUMENT_POSITION_CONTAINED_BY = 16
584
- NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32
585
- NodeService.nextNodeId = 1
586
- /**
587
- * Simulate the behaviour of the Text Class when there is no DOM available: the text in an element.
588
- * @author Joshua Heagle <joshuaheagle@gmail.com>
589
- * @class
590
- * @augments NodeService
591
- * @property {string} data - The text
592
- * @property {number} length - How many characters there are
593
- * @property {string} wholeText - The text of this node and of the text nodes next to it
594
- */
595
- class TextService extends NodeService {
596
- /**
597
- * @param {string} [data=''] The text
598
- * @constructor
599
- */
600
- constructor (data = '') {
601
- super()
602
- this.nodeValue = String(data)
603
- }
604
-
605
- get acceptsChildren () {
606
- return false
607
- }
608
-
609
- get nodeName () {
610
- return '#text'
611
- }
612
-
613
- get nodeType () {
614
- return NodeService.TEXT_NODE
615
- }
616
-
617
- get data () {
618
- return this.nodeValue
619
- }
620
-
621
- set data (data) {
622
- this.nodeValue = String(data)
623
- }
624
-
625
- get length () {
626
- return this.data.length
627
- }
628
-
629
- get textContent () {
630
- return this.data
631
- }
632
-
633
- set textContent (text) {
634
- this.data = text === null ? '' : String(text)
635
- }
636
-
637
- get wholeText () {
638
- let first = this
639
- while (first.previousSibling && first.previousSibling.nodeType === NodeService.TEXT_NODE) {
640
- first = first.previousSibling
641
- }
642
- let text = ''
643
- for (let current = first; current && current.nodeType === NodeService.TEXT_NODE; current = current.nextSibling) {
644
- text += current.nodeValue
645
- }
646
- return text
647
- }
648
-
649
- /**
650
- * 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
651
- * after this one.
652
- * @param {number} offset How many characters stay in this node
653
- * @returns {TextService} The new node
654
- * @throws {Error} When the offset is beyond the end of the text
655
- */
656
- splitText (offset) {
657
- if (offset < 0 || offset > this.length) {
658
- throw new Error('The offset is beyond the end of the text.')
659
- }
660
- const rest = new TextService(this.data.slice(offset))
661
- rest.ownerDocumentStore = this.ownerDocumentStore
662
- this.data = this.data.slice(0, offset)
663
- if (this.parentNode) {
664
- this.parentNode.insertBefore(rest, this.nextSibling)
665
- }
666
- return rest
667
- }
668
-
669
- cloneShallow () {
670
- const copy = new TextService(this.data)
671
- copy.ownerDocumentStore = this.ownerDocumentStore
672
- return copy
673
- }
674
- }
675
- exports.TextService = TextService
676
- /**
677
- * Simulate the behaviour of the Comment Class when there is no DOM available: a note in the markup which is not shown.
678
- * @author Joshua Heagle <joshuaheagle@gmail.com>
679
- * @class
680
- * @augments NodeService
681
- * @property {string} data - The comment
682
- * @property {number} length - How many characters there are
683
- */
684
- class CommentService extends NodeService {
685
- /**
686
- * @param {string} [data=''] The comment
687
- * @constructor
688
- */
689
- constructor (data = '') {
690
- super()
691
- this.nodeValue = String(data)
692
- }
693
-
694
- get acceptsChildren () {
695
- return false
696
- }
697
-
698
- get nodeName () {
699
- return '#comment'
700
- }
701
-
702
- get nodeType () {
703
- return NodeService.COMMENT_NODE
704
- }
705
-
706
- get data () {
707
- return this.nodeValue
708
- }
709
-
710
- set data (data) {
711
- this.nodeValue = String(data)
712
- }
713
-
714
- get length () {
715
- return this.data.length
716
- }
717
-
718
- get textContent () {
719
- return this.data
720
- }
721
-
722
- set textContent (text) {
723
- this.data = text === null ? '' : String(text)
724
- }
725
-
726
- cloneShallow () {
727
- const copy = new CommentService(this.data)
728
- copy.ownerDocumentStore = this.ownerDocumentStore
729
- return copy
730
- }
731
- }
732
- 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
+ * Every element below this node with the given tag name, live. There is no real namespace parsing here, so this
302
+ * ignores the namespace and behaves exactly like getElementsByTagName.
303
+ * @param {string} namespace Ignored
304
+ * @param {string} tagName
305
+ * @returns {PseudoHTMLCollection}
306
+ */
307
+ getElementsByTagNameNS (namespace, tagName) {
308
+ return this.getElementsByTagName(tagName)
309
+ }
310
+
311
+ /**
312
+ * The first element below this node which matches the CSS selector, in tree order, or null when there is none.
313
+ * @param {string} selectors A CSS selector
314
+ * @returns {PseudoElement|null}
315
+ */
316
+ querySelector (selectors) {
317
+ return (0, query_1.querySelector)(selectors, this)
318
+ }
319
+
320
+ /**
321
+ * Every element below this node which matches the CSS selector, in tree order. A plain array (not a live
322
+ * collection): like the DOM's querySelectorAll, it is a snapshot taken when it is called.
323
+ * @param {string} selectors A CSS selector
324
+ * @returns {Array<PseudoElement>}
325
+ */
326
+ querySelectorAll (selectors) {
327
+ return (0, query_1.querySelectorAll)(selectors, this)
328
+ }
329
+
330
+ /**
331
+ * Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
332
+ * (for example elements applying default events) can do so.
333
+ * @param {NodeService} child The node which was inserted
334
+ */
335
+ childInserted (child) {}
336
+ /**
337
+ * Make a copy of this node (without its parent, and without its event listeners). With deep the children are copied
338
+ * too, all the way down.
339
+ * @param {boolean} [deep=false] Copy the children as well
340
+ * @returns {PseudoNode}
341
+ */
342
+ cloneNode (deep = false) {
343
+ const copy = this.cloneShallow()
344
+ if (deep) {
345
+ Array.from(this.childNodes).forEach(child => copy.appendChild(child.cloneNode(true)))
346
+ }
347
+ return copy
348
+ }
349
+
350
+ /**
351
+ * Say where another node is in relation to this one, as the bits of NodeService.DOCUMENT_POSITION_*: 0 for this node
352
+ * itself, DISCONNECTED (with IMPLEMENTATION_SPECIFIC and a consistent PRECEDING or FOLLOWING) for a node in another tree,
353
+ * CONTAINS + PRECEDING when the other node is an ancestor, CONTAINED_BY + FOLLOWING when it is a descendant,
354
+ * otherwise PRECEDING or FOLLOWING by their order in the tree.
355
+ * @param {PseudoNode} otherNode The node to locate
356
+ * @returns {number}
357
+ */
358
+ compareDocumentPosition (otherNode) {
359
+ if (otherNode === this) {
360
+ return 0
361
+ }
362
+ const pathFromRoot = node => {
363
+ const path = []
364
+ for (let current = node; current; current = current.parentNode) {
365
+ path.unshift(current)
366
+ }
367
+ return path
368
+ }
369
+ const mine = pathFromRoot(this)
370
+ const theirs = pathFromRoot(otherNode)
371
+ if (mine[0] !== theirs[0]) {
372
+ // Not in the same tree, so there is no real order: use a consistent one (the order the nodes were made in)
373
+ const before = otherNode.nodeId < this.nodeId ? NodeService.DOCUMENT_POSITION_PRECEDING : NodeService.DOCUMENT_POSITION_FOLLOWING
374
+ return NodeService.DOCUMENT_POSITION_DISCONNECTED | NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | before
375
+ }
376
+ if (theirs.length < mine.length && theirs.every((node, index) => mine[index] === node)) {
377
+ return NodeService.DOCUMENT_POSITION_CONTAINS | NodeService.DOCUMENT_POSITION_PRECEDING
378
+ }
379
+ if (mine.length < theirs.length && mine.every((node, index) => theirs[index] === node)) {
380
+ return NodeService.DOCUMENT_POSITION_CONTAINED_BY | NodeService.DOCUMENT_POSITION_FOLLOWING
381
+ }
382
+ // The paths part ways at siblings: whichever comes first among them is first in the tree
383
+ let depth = 0
384
+ while (mine[depth] === theirs[depth]) {
385
+ ++depth
386
+ }
387
+ for (let sibling = mine[depth].nextSibling; sibling; sibling = sibling.nextSibling) {
388
+ if (sibling === theirs[depth]) {
389
+ return NodeService.DOCUMENT_POSITION_FOLLOWING
390
+ }
391
+ }
392
+ return NodeService.DOCUMENT_POSITION_PRECEDING
393
+ }
394
+
395
+ /**
396
+ * Check whether a node is this node or one of its descendants.
397
+ * @param {PseudoNode|null} otherNode The node to look for
398
+ * @returns {boolean}
399
+ */
400
+ contains (otherNode) {
401
+ let current = otherNode
402
+ while (current) {
403
+ if (current === this) {
404
+ return true
405
+ }
406
+ current = current.parentNode
407
+ }
408
+ return false
409
+ }
410
+
411
+ getRootNode (options = {
412
+ composed: false
413
+ }) {
414
+ return this.parent ? this.parent.getRootNode(options) : this
415
+ }
416
+
417
+ hasChildNodes () {
418
+ return this.childList.length > 0
419
+ }
420
+
421
+ /**
422
+ * 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
423
+ * already in a tree is moved, and the children of a document fragment are moved in order.
424
+ * @param {PseudoNode} newNode The node to insert
425
+ * @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
426
+ * @returns {PseudoNode} The inserted node
427
+ * @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
428
+ */
429
+ insertBefore (newNode, referenceNode = null) {
430
+ if (!this.acceptsChildren) {
431
+ throw new Error('This kind of node cannot have children.')
432
+ }
433
+ if (referenceNode !== null && referenceNode.parentNode !== this) {
434
+ throw new Error('The node before which the new node is to be inserted is not a child of this node.')
435
+ }
436
+ if (newNode === referenceNode) {
437
+ // Inserting a node before itself leaves it where it is
438
+ return newNode
439
+ }
440
+ if (typeof newNode.contains === 'function' && newNode.contains(this)) {
441
+ throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
442
+ }
443
+ if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
444
+ // The children of a fragment are inserted (moved) in order, and the fragment is left empty
445
+ while (newNode.firstChild) {
446
+ this.insertBefore(newNode.firstChild, referenceNode)
447
+ }
448
+ return newNode
449
+ }
450
+ if (newNode.parentNode) {
451
+ // A node can only be in one place, so it is moved from where it was
452
+ newNode.parentNode.removeChild(newNode)
453
+ }
454
+ const linker = new TreeLinker_1.TreeLinker({
455
+ data: newNode
456
+ })
457
+ this.childList.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
458
+ const inserted = newNode
459
+ inserted.parent = this
460
+ inserted.listLinker = linker
461
+ this.childInserted(inserted)
462
+ return newNode
463
+ }
464
+
465
+ isDefaultNamespace (namespaceURI) {
466
+ return namespaceURI === null
467
+ }
468
+
469
+ /**
470
+ * Whether another node is the same as this one, by what they hold: the same type, name and value (an element also
471
+ * needs the same attributes), and children which are equal in the same order.
472
+ * @param {PseudoNode|null} otherNode The node to compare with
473
+ * @returns {boolean}
474
+ */
475
+ isEqualNode (otherNode) {
476
+ if (!otherNode || otherNode.nodeType !== this.nodeType || !this.equalsShallow(otherNode)) {
477
+ return false
478
+ }
479
+ const mine = Array.from(this.childNodes)
480
+ const theirs = Array.from(otherNode.childNodes)
481
+ return mine.length === theirs.length && mine.every((child, index) => child.isEqualNode(theirs[index]))
482
+ }
483
+
484
+ isSameNode (otherNode) {
485
+ return this === otherNode
486
+ }
487
+
488
+ lookupPrefix (namespace) {
489
+ return null
490
+ }
491
+
492
+ lookupNamespaceURI (prefix) {
493
+ return null
494
+ }
495
+
496
+ /**
497
+ * Tidy the text below this node: neighbouring text nodes are joined into one and empty text nodes are removed.
498
+ */
499
+ normalize () {
500
+ let child = this.firstChild
501
+ while (child) {
502
+ if (child.nodeType === NodeService.TEXT_NODE) {
503
+ // Join the text nodes which follow into this one, and drop it when it is empty
504
+ let text = child.nodeValue
505
+ let following = child.nextSibling
506
+ while (following && following.nodeType === NodeService.TEXT_NODE) {
507
+ text += following.nodeValue
508
+ const after = following.nextSibling
509
+ this.removeChild(following)
510
+ following = after
511
+ }
512
+ child.nodeValue = text
513
+ if (text === '') {
514
+ this.removeChild(child)
515
+ }
516
+ child = following
517
+ } else {
518
+ child.normalize()
519
+ child = child.nextSibling
520
+ }
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Remove a child from this node, it no longer has a parent or siblings afterwards.
526
+ * @param {PseudoNode} childElement The child node to remove
527
+ * @returns {PseudoNode} The removed node
528
+ * @throws {Error} When the node is not a child of this node
529
+ */
530
+ removeChild (childElement) {
531
+ if (!childElement || childElement.parentNode !== this) {
532
+ throw new Error('The node to be removed is not a child of this node.')
533
+ }
534
+ const removed = childElement
535
+ this.childList.remove(removed.listLinker)
536
+ removed.parent = null
537
+ removed.listLinker = null
538
+ return childElement
539
+ }
540
+
541
+ /**
542
+ * Replace a child of this node with another node (which is moved if it is already in a tree).
543
+ * @param {PseudoNode} newChild The node which takes the place
544
+ * @param {PseudoNode} oldChild The child of this node to replace
545
+ * @returns {PseudoNode} The replaced node
546
+ * @throws {Error} When the old node is not a child of this node
547
+ */
548
+ replaceChild (newChild, oldChild) {
549
+ if (!oldChild || oldChild.parentNode !== this) {
550
+ throw new Error('The node to be replaced is not a child of this node.')
551
+ }
552
+ if (newChild === oldChild) {
553
+ return oldChild
554
+ }
555
+ // The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
556
+ let reference = oldChild.nextSibling
557
+ if (reference === newChild) {
558
+ reference = newChild.nextSibling
559
+ }
560
+ this.removeChild(oldChild)
561
+ this.insertBefore(newChild, reference)
562
+ return oldChild
563
+ }
564
+ }
565
+ exports.NodeService = NodeService
566
+ NodeService.DEFAULT_NODE = 0
567
+ NodeService.ELEMENT_NODE = 1
568
+ NodeService.ATTRIBUTE_NODE = 2
569
+ NodeService.TEXT_NODE = 3
570
+ NodeService.CDATA_SECTION_NODE = 4
571
+ NodeService.ENTITY_REFERENCE_NODE = 5
572
+ NodeService.ENTITY_NODE = 6
573
+ NodeService.PROCESSING_INSTRUCTION_NODE = 7
574
+ NodeService.COMMENT_NODE = 8
575
+ NodeService.DOCUMENT_NODE = 9
576
+ NodeService.DOCUMENT_TYPE_NODE = 10
577
+ NodeService.DOCUMENT_FRAGMENT_NODE = 11
578
+ NodeService.NOTATION_NODE = 12
579
+ NodeService.DOCUMENT_POSITION_DISCONNECTED = 1
580
+ NodeService.DOCUMENT_POSITION_PRECEDING = 2
581
+ NodeService.DOCUMENT_POSITION_FOLLOWING = 4
582
+ NodeService.DOCUMENT_POSITION_CONTAINS = 8
583
+ NodeService.DOCUMENT_POSITION_CONTAINED_BY = 16
584
+ NodeService.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32
585
+ NodeService.nextNodeId = 1
586
+ /**
587
+ * Simulate the behaviour of the Text Class when there is no DOM available: the text in an element.
588
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
589
+ * @class
590
+ * @augments NodeService
591
+ * @property {string} data - The text
592
+ * @property {number} length - How many characters there are
593
+ * @property {string} wholeText - The text of this node and of the text nodes next to it
594
+ */
595
+ class TextService extends NodeService {
596
+ /**
597
+ * @param {string} [data=''] The text
598
+ * @constructor
599
+ */
600
+ constructor (data = '') {
601
+ super()
602
+ this.nodeValue = String(data)
603
+ }
604
+
605
+ get acceptsChildren () {
606
+ return false
607
+ }
608
+
609
+ get nodeName () {
610
+ return '#text'
611
+ }
612
+
613
+ get nodeType () {
614
+ return NodeService.TEXT_NODE
615
+ }
616
+
617
+ get data () {
618
+ return this.nodeValue
619
+ }
620
+
621
+ set data (data) {
622
+ this.nodeValue = String(data)
623
+ }
624
+
625
+ get length () {
626
+ return this.data.length
627
+ }
628
+
629
+ get textContent () {
630
+ return this.data
631
+ }
632
+
633
+ set textContent (text) {
634
+ this.data = text === null ? '' : String(text)
635
+ }
636
+
637
+ get wholeText () {
638
+ let first = this
639
+ while (first.previousSibling && first.previousSibling.nodeType === NodeService.TEXT_NODE) {
640
+ first = first.previousSibling
641
+ }
642
+ let text = ''
643
+ for (let current = first; current && current.nodeType === NodeService.TEXT_NODE; current = current.nextSibling) {
644
+ text += current.nodeValue
645
+ }
646
+ return text
647
+ }
648
+
649
+ /**
650
+ * 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
651
+ * after this one.
652
+ * @param {number} offset How many characters stay in this node
653
+ * @returns {TextService} The new node
654
+ * @throws {Error} When the offset is beyond the end of the text
655
+ */
656
+ splitText (offset) {
657
+ if (offset < 0 || offset > this.length) {
658
+ throw new Error('The offset is beyond the end of the text.')
659
+ }
660
+ const rest = new TextService(this.data.slice(offset))
661
+ rest.ownerDocumentStore = this.ownerDocumentStore
662
+ this.data = this.data.slice(0, offset)
663
+ if (this.parentNode) {
664
+ this.parentNode.insertBefore(rest, this.nextSibling)
665
+ }
666
+ return rest
667
+ }
668
+
669
+ cloneShallow () {
670
+ const copy = new TextService(this.data)
671
+ copy.ownerDocumentStore = this.ownerDocumentStore
672
+ return copy
673
+ }
674
+ }
675
+ exports.TextService = TextService
676
+ /**
677
+ * Simulate the behaviour of the Comment Class when there is no DOM available: a note in the markup which is not shown.
678
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
679
+ * @class
680
+ * @augments NodeService
681
+ * @property {string} data - The comment
682
+ * @property {number} length - How many characters there are
683
+ */
684
+ class CommentService extends NodeService {
685
+ /**
686
+ * @param {string} [data=''] The comment
687
+ * @constructor
688
+ */
689
+ constructor (data = '') {
690
+ super()
691
+ this.nodeValue = String(data)
692
+ }
693
+
694
+ get acceptsChildren () {
695
+ return false
696
+ }
697
+
698
+ get nodeName () {
699
+ return '#comment'
700
+ }
701
+
702
+ get nodeType () {
703
+ return NodeService.COMMENT_NODE
704
+ }
705
+
706
+ get data () {
707
+ return this.nodeValue
708
+ }
709
+
710
+ set data (data) {
711
+ this.nodeValue = String(data)
712
+ }
713
+
714
+ get length () {
715
+ return this.data.length
716
+ }
717
+
718
+ get textContent () {
719
+ return this.data
720
+ }
721
+
722
+ set textContent (text) {
723
+ this.data = text === null ? '' : String(text)
724
+ }
725
+
726
+ cloneShallow () {
727
+ const copy = new CommentService(this.data)
728
+ copy.ownerDocumentStore = this.ownerDocumentStore
729
+ return copy
730
+ }
731
+ }
732
+ exports.CommentService = CommentService