pseudo-dom 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +496 -72
- package/browser/pseudo-dom.js +619 -284
- package/browser/pseudo-dom.min.js +1 -1
- package/dist/classes/PseudoHTMLDocument.d.ts +2 -2
- package/dist/classes/PseudoHTMLDocument.js +10 -21
- package/dist/classes/PseudoHTMLDocument.min.js +1 -1
- package/dist/classes/PseudoNodeList.js +14 -8
- package/dist/classes/PseudoNodeList.min.js +1 -1
- package/dist/factories/generateNode.d.ts +2 -10
- package/dist/factories/generateNode.js +9 -42
- package/dist/factories/generateNode.min.js +1 -1
- package/dist/functions/getParentNodes.d.ts +8 -6
- package/dist/functions/getParentNodes.js +16 -9
- package/dist/functions/getParentNodes.min.js +1 -1
- package/dist/functions/getParentNodesFromAttribute.d.ts +7 -7
- package/dist/functions/getParentNodesFromAttribute.js +21 -1
- package/dist/functions/getParentNodesFromAttribute.min.js +1 -1
- package/dist/functions.d.ts +1 -1
- package/dist/services/DocumentFragmentService.d.ts +9 -0
- package/dist/services/DocumentFragmentService.js +16 -1
- package/dist/services/DocumentFragmentService.min.js +1 -1
- package/dist/services/DocumentService.d.ts +8 -0
- package/dist/services/DocumentService.js +15 -1
- package/dist/services/DocumentService.min.js +1 -1
- package/dist/services/ElementService.d.ts +5 -6
- package/dist/services/ElementService.js +17 -13
- package/dist/services/ElementService.min.js +1 -1
- package/dist/services/NodeService.d.ts +31 -16
- package/dist/services/NodeService.js +90 -33
- package/dist/services/NodeService.min.js +1 -1
- package/package.json +2 -2
package/browser/pseudo-dom.js
CHANGED
|
@@ -152,13 +152,6 @@
|
|
|
152
152
|
2: [function (require, module, exports) {
|
|
153
153
|
'use strict'
|
|
154
154
|
|
|
155
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
156
|
-
return mod && mod.__esModule
|
|
157
|
-
? mod
|
|
158
|
-
: {
|
|
159
|
-
default: mod
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
155
|
Object.defineProperty(exports, '__esModule', {
|
|
163
156
|
value: true
|
|
164
157
|
})
|
|
@@ -172,8 +165,6 @@
|
|
|
172
165
|
* @type {PseudoHTMLElement}
|
|
173
166
|
*/
|
|
174
167
|
const HTMLElementService_1 = require('../services/HTMLElementService')
|
|
175
|
-
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
176
|
-
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
177
168
|
/**
|
|
178
169
|
* Simulate the behaviour of the HTMLDocument Class when there is no DOM available.
|
|
179
170
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
@@ -181,7 +172,7 @@
|
|
|
181
172
|
* @augments PseudoHTMLElement
|
|
182
173
|
* @property {PseudoHTMLElement} head - A reference to the Head child element
|
|
183
174
|
* @property {PseudoHTMLElement} body - A reference to the Body child element
|
|
184
|
-
* @property {function} createElement - Generate a new PseudoHTMLElement
|
|
175
|
+
* @property {function} createElement - Generate a new PseudoHTMLElement (which is not in the document until it is appended)
|
|
185
176
|
*/
|
|
186
177
|
class PseudoHTMLDocument extends HTMLElementService_1.HTMLElementService {
|
|
187
178
|
/**
|
|
@@ -191,43 +182,41 @@
|
|
|
191
182
|
constructor () {
|
|
192
183
|
super()
|
|
193
184
|
const html = new HTMLElementService_1.HTMLElementService({
|
|
194
|
-
tagName: 'html'
|
|
195
|
-
parent: this
|
|
185
|
+
tagName: 'html'
|
|
196
186
|
})
|
|
187
|
+
this.appendChild(html)
|
|
197
188
|
/**
|
|
198
189
|
* Create document head element
|
|
199
190
|
* @type {PseudoHTMLElement}
|
|
200
191
|
*/
|
|
201
192
|
this.head = new HTMLElementService_1.HTMLElementService({
|
|
202
|
-
tagName: 'head'
|
|
203
|
-
parent: html
|
|
193
|
+
tagName: 'head'
|
|
204
194
|
})
|
|
195
|
+
html.appendChild(this.head)
|
|
205
196
|
/**
|
|
206
197
|
* Create document body element
|
|
207
198
|
* @type {PseudoHTMLElement}
|
|
208
199
|
*/
|
|
209
200
|
this.body = new HTMLElementService_1.HTMLElementService({
|
|
210
|
-
tagName: 'body'
|
|
211
|
-
parent: html
|
|
201
|
+
tagName: 'body'
|
|
212
202
|
})
|
|
213
|
-
html.
|
|
203
|
+
html.appendChild(this.body)
|
|
214
204
|
}
|
|
215
205
|
|
|
216
206
|
/**
|
|
217
|
-
* Create and return a PseudoHTMLElement
|
|
207
|
+
* Create and return a PseudoHTMLElement, which is not added to the document until it is appended somewhere
|
|
218
208
|
* @param {string} tagName - Tag Name is a string representing the type of Dom element this represents
|
|
219
209
|
* @returns {PseudoHTMLElement}
|
|
220
210
|
*/
|
|
221
211
|
createElement (tagName = 'div') {
|
|
222
|
-
|
|
212
|
+
// Like the DOM, the new element is not added anywhere: it has no parent until it is appended
|
|
213
|
+
return new HTMLElementService_1.HTMLElementService({
|
|
223
214
|
tagName
|
|
224
215
|
})
|
|
225
|
-
returnElement.parent = this
|
|
226
|
-
return returnElement
|
|
227
216
|
}
|
|
228
217
|
}
|
|
229
218
|
exports.default = PseudoHTMLDocument
|
|
230
|
-
}, { '../
|
|
219
|
+
}, { '../services/HTMLElementService': 14 }],
|
|
231
220
|
3: [function (require, module, exports) {
|
|
232
221
|
'use strict'
|
|
233
222
|
|
|
@@ -255,16 +244,22 @@
|
|
|
255
244
|
* @returns {Iterator}
|
|
256
245
|
*/
|
|
257
246
|
[Symbol.iterator] () {
|
|
258
|
-
|
|
247
|
+
// Walk the nodes of this list only (the linkers of a child list have no children of their own)
|
|
248
|
+
let current = this.first
|
|
259
249
|
return {
|
|
260
250
|
next: () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
251
|
+
if (current === null) {
|
|
252
|
+
return {
|
|
253
|
+
done: true,
|
|
254
|
+
value: undefined
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const result = {
|
|
258
|
+
done: false,
|
|
259
|
+
value: current.data
|
|
260
|
+
}
|
|
261
|
+
current = current.next
|
|
262
|
+
return result
|
|
268
263
|
}
|
|
269
264
|
}
|
|
270
265
|
}
|
|
@@ -380,31 +375,58 @@
|
|
|
380
375
|
6: [function (require, module, exports) {
|
|
381
376
|
'use strict'
|
|
382
377
|
|
|
383
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
384
|
-
return mod && mod.__esModule
|
|
385
|
-
? mod
|
|
386
|
-
: {
|
|
387
|
-
default: mod
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
378
|
Object.defineProperty(exports, '__esModule', {
|
|
391
379
|
value: true
|
|
392
380
|
})
|
|
393
|
-
|
|
394
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Get all of the ancestors of a node, starting with the root of the tree and ending with the node's own parent (the
|
|
383
|
+
* order in which an event travels down through them). A node which has no parent has no ancestors.
|
|
384
|
+
* @function getParentNodes
|
|
385
|
+
* @param {PseudoEventTarget|PseudoNode|*} node The node to find the ancestors of
|
|
386
|
+
* @returns {Array<PseudoNode>}
|
|
387
|
+
*/
|
|
388
|
+
const getParentNodes = node => {
|
|
389
|
+
const parents = []
|
|
390
|
+
let current = node && node.parentNode ? node.parentNode : null
|
|
391
|
+
while (current) {
|
|
392
|
+
parents.unshift(current)
|
|
393
|
+
current = current.parentNode
|
|
394
|
+
}
|
|
395
|
+
return parents
|
|
396
|
+
}
|
|
395
397
|
exports.default = getParentNodes
|
|
396
|
-
}, {
|
|
398
|
+
}, {}],
|
|
397
399
|
7: [function (require, module, exports) {
|
|
398
400
|
'use strict'
|
|
399
401
|
|
|
402
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
403
|
+
require('core-js/modules/esnext.iterator.filter.js')
|
|
404
|
+
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
405
|
+
return mod && mod.__esModule
|
|
406
|
+
? mod
|
|
407
|
+
: {
|
|
408
|
+
default: mod
|
|
409
|
+
}
|
|
410
|
+
}
|
|
400
411
|
Object.defineProperty(exports, '__esModule', {
|
|
401
412
|
value: true
|
|
402
413
|
})
|
|
414
|
+
const getParentNodes_1 = __importDefault(require('./getParentNodes'))
|
|
415
|
+
/**
|
|
416
|
+
* A selector function for retrieving existing parent PseudoNode from the given child item.
|
|
417
|
+
* This function will check all the parents starting from node, and scan the attributes
|
|
418
|
+
* property for matches. The return array contains all matching parent ancestors, starting with the root of the tree.
|
|
419
|
+
* @function getParentNodesFromAttribute
|
|
420
|
+
* @param {string} attr The property to compare on each ancestor (a missing property counts as false)
|
|
421
|
+
* @param {boolean|number|string} value The value the property must have
|
|
422
|
+
* @param {PseudoEventTarget|PseudoNode|*} node The node to find the matching ancestors of
|
|
423
|
+
* @returns {Array.<PseudoNode>}
|
|
424
|
+
*/
|
|
403
425
|
const getParentNodesFromAttribute = (attr, value, node) => {
|
|
404
|
-
return
|
|
426
|
+
return (0, getParentNodes_1.default)(node).filter(parent => (parent[attr] || false) === value)
|
|
405
427
|
}
|
|
406
428
|
exports.default = getParentNodesFromAttribute
|
|
407
|
-
}, {}],
|
|
429
|
+
}, { './getParentNodes': 6, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.filter.js': 131 }],
|
|
408
430
|
8: [function (require, module, exports) {
|
|
409
431
|
'use strict'
|
|
410
432
|
|
|
@@ -679,8 +701,6 @@
|
|
|
679
701
|
value: true
|
|
680
702
|
})
|
|
681
703
|
exports.ElementService = void 0
|
|
682
|
-
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
683
|
-
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
684
704
|
const NodeService_1 = require('./NodeService')
|
|
685
705
|
const AttrService_1 = require('./AttrService')
|
|
686
706
|
const DOMTokenListService_1 = require('./DOMTokenListService')
|
|
@@ -706,8 +726,8 @@
|
|
|
706
726
|
* @param {Object} [settings={}]
|
|
707
727
|
* @param {string} [settings.tagName=''] The name of the tag this element represents
|
|
708
728
|
* @param {Array<{name: string, value: *}>} [settings.attributes=[]] The attributes (also assigned as properties) to start with
|
|
709
|
-
* @param {PseudoNode|null} [settings.parent=null] The
|
|
710
|
-
* @param {Array} [settings.children=[]] The
|
|
729
|
+
* @param {PseudoNode|null} [settings.parent=null] The node to add this element to as its last child
|
|
730
|
+
* @param {Array<PseudoNode>} [settings.children=[]] The nodes to start as children
|
|
711
731
|
* @constructor
|
|
712
732
|
*/
|
|
713
733
|
constructor ({
|
|
@@ -718,8 +738,6 @@
|
|
|
718
738
|
} = {}) {
|
|
719
739
|
super()
|
|
720
740
|
this.tokenList = new DOMTokenListService_1.DOMTokenListService()
|
|
721
|
-
this.parent = parent
|
|
722
|
-
this.children = (0, generateNodeList_1.default)(TreeLinker_1.TreeLinker.fromArray(children).head)
|
|
723
741
|
this.tag = tagName
|
|
724
742
|
this.attributeList = attributes.concat([{
|
|
725
743
|
name: 'className',
|
|
@@ -740,6 +758,15 @@
|
|
|
740
758
|
}) => {
|
|
741
759
|
this[name] = value
|
|
742
760
|
})
|
|
761
|
+
children.forEach(child => {
|
|
762
|
+
if (!child || typeof child.nodeType !== 'number') {
|
|
763
|
+
throw new TypeError('The children of an element must be nodes.')
|
|
764
|
+
}
|
|
765
|
+
this.appendChild(child)
|
|
766
|
+
})
|
|
767
|
+
if (parent) {
|
|
768
|
+
parent.appendChild(this)
|
|
769
|
+
}
|
|
743
770
|
}
|
|
744
771
|
|
|
745
772
|
get tagName () {
|
|
@@ -795,14 +822,13 @@
|
|
|
795
822
|
}
|
|
796
823
|
|
|
797
824
|
/**
|
|
798
|
-
*
|
|
799
|
-
* @param {
|
|
800
|
-
* @returns {PseudoNode}
|
|
825
|
+
* An element which is added as a child gets its default events (for example a submit button submits its form).
|
|
826
|
+
* @param {NodeService} child The node which was inserted
|
|
801
827
|
*/
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
828
|
+
childInserted (child) {
|
|
829
|
+
if (typeof child.applyDefaultEvent === 'function') {
|
|
830
|
+
child.applyDefaultEvent()
|
|
831
|
+
}
|
|
806
832
|
}
|
|
807
833
|
|
|
808
834
|
/**
|
|
@@ -866,7 +892,7 @@
|
|
|
866
892
|
}
|
|
867
893
|
}
|
|
868
894
|
exports.ElementService = ElementService
|
|
869
|
-
}, { '../
|
|
895
|
+
}, { '../functions/getParentNodesFromAttribute': 7, './AttrService': 9, './DOMTokenListService': 10, './NamedNodeMapService': 15, './NodeService': 16, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.find.js': 132, 'core-js/modules/esnext.iterator.for-each.js': 133, 'core-js/modules/esnext.iterator.map.js': 134, 'core-js/modules/esnext.iterator.some.js': 136 }],
|
|
870
896
|
12: [function (require, module, exports) {
|
|
871
897
|
'use strict'
|
|
872
898
|
|
|
@@ -1402,8 +1428,6 @@
|
|
|
1402
1428
|
16: [function (require, module, exports) {
|
|
1403
1429
|
'use strict'
|
|
1404
1430
|
|
|
1405
|
-
require('core-js/modules/esnext.iterator.constructor.js')
|
|
1406
|
-
require('core-js/modules/esnext.iterator.for-each.js')
|
|
1407
1431
|
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
1408
1432
|
return mod && mod.__esModule
|
|
1409
1433
|
? mod
|
|
@@ -1421,6 +1445,7 @@
|
|
|
1421
1445
|
* @version 1.0.0
|
|
1422
1446
|
*/
|
|
1423
1447
|
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
1448
|
+
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
1424
1449
|
const EventTargetService_1 = __importDefault(require('./EventTargetService'))
|
|
1425
1450
|
/**
|
|
1426
1451
|
* Simulate the behaviour of the Node Class when there is no DOM available.
|
|
@@ -1443,8 +1468,7 @@
|
|
|
1443
1468
|
this.nodeNameValue = ''
|
|
1444
1469
|
this.children = (0, generateNodeList_1.default)()
|
|
1445
1470
|
this.parent = null
|
|
1446
|
-
this.
|
|
1447
|
-
this.prev = null
|
|
1471
|
+
this.listLinker = null
|
|
1448
1472
|
}
|
|
1449
1473
|
|
|
1450
1474
|
get baseURI () {
|
|
@@ -1468,7 +1492,7 @@
|
|
|
1468
1492
|
}
|
|
1469
1493
|
|
|
1470
1494
|
get nextSibling () {
|
|
1471
|
-
return this.
|
|
1495
|
+
return this.listLinker && this.listLinker.next ? this.listLinker.next.data : null
|
|
1472
1496
|
}
|
|
1473
1497
|
|
|
1474
1498
|
get nodeName () {
|
|
@@ -1500,7 +1524,7 @@
|
|
|
1500
1524
|
}
|
|
1501
1525
|
|
|
1502
1526
|
get previousSibling () {
|
|
1503
|
-
return this.
|
|
1527
|
+
return this.listLinker && this.listLinker.prev ? this.listLinker.prev.data : null
|
|
1504
1528
|
}
|
|
1505
1529
|
|
|
1506
1530
|
get textContent () {
|
|
@@ -1512,15 +1536,20 @@
|
|
|
1512
1536
|
}
|
|
1513
1537
|
|
|
1514
1538
|
/**
|
|
1515
|
-
*
|
|
1516
|
-
* @param {PseudoNode} childNode
|
|
1517
|
-
* @returns {PseudoNode}
|
|
1539
|
+
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
1540
|
+
* @param {PseudoNode} childNode The node to add
|
|
1541
|
+
* @returns {PseudoNode} The added node
|
|
1518
1542
|
*/
|
|
1519
1543
|
appendChild (childNode) {
|
|
1520
|
-
this.
|
|
1521
|
-
return childNode
|
|
1544
|
+
return this.insertBefore(childNode, null)
|
|
1522
1545
|
}
|
|
1523
1546
|
|
|
1547
|
+
/**
|
|
1548
|
+
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
1549
|
+
* (for example elements applying default events) can do so.
|
|
1550
|
+
* @param {NodeService} child The node which was inserted
|
|
1551
|
+
*/
|
|
1552
|
+
childInserted (child) {}
|
|
1524
1553
|
/**
|
|
1525
1554
|
* Not implemented yet.
|
|
1526
1555
|
* @throws {Error}
|
|
@@ -1538,11 +1567,19 @@
|
|
|
1538
1567
|
}
|
|
1539
1568
|
|
|
1540
1569
|
/**
|
|
1541
|
-
*
|
|
1542
|
-
* @
|
|
1570
|
+
* Check whether a node is this node or one of its descendants.
|
|
1571
|
+
* @param {PseudoNode|null} otherNode The node to look for
|
|
1572
|
+
* @returns {boolean}
|
|
1543
1573
|
*/
|
|
1544
1574
|
contains (otherNode) {
|
|
1545
|
-
|
|
1575
|
+
let current = otherNode
|
|
1576
|
+
while (current) {
|
|
1577
|
+
if (current === this) {
|
|
1578
|
+
return true
|
|
1579
|
+
}
|
|
1580
|
+
current = current.parentNode
|
|
1581
|
+
}
|
|
1582
|
+
return false
|
|
1546
1583
|
}
|
|
1547
1584
|
|
|
1548
1585
|
getRootNode (options = {
|
|
@@ -1556,11 +1593,44 @@
|
|
|
1556
1593
|
}
|
|
1557
1594
|
|
|
1558
1595
|
/**
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1596
|
+
* 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
|
|
1597
|
+
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
1598
|
+
* @param {PseudoNode} newNode The node to insert
|
|
1599
|
+
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
1600
|
+
* @returns {PseudoNode} The inserted node
|
|
1601
|
+
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
1561
1602
|
*/
|
|
1562
|
-
insertBefore (newNode, referenceNode) {
|
|
1563
|
-
|
|
1603
|
+
insertBefore (newNode, referenceNode = null) {
|
|
1604
|
+
if (referenceNode !== null && referenceNode.parentNode !== this) {
|
|
1605
|
+
throw new Error('The node before which the new node is to be inserted is not a child of this node.')
|
|
1606
|
+
}
|
|
1607
|
+
if (newNode === referenceNode) {
|
|
1608
|
+
// Inserting a node before itself leaves it where it is
|
|
1609
|
+
return newNode
|
|
1610
|
+
}
|
|
1611
|
+
if (typeof newNode.contains === 'function' && newNode.contains(this)) {
|
|
1612
|
+
throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
|
|
1613
|
+
}
|
|
1614
|
+
if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
|
|
1615
|
+
// The children of a fragment are inserted (moved) in order, and the fragment is left empty
|
|
1616
|
+
while (newNode.firstChild) {
|
|
1617
|
+
this.insertBefore(newNode.firstChild, referenceNode)
|
|
1618
|
+
}
|
|
1619
|
+
return newNode
|
|
1620
|
+
}
|
|
1621
|
+
if (newNode.parentNode) {
|
|
1622
|
+
// A node can only be in one place, so it is moved from where it was
|
|
1623
|
+
newNode.parentNode.removeChild(newNode)
|
|
1624
|
+
}
|
|
1625
|
+
const linker = new TreeLinker_1.TreeLinker({
|
|
1626
|
+
data: newNode
|
|
1627
|
+
})
|
|
1628
|
+
this.children.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
|
|
1629
|
+
const inserted = newNode
|
|
1630
|
+
inserted.parent = this
|
|
1631
|
+
inserted.listLinker = linker
|
|
1632
|
+
this.childInserted(inserted)
|
|
1633
|
+
return newNode
|
|
1564
1634
|
}
|
|
1565
1635
|
|
|
1566
1636
|
isDefaultNamespace (namespaceURI) {
|
|
@@ -1589,31 +1659,44 @@
|
|
|
1589
1659
|
|
|
1590
1660
|
normalize () {}
|
|
1591
1661
|
/**
|
|
1592
|
-
* Remove
|
|
1593
|
-
* @param {PseudoNode} childElement The child node
|
|
1594
|
-
* @returns {PseudoNode}
|
|
1662
|
+
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
1663
|
+
* @param {PseudoNode} childElement The child node to remove
|
|
1664
|
+
* @returns {PseudoNode} The removed node
|
|
1595
1665
|
* @throws {Error} When the node is not a child of this node
|
|
1596
1666
|
*/
|
|
1597
1667
|
removeChild (childElement) {
|
|
1598
|
-
|
|
1599
|
-
this.children.forEach(linker => {
|
|
1600
|
-
if (found === null && (linker === childElement || linker.data === childElement)) {
|
|
1601
|
-
found = linker
|
|
1602
|
-
}
|
|
1603
|
-
})
|
|
1604
|
-
if (found === null) {
|
|
1668
|
+
if (!childElement || childElement.parentNode !== this) {
|
|
1605
1669
|
throw new Error('The node to be removed is not a child of this node.')
|
|
1606
1670
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1671
|
+
const removed = childElement
|
|
1672
|
+
this.children.remove(removed.listLinker)
|
|
1673
|
+
removed.parent = null
|
|
1674
|
+
removed.listLinker = null
|
|
1675
|
+
return childElement
|
|
1609
1676
|
}
|
|
1610
1677
|
|
|
1611
1678
|
/**
|
|
1612
|
-
*
|
|
1613
|
-
* @
|
|
1679
|
+
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
1680
|
+
* @param {PseudoNode} newChild The node which takes the place
|
|
1681
|
+
* @param {PseudoNode} oldChild The child of this node to replace
|
|
1682
|
+
* @returns {PseudoNode} The replaced node
|
|
1683
|
+
* @throws {Error} When the old node is not a child of this node
|
|
1614
1684
|
*/
|
|
1615
1685
|
replaceChild (newChild, oldChild) {
|
|
1616
|
-
|
|
1686
|
+
if (!oldChild || oldChild.parentNode !== this) {
|
|
1687
|
+
throw new Error('The node to be replaced is not a child of this node.')
|
|
1688
|
+
}
|
|
1689
|
+
if (newChild === oldChild) {
|
|
1690
|
+
return oldChild
|
|
1691
|
+
}
|
|
1692
|
+
// The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
|
|
1693
|
+
let reference = oldChild.nextSibling
|
|
1694
|
+
if (reference === newChild) {
|
|
1695
|
+
reference = newChild.nextSibling
|
|
1696
|
+
}
|
|
1697
|
+
this.removeChild(oldChild)
|
|
1698
|
+
this.insertBefore(newChild, reference)
|
|
1699
|
+
return oldChild
|
|
1617
1700
|
}
|
|
1618
1701
|
}
|
|
1619
1702
|
exports.NodeService = NodeService
|
|
@@ -1630,7 +1713,7 @@
|
|
|
1630
1713
|
NodeService.DOCUMENT_TYPE_NODE = 10
|
|
1631
1714
|
NodeService.DOCUMENT_FRAGMENT_NODE = 11
|
|
1632
1715
|
NodeService.NOTATION_NODE = 12
|
|
1633
|
-
}, { '../factories/generateNodeList': 5, './EventTargetService': 13, '
|
|
1716
|
+
}, { '../factories/generateNodeList': 5, './EventTargetService': 13, 'collect-your-stuff/dist/collections/linked-tree-list/TreeLinker': 24 }],
|
|
1634
1717
|
17: [function (require, module, exports) {
|
|
1635
1718
|
'use strict'
|
|
1636
1719
|
|
|
@@ -1649,7 +1732,9 @@
|
|
|
1649
1732
|
* @param {*} [data=null] The data to be stored in this element.
|
|
1650
1733
|
*/
|
|
1651
1734
|
constructor (data = null) {
|
|
1735
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1652
1736
|
this.classType = ArrayElement
|
|
1737
|
+
/** The data stored in this element. */
|
|
1653
1738
|
this.data = null
|
|
1654
1739
|
this.data = data
|
|
1655
1740
|
}
|
|
@@ -1662,8 +1747,8 @@
|
|
|
1662
1747
|
*/
|
|
1663
1748
|
exports.ArrayElement = ArrayElement
|
|
1664
1749
|
ArrayElement.make = (element, classType = ArrayElement) => {
|
|
1665
|
-
if (typeof element !== 'object') {
|
|
1666
|
-
// It is not an object, so instantiate the Element with element as the data
|
|
1750
|
+
if (element === null || typeof element !== 'object') {
|
|
1751
|
+
// It is not an object (or it is null), so instantiate the Element with element as the data
|
|
1667
1752
|
return new classType(element)
|
|
1668
1753
|
}
|
|
1669
1754
|
if (element.classType) {
|
|
@@ -1719,14 +1804,32 @@
|
|
|
1719
1804
|
class Arrayable {
|
|
1720
1805
|
/**
|
|
1721
1806
|
* Create the new Arrayable instance, configure the Arrayable class.
|
|
1807
|
+
* @param {ArrayElement} [elementClass=ArrayElement] The class used to wrap given data as elements.
|
|
1722
1808
|
*/
|
|
1723
1809
|
constructor (elementClass = _ArrayElement.ArrayElement) {
|
|
1810
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1724
1811
|
this.classType = Arrayable
|
|
1812
|
+
/** The array which stores the elements of this Arrayable. */
|
|
1725
1813
|
this.innerList = []
|
|
1814
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
1726
1815
|
this.initialized = false
|
|
1727
1816
|
this.elementClass = elementClass
|
|
1728
1817
|
}
|
|
1729
1818
|
|
|
1819
|
+
/**
|
|
1820
|
+
* Find the position of an element which must be in this list.
|
|
1821
|
+
* @param {ArrayElement} node The element to find
|
|
1822
|
+
* @returns {number}
|
|
1823
|
+
* @throws {Error} When the element is not in this list
|
|
1824
|
+
*/
|
|
1825
|
+
indexOfElement (node) {
|
|
1826
|
+
const index = this.innerList.indexOf(node)
|
|
1827
|
+
if (index < 0) {
|
|
1828
|
+
throw new Error('The reference element is not in this list.')
|
|
1829
|
+
}
|
|
1830
|
+
return index
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1730
1833
|
/**
|
|
1731
1834
|
* Initialize the inner list, should only run once.
|
|
1732
1835
|
* @param {Array<ArrayElement>} initialList Give the array of elements to start in this Arrayable.
|
|
@@ -1743,7 +1846,7 @@
|
|
|
1743
1846
|
}
|
|
1744
1847
|
|
|
1745
1848
|
/**
|
|
1746
|
-
* Retrieve
|
|
1849
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
1747
1850
|
* @returns {Array<ArrayElement>}
|
|
1748
1851
|
*/
|
|
1749
1852
|
get list () {
|
|
@@ -1752,18 +1855,18 @@
|
|
|
1752
1855
|
|
|
1753
1856
|
/**
|
|
1754
1857
|
* Retrieve the first Element from the Arrayable
|
|
1755
|
-
* @returns {ArrayElement}
|
|
1858
|
+
* @returns {ArrayElement|null} The first element, or null when the Arrayable is empty
|
|
1756
1859
|
*/
|
|
1757
1860
|
get first () {
|
|
1758
|
-
return this.innerList[0]
|
|
1861
|
+
return this.length ? this.innerList[0] : null
|
|
1759
1862
|
}
|
|
1760
1863
|
|
|
1761
1864
|
/**
|
|
1762
1865
|
* Retrieve the last Element from the Arrayable
|
|
1763
|
-
* @returns {ArrayElement}
|
|
1866
|
+
* @returns {ArrayElement|null} The last element, or null when the Arrayable is empty
|
|
1764
1867
|
*/
|
|
1765
1868
|
get last () {
|
|
1766
|
-
return this.innerList[this.length - 1]
|
|
1869
|
+
return this.length ? this.innerList[this.length - 1] : null
|
|
1767
1870
|
}
|
|
1768
1871
|
|
|
1769
1872
|
/**
|
|
@@ -1776,25 +1879,29 @@
|
|
|
1776
1879
|
|
|
1777
1880
|
/**
|
|
1778
1881
|
* Insert a new node (or data) after a node.
|
|
1779
|
-
* @param {ArrayElement
|
|
1882
|
+
* @param {ArrayElement|null} node The existing node as reference, or null to insert at the start of the list
|
|
1780
1883
|
* @param {ArrayElement|*} newNode The new node to go after the existing node
|
|
1781
1884
|
* @returns {Arrayable}
|
|
1885
|
+
* @throws {Error} When the reference node is not in this list
|
|
1782
1886
|
*/
|
|
1783
1887
|
insertAfter (node, newNode) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1888
|
+
// With no reference element, the new one goes after nothing: at the start of the list
|
|
1889
|
+
const insertAt = node === null || typeof node === 'undefined' ? -1 : this.indexOfElement(node)
|
|
1890
|
+
this.innerList.splice(insertAt + 1, 0, this.elementClass.make(newNode, this.elementClass))
|
|
1786
1891
|
return this
|
|
1787
1892
|
}
|
|
1788
1893
|
|
|
1789
1894
|
/**
|
|
1790
1895
|
* Insert a new node (or data) before a node.
|
|
1791
|
-
* @param {ArrayElement
|
|
1896
|
+
* @param {ArrayElement|null} node The existing node as reference, or null to insert at the end of the list
|
|
1792
1897
|
* @param {ArrayElement|*} newNode The new node to go before the existing node
|
|
1793
1898
|
* @returns {Arrayable}
|
|
1899
|
+
* @throws {Error} When the reference node is not in this list
|
|
1794
1900
|
*/
|
|
1795
1901
|
insertBefore (node, newNode) {
|
|
1796
|
-
|
|
1797
|
-
this.
|
|
1902
|
+
// With no reference element, the new one goes before nothing: at the end of the list
|
|
1903
|
+
const insertAt = node === null || typeof node === 'undefined' ? this.length : this.indexOfElement(node)
|
|
1904
|
+
this.innerList.splice(insertAt, 0, this.elementClass.make(newNode, this.elementClass))
|
|
1798
1905
|
return this
|
|
1799
1906
|
}
|
|
1800
1907
|
|
|
@@ -1805,6 +1912,11 @@
|
|
|
1805
1912
|
* @returns {Arrayable}
|
|
1806
1913
|
*/
|
|
1807
1914
|
append (node, after = this.last) {
|
|
1915
|
+
if (after === this.last) {
|
|
1916
|
+
// Adding to the end does not need to search for where that is
|
|
1917
|
+
this.innerList.push(this.elementClass.make(node, this.elementClass))
|
|
1918
|
+
return this
|
|
1919
|
+
}
|
|
1808
1920
|
return this.insertAfter(after, node)
|
|
1809
1921
|
}
|
|
1810
1922
|
|
|
@@ -1815,16 +1927,24 @@
|
|
|
1815
1927
|
* @returns {Arrayable}
|
|
1816
1928
|
*/
|
|
1817
1929
|
prepend (node, before = this.first) {
|
|
1930
|
+
if (before === this.first) {
|
|
1931
|
+
// Adding to the start does not need to search for where that is
|
|
1932
|
+
this.innerList.unshift(this.elementClass.make(node, this.elementClass))
|
|
1933
|
+
return this
|
|
1934
|
+
}
|
|
1818
1935
|
return this.insertBefore(before, node)
|
|
1819
1936
|
}
|
|
1820
1937
|
|
|
1821
1938
|
/**
|
|
1822
1939
|
* Remove an element from this arrayable.
|
|
1823
1940
|
* @param {ArrayElement} node The node we wish to remove (and it will be returned after removal)
|
|
1824
|
-
* @return {ArrayElement}
|
|
1941
|
+
* @return {ArrayElement|null} The removed node, or null when it was not in this list (nothing is removed)
|
|
1825
1942
|
*/
|
|
1826
1943
|
remove (node) {
|
|
1827
1944
|
const deleteAt = this.innerList.indexOf(node)
|
|
1945
|
+
if (deleteAt < 0) {
|
|
1946
|
+
return null
|
|
1947
|
+
}
|
|
1828
1948
|
this.innerList.splice(deleteAt, 1)
|
|
1829
1949
|
return node
|
|
1830
1950
|
}
|
|
@@ -1904,7 +2024,7 @@
|
|
|
1904
2024
|
class DoubleLinker {
|
|
1905
2025
|
/**
|
|
1906
2026
|
* Create the new DoubleLinker instance, provide the data and optionally the next and prev references.
|
|
1907
|
-
* @param {Object} [nodeData={}]
|
|
2027
|
+
* @param {Object} [nodeData={}] The settings for the new linker.
|
|
1908
2028
|
* @param {*} [nodeData.data=null] The data to be stored in this linker
|
|
1909
2029
|
* @param {DoubleLinker|null} [nodeData.next=null] The reference to the next linker if any
|
|
1910
2030
|
* @param {DoubleLinker|null} [nodeData.prev=null] The reference to the previous linker if any
|
|
@@ -1914,9 +2034,13 @@
|
|
|
1914
2034
|
next = null,
|
|
1915
2035
|
prev = null
|
|
1916
2036
|
} = {}) {
|
|
2037
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1917
2038
|
this.classType = DoubleLinker
|
|
2039
|
+
/** The data stored in this linker. */
|
|
1918
2040
|
this.data = null
|
|
2041
|
+
/** The linker after this one, or null when this is the last. */
|
|
1919
2042
|
this.next = null
|
|
2043
|
+
/** The linker before this one, or null when this is the first. */
|
|
1920
2044
|
this.prev = null
|
|
1921
2045
|
this.data = data
|
|
1922
2046
|
this.next = next
|
|
@@ -1984,11 +2108,19 @@
|
|
|
1984
2108
|
class DoublyLinkedList {
|
|
1985
2109
|
/**
|
|
1986
2110
|
* Create the new DoublyLinkedList instance.
|
|
2111
|
+
* @param {DoubleLinker} [linkerClass=DoubleLinker] The class used to wrap given data as linkers.
|
|
1987
2112
|
*/
|
|
1988
2113
|
constructor (linkerClass = _DoubleLinker.DoubleLinker) {
|
|
2114
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1989
2115
|
this.classType = DoublyLinkedList
|
|
2116
|
+
/** A linker of the list (null when the list is empty); the head is found by walking back from it. */
|
|
1990
2117
|
this.innerList = null
|
|
2118
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
1991
2119
|
this.initialized = false
|
|
2120
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2121
|
+
this.tailCache = null
|
|
2122
|
+
/** The number of linkers, kept up to date by the list's own methods so that the length does not need to walk the whole list (null when not known yet). */
|
|
2123
|
+
this.countCache = null
|
|
1992
2124
|
this.linkerClass = linkerClass
|
|
1993
2125
|
}
|
|
1994
2126
|
|
|
@@ -1998,11 +2130,12 @@
|
|
|
1998
2130
|
* @return {DoublyLinkedList}
|
|
1999
2131
|
*/
|
|
2000
2132
|
initialize (initialList) {
|
|
2133
|
+
// Borrowed from LinkedList, which types its return as a LinkedList although it returns whatever list called it
|
|
2001
2134
|
return _LinkedList.LinkedList.prototype.initialize.call(this, initialList)
|
|
2002
2135
|
}
|
|
2003
2136
|
|
|
2004
2137
|
/**
|
|
2005
|
-
* Retrieve
|
|
2138
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2006
2139
|
* @returns {DoubleLinker}
|
|
2007
2140
|
*/
|
|
2008
2141
|
get list () {
|
|
@@ -2014,91 +2147,122 @@
|
|
|
2014
2147
|
* @returns {DoubleLinker}
|
|
2015
2148
|
*/
|
|
2016
2149
|
get first () {
|
|
2017
|
-
|
|
2150
|
+
let head = this.innerList
|
|
2151
|
+
if (head === null) {
|
|
2152
|
+
return null
|
|
2153
|
+
}
|
|
2154
|
+
// innerList is normally the head already, walking back also finds anything linked on before it outside of this list
|
|
2155
|
+
while (head.prev !== null) {
|
|
2156
|
+
head = head.prev
|
|
2157
|
+
}
|
|
2158
|
+
this.innerList = head
|
|
2159
|
+
return head
|
|
2018
2160
|
}
|
|
2019
2161
|
|
|
2020
2162
|
/**
|
|
2021
|
-
* Retrieve the last DoubleLinker in the list.
|
|
2163
|
+
* Retrieve the last DoubleLinker in the list. The end is remembered, so this does not walk the list.
|
|
2022
2164
|
* @returns {DoubleLinker}
|
|
2023
2165
|
*/
|
|
2024
2166
|
get last () {
|
|
2025
|
-
|
|
2026
|
-
if (tail === null) {
|
|
2167
|
+
if (this.innerList === null) {
|
|
2027
2168
|
return null
|
|
2028
2169
|
}
|
|
2029
|
-
let
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2170
|
+
let tail = this.tailCache !== null ? this.tailCache : this.innerList
|
|
2171
|
+
// The remembered tail is normally the end already, walking on from it also finds anything linked on outside of this list
|
|
2172
|
+
while (tail.next !== null) {
|
|
2173
|
+
tail = tail.next
|
|
2033
2174
|
}
|
|
2175
|
+
this.tailCache = tail
|
|
2034
2176
|
return tail
|
|
2035
2177
|
}
|
|
2036
2178
|
|
|
2037
2179
|
/**
|
|
2038
|
-
* Return the length of the list.
|
|
2180
|
+
* Return the length of the list. It is kept up to date by the list's own methods, so this does not walk the list
|
|
2181
|
+
* (call reset() after linkers were changed directly).
|
|
2039
2182
|
* @returns {number}
|
|
2040
2183
|
*/
|
|
2041
2184
|
get length () {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
while (current !== null) {
|
|
2045
|
-
++length
|
|
2046
|
-
current = current.next
|
|
2185
|
+
if (this.countCache === null) {
|
|
2186
|
+
this.reset()
|
|
2047
2187
|
}
|
|
2048
|
-
return
|
|
2188
|
+
return this.countCache
|
|
2049
2189
|
}
|
|
2050
2190
|
|
|
2051
2191
|
/**
|
|
2052
2192
|
* Insert a new node (or data) after a node.
|
|
2053
|
-
* @param {DoubleLinker|*} node The existing node as reference
|
|
2193
|
+
* @param {DoubleLinker|*} node The existing node as reference (which must be in this list, this is not checked), or null to insert at the start of the list
|
|
2054
2194
|
* @param {DoubleLinker|*} newNode The new node to go after the existing node
|
|
2055
2195
|
* @returns {DoublyLinkedList}
|
|
2056
2196
|
*/
|
|
2057
2197
|
insertAfter (node, newNode) {
|
|
2058
|
-
newNode = this.linkerClass.make(newNode)
|
|
2059
|
-
if (node
|
|
2198
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2199
|
+
if (node === null || typeof node === 'undefined') {
|
|
2200
|
+
// After nothing means at the start of the list
|
|
2201
|
+
const head = this.first
|
|
2202
|
+
newNode.prev = null
|
|
2203
|
+
newNode.next = head
|
|
2204
|
+
if (head) {
|
|
2205
|
+
head.prev = newNode
|
|
2206
|
+
} else {
|
|
2207
|
+
this.tailCache = newNode
|
|
2208
|
+
}
|
|
2209
|
+
this.innerList = newNode
|
|
2210
|
+
} else {
|
|
2060
2211
|
// Ensure the next reference of this node is assigned to the new node
|
|
2061
2212
|
newNode.next = node.next
|
|
2062
2213
|
// Ensure this node is assigned as the prev reference of the new node
|
|
2063
2214
|
newNode.prev = node
|
|
2064
2215
|
// Then set this node's next reference to the new node
|
|
2065
2216
|
node.next = newNode
|
|
2217
|
+
if (newNode.next) {
|
|
2218
|
+
// Update the next reference to ensure circular reference for prev points to the new node
|
|
2219
|
+
newNode.next.prev = newNode
|
|
2220
|
+
} else {
|
|
2221
|
+
this.tailCache = newNode
|
|
2222
|
+
}
|
|
2066
2223
|
}
|
|
2067
|
-
if (
|
|
2068
|
-
|
|
2069
|
-
newNode.next.prev = newNode
|
|
2224
|
+
if (this.countCache !== null) {
|
|
2225
|
+
++this.countCache
|
|
2070
2226
|
}
|
|
2071
|
-
if (!this.length) {
|
|
2072
|
-
this.innerList = newNode
|
|
2073
|
-
}
|
|
2074
|
-
this.reset()
|
|
2075
2227
|
return this
|
|
2076
2228
|
}
|
|
2077
2229
|
|
|
2078
2230
|
/**
|
|
2079
2231
|
* Insert a new node (or data) before a node.
|
|
2080
|
-
* @param {DoubleLinker|*} node The existing node as reference
|
|
2232
|
+
* @param {DoubleLinker|*} node The existing node as reference (which must be in this list, this is not checked), or null to insert at the end of the list
|
|
2081
2233
|
* @param {DoubleLinker|*} newNode The new node to go before the existing node
|
|
2082
2234
|
* @returns {DoublyLinkedList}
|
|
2083
2235
|
*/
|
|
2084
2236
|
insertBefore (node, newNode) {
|
|
2085
|
-
newNode = this.linkerClass.make(newNode)
|
|
2086
|
-
if (node
|
|
2237
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2238
|
+
if (node === null || typeof node === 'undefined') {
|
|
2239
|
+
// Before nothing means at the end of the list
|
|
2240
|
+
const tail = this.last
|
|
2241
|
+
newNode.next = null
|
|
2242
|
+
newNode.prev = tail
|
|
2243
|
+
if (tail === null) {
|
|
2244
|
+
this.innerList = newNode
|
|
2245
|
+
} else {
|
|
2246
|
+
tail.next = newNode
|
|
2247
|
+
}
|
|
2248
|
+
this.tailCache = newNode
|
|
2249
|
+
} else {
|
|
2087
2250
|
// The new node will reference this prev node as prev
|
|
2088
2251
|
newNode.prev = node.prev
|
|
2089
2252
|
// The new node will reference this node as next
|
|
2090
2253
|
newNode.next = node
|
|
2091
2254
|
// This prev will reference the new node
|
|
2092
2255
|
node.prev = newNode
|
|
2256
|
+
if (newNode.prev) {
|
|
2257
|
+
// Update the prev reference to ensure circular reference for next points to the new node
|
|
2258
|
+
newNode.prev.next = newNode
|
|
2259
|
+
} else {
|
|
2260
|
+
this.innerList = newNode
|
|
2261
|
+
}
|
|
2093
2262
|
}
|
|
2094
|
-
if (
|
|
2095
|
-
|
|
2096
|
-
newNode.prev.next = newNode
|
|
2097
|
-
}
|
|
2098
|
-
if (!this.length) {
|
|
2099
|
-
this.innerList = newNode
|
|
2263
|
+
if (this.countCache !== null) {
|
|
2264
|
+
++this.countCache
|
|
2100
2265
|
}
|
|
2101
|
-
this.reset()
|
|
2102
2266
|
return this
|
|
2103
2267
|
}
|
|
2104
2268
|
|
|
@@ -2128,7 +2292,7 @@
|
|
|
2128
2292
|
* @return {DoubleLinker}
|
|
2129
2293
|
*/
|
|
2130
2294
|
remove (node) {
|
|
2131
|
-
if (node === null) {
|
|
2295
|
+
if (node === null || typeof node === 'undefined') {
|
|
2132
2296
|
return null
|
|
2133
2297
|
}
|
|
2134
2298
|
if (node.prev) {
|
|
@@ -2144,35 +2308,47 @@
|
|
|
2144
2308
|
if (this.innerList === node) {
|
|
2145
2309
|
this.innerList = node.next || node.prev || null
|
|
2146
2310
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2311
|
+
if (this.tailCache === node) {
|
|
2312
|
+
this.tailCache = node.prev
|
|
2313
|
+
}
|
|
2314
|
+
if (this.innerList === null) {
|
|
2315
|
+
this.tailCache = null
|
|
2316
|
+
}
|
|
2317
|
+
if (this.countCache !== null) {
|
|
2318
|
+
--this.countCache
|
|
2319
|
+
}
|
|
2149
2320
|
return node
|
|
2150
2321
|
}
|
|
2151
2322
|
|
|
2152
2323
|
/**
|
|
2153
|
-
* Refresh all references and return head
|
|
2154
|
-
*
|
|
2324
|
+
* Refresh all references (the head, the end and the length) by walking the list once, and return the head. The list's
|
|
2325
|
+
* own methods keep these up to date, so this is only needed after linkers were changed directly.
|
|
2326
|
+
* @return {DoubleLinker|null}
|
|
2155
2327
|
*/
|
|
2156
2328
|
reset () {
|
|
2157
2329
|
// Start at the pointer for the list
|
|
2158
2330
|
let pointer = this.innerList
|
|
2159
2331
|
if (pointer === null) {
|
|
2332
|
+
this.countCache = 0
|
|
2333
|
+
this.tailCache = null
|
|
2160
2334
|
return null
|
|
2161
2335
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
pointer = next
|
|
2166
|
-
next = pointer.next
|
|
2167
|
-
}
|
|
2168
|
-
let prev = pointer.prev
|
|
2169
|
-
// From final reference, follow references back to the beginning
|
|
2170
|
-
while (prev !== null) {
|
|
2171
|
-
pointer = prev
|
|
2172
|
-
prev = pointer.prev
|
|
2336
|
+
// Follow references back to the beginning
|
|
2337
|
+
while (pointer.prev !== null) {
|
|
2338
|
+
pointer = pointer.prev
|
|
2173
2339
|
}
|
|
2174
|
-
//
|
|
2340
|
+
// We are pointing to the true head, now count along to the end to find the tail and the length
|
|
2175
2341
|
this.innerList = pointer
|
|
2342
|
+
let count = 0
|
|
2343
|
+
let tail = pointer
|
|
2344
|
+
let current = pointer
|
|
2345
|
+
while (current !== null) {
|
|
2346
|
+
++count
|
|
2347
|
+
tail = current
|
|
2348
|
+
current = current.next
|
|
2349
|
+
}
|
|
2350
|
+
this.countCache = count
|
|
2351
|
+
this.tailCache = tail
|
|
2176
2352
|
return pointer
|
|
2177
2353
|
}
|
|
2178
2354
|
|
|
@@ -2208,6 +2384,7 @@
|
|
|
2208
2384
|
* Be able to run forEach on this DoublyLinkedList to iterate over the DoubleLinker Items.
|
|
2209
2385
|
* @param {forEachCallback} callback The function to call for-each double linker
|
|
2210
2386
|
* @param {DoublyLinkedList} thisArg Optional, 'this' reference
|
|
2387
|
+
* @return {DoublyLinkedList} The list which was iterated.
|
|
2211
2388
|
*/
|
|
2212
2389
|
forEach (callback, thisArg = this) {
|
|
2213
2390
|
return _LinkedList.LinkedList.prototype.forEach.call(this, callback, thisArg)
|
|
@@ -2251,11 +2428,19 @@
|
|
|
2251
2428
|
class LinkedList {
|
|
2252
2429
|
/**
|
|
2253
2430
|
* Create the new LinkedList instance.
|
|
2431
|
+
* @param {Linker} [linkerClass=Linker] The class used to wrap given data as linkers.
|
|
2254
2432
|
*/
|
|
2255
2433
|
constructor (linkerClass = _Linker.Linker) {
|
|
2434
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2256
2435
|
this.classType = LinkedList
|
|
2436
|
+
/** The first linker of the list (null when the list is empty), from which the whole list is reached. */
|
|
2257
2437
|
this.innerList = null
|
|
2438
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
2258
2439
|
this.initialized = false
|
|
2440
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2441
|
+
this.tailCache = null
|
|
2442
|
+
/** The number of linkers, kept up to date by the list's own methods so that the length does not need to walk the whole list (null when not known yet). */
|
|
2443
|
+
this.countCache = null
|
|
2259
2444
|
this.linkerClass = linkerClass
|
|
2260
2445
|
}
|
|
2261
2446
|
|
|
@@ -2265,11 +2450,12 @@
|
|
|
2265
2450
|
* @return {LinkedList}
|
|
2266
2451
|
*/
|
|
2267
2452
|
initialize (initialList) {
|
|
2453
|
+
// Borrowed from Arrayable, which types its return as an Arrayable although it returns whatever list called it
|
|
2268
2454
|
return _Arrayable.Arrayable.prototype.initialize.call(this, initialList)
|
|
2269
2455
|
}
|
|
2270
2456
|
|
|
2271
2457
|
/**
|
|
2272
|
-
* Retrieve
|
|
2458
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2273
2459
|
* @returns {Linker}
|
|
2274
2460
|
*/
|
|
2275
2461
|
get list () {
|
|
@@ -2285,78 +2471,100 @@
|
|
|
2285
2471
|
}
|
|
2286
2472
|
|
|
2287
2473
|
/**
|
|
2288
|
-
* Retrieve the last Linker in the list.
|
|
2474
|
+
* Retrieve the last Linker in the list. The end is remembered, so this does not walk the list.
|
|
2289
2475
|
* @returns {Linker}
|
|
2290
2476
|
*/
|
|
2291
2477
|
get last () {
|
|
2292
|
-
|
|
2293
|
-
if (tail === null) {
|
|
2478
|
+
if (this.innerList === null) {
|
|
2294
2479
|
return null
|
|
2295
2480
|
}
|
|
2296
|
-
let
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2481
|
+
let tail = this.tailCache !== null ? this.tailCache : this.innerList
|
|
2482
|
+
// The remembered tail is normally the end already, walking on from it also finds anything linked on outside of this list
|
|
2483
|
+
while (tail.next !== null) {
|
|
2484
|
+
tail = tail.next
|
|
2300
2485
|
}
|
|
2486
|
+
this.tailCache = tail
|
|
2301
2487
|
return tail
|
|
2302
2488
|
}
|
|
2303
2489
|
|
|
2304
2490
|
/**
|
|
2305
|
-
* Return the length of the list.
|
|
2491
|
+
* Return the length of the list. It is kept up to date by the list's own methods, so this does not walk the list
|
|
2492
|
+
* (call reset() after linkers were changed directly).
|
|
2306
2493
|
* @returns {number}
|
|
2307
2494
|
*/
|
|
2308
2495
|
get length () {
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
while (current !== null) {
|
|
2312
|
-
++length
|
|
2313
|
-
current = current.next
|
|
2496
|
+
if (this.countCache === null) {
|
|
2497
|
+
this.reset()
|
|
2314
2498
|
}
|
|
2315
|
-
return
|
|
2499
|
+
return this.countCache
|
|
2316
2500
|
}
|
|
2317
2501
|
|
|
2318
2502
|
/**
|
|
2319
2503
|
* Insert a new node (or data) after a node.
|
|
2320
|
-
* @param {Linker|*} node The existing node as reference
|
|
2504
|
+
* @param {Linker|*} node The existing node as reference, or null to insert at the start of the list
|
|
2321
2505
|
* @param {Linker|*} newNode The new node to go after the existing node
|
|
2322
2506
|
* @returns {LinkedList}
|
|
2323
2507
|
*/
|
|
2324
2508
|
insertAfter (node, newNode) {
|
|
2325
|
-
newNode = this.linkerClass.make(newNode)
|
|
2326
|
-
if (node
|
|
2327
|
-
//
|
|
2509
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2510
|
+
if (node === null || typeof node === 'undefined') {
|
|
2511
|
+
// After nothing means at the start of the list
|
|
2512
|
+
newNode.next = this.innerList
|
|
2513
|
+
if (this.innerList === null) {
|
|
2514
|
+
this.tailCache = newNode
|
|
2515
|
+
}
|
|
2516
|
+
this.innerList = newNode
|
|
2517
|
+
} else {
|
|
2328
2518
|
newNode.next = node.next
|
|
2329
|
-
// Then set this node's next reference to the new node
|
|
2330
2519
|
node.next = newNode
|
|
2520
|
+
if (newNode.next === null) {
|
|
2521
|
+
this.tailCache = newNode
|
|
2522
|
+
}
|
|
2331
2523
|
}
|
|
2332
|
-
if (
|
|
2333
|
-
this.
|
|
2524
|
+
if (this.countCache !== null) {
|
|
2525
|
+
++this.countCache
|
|
2334
2526
|
}
|
|
2335
2527
|
return this
|
|
2336
2528
|
}
|
|
2337
2529
|
|
|
2338
2530
|
/**
|
|
2339
2531
|
* Insert a new node (or data) before a node.
|
|
2340
|
-
* @param {Linker|*} node The existing node as reference
|
|
2532
|
+
* @param {Linker|*} node The existing node as reference, or null to insert at the end of the list
|
|
2341
2533
|
* @param {Linker|*} newNode The new node to go before the existing node
|
|
2342
2534
|
* @returns {LinkedList}
|
|
2535
|
+
* @throws {Error} When the reference node is not in this list
|
|
2343
2536
|
*/
|
|
2344
2537
|
insertBefore (node, newNode) {
|
|
2345
|
-
newNode = this.linkerClass.make(newNode)
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2538
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2539
|
+
if (node === null || typeof node === 'undefined') {
|
|
2540
|
+
// Before nothing means at the end of the list
|
|
2541
|
+
const tail = this.last
|
|
2542
|
+
newNode.next = null
|
|
2543
|
+
if (tail === null) {
|
|
2544
|
+
this.innerList = newNode
|
|
2545
|
+
} else {
|
|
2546
|
+
tail.next = newNode
|
|
2547
|
+
}
|
|
2548
|
+
this.tailCache = newNode
|
|
2549
|
+
} else {
|
|
2550
|
+
let prevNode = null
|
|
2551
|
+
let currentNode = this.first
|
|
2552
|
+
while (currentNode !== null && currentNode !== node) {
|
|
2553
|
+
prevNode = currentNode
|
|
2554
|
+
currentNode = currentNode.next
|
|
2555
|
+
}
|
|
2556
|
+
if (currentNode === null) {
|
|
2557
|
+
throw new Error('The reference node is not in this list.')
|
|
2558
|
+
}
|
|
2559
|
+
newNode.next = node
|
|
2560
|
+
if (prevNode) {
|
|
2561
|
+
prevNode.next = newNode
|
|
2562
|
+
} else {
|
|
2563
|
+
this.innerList = newNode
|
|
2564
|
+
}
|
|
2357
2565
|
}
|
|
2358
|
-
if (
|
|
2359
|
-
this.
|
|
2566
|
+
if (this.countCache !== null) {
|
|
2567
|
+
++this.countCache
|
|
2360
2568
|
}
|
|
2361
2569
|
return this
|
|
2362
2570
|
}
|
|
@@ -2384,26 +2592,58 @@
|
|
|
2384
2592
|
/**
|
|
2385
2593
|
* Remove a linker from this linked list.
|
|
2386
2594
|
* @param {Linker} node The node we wish to remove (and it will be returned after removal)
|
|
2387
|
-
* @return {Linker}
|
|
2595
|
+
* @return {Linker|null} The removed node, or null when it was not in this list (nothing is removed)
|
|
2388
2596
|
*/
|
|
2389
2597
|
remove (node) {
|
|
2598
|
+
if (node === null || typeof node === 'undefined') {
|
|
2599
|
+
return null
|
|
2600
|
+
}
|
|
2390
2601
|
let prevNode = null
|
|
2391
2602
|
let currentNode = this.first
|
|
2392
|
-
while (currentNode !== node) {
|
|
2603
|
+
while (currentNode !== null && currentNode !== node) {
|
|
2393
2604
|
prevNode = currentNode
|
|
2394
2605
|
currentNode = currentNode.next
|
|
2395
2606
|
}
|
|
2607
|
+
if (currentNode === null) {
|
|
2608
|
+
// The node is not in this list, so there is nothing to remove
|
|
2609
|
+
return null
|
|
2610
|
+
}
|
|
2396
2611
|
if (prevNode) {
|
|
2397
|
-
// Ensure the next reference of the previous node skips over the removed node
|
|
2398
2612
|
prevNode.next = node.next
|
|
2399
|
-
}
|
|
2400
|
-
if (node === this.first && node !== null) {
|
|
2401
|
-
// Update list head to point to next if it was this node
|
|
2613
|
+
} else {
|
|
2402
2614
|
this.innerList = node.next
|
|
2403
2615
|
}
|
|
2616
|
+
if (this.tailCache === node) {
|
|
2617
|
+
this.tailCache = prevNode
|
|
2618
|
+
}
|
|
2619
|
+
if (this.innerList === null) {
|
|
2620
|
+
this.tailCache = null
|
|
2621
|
+
}
|
|
2622
|
+
if (this.countCache !== null) {
|
|
2623
|
+
--this.countCache
|
|
2624
|
+
}
|
|
2404
2625
|
return node
|
|
2405
2626
|
}
|
|
2406
2627
|
|
|
2628
|
+
/**
|
|
2629
|
+
* Refresh the remembered end and length of the list by walking it once. The list's own methods keep these up to date,
|
|
2630
|
+
* so this is only needed after linkers were changed directly (for example by setting next on a linker).
|
|
2631
|
+
* @return {Linker|null} The first linker of the list
|
|
2632
|
+
*/
|
|
2633
|
+
reset () {
|
|
2634
|
+
let count = 0
|
|
2635
|
+
let tail = null
|
|
2636
|
+
let current = this.innerList
|
|
2637
|
+
while (current !== null) {
|
|
2638
|
+
++count
|
|
2639
|
+
tail = current
|
|
2640
|
+
current = current.next
|
|
2641
|
+
}
|
|
2642
|
+
this.countCache = count
|
|
2643
|
+
this.tailCache = tail
|
|
2644
|
+
return this.innerList
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2407
2647
|
/**
|
|
2408
2648
|
* Retrieve a Linker item from this list by numeric index, otherwise return null.
|
|
2409
2649
|
* @param {number} index The integer number for retrieving a node by position.
|
|
@@ -2486,7 +2726,7 @@
|
|
|
2486
2726
|
class Linker {
|
|
2487
2727
|
/**
|
|
2488
2728
|
* Create the new Linker instance, provide the data and optionally give the next Linker.
|
|
2489
|
-
* @param {Object} [nodeData={}]
|
|
2729
|
+
* @param {Object} [nodeData={}] The settings for the new linker.
|
|
2490
2730
|
* @param {*} [nodeData.data=null] The data to be stored in this linker
|
|
2491
2731
|
* @param {Linker|null} [nodeData.next=null] The reference to the next linker if any
|
|
2492
2732
|
*/
|
|
@@ -2494,8 +2734,11 @@
|
|
|
2494
2734
|
data = null,
|
|
2495
2735
|
next = null
|
|
2496
2736
|
} = {}) {
|
|
2737
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2497
2738
|
this.classType = Linker
|
|
2739
|
+
/** The data stored in this linker. */
|
|
2498
2740
|
this.data = null
|
|
2741
|
+
/** The linker after this one, or null when this is the last. */
|
|
2499
2742
|
this.next = null
|
|
2500
2743
|
this.data = data
|
|
2501
2744
|
this.next = next
|
|
@@ -2509,8 +2752,8 @@
|
|
|
2509
2752
|
*/
|
|
2510
2753
|
exports.Linker = Linker
|
|
2511
2754
|
Linker.make = (linker, classType = Linker) => {
|
|
2512
|
-
if (typeof linker !== 'object') {
|
|
2513
|
-
// It is not an object, so instantiate the Linker with element as the data
|
|
2755
|
+
if (linker === null || typeof linker !== 'object') {
|
|
2756
|
+
// It is not an object (or it is null), so instantiate the Linker with element as the data
|
|
2514
2757
|
return new classType({
|
|
2515
2758
|
data: linker
|
|
2516
2759
|
})
|
|
@@ -2519,7 +2762,8 @@
|
|
|
2519
2762
|
// Already valid Linker, return as-is
|
|
2520
2763
|
return linker
|
|
2521
2764
|
}
|
|
2522
|
-
if (!linker
|
|
2765
|
+
if (!('data' in linker)) {
|
|
2766
|
+
// Not the settings for a linker (which would have data, even if it is falsy), so it is the data itself
|
|
2523
2767
|
linker = {
|
|
2524
2768
|
data: linker
|
|
2525
2769
|
}
|
|
@@ -2533,7 +2777,7 @@
|
|
|
2533
2777
|
* @param {IsLinker} [classType=Linker] Provide the type of IsLinker to use.
|
|
2534
2778
|
* @returns {{head: Linker, tail: Linker}}
|
|
2535
2779
|
*/
|
|
2536
|
-
Linker.fromArray = (values, classType = Linker) => values.reduce((references, linker) => {
|
|
2780
|
+
Linker.fromArray = (values = [], classType = Linker) => values.reduce((references, linker) => {
|
|
2537
2781
|
const newLinker = classType.make(linker, classType)
|
|
2538
2782
|
if (references.head === null) {
|
|
2539
2783
|
// Initialize the head and tail with the new node
|
|
@@ -2558,6 +2802,8 @@
|
|
|
2558
2802
|
value: true
|
|
2559
2803
|
})
|
|
2560
2804
|
exports.LinkedTreeList = void 0
|
|
2805
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
2806
|
+
require('core-js/modules/esnext.iterator.for-each.js')
|
|
2561
2807
|
const _TreeLinker = require('./TreeLinker')
|
|
2562
2808
|
const _TreeLinkerIterator = require('../../recipes/TreeLinkerIterator')
|
|
2563
2809
|
const _DoublyLinkedList = require('../doubly-linked-list/DoublyLinkedList')
|
|
@@ -2568,6 +2814,13 @@
|
|
|
2568
2814
|
* @memberOf module:collect-your-stuff
|
|
2569
2815
|
*/
|
|
2570
2816
|
|
|
2817
|
+
/**
|
|
2818
|
+
* Use one of the accessors of DoublyLinkedList (which keeps track of the head, tail and length) for a LinkedTreeList.
|
|
2819
|
+
* @param {string} name The accessor to use
|
|
2820
|
+
* @param {LinkedTreeList} list The list to use it on
|
|
2821
|
+
* @returns {*}
|
|
2822
|
+
*/
|
|
2823
|
+
const borrowedGetter = (name, list) => Object.getOwnPropertyDescriptor(_DoublyLinkedList.DoublyLinkedList.prototype, name).get.call(list)
|
|
2571
2824
|
/**
|
|
2572
2825
|
* LinkedTreeList represents a collection stored with a root and spreading in branching (tree) formation.
|
|
2573
2826
|
* @extends DoublyLinkedList
|
|
@@ -2575,11 +2828,21 @@
|
|
|
2575
2828
|
class LinkedTreeList {
|
|
2576
2829
|
/**
|
|
2577
2830
|
* Create the new LinkedTreeList instance, configure the list class.
|
|
2831
|
+
* @param {TreeLinker} [linkerClass=TreeLinker] The class used to wrap given data as tree linkers.
|
|
2578
2832
|
*/
|
|
2579
2833
|
constructor (linkerClass = _TreeLinker.TreeLinker) {
|
|
2834
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2580
2835
|
this.classType = LinkedTreeList
|
|
2836
|
+
/** A linker of the list (null when the list is empty); the head is found by walking back from it. */
|
|
2581
2837
|
this.innerList = null
|
|
2838
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
2582
2839
|
this.initialized = false
|
|
2840
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2841
|
+
this.tailCache = null
|
|
2842
|
+
/** The number of linkers, kept up to date by the list's own methods so that the length does not need to walk the whole list (null when not known yet). */
|
|
2843
|
+
this.countCache = null
|
|
2844
|
+
/** The node these linkers are the children of, remembered so that it is known even while the list is empty (undefined until it is known). */
|
|
2845
|
+
this.ownerNode = undefined
|
|
2583
2846
|
this.linkerClass = linkerClass
|
|
2584
2847
|
}
|
|
2585
2848
|
|
|
@@ -2599,7 +2862,7 @@
|
|
|
2599
2862
|
}
|
|
2600
2863
|
|
|
2601
2864
|
/**
|
|
2602
|
-
* Retrieve
|
|
2865
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2603
2866
|
* @returns {TreeLinker}
|
|
2604
2867
|
*/
|
|
2605
2868
|
get list () {
|
|
@@ -2611,57 +2874,46 @@
|
|
|
2611
2874
|
* @returns {TreeLinker}
|
|
2612
2875
|
*/
|
|
2613
2876
|
get first () {
|
|
2614
|
-
return this
|
|
2877
|
+
return borrowedGetter('first', this)
|
|
2615
2878
|
}
|
|
2616
2879
|
|
|
2617
2880
|
/**
|
|
2618
|
-
* Retrieve the last TreeLinker in the list.
|
|
2881
|
+
* Retrieve the last TreeLinker in the list. The end is remembered, so this does not walk the list.
|
|
2619
2882
|
* @returns {TreeLinker}
|
|
2620
2883
|
*/
|
|
2621
2884
|
get last () {
|
|
2622
|
-
|
|
2623
|
-
if (tail === null) {
|
|
2624
|
-
return null
|
|
2625
|
-
}
|
|
2626
|
-
let next = tail.next
|
|
2627
|
-
while (next !== null) {
|
|
2628
|
-
tail = next
|
|
2629
|
-
next = tail.next
|
|
2630
|
-
}
|
|
2631
|
-
return tail
|
|
2885
|
+
return borrowedGetter('last', this)
|
|
2632
2886
|
}
|
|
2633
2887
|
|
|
2634
2888
|
/**
|
|
2635
|
-
* Return the length of the list.
|
|
2889
|
+
* Return the length of the list. It is kept up to date by the list's own methods, so this does not walk the list
|
|
2890
|
+
* (call reset() after linkers were changed directly).
|
|
2636
2891
|
* @returns {number}
|
|
2637
2892
|
*/
|
|
2638
2893
|
get length () {
|
|
2639
|
-
|
|
2640
|
-
let length = 0
|
|
2641
|
-
while (current !== null) {
|
|
2642
|
-
++length
|
|
2643
|
-
current = current.next
|
|
2644
|
-
}
|
|
2645
|
-
return length
|
|
2894
|
+
return borrowedGetter('length', this)
|
|
2646
2895
|
}
|
|
2647
2896
|
|
|
2648
2897
|
/**
|
|
2649
|
-
* Get the parent of this tree list
|
|
2650
|
-
*
|
|
2898
|
+
* Get the parent of this tree list: the node these linkers are the children of (remembered even while the list is
|
|
2899
|
+
* empty), or null for the linkers at the top of a tree.
|
|
2900
|
+
* @return {TreeLinker|null}
|
|
2651
2901
|
*/
|
|
2652
2902
|
get parent () {
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
return null
|
|
2903
|
+
if (this.ownerNode !== undefined) {
|
|
2904
|
+
return this.ownerNode
|
|
2656
2905
|
}
|
|
2657
|
-
|
|
2906
|
+
const first = this.first
|
|
2907
|
+
return first === null ? null : first.parent
|
|
2658
2908
|
}
|
|
2659
2909
|
|
|
2660
2910
|
/**
|
|
2661
|
-
* Set the parent of this tree list
|
|
2662
|
-
*
|
|
2911
|
+
* Set the parent of this tree list: every linker in it gets the node as its parent, and the node gets this list as its
|
|
2912
|
+
* children. Linkers added to the list later get this parent too.
|
|
2913
|
+
* @param {TreeLinker|null} parent The new node to use as the parent for this group of children
|
|
2663
2914
|
*/
|
|
2664
2915
|
set parent (parent) {
|
|
2916
|
+
this.ownerNode = parent
|
|
2665
2917
|
let current = this.first
|
|
2666
2918
|
while (current !== null) {
|
|
2667
2919
|
current.parent = parent
|
|
@@ -2691,34 +2943,57 @@
|
|
|
2691
2943
|
|
|
2692
2944
|
/**
|
|
2693
2945
|
* Set the children on a parent item.
|
|
2694
|
-
* @param {TreeLinker} item The TreeLinker node that will be the parent of the children
|
|
2695
|
-
* @param {LinkedTreeList} children The LinkedTreeList which has the child nodes to use
|
|
2946
|
+
* @param {TreeLinker} item The TreeLinker node (one of the linkers of this list) that will be the parent of the children
|
|
2947
|
+
* @param {LinkedTreeList|null} [children=null] The LinkedTreeList which has the child nodes to use, or null to remove the children of the item
|
|
2948
|
+
* @throws {Error} When the item is not one of the linkers of this list
|
|
2696
2949
|
*/
|
|
2697
2950
|
setChildren (item, children = null) {
|
|
2698
|
-
|
|
2699
|
-
|
|
2951
|
+
// The item must be one of the linkers of this list (only the siblings are checked, not the whole tree)
|
|
2952
|
+
let isChild = false
|
|
2953
|
+
this.forEach(linker => {
|
|
2954
|
+
if (linker === item) {
|
|
2955
|
+
isChild = true
|
|
2956
|
+
}
|
|
2957
|
+
})
|
|
2958
|
+
if (!isChild) {
|
|
2959
|
+
throw new Error('The item is not one of the linkers of this list.')
|
|
2960
|
+
}
|
|
2961
|
+
if (children === null || typeof children === 'undefined') {
|
|
2962
|
+
item.children = null
|
|
2963
|
+
return
|
|
2700
2964
|
}
|
|
2701
2965
|
children.parent = item
|
|
2702
2966
|
}
|
|
2703
2967
|
|
|
2704
2968
|
/**
|
|
2705
|
-
*
|
|
2706
|
-
* @param {TreeLinker|*}
|
|
2969
|
+
* Make a linker of the given node (or data) and make this list's parent its parent.
|
|
2970
|
+
* @param {TreeLinker|*} newNode The node (or data) which is being added to this list
|
|
2971
|
+
* @returns {TreeLinker}
|
|
2972
|
+
*/
|
|
2973
|
+
adopt (newNode) {
|
|
2974
|
+
const linker = this.linkerClass.make(newNode, this.linkerClass)
|
|
2975
|
+
linker.parent = this.parent
|
|
2976
|
+
return linker
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/**
|
|
2980
|
+
* Insert a new node (or data) after a node. The new node gets the parent of this list.
|
|
2981
|
+
* @param {TreeLinker|*} node The existing node as reference, or null to insert at the start of the list
|
|
2707
2982
|
* @param {TreeLinker|*} newNode The new node to go after the existing node
|
|
2708
2983
|
* @returns {LinkedTreeList}
|
|
2709
2984
|
*/
|
|
2710
2985
|
insertAfter (node, newNode) {
|
|
2711
|
-
return _DoublyLinkedList.DoublyLinkedList.prototype.insertAfter.call(this, node, newNode)
|
|
2986
|
+
return _DoublyLinkedList.DoublyLinkedList.prototype.insertAfter.call(this, node, this.adopt(newNode))
|
|
2712
2987
|
}
|
|
2713
2988
|
|
|
2714
2989
|
/**
|
|
2715
|
-
* Insert a new node (or data) before a node.
|
|
2716
|
-
* @param {TreeLinker|*} node The existing node as reference
|
|
2990
|
+
* Insert a new node (or data) before a node. The new node gets the parent of this list.
|
|
2991
|
+
* @param {TreeLinker|*} node The existing node as reference, or null to insert at the end of the list
|
|
2717
2992
|
* @param {TreeLinker|*} newNode The new node to go before the existing node
|
|
2718
2993
|
* @returns {LinkedTreeList}
|
|
2719
2994
|
*/
|
|
2720
2995
|
insertBefore (node, newNode) {
|
|
2721
|
-
return _DoublyLinkedList.DoublyLinkedList.prototype.insertBefore.call(this, node, newNode)
|
|
2996
|
+
return _DoublyLinkedList.DoublyLinkedList.prototype.insertBefore.call(this, node, this.adopt(newNode))
|
|
2722
2997
|
}
|
|
2723
2998
|
|
|
2724
2999
|
/**
|
|
@@ -2742,16 +3017,24 @@
|
|
|
2742
3017
|
}
|
|
2743
3018
|
|
|
2744
3019
|
/**
|
|
2745
|
-
* Remove a linker from this linked list.
|
|
3020
|
+
* Remove a linker from this linked list. The removed node no longer has a parent.
|
|
2746
3021
|
* @param {TreeLinker} node The node we wish to remove (and it will be returned after removal)
|
|
2747
|
-
* @return {TreeLinker}
|
|
3022
|
+
* @return {TreeLinker|null} The removed node, or null when there was nothing to remove
|
|
2748
3023
|
*/
|
|
2749
3024
|
remove (node) {
|
|
2750
|
-
|
|
3025
|
+
const owner = this.parent
|
|
3026
|
+
const removed = _DoublyLinkedList.DoublyLinkedList.prototype.remove.call(this, node)
|
|
3027
|
+
if (removed && removed.parent === owner) {
|
|
3028
|
+
// Remember whose children these are (the list may now be empty), the removed node no longer has that parent
|
|
3029
|
+
this.ownerNode = owner
|
|
3030
|
+
removed.parent = null
|
|
3031
|
+
}
|
|
3032
|
+
return removed
|
|
2751
3033
|
}
|
|
2752
3034
|
|
|
2753
3035
|
/**
|
|
2754
|
-
* Refresh all references and return head
|
|
3036
|
+
* Refresh all references (the head, the end and the length) by walking the list once, and return the head. The
|
|
3037
|
+
* list's own methods keep these up to date, so this is only needed after linkers were changed directly.
|
|
2755
3038
|
* @return {TreeLinker}
|
|
2756
3039
|
*/
|
|
2757
3040
|
reset () {
|
|
@@ -2771,6 +3054,7 @@
|
|
|
2771
3054
|
* Be able to run forEach on this LinkedTreeList to iterate over the TreeLinker Items.
|
|
2772
3055
|
* @param {forEachCallback} callback The function to call for-each tree node
|
|
2773
3056
|
* @param {LinkedTreeList} thisArg Optional, 'this' reference
|
|
3057
|
+
* @return {LinkedTreeList} The list which was iterated.
|
|
2774
3058
|
*/
|
|
2775
3059
|
forEach (callback, thisArg = this) {
|
|
2776
3060
|
let index = 0
|
|
@@ -2784,12 +3068,14 @@
|
|
|
2784
3068
|
}
|
|
2785
3069
|
|
|
2786
3070
|
/**
|
|
2787
|
-
* Be able to iterate over this class.
|
|
3071
|
+
* Be able to iterate over this class: the linkers of this list and everything below them (left-first). It stays within
|
|
3072
|
+
* this list (it does not start at, or climb up to, the parents), use the parseTree service to parse a whole tree.
|
|
2788
3073
|
* @returns {Iterator}
|
|
2789
3074
|
*/
|
|
2790
3075
|
[Symbol.iterator] () {
|
|
2791
|
-
|
|
2792
|
-
|
|
3076
|
+
// The linkers of this list and everything below them, left-first. It stays within this list: it does not start at,
|
|
3077
|
+
// or climb up to, the parents (use the parseTree service to parse a whole tree)
|
|
3078
|
+
return new _TreeLinkerIterator.TreeLinkerIterator(this.first, this.parent)
|
|
2793
3079
|
}
|
|
2794
3080
|
}
|
|
2795
3081
|
/**
|
|
@@ -2804,7 +3090,7 @@
|
|
|
2804
3090
|
const list = new classType(linkerClass)
|
|
2805
3091
|
return list.initialize(linkerClass.fromArray(values).head)
|
|
2806
3092
|
}
|
|
2807
|
-
}, { '../../recipes/TreeLinkerIterator': 28, '../doubly-linked-list/DoublyLinkedList': 20, './TreeLinker': 24 }],
|
|
3093
|
+
}, { '../../recipes/TreeLinkerIterator': 28, '../doubly-linked-list/DoublyLinkedList': 20, './TreeLinker': 24, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.for-each.js': 133 }],
|
|
2808
3094
|
24: [function (require, module, exports) {
|
|
2809
3095
|
'use strict'
|
|
2810
3096
|
|
|
@@ -2823,7 +3109,7 @@
|
|
|
2823
3109
|
class TreeLinker {
|
|
2824
3110
|
/**
|
|
2825
3111
|
* Create the new TreeLinker instance, provide the data and optionally set references for next, prev, parent, or children.
|
|
2826
|
-
* @param {Object} [settings={}]
|
|
3112
|
+
* @param {Object} [settings={}] The settings for the new tree node.
|
|
2827
3113
|
* @param {*} [settings.data=null] The data to be stored in this tree node
|
|
2828
3114
|
* @param {TreeLinker} [settings.next=null] The reference to the next linker if any
|
|
2829
3115
|
* @param {TreeLinker} [settings.prev=null] The reference to the previous linker if any
|
|
@@ -2839,11 +3125,17 @@
|
|
|
2839
3125
|
parent = null,
|
|
2840
3126
|
listClass = _LinkedTreeList.LinkedTreeList
|
|
2841
3127
|
} = {}) {
|
|
3128
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2842
3129
|
this.classType = TreeLinker
|
|
3130
|
+
/** The data stored in this tree node. */
|
|
2843
3131
|
this.data = null
|
|
3132
|
+
/** The sibling after this node, or null when this is the last child. */
|
|
2844
3133
|
this.next = null
|
|
3134
|
+
/** The sibling before this node, or null when this is the first child. */
|
|
2845
3135
|
this.prev = null
|
|
3136
|
+
/** The node this node is a child of, or null for a root node. */
|
|
2846
3137
|
this.parent = null
|
|
3138
|
+
/** The list of the children of this node, or null when it has none. */
|
|
2847
3139
|
this.children = null
|
|
2848
3140
|
this.data = data
|
|
2849
3141
|
this.next = next
|
|
@@ -2853,7 +3145,9 @@
|
|
|
2853
3145
|
}
|
|
2854
3146
|
|
|
2855
3147
|
/**
|
|
2856
|
-
* Create the children for this tree from an array.
|
|
3148
|
+
* Create the children for this tree from an array. Each child becomes a tree linker with this node as its parent: an
|
|
3149
|
+
* existing linker is kept as it is, an object with a data property gives the settings of the linker, and anything
|
|
3150
|
+
* else is the data of the linker.
|
|
2857
3151
|
* @param {Array|null} children Provide an array of data / linker references to be children of this tree node.
|
|
2858
3152
|
* @param {IsArrayable<IsTreeNode>} listClass Give the type of list to use for storing the children
|
|
2859
3153
|
* @return {LinkedTreeList|null}
|
|
@@ -2862,10 +3156,17 @@
|
|
|
2862
3156
|
if (children === null) {
|
|
2863
3157
|
return null
|
|
2864
3158
|
}
|
|
2865
|
-
//
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3159
|
+
// Every child is made into a tree linker (an existing one is kept as it is, and a plain value is the data) and is
|
|
3160
|
+
// given this node as its parent
|
|
3161
|
+
const nodes = children.map(child => {
|
|
3162
|
+
const linker = this.classType.make(child, this.classType)
|
|
3163
|
+
linker.parent = this
|
|
3164
|
+
return linker
|
|
3165
|
+
})
|
|
3166
|
+
// Creates a linked-tree-list to store the children, which remembers this node as its parent even when it is empty
|
|
3167
|
+
const list = listClass.fromArray(nodes, this.classType)
|
|
3168
|
+
list.parent = this
|
|
3169
|
+
return list
|
|
2869
3170
|
}
|
|
2870
3171
|
}
|
|
2871
3172
|
/**
|
|
@@ -2897,11 +3198,21 @@
|
|
|
2897
3198
|
* Class ArrayIterator returns the next value when using elements of array type list.
|
|
2898
3199
|
*/
|
|
2899
3200
|
class ArrayIterator {
|
|
3201
|
+
/**
|
|
3202
|
+
* Create an iterator over the given array.
|
|
3203
|
+
* @param {Array<IsElement>} innerList The elements to iterate over.
|
|
3204
|
+
* @param {number} [index=0] The position to start from.
|
|
3205
|
+
*/
|
|
2900
3206
|
constructor (innerList, index = 0) {
|
|
2901
3207
|
this.innerList = innerList
|
|
2902
3208
|
this.index = index
|
|
2903
3209
|
}
|
|
2904
3210
|
|
|
3211
|
+
/**
|
|
3212
|
+
* Get the next element, moving the iterator forward.
|
|
3213
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3214
|
+
* @return {IteratorResult<IsElement>} The next element, or done when there are no more.
|
|
3215
|
+
*/
|
|
2905
3216
|
next (value) {
|
|
2906
3217
|
if (this.index < this.innerList.length) {
|
|
2907
3218
|
return {
|
|
@@ -2928,10 +3239,19 @@
|
|
|
2928
3239
|
* Class DoubleLinkerIterator returns the next value when using linkers of linked type lists.
|
|
2929
3240
|
*/
|
|
2930
3241
|
class DoubleLinkerIterator {
|
|
3242
|
+
/**
|
|
3243
|
+
* Create an iterator starting at the given item.
|
|
3244
|
+
* @param {IsDoubleLinker} current The item to start from.
|
|
3245
|
+
*/
|
|
2931
3246
|
constructor (current) {
|
|
2932
3247
|
this.current = current
|
|
2933
3248
|
}
|
|
2934
3249
|
|
|
3250
|
+
/**
|
|
3251
|
+
* Get the current item and move on to the following one.
|
|
3252
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3253
|
+
* @return {IteratorResult<IsDoubleLinker>} The current item, or done when there are no more.
|
|
3254
|
+
*/
|
|
2935
3255
|
next (value) {
|
|
2936
3256
|
const result = {
|
|
2937
3257
|
value: this.current,
|
|
@@ -2954,10 +3274,19 @@
|
|
|
2954
3274
|
* Class LinkerIterator returns the next value when using linkers of linked type lists.
|
|
2955
3275
|
*/
|
|
2956
3276
|
class LinkerIterator {
|
|
3277
|
+
/**
|
|
3278
|
+
* Create an iterator starting at the given item.
|
|
3279
|
+
* @param {IsLinker} current The item to start from.
|
|
3280
|
+
*/
|
|
2957
3281
|
constructor (current) {
|
|
2958
3282
|
this.current = current
|
|
2959
3283
|
}
|
|
2960
3284
|
|
|
3285
|
+
/**
|
|
3286
|
+
* Get the current item and move on to the following one.
|
|
3287
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3288
|
+
* @return {IteratorResult<IsLinker>} The current item, or done when there are no more.
|
|
3289
|
+
*/
|
|
2961
3290
|
next (value) {
|
|
2962
3291
|
const result = {
|
|
2963
3292
|
value: this.current,
|
|
@@ -2981,16 +3310,27 @@
|
|
|
2981
3310
|
* Class TreeLinkerIterator returns the next value taking a left-first approach down a tree.
|
|
2982
3311
|
*/
|
|
2983
3312
|
class TreeLinkerIterator {
|
|
2984
|
-
|
|
3313
|
+
/**
|
|
3314
|
+
* Create an iterator starting at the given item.
|
|
3315
|
+
* @param {IsTreeNode} current The item to start from.
|
|
3316
|
+
* @param {IsTreeNode|null} [boundaryParent] The parent of the nodes to stay within (null for the top of a tree), the whole tree when not given.
|
|
3317
|
+
*/
|
|
3318
|
+
constructor (current, boundaryParent) {
|
|
2985
3319
|
this.current = current
|
|
3320
|
+
this.boundaryParent = boundaryParent
|
|
2986
3321
|
}
|
|
2987
3322
|
|
|
3323
|
+
/**
|
|
3324
|
+
* Get the current item and move on to the following one (left-first, down each branch).
|
|
3325
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3326
|
+
* @return {IteratorResult<IsTreeNode>} The current item, or done when there are no more.
|
|
3327
|
+
*/
|
|
2988
3328
|
next (value) {
|
|
2989
3329
|
const result = {
|
|
2990
3330
|
value: this.current,
|
|
2991
3331
|
done: !this.current
|
|
2992
3332
|
}
|
|
2993
|
-
this.current = (0, _parseTreeNext.parseTreeNext)(this.current)
|
|
3333
|
+
this.current = (0, _parseTreeNext.parseTreeNext)(this.current, this.boundaryParent)
|
|
2994
3334
|
return result
|
|
2995
3335
|
}
|
|
2996
3336
|
}
|
|
@@ -3012,36 +3352,31 @@
|
|
|
3012
3352
|
* 5. Repeat 3
|
|
3013
3353
|
* 6. If no next child, return to parent and repeat 3
|
|
3014
3354
|
* 7. Stop at root (next is null and parent is null
|
|
3355
|
+
* A boundary can be given to parse only part of a tree: going back up to the parents stops at the boundary, so the
|
|
3356
|
+
* parsing stays within the nodes whose parent is the boundary (and everything below them).
|
|
3015
3357
|
* @param {IsTreeNode} treeNode Provide a node in a tree and get the next node (left-first approach)
|
|
3358
|
+
* @param {IsTreeNode|null} [boundaryParent] The parent of the nodes to stay within, null for the nodes at the top of a tree. When it is not given the whole tree is parsed.
|
|
3016
3359
|
* @returns {IsTreeNode|null}
|
|
3017
3360
|
*/
|
|
3018
|
-
const parseTreeNext = treeNode => {
|
|
3361
|
+
const parseTreeNext = (treeNode, boundaryParent) => {
|
|
3019
3362
|
if (!treeNode) {
|
|
3020
3363
|
return null
|
|
3021
3364
|
}
|
|
3022
|
-
let test = null
|
|
3023
3365
|
if (treeNode.children && treeNode.children.length) {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
parent = parent.parent
|
|
3039
|
-
}
|
|
3040
|
-
// This may be the parent sibling, or it could be null indicating we are done
|
|
3041
|
-
test = parentNext
|
|
3042
|
-
}
|
|
3043
|
-
// Finally, either use the node we found, or it may be null
|
|
3044
|
-
return test
|
|
3366
|
+
return treeNode.children.first
|
|
3367
|
+
}
|
|
3368
|
+
if (treeNode.next) {
|
|
3369
|
+
return treeNode.next
|
|
3370
|
+
}
|
|
3371
|
+
// Nothing more below or beside this node, so go back up until there is a node which has a next (or the boundary)
|
|
3372
|
+
let parent = treeNode.parent
|
|
3373
|
+
while (parent && parent !== boundaryParent) {
|
|
3374
|
+
if (parent.next) {
|
|
3375
|
+
return parent.next
|
|
3376
|
+
}
|
|
3377
|
+
parent = parent.parent
|
|
3378
|
+
}
|
|
3379
|
+
return null
|
|
3045
3380
|
}
|
|
3046
3381
|
exports.parseTreeNext = parseTreeNext
|
|
3047
3382
|
}, {}],
|