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.
Files changed (57) hide show
  1. package/README.md +468 -237
  2. package/browser/pseudo-dom.js +986 -563
  3. package/browser/pseudo-dom.min.js +1 -1
  4. package/dist/classes/PseudoHTMLDocument.d.ts +17 -43
  5. package/dist/classes/PseudoHTMLDocument.js +24 -94
  6. package/dist/classes/PseudoHTMLDocument.min.js +1 -1
  7. package/dist/factories/createDataset.d.ts +10 -0
  8. package/dist/factories/createDataset.js +78 -0
  9. package/dist/factories/createDataset.min.js +1 -0
  10. package/dist/factories/createStyleDeclaration.d.ts +30 -0
  11. package/dist/factories/createStyleDeclaration.js +64 -0
  12. package/dist/factories/createStyleDeclaration.min.js +1 -0
  13. package/dist/factories/cssSelectAdapter.d.ts +23 -23
  14. package/dist/factories/cssSelectAdapter.js +87 -87
  15. package/dist/factories/query.d.ts +33 -33
  16. package/dist/factories/query.js +66 -66
  17. package/dist/functions/getElementById.d.ts +13 -0
  18. package/dist/functions/getElementById.js +33 -0
  19. package/dist/functions/getElementById.min.js +1 -0
  20. package/dist/interfaces/PseudoCSSStyleDeclaration.d.ts +14 -0
  21. package/dist/interfaces/PseudoCSSStyleDeclaration.js +5 -0
  22. package/dist/interfaces/PseudoCSSStyleDeclaration.min.js +1 -0
  23. package/dist/interfaces/PseudoDocument.d.ts +35 -10
  24. package/dist/interfaces/PseudoDocumentFragment.d.ts +7 -0
  25. package/dist/interfaces/PseudoElement.d.ts +702 -702
  26. package/dist/interfaces/PseudoHTMLElement.d.ts +33 -22
  27. package/dist/services/CSSStyleDeclarationService.d.ts +73 -0
  28. package/dist/services/CSSStyleDeclarationService.js +156 -0
  29. package/dist/services/CSSStyleDeclarationService.min.js +1 -0
  30. package/dist/services/DocumentFragmentService.d.ts +8 -0
  31. package/dist/services/DocumentFragmentService.js +18 -0
  32. package/dist/services/DocumentFragmentService.min.js +1 -1
  33. package/dist/services/DocumentService.d.ts +46 -19
  34. package/dist/services/DocumentService.js +95 -46
  35. package/dist/services/DocumentService.min.js +1 -1
  36. package/dist/services/ElementService.d.ts +191 -191
  37. package/dist/services/ElementService.js +426 -426
  38. package/dist/services/HTMLCollectionService.d.ts +54 -54
  39. package/dist/services/HTMLCollectionService.js +92 -92
  40. package/dist/services/HTMLElementService.d.ts +87 -57
  41. package/dist/services/HTMLElementService.js +202 -163
  42. package/dist/services/HTMLElementService.min.js +1 -1
  43. package/dist/services/NodeService.d.ts +297 -297
  44. package/dist/services/NodeService.js +721 -721
  45. package/package.json +2 -2
  46. package/dist/classes.d.ts +0 -20
  47. package/dist/classes.js +0 -31
  48. package/dist/classes.min.js +0 -1
  49. package/dist/factories/generateNode.d.ts +0 -32
  50. package/dist/factories/generateNode.js +0 -85
  51. package/dist/factories/generateNode.min.js +0 -1
  52. package/dist/factories.d.ts +0 -11
  53. package/dist/factories.js +0 -27
  54. package/dist/factories.min.js +0 -1
  55. package/dist/functions.d.ts +0 -5
  56. package/dist/functions.js +0 -23
  57. 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