pseudo-dom 0.1.0 → 0.3.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 +626 -116
- package/browser/pseudo-dom.js +849 -407
- package/browser/pseudo-dom.min.js +1 -1
- package/dist/classes/PseudoEventListener.d.ts +25 -25
- package/dist/classes/PseudoEventListener.js +37 -34
- package/dist/classes/PseudoEventListener.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/interfaces/PseudoEventTarget.d.ts +2 -2
- 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 +6 -6
- package/dist/services/ElementService.js +28 -17
- package/dist/services/ElementService.min.js +1 -1
- package/dist/services/EventService.d.ts +16 -4
- package/dist/services/EventService.js +59 -31
- package/dist/services/EventService.min.js +1 -1
- package/dist/services/EventTargetService.d.ts +41 -9
- package/dist/services/EventTargetService.js +121 -52
- package/dist/services/EventTargetService.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
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
* @property {boolean} isDefault
|
|
21
21
|
*/
|
|
22
22
|
class PseudoEventListener {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} eventType The type of event this listens for
|
|
25
|
+
* @param {Object} [options] The capture, once and passive options
|
|
26
|
+
* @param {Function} handleEvent The function which is called with the event, already bound to what it should run as
|
|
27
|
+
* @param {Function} [originalCallback=handleEvent] The function (or object) which was given when registering, used to find this listener again
|
|
28
|
+
* @constructor
|
|
29
|
+
*/
|
|
23
30
|
constructor (eventType, {
|
|
24
31
|
capture = false,
|
|
25
32
|
once = false,
|
|
@@ -32,6 +39,7 @@
|
|
|
32
39
|
}
|
|
33
40
|
this.eventType = ''
|
|
34
41
|
this.defaultListener = false
|
|
42
|
+
this.isRemoved = false
|
|
35
43
|
this.eventOptions = {
|
|
36
44
|
capture,
|
|
37
45
|
once,
|
|
@@ -49,6 +57,11 @@
|
|
|
49
57
|
return this.originalCallback
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
/** Whether this listener listens in the capture phase (and at the target) rather than in the bubble phase. */
|
|
61
|
+
get capture () {
|
|
62
|
+
return this.eventOptions.capture
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
get isDefault () {
|
|
53
66
|
return this.defaultListener
|
|
54
67
|
}
|
|
@@ -57,6 +70,20 @@
|
|
|
57
70
|
return this.eventOptions.once
|
|
58
71
|
}
|
|
59
72
|
|
|
73
|
+
/** Whether the listener promises not to prevent the default (preventDefault does nothing while it runs). */
|
|
74
|
+
get passive () {
|
|
75
|
+
return this.eventOptions.passive
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Whether this listener has been removed, a removed listener does not run even if the event already started. */
|
|
79
|
+
get removed () {
|
|
80
|
+
return this.isRemoved
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
set removed (removed) {
|
|
84
|
+
this.isRemoved = removed
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
/**
|
|
61
88
|
* @method
|
|
62
89
|
* @name PseudoEventListener#handleEvent
|
|
@@ -68,6 +95,7 @@
|
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
/**
|
|
98
|
+
* A capture listener runs while the event travels down to the target.
|
|
71
99
|
* @method
|
|
72
100
|
* @name PseudoEventListener#doCapturePhase
|
|
73
101
|
* @param {PseudoEvent} event
|
|
@@ -78,6 +106,7 @@
|
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
/**
|
|
109
|
+
* Every listener of the target itself runs, capture listeners first.
|
|
81
110
|
* @method
|
|
82
111
|
* @name PseudoEventListener#doTargetPhase
|
|
83
112
|
* @param {PseudoEvent} event
|
|
@@ -88,13 +117,14 @@
|
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
/**
|
|
120
|
+
* A listener which is not a capture listener runs while the event travels back up (when it bubbles).
|
|
91
121
|
* @method
|
|
92
122
|
* @name PseudoEventListener#doBubblePhase
|
|
93
123
|
* @param {PseudoEvent} event
|
|
94
|
-
* @returns {boolean
|
|
124
|
+
* @returns {boolean}
|
|
95
125
|
*/
|
|
96
126
|
doBubblePhase (event) {
|
|
97
|
-
return event.eventPhase === EventService_1.EventService.BUBBLING_PHASE &&
|
|
127
|
+
return event.eventPhase === EventService_1.EventService.BUBBLING_PHASE && !this.eventOptions.capture
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
/**
|
|
@@ -108,43 +138,16 @@
|
|
|
108
138
|
}
|
|
109
139
|
|
|
110
140
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* @returns {boolean|*}
|
|
115
|
-
*/
|
|
116
|
-
skipDefault (event) {
|
|
117
|
-
return this.isDefault && event.defaultPrevented
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @method
|
|
122
|
-
* @name PseudoEventListener#stopPropagation
|
|
123
|
-
* @param {PseudoEvent} event
|
|
124
|
-
* @returns {boolean}
|
|
125
|
-
*/
|
|
126
|
-
stopPropagation (event) {
|
|
127
|
-
return !this.doTargetPhase(event) && event.inner.propagationStopped
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @method
|
|
132
|
-
* @name PseudoEventListener#nonPassiveHalt
|
|
133
|
-
* @param {PseudoEvent} event
|
|
134
|
-
* @returns {boolean|*}
|
|
135
|
-
*/
|
|
136
|
-
nonPassiveHalt (event) {
|
|
137
|
-
return !this.eventOptions.passive && (this.skipDefault(event) || event.inner.immediatePropagationStopped || this.stopPropagation(event))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
+
* Whether this listener should not run for the event as it is now (it was removed, or it is for another phase).
|
|
142
|
+
* Stopping propagation is handled by the dispatching, since it stops other targets and not the listeners of the
|
|
143
|
+
* current one.
|
|
141
144
|
* @method
|
|
142
145
|
* @name PseudoEventListener#rejectEvent
|
|
143
146
|
* @param {PseudoEvent} event
|
|
144
|
-
* @returns {
|
|
147
|
+
* @returns {boolean}
|
|
145
148
|
*/
|
|
146
149
|
rejectEvent (event) {
|
|
147
|
-
return this.
|
|
150
|
+
return this.isRemoved || this.skipPhase(event)
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
exports.default = PseudoEventListener
|
|
@@ -152,13 +155,6 @@
|
|
|
152
155
|
2: [function (require, module, exports) {
|
|
153
156
|
'use strict'
|
|
154
157
|
|
|
155
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
156
|
-
return mod && mod.__esModule
|
|
157
|
-
? mod
|
|
158
|
-
: {
|
|
159
|
-
default: mod
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
158
|
Object.defineProperty(exports, '__esModule', {
|
|
163
159
|
value: true
|
|
164
160
|
})
|
|
@@ -172,8 +168,6 @@
|
|
|
172
168
|
* @type {PseudoHTMLElement}
|
|
173
169
|
*/
|
|
174
170
|
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
171
|
/**
|
|
178
172
|
* Simulate the behaviour of the HTMLDocument Class when there is no DOM available.
|
|
179
173
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
@@ -181,7 +175,7 @@
|
|
|
181
175
|
* @augments PseudoHTMLElement
|
|
182
176
|
* @property {PseudoHTMLElement} head - A reference to the Head child element
|
|
183
177
|
* @property {PseudoHTMLElement} body - A reference to the Body child element
|
|
184
|
-
* @property {function} createElement - Generate a new PseudoHTMLElement
|
|
178
|
+
* @property {function} createElement - Generate a new PseudoHTMLElement (which is not in the document until it is appended)
|
|
185
179
|
*/
|
|
186
180
|
class PseudoHTMLDocument extends HTMLElementService_1.HTMLElementService {
|
|
187
181
|
/**
|
|
@@ -191,43 +185,41 @@
|
|
|
191
185
|
constructor () {
|
|
192
186
|
super()
|
|
193
187
|
const html = new HTMLElementService_1.HTMLElementService({
|
|
194
|
-
tagName: 'html'
|
|
195
|
-
parent: this
|
|
188
|
+
tagName: 'html'
|
|
196
189
|
})
|
|
190
|
+
this.appendChild(html)
|
|
197
191
|
/**
|
|
198
192
|
* Create document head element
|
|
199
193
|
* @type {PseudoHTMLElement}
|
|
200
194
|
*/
|
|
201
195
|
this.head = new HTMLElementService_1.HTMLElementService({
|
|
202
|
-
tagName: 'head'
|
|
203
|
-
parent: html
|
|
196
|
+
tagName: 'head'
|
|
204
197
|
})
|
|
198
|
+
html.appendChild(this.head)
|
|
205
199
|
/**
|
|
206
200
|
* Create document body element
|
|
207
201
|
* @type {PseudoHTMLElement}
|
|
208
202
|
*/
|
|
209
203
|
this.body = new HTMLElementService_1.HTMLElementService({
|
|
210
|
-
tagName: 'body'
|
|
211
|
-
parent: html
|
|
204
|
+
tagName: 'body'
|
|
212
205
|
})
|
|
213
|
-
html.
|
|
206
|
+
html.appendChild(this.body)
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
/**
|
|
217
|
-
* Create and return a PseudoHTMLElement
|
|
210
|
+
* Create and return a PseudoHTMLElement, which is not added to the document until it is appended somewhere
|
|
218
211
|
* @param {string} tagName - Tag Name is a string representing the type of Dom element this represents
|
|
219
212
|
* @returns {PseudoHTMLElement}
|
|
220
213
|
*/
|
|
221
214
|
createElement (tagName = 'div') {
|
|
222
|
-
|
|
215
|
+
// Like the DOM, the new element is not added anywhere: it has no parent until it is appended
|
|
216
|
+
return new HTMLElementService_1.HTMLElementService({
|
|
223
217
|
tagName
|
|
224
218
|
})
|
|
225
|
-
returnElement.parent = this
|
|
226
|
-
return returnElement
|
|
227
219
|
}
|
|
228
220
|
}
|
|
229
221
|
exports.default = PseudoHTMLDocument
|
|
230
|
-
}, { '../
|
|
222
|
+
}, { '../services/HTMLElementService': 14 }],
|
|
231
223
|
3: [function (require, module, exports) {
|
|
232
224
|
'use strict'
|
|
233
225
|
|
|
@@ -255,16 +247,22 @@
|
|
|
255
247
|
* @returns {Iterator}
|
|
256
248
|
*/
|
|
257
249
|
[Symbol.iterator] () {
|
|
258
|
-
|
|
250
|
+
// Walk the nodes of this list only (the linkers of a child list have no children of their own)
|
|
251
|
+
let current = this.first
|
|
259
252
|
return {
|
|
260
253
|
next: () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
254
|
+
if (current === null) {
|
|
255
|
+
return {
|
|
256
|
+
done: true,
|
|
257
|
+
value: undefined
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const result = {
|
|
261
|
+
done: false,
|
|
262
|
+
value: current.data
|
|
263
|
+
}
|
|
264
|
+
current = current.next
|
|
265
|
+
return result
|
|
268
266
|
}
|
|
269
267
|
}
|
|
270
268
|
}
|
|
@@ -380,31 +378,58 @@
|
|
|
380
378
|
6: [function (require, module, exports) {
|
|
381
379
|
'use strict'
|
|
382
380
|
|
|
383
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
384
|
-
return mod && mod.__esModule
|
|
385
|
-
? mod
|
|
386
|
-
: {
|
|
387
|
-
default: mod
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
381
|
Object.defineProperty(exports, '__esModule', {
|
|
391
382
|
value: true
|
|
392
383
|
})
|
|
393
|
-
|
|
394
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Get all of the ancestors of a node, starting with the root of the tree and ending with the node's own parent (the
|
|
386
|
+
* order in which an event travels down through them). A node which has no parent has no ancestors.
|
|
387
|
+
* @function getParentNodes
|
|
388
|
+
* @param {PseudoEventTarget|PseudoNode|*} node The node to find the ancestors of
|
|
389
|
+
* @returns {Array<PseudoNode>}
|
|
390
|
+
*/
|
|
391
|
+
const getParentNodes = node => {
|
|
392
|
+
const parents = []
|
|
393
|
+
let current = node && node.parentNode ? node.parentNode : null
|
|
394
|
+
while (current) {
|
|
395
|
+
parents.unshift(current)
|
|
396
|
+
current = current.parentNode
|
|
397
|
+
}
|
|
398
|
+
return parents
|
|
399
|
+
}
|
|
395
400
|
exports.default = getParentNodes
|
|
396
|
-
}, {
|
|
401
|
+
}, {}],
|
|
397
402
|
7: [function (require, module, exports) {
|
|
398
403
|
'use strict'
|
|
399
404
|
|
|
405
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
406
|
+
require('core-js/modules/esnext.iterator.filter.js')
|
|
407
|
+
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
408
|
+
return mod && mod.__esModule
|
|
409
|
+
? mod
|
|
410
|
+
: {
|
|
411
|
+
default: mod
|
|
412
|
+
}
|
|
413
|
+
}
|
|
400
414
|
Object.defineProperty(exports, '__esModule', {
|
|
401
415
|
value: true
|
|
402
416
|
})
|
|
417
|
+
const getParentNodes_1 = __importDefault(require('./getParentNodes'))
|
|
418
|
+
/**
|
|
419
|
+
* A selector function for retrieving existing parent PseudoNode from the given child item.
|
|
420
|
+
* This function will check all the parents starting from node, and scan the attributes
|
|
421
|
+
* property for matches. The return array contains all matching parent ancestors, starting with the root of the tree.
|
|
422
|
+
* @function getParentNodesFromAttribute
|
|
423
|
+
* @param {string} attr The property to compare on each ancestor (a missing property counts as false)
|
|
424
|
+
* @param {boolean|number|string} value The value the property must have
|
|
425
|
+
* @param {PseudoEventTarget|PseudoNode|*} node The node to find the matching ancestors of
|
|
426
|
+
* @returns {Array.<PseudoNode>}
|
|
427
|
+
*/
|
|
403
428
|
const getParentNodesFromAttribute = (attr, value, node) => {
|
|
404
|
-
return
|
|
429
|
+
return (0, getParentNodes_1.default)(node).filter(parent => (parent[attr] || false) === value)
|
|
405
430
|
}
|
|
406
431
|
exports.default = getParentNodesFromAttribute
|
|
407
|
-
}, {}],
|
|
432
|
+
}, { './getParentNodes': 6, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.filter.js': 131 }],
|
|
408
433
|
8: [function (require, module, exports) {
|
|
409
434
|
'use strict'
|
|
410
435
|
|
|
@@ -679,8 +704,7 @@
|
|
|
679
704
|
value: true
|
|
680
705
|
})
|
|
681
706
|
exports.ElementService = void 0
|
|
682
|
-
const
|
|
683
|
-
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
707
|
+
const EventService_1 = require('./EventService')
|
|
684
708
|
const NodeService_1 = require('./NodeService')
|
|
685
709
|
const AttrService_1 = require('./AttrService')
|
|
686
710
|
const DOMTokenListService_1 = require('./DOMTokenListService')
|
|
@@ -706,8 +730,8 @@
|
|
|
706
730
|
* @param {Object} [settings={}]
|
|
707
731
|
* @param {string} [settings.tagName=''] The name of the tag this element represents
|
|
708
732
|
* @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
|
|
733
|
+
* @param {PseudoNode|null} [settings.parent=null] The node to add this element to as its last child
|
|
734
|
+
* @param {Array<PseudoNode>} [settings.children=[]] The nodes to start as children
|
|
711
735
|
* @constructor
|
|
712
736
|
*/
|
|
713
737
|
constructor ({
|
|
@@ -717,9 +741,8 @@
|
|
|
717
741
|
children = []
|
|
718
742
|
} = {}) {
|
|
719
743
|
super()
|
|
744
|
+
this.defaultEventApplied = false
|
|
720
745
|
this.tokenList = new DOMTokenListService_1.DOMTokenListService()
|
|
721
|
-
this.parent = parent
|
|
722
|
-
this.children = (0, generateNodeList_1.default)(TreeLinker_1.TreeLinker.fromArray(children).head)
|
|
723
746
|
this.tag = tagName
|
|
724
747
|
this.attributeList = attributes.concat([{
|
|
725
748
|
name: 'className',
|
|
@@ -740,6 +763,15 @@
|
|
|
740
763
|
}) => {
|
|
741
764
|
this[name] = value
|
|
742
765
|
})
|
|
766
|
+
children.forEach(child => {
|
|
767
|
+
if (!child || typeof child.nodeType !== 'number') {
|
|
768
|
+
throw new TypeError('The children of an element must be nodes.')
|
|
769
|
+
}
|
|
770
|
+
this.appendChild(child)
|
|
771
|
+
})
|
|
772
|
+
if (parent) {
|
|
773
|
+
parent.appendChild(this)
|
|
774
|
+
}
|
|
743
775
|
}
|
|
744
776
|
|
|
745
777
|
get tagName () {
|
|
@@ -775,34 +807,38 @@
|
|
|
775
807
|
*/
|
|
776
808
|
applyDefaultEvent () {
|
|
777
809
|
let callback = event => undefined
|
|
810
|
+
if (this.defaultEventApplied) {
|
|
811
|
+
return callback
|
|
812
|
+
}
|
|
778
813
|
switch (this.tagName) {
|
|
779
|
-
case 'form':
|
|
780
|
-
this.addEventListener('submit', callback)
|
|
781
|
-
break
|
|
782
814
|
case 'button':
|
|
783
815
|
case 'input':
|
|
784
816
|
if (/^(submit|image)$/i.test(this.type || '')) {
|
|
817
|
+
// Clicking a submit button submits the form it is in: the form gets a submit event, which can be cancelled
|
|
785
818
|
callback = event => {
|
|
786
819
|
const forms = (0, getParentNodesFromAttribute_1.default)('tagName', 'form', this)
|
|
787
820
|
if (forms.length) {
|
|
788
|
-
forms[
|
|
821
|
+
forms[forms.length - 1].dispatchEvent(new EventService_1.EventService('submit', {
|
|
822
|
+
bubbles: true,
|
|
823
|
+
cancelable: true
|
|
824
|
+
}))
|
|
789
825
|
}
|
|
790
826
|
}
|
|
791
827
|
super.setDefaultEvent('click', callback)
|
|
828
|
+
this.defaultEventApplied = true
|
|
792
829
|
}
|
|
793
830
|
}
|
|
794
831
|
return callback
|
|
795
832
|
}
|
|
796
833
|
|
|
797
834
|
/**
|
|
798
|
-
*
|
|
799
|
-
* @param {
|
|
800
|
-
* @returns {PseudoNode}
|
|
835
|
+
* An element which is added as a child gets its default events (for example a submit button submits its form).
|
|
836
|
+
* @param {NodeService} child The node which was inserted
|
|
801
837
|
*/
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
838
|
+
childInserted (child) {
|
|
839
|
+
if (typeof child.applyDefaultEvent === 'function') {
|
|
840
|
+
child.applyDefaultEvent()
|
|
841
|
+
}
|
|
806
842
|
}
|
|
807
843
|
|
|
808
844
|
/**
|
|
@@ -866,7 +902,7 @@
|
|
|
866
902
|
}
|
|
867
903
|
}
|
|
868
904
|
exports.ElementService = ElementService
|
|
869
|
-
}, { '../
|
|
905
|
+
}, { '../functions/getParentNodesFromAttribute': 7, './AttrService': 9, './DOMTokenListService': 10, './EventService': 12, './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
906
|
12: [function (require, module, exports) {
|
|
871
907
|
'use strict'
|
|
872
908
|
|
|
@@ -875,18 +911,10 @@
|
|
|
875
911
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
876
912
|
* @version 1.0.0
|
|
877
913
|
*/
|
|
878
|
-
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
879
|
-
return mod && mod.__esModule
|
|
880
|
-
? mod
|
|
881
|
-
: {
|
|
882
|
-
default: mod
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
914
|
Object.defineProperty(exports, '__esModule', {
|
|
886
915
|
value: true
|
|
887
916
|
})
|
|
888
917
|
exports.EventService = void 0
|
|
889
|
-
const getParentNodes_1 = __importDefault(require('../functions/getParentNodes'))
|
|
890
918
|
/**
|
|
891
919
|
* Simulate the behaviour of the Event Class when there is no DOM available.
|
|
892
920
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
@@ -921,20 +949,20 @@
|
|
|
921
949
|
*
|
|
922
950
|
* @param {string} typeArg
|
|
923
951
|
* @param {Object} [eventOptions={}]
|
|
924
|
-
* @param {boolean} [eventOptions.bubbles=
|
|
925
|
-
* @param {boolean} [eventOptions.cancelable=
|
|
926
|
-
* @param {boolean} [eventOptions.composed=
|
|
952
|
+
* @param {boolean} [eventOptions.bubbles=false]
|
|
953
|
+
* @param {boolean} [eventOptions.cancelable=false]
|
|
954
|
+
* @param {boolean} [eventOptions.composed=false]
|
|
927
955
|
* @constructor
|
|
928
956
|
*/
|
|
929
957
|
constructor (typeArg = '', {
|
|
930
|
-
bubbles =
|
|
931
|
-
cancelable =
|
|
932
|
-
composed =
|
|
958
|
+
bubbles = false,
|
|
959
|
+
cancelable = false,
|
|
960
|
+
composed = false
|
|
933
961
|
} = {}) {
|
|
934
962
|
this.properties = {
|
|
935
|
-
bubbles:
|
|
936
|
-
cancelable:
|
|
937
|
-
composed:
|
|
963
|
+
bubbles: false,
|
|
964
|
+
cancelable: false,
|
|
965
|
+
composed: false,
|
|
938
966
|
currentTarget: null,
|
|
939
967
|
defaultPrevented: false,
|
|
940
968
|
immediatePropagationStopped: false,
|
|
@@ -943,7 +971,10 @@
|
|
|
943
971
|
target: null,
|
|
944
972
|
timeStamp: Math.floor(Date.now() / 1000),
|
|
945
973
|
type: '',
|
|
946
|
-
isTrusted: true
|
|
974
|
+
isTrusted: true,
|
|
975
|
+
dispatching: false,
|
|
976
|
+
inPassiveListener: false,
|
|
977
|
+
path: []
|
|
947
978
|
}
|
|
948
979
|
this.setReadOnlyProperties({
|
|
949
980
|
type: typeArg,
|
|
@@ -1000,12 +1031,21 @@
|
|
|
1000
1031
|
get inner () {
|
|
1001
1032
|
const self = this
|
|
1002
1033
|
return {
|
|
1034
|
+
get currentTarget () {
|
|
1035
|
+
return self.properties.currentTarget
|
|
1036
|
+
},
|
|
1003
1037
|
set currentTarget (target) {
|
|
1004
1038
|
self.properties.currentTarget = target
|
|
1005
1039
|
},
|
|
1040
|
+
get eventPhase () {
|
|
1041
|
+
return self.properties.eventPhase
|
|
1042
|
+
},
|
|
1006
1043
|
set eventPhase (phase) {
|
|
1007
1044
|
self.properties.eventPhase = phase
|
|
1008
1045
|
},
|
|
1046
|
+
get target () {
|
|
1047
|
+
return self.properties.target
|
|
1048
|
+
},
|
|
1009
1049
|
set target (target) {
|
|
1010
1050
|
self.properties.target = target
|
|
1011
1051
|
},
|
|
@@ -1014,6 +1054,35 @@
|
|
|
1014
1054
|
},
|
|
1015
1055
|
get propagationStopped () {
|
|
1016
1056
|
return self.properties.propagationStopped
|
|
1057
|
+
},
|
|
1058
|
+
get dispatching () {
|
|
1059
|
+
return self.properties.dispatching
|
|
1060
|
+
},
|
|
1061
|
+
set dispatching (dispatching) {
|
|
1062
|
+
self.properties.dispatching = dispatching
|
|
1063
|
+
},
|
|
1064
|
+
get inPassiveListener () {
|
|
1065
|
+
return self.properties.inPassiveListener
|
|
1066
|
+
},
|
|
1067
|
+
set inPassiveListener (passive) {
|
|
1068
|
+
self.properties.inPassiveListener = passive
|
|
1069
|
+
},
|
|
1070
|
+
get path () {
|
|
1071
|
+
return self.properties.path
|
|
1072
|
+
},
|
|
1073
|
+
set path (path) {
|
|
1074
|
+
self.properties.path = path
|
|
1075
|
+
},
|
|
1076
|
+
finishDispatch () {
|
|
1077
|
+
self.setReadOnlyProperties({
|
|
1078
|
+
currentTarget: null,
|
|
1079
|
+
eventPhase: EventService.NONE,
|
|
1080
|
+
path: [],
|
|
1081
|
+
dispatching: false,
|
|
1082
|
+
inPassiveListener: false,
|
|
1083
|
+
propagationStopped: false,
|
|
1084
|
+
immediatePropagationStopped: false
|
|
1085
|
+
})
|
|
1017
1086
|
}
|
|
1018
1087
|
}
|
|
1019
1088
|
}
|
|
@@ -1024,16 +1093,8 @@
|
|
|
1024
1093
|
* @returns {Array.<PseudoEventTarget>}
|
|
1025
1094
|
*/
|
|
1026
1095
|
composedPath () {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return (0, getParentNodes_1.default)(this.target)
|
|
1030
|
-
case EventService.BUBBLING_PHASE:
|
|
1031
|
-
return (0, getParentNodes_1.default)(this.target).slice().reverse()
|
|
1032
|
-
case EventService.AT_TARGET:
|
|
1033
|
-
return [this.target]
|
|
1034
|
-
default:
|
|
1035
|
-
return []
|
|
1036
|
-
}
|
|
1096
|
+
// While the event is being dispatched this is every target it travels through, the target first and the root last
|
|
1097
|
+
return this.properties.dispatching ? this.properties.path.slice() : []
|
|
1037
1098
|
}
|
|
1038
1099
|
|
|
1039
1100
|
/**
|
|
@@ -1042,9 +1103,12 @@
|
|
|
1042
1103
|
* @returns {null}
|
|
1043
1104
|
*/
|
|
1044
1105
|
preventDefault () {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1106
|
+
// Only an event which can be cancelled can be prevented, and a passive listener cannot prevent the default
|
|
1107
|
+
if (this.cancelable && !this.properties.inPassiveListener) {
|
|
1108
|
+
this.setReadOnlyProperties({
|
|
1109
|
+
defaultPrevented: true
|
|
1110
|
+
})
|
|
1111
|
+
}
|
|
1048
1112
|
return null
|
|
1049
1113
|
}
|
|
1050
1114
|
|
|
@@ -1084,7 +1148,7 @@
|
|
|
1084
1148
|
EventService.CAPTURING_PHASE = 1
|
|
1085
1149
|
EventService.AT_TARGET = 2
|
|
1086
1150
|
EventService.BUBBLING_PHASE = 3
|
|
1087
|
-
}, {
|
|
1151
|
+
}, {}],
|
|
1088
1152
|
13: [function (require, module, exports) {
|
|
1089
1153
|
'use strict'
|
|
1090
1154
|
|
|
@@ -1092,6 +1156,8 @@
|
|
|
1092
1156
|
require('core-js/modules/esnext.iterator.filter.js')
|
|
1093
1157
|
require('core-js/modules/esnext.iterator.find.js')
|
|
1094
1158
|
require('core-js/modules/esnext.iterator.for-each.js')
|
|
1159
|
+
require('core-js/modules/esnext.iterator.map.js')
|
|
1160
|
+
require('core-js/modules/esnext.iterator.some.js')
|
|
1095
1161
|
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
1096
1162
|
return mod && mod.__esModule
|
|
1097
1163
|
? mod
|
|
@@ -1109,9 +1175,13 @@
|
|
|
1109
1175
|
*/
|
|
1110
1176
|
const EventService_1 = require('./EventService')
|
|
1111
1177
|
const PseudoEventListener_1 = __importDefault(require('../classes/PseudoEventListener'))
|
|
1178
|
+
const getParentNodes_1 = __importDefault(require('../functions/getParentNodes'))
|
|
1112
1179
|
const LinkedList_1 = require('collect-your-stuff/dist/collections/linked-list/LinkedList')
|
|
1113
1180
|
/**
|
|
1114
1181
|
* Simulate the behaviour of the EventTarget Class when there is no DOM available.
|
|
1182
|
+
* Dispatching an event sends it through the tree the way the DOM does: down from the root to the target (capture
|
|
1183
|
+
* listeners), to the target itself, then back up to the root (the listeners which are not capture listeners, when the
|
|
1184
|
+
* event bubbles).
|
|
1115
1185
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
1116
1186
|
* @class
|
|
1117
1187
|
* @property {Object.<string, Array.<PseudoEventListener>>} listeners
|
|
@@ -1141,33 +1211,51 @@
|
|
|
1141
1211
|
}
|
|
1142
1212
|
|
|
1143
1213
|
/**
|
|
1144
|
-
* Run
|
|
1145
|
-
*
|
|
1146
|
-
* and listeners
|
|
1147
|
-
*
|
|
1148
|
-
* @
|
|
1214
|
+
* Run the listeners registered on this target for the type of the event which apply to the phase the event is in
|
|
1215
|
+
* (at the target, the capture listeners run before the others). Listeners which are added while this runs do not run
|
|
1216
|
+
* for this event, and listeners which are removed while it runs no longer do. Running stops as soon as immediate
|
|
1217
|
+
* propagation is stopped. A listener which throws does not stop the others.
|
|
1218
|
+
* @param {EventService} event The event, which is at a phase and has a current target
|
|
1219
|
+
* @returns {Array<*>} The errors which the listeners threw
|
|
1149
1220
|
*/
|
|
1150
1221
|
runEvents (event) {
|
|
1222
|
+
const errors = []
|
|
1151
1223
|
if (!(event.type in this.listeners)) {
|
|
1152
|
-
return
|
|
1224
|
+
return errors
|
|
1153
1225
|
}
|
|
1154
|
-
const listeners = this.listeners[event.type]
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
for (const
|
|
1158
|
-
const listener = linker.data
|
|
1226
|
+
const listeners = Array.from(this.listeners[event.type]).map(linker => linker.data)
|
|
1227
|
+
// At the target the capture listeners come first, otherwise the order is the order they were added
|
|
1228
|
+
const ordered = event.eventPhase === EventService_1.EventService.AT_TARGET ? listeners.filter(listener => listener.capture).concat(listeners.filter(listener => !listener.capture)) : listeners
|
|
1229
|
+
for (const listener of ordered) {
|
|
1159
1230
|
if (event.inner.immediatePropagationStopped) {
|
|
1160
1231
|
break
|
|
1161
1232
|
}
|
|
1162
1233
|
if (listener.rejectEvent(event)) {
|
|
1163
1234
|
continue
|
|
1164
1235
|
}
|
|
1165
|
-
eventReturn = listener.handleEvent(event)
|
|
1166
1236
|
if (listener.once) {
|
|
1167
|
-
|
|
1237
|
+
this.removeListener(event.type, listener)
|
|
1238
|
+
}
|
|
1239
|
+
event.inner.inPassiveListener = listener.passive
|
|
1240
|
+
try {
|
|
1241
|
+
listener.handleEvent(event)
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
errors.push(error)
|
|
1168
1244
|
}
|
|
1245
|
+
event.inner.inPassiveListener = false
|
|
1169
1246
|
}
|
|
1170
|
-
return
|
|
1247
|
+
return errors
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Take a listener out of the registered listeners, so that it does not run again.
|
|
1252
|
+
* @param {string} type
|
|
1253
|
+
* @param {PseudoEventListener} listener
|
|
1254
|
+
*/
|
|
1255
|
+
removeListener (type, listener) {
|
|
1256
|
+
listener.removed = true
|
|
1257
|
+
const registered = this.listeners[type]
|
|
1258
|
+
Array.from(registered).filter(linker => linker.data === listener).forEach(linker => registered.remove(linker))
|
|
1171
1259
|
}
|
|
1172
1260
|
|
|
1173
1261
|
/**
|
|
@@ -1180,48 +1268,34 @@
|
|
|
1180
1268
|
this.defaultEvent[type] = callback
|
|
1181
1269
|
}
|
|
1182
1270
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
startEvents (eventType) {
|
|
1192
|
-
const event = new EventService_1.EventService(eventType)
|
|
1193
|
-
event.inner.target = this;
|
|
1194
|
-
[EventService_1.EventService.CAPTURING_PHASE, EventService_1.EventService.AT_TARGET, EventService_1.EventService.BUBBLING_PHASE].forEach(phase => {
|
|
1195
|
-
let continueEvents = null
|
|
1196
|
-
if (phase === EventService_1.EventService.AT_TARGET || !event.inner.propagationStopped) {
|
|
1197
|
-
event.inner.eventPhase = phase
|
|
1198
|
-
event.composedPath().forEach(target => {
|
|
1199
|
-
event.inner.currentTarget = target
|
|
1200
|
-
continueEvents = event.currentTarget.runEvents(event)
|
|
1201
|
-
})
|
|
1202
|
-
}
|
|
1203
|
-
if (event.eventPhase === EventService_1.EventService.AT_TARGET && typeof continueEvents !== 'boolean' && this.defaultEvent[eventType]) {
|
|
1204
|
-
this.runDefaultEvent(event)
|
|
1205
|
-
}
|
|
1206
|
-
})
|
|
1207
|
-
return true
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1271
|
+
/**
|
|
1272
|
+
* Registers an event handler of a specific event type. Adding the same handler again for the same type and phase does
|
|
1273
|
+
* nothing, like the DOM.
|
|
1274
|
+
* @param {string} type The type of event to listen for
|
|
1275
|
+
* @param {Function|Object} callback The function to call (or an object with a handleEvent function)
|
|
1276
|
+
* @param {Object|boolean} [useCapture=false] Listen while the event travels down to the target (true), or an object with capture, once and passive
|
|
1277
|
+
*/
|
|
1210
1278
|
addEventListener (type, callback, useCapture = false) {
|
|
1211
1279
|
let options = {
|
|
1212
1280
|
capture: false,
|
|
1213
1281
|
once: false,
|
|
1214
1282
|
passive: false
|
|
1215
1283
|
}
|
|
1216
|
-
if (typeof useCapture === 'object') {
|
|
1284
|
+
if (typeof useCapture === 'object' && useCapture !== null) {
|
|
1217
1285
|
// Originally useCapture was a single boolean flag, later optional other flags can be used
|
|
1218
1286
|
// Here we take all the given flags from the object and assign them as the options
|
|
1219
1287
|
options = Object.assign(options, useCapture)
|
|
1220
1288
|
} else {
|
|
1221
|
-
options.capture = useCapture
|
|
1289
|
+
options.capture = !!useCapture
|
|
1222
1290
|
}
|
|
1223
|
-
const listener = new PseudoEventListener_1.default(type, options, (callback.handleEvent || callback).bind(this), callback)
|
|
1224
1291
|
const listeners = this.listenersFor(type)
|
|
1292
|
+
const alreadyAdded = Array.from(listeners).some(linker => linker.data.callback === callback && linker.data.capture === options.capture)
|
|
1293
|
+
if (alreadyAdded) {
|
|
1294
|
+
return
|
|
1295
|
+
}
|
|
1296
|
+
// A function runs with this target as this, an object runs its handleEvent as itself
|
|
1297
|
+
const handler = typeof callback === 'function' ? callback.bind(this) : callback.handleEvent.bind(callback)
|
|
1298
|
+
const listener = new PseudoEventListener_1.default(type, options, handler, callback)
|
|
1225
1299
|
// Listeners run in the order they were added, except that listeners which are not defaults always come before the defaults
|
|
1226
1300
|
const firstDefault = Array.from(listeners).find(linker => linker.data.isDefault)
|
|
1227
1301
|
if (firstDefault && !listener.isDefault) {
|
|
@@ -1231,25 +1305,84 @@
|
|
|
1231
1305
|
}
|
|
1232
1306
|
}
|
|
1233
1307
|
|
|
1234
|
-
|
|
1308
|
+
/**
|
|
1309
|
+
* Removes an event listener, the one which was added with the same type, handler and phase.
|
|
1310
|
+
* @param {string} type The type of event
|
|
1311
|
+
* @param {Function|Object} callback The handler which was added
|
|
1312
|
+
* @param {Object|boolean} [options=false] Whether the listener was a capture listener (true), or an object with capture
|
|
1313
|
+
*/
|
|
1314
|
+
removeEventListener (type, callback, options = false) {
|
|
1235
1315
|
if (!(type in this.listeners)) {
|
|
1236
1316
|
return
|
|
1237
1317
|
}
|
|
1238
|
-
const
|
|
1239
|
-
Array.from(listeners).
|
|
1318
|
+
const capture = typeof options === 'object' && options !== null ? !!options.capture : !!options
|
|
1319
|
+
Array.from(this.listeners[type]).map(linker => linker.data).filter(listener => !listener.isDefault && listener.callback === callback && listener.capture === capture).forEach(listener => this.removeListener(type, listener))
|
|
1240
1320
|
}
|
|
1241
1321
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1322
|
+
/**
|
|
1323
|
+
* Dispatches an event to this target and through the tree: capture listeners of the ancestors from the root down,
|
|
1324
|
+
* then the listeners of this target, then (when the event bubbles) the other listeners of the ancestors from the
|
|
1325
|
+
* parent up to the root. stopPropagation() stops it reaching further targets, stopImmediatePropagation() also stops
|
|
1326
|
+
* the remaining listeners of the current target. Afterwards, unless the default was prevented, the default action
|
|
1327
|
+
* of this target (see setDefaultEvent) runs. The event can be dispatched again afterwards.
|
|
1328
|
+
* @param {EventService} event The event to dispatch
|
|
1329
|
+
* @returns {boolean} False when the event was cancelable and a listener prevented the default, otherwise true
|
|
1330
|
+
* @throws {Error} When the event is already being dispatched, or (after the whole dispatch has finished) the error
|
|
1331
|
+
* which a listener threw (an error with all of them in its errors property when several did)
|
|
1332
|
+
*/
|
|
1333
|
+
dispatchEvent (event) {
|
|
1334
|
+
if (event.inner.dispatching) {
|
|
1335
|
+
throw new Error('The event is already being dispatched.')
|
|
1336
|
+
}
|
|
1337
|
+
event.inner.dispatching = true
|
|
1338
|
+
event.inner.target = this
|
|
1339
|
+
// The ancestors, the root first, which can have listeners
|
|
1340
|
+
const ancestors = (0, getParentNodes_1.default)(this).filter(node => node instanceof EventTargetService)
|
|
1341
|
+
event.inner.path = [this].concat(ancestors.slice().reverse())
|
|
1342
|
+
const errors = []
|
|
1343
|
+
const visit = (target, phase) => {
|
|
1344
|
+
event.inner.eventPhase = phase
|
|
1345
|
+
event.inner.currentTarget = target
|
|
1346
|
+
errors.push(...target.runEvents(event))
|
|
1347
|
+
}
|
|
1348
|
+
for (const ancestor of ancestors) {
|
|
1349
|
+
if (event.inner.propagationStopped) {
|
|
1350
|
+
break
|
|
1351
|
+
}
|
|
1352
|
+
visit(ancestor, EventService_1.EventService.CAPTURING_PHASE)
|
|
1353
|
+
}
|
|
1354
|
+
if (!event.inner.propagationStopped) {
|
|
1355
|
+
visit(this, EventService_1.EventService.AT_TARGET)
|
|
1356
|
+
}
|
|
1357
|
+
if (event.bubbles) {
|
|
1358
|
+
for (const ancestor of ancestors.slice().reverse()) {
|
|
1359
|
+
if (event.inner.propagationStopped) {
|
|
1360
|
+
break
|
|
1361
|
+
}
|
|
1362
|
+
visit(ancestor, EventService_1.EventService.BUBBLING_PHASE)
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
event.inner.finishDispatch()
|
|
1366
|
+
if (!event.defaultPrevented && typeof this.defaultEvent[event.type] === 'function') {
|
|
1367
|
+
try {
|
|
1368
|
+
this.defaultEvent[event.type](event)
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
errors.push(error)
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (errors.length === 1) {
|
|
1374
|
+
throw errors[0]
|
|
1375
|
+
}
|
|
1376
|
+
if (errors.length > 1) {
|
|
1377
|
+
throw Object.assign(new Error(`${errors.length} listeners threw an error while dispatching the ${event.type} event.`), {
|
|
1378
|
+
errors
|
|
1379
|
+
})
|
|
1246
1380
|
}
|
|
1247
|
-
this.runEvents(event)
|
|
1248
1381
|
return !event.defaultPrevented
|
|
1249
1382
|
}
|
|
1250
1383
|
}
|
|
1251
1384
|
exports.default = EventTargetService
|
|
1252
|
-
}, { '../classes/PseudoEventListener': 1, './EventService': 12, 'collect-your-stuff/dist/collections/linked-list/LinkedList': 21, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.filter.js': 131, 'core-js/modules/esnext.iterator.find.js': 132, 'core-js/modules/esnext.iterator.for-each.js': 133 }],
|
|
1385
|
+
}, { '../classes/PseudoEventListener': 1, '../functions/getParentNodes': 6, './EventService': 12, 'collect-your-stuff/dist/collections/linked-list/LinkedList': 21, 'core-js/modules/esnext.iterator.constructor.js': 130, 'core-js/modules/esnext.iterator.filter.js': 131, '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 }],
|
|
1253
1386
|
14: [function (require, module, exports) {
|
|
1254
1387
|
'use strict'
|
|
1255
1388
|
|
|
@@ -1402,8 +1535,6 @@
|
|
|
1402
1535
|
16: [function (require, module, exports) {
|
|
1403
1536
|
'use strict'
|
|
1404
1537
|
|
|
1405
|
-
require('core-js/modules/esnext.iterator.constructor.js')
|
|
1406
|
-
require('core-js/modules/esnext.iterator.for-each.js')
|
|
1407
1538
|
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
1408
1539
|
return mod && mod.__esModule
|
|
1409
1540
|
? mod
|
|
@@ -1421,6 +1552,7 @@
|
|
|
1421
1552
|
* @version 1.0.0
|
|
1422
1553
|
*/
|
|
1423
1554
|
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
1555
|
+
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
1424
1556
|
const EventTargetService_1 = __importDefault(require('./EventTargetService'))
|
|
1425
1557
|
/**
|
|
1426
1558
|
* Simulate the behaviour of the Node Class when there is no DOM available.
|
|
@@ -1443,8 +1575,7 @@
|
|
|
1443
1575
|
this.nodeNameValue = ''
|
|
1444
1576
|
this.children = (0, generateNodeList_1.default)()
|
|
1445
1577
|
this.parent = null
|
|
1446
|
-
this.
|
|
1447
|
-
this.prev = null
|
|
1578
|
+
this.listLinker = null
|
|
1448
1579
|
}
|
|
1449
1580
|
|
|
1450
1581
|
get baseURI () {
|
|
@@ -1468,7 +1599,7 @@
|
|
|
1468
1599
|
}
|
|
1469
1600
|
|
|
1470
1601
|
get nextSibling () {
|
|
1471
|
-
return this.
|
|
1602
|
+
return this.listLinker && this.listLinker.next ? this.listLinker.next.data : null
|
|
1472
1603
|
}
|
|
1473
1604
|
|
|
1474
1605
|
get nodeName () {
|
|
@@ -1500,7 +1631,7 @@
|
|
|
1500
1631
|
}
|
|
1501
1632
|
|
|
1502
1633
|
get previousSibling () {
|
|
1503
|
-
return this.
|
|
1634
|
+
return this.listLinker && this.listLinker.prev ? this.listLinker.prev.data : null
|
|
1504
1635
|
}
|
|
1505
1636
|
|
|
1506
1637
|
get textContent () {
|
|
@@ -1512,15 +1643,20 @@
|
|
|
1512
1643
|
}
|
|
1513
1644
|
|
|
1514
1645
|
/**
|
|
1515
|
-
*
|
|
1516
|
-
* @param {PseudoNode} childNode
|
|
1517
|
-
* @returns {PseudoNode}
|
|
1646
|
+
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
1647
|
+
* @param {PseudoNode} childNode The node to add
|
|
1648
|
+
* @returns {PseudoNode} The added node
|
|
1518
1649
|
*/
|
|
1519
1650
|
appendChild (childNode) {
|
|
1520
|
-
this.
|
|
1521
|
-
return childNode
|
|
1651
|
+
return this.insertBefore(childNode, null)
|
|
1522
1652
|
}
|
|
1523
1653
|
|
|
1654
|
+
/**
|
|
1655
|
+
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
1656
|
+
* (for example elements applying default events) can do so.
|
|
1657
|
+
* @param {NodeService} child The node which was inserted
|
|
1658
|
+
*/
|
|
1659
|
+
childInserted (child) {}
|
|
1524
1660
|
/**
|
|
1525
1661
|
* Not implemented yet.
|
|
1526
1662
|
* @throws {Error}
|
|
@@ -1538,11 +1674,19 @@
|
|
|
1538
1674
|
}
|
|
1539
1675
|
|
|
1540
1676
|
/**
|
|
1541
|
-
*
|
|
1542
|
-
* @
|
|
1677
|
+
* Check whether a node is this node or one of its descendants.
|
|
1678
|
+
* @param {PseudoNode|null} otherNode The node to look for
|
|
1679
|
+
* @returns {boolean}
|
|
1543
1680
|
*/
|
|
1544
1681
|
contains (otherNode) {
|
|
1545
|
-
|
|
1682
|
+
let current = otherNode
|
|
1683
|
+
while (current) {
|
|
1684
|
+
if (current === this) {
|
|
1685
|
+
return true
|
|
1686
|
+
}
|
|
1687
|
+
current = current.parentNode
|
|
1688
|
+
}
|
|
1689
|
+
return false
|
|
1546
1690
|
}
|
|
1547
1691
|
|
|
1548
1692
|
getRootNode (options = {
|
|
@@ -1556,11 +1700,44 @@
|
|
|
1556
1700
|
}
|
|
1557
1701
|
|
|
1558
1702
|
/**
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1703
|
+
* 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
|
|
1704
|
+
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
1705
|
+
* @param {PseudoNode} newNode The node to insert
|
|
1706
|
+
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
1707
|
+
* @returns {PseudoNode} The inserted node
|
|
1708
|
+
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
1561
1709
|
*/
|
|
1562
|
-
insertBefore (newNode, referenceNode) {
|
|
1563
|
-
|
|
1710
|
+
insertBefore (newNode, referenceNode = null) {
|
|
1711
|
+
if (referenceNode !== null && referenceNode.parentNode !== this) {
|
|
1712
|
+
throw new Error('The node before which the new node is to be inserted is not a child of this node.')
|
|
1713
|
+
}
|
|
1714
|
+
if (newNode === referenceNode) {
|
|
1715
|
+
// Inserting a node before itself leaves it where it is
|
|
1716
|
+
return newNode
|
|
1717
|
+
}
|
|
1718
|
+
if (typeof newNode.contains === 'function' && newNode.contains(this)) {
|
|
1719
|
+
throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
|
|
1720
|
+
}
|
|
1721
|
+
if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
|
|
1722
|
+
// The children of a fragment are inserted (moved) in order, and the fragment is left empty
|
|
1723
|
+
while (newNode.firstChild) {
|
|
1724
|
+
this.insertBefore(newNode.firstChild, referenceNode)
|
|
1725
|
+
}
|
|
1726
|
+
return newNode
|
|
1727
|
+
}
|
|
1728
|
+
if (newNode.parentNode) {
|
|
1729
|
+
// A node can only be in one place, so it is moved from where it was
|
|
1730
|
+
newNode.parentNode.removeChild(newNode)
|
|
1731
|
+
}
|
|
1732
|
+
const linker = new TreeLinker_1.TreeLinker({
|
|
1733
|
+
data: newNode
|
|
1734
|
+
})
|
|
1735
|
+
this.children.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
|
|
1736
|
+
const inserted = newNode
|
|
1737
|
+
inserted.parent = this
|
|
1738
|
+
inserted.listLinker = linker
|
|
1739
|
+
this.childInserted(inserted)
|
|
1740
|
+
return newNode
|
|
1564
1741
|
}
|
|
1565
1742
|
|
|
1566
1743
|
isDefaultNamespace (namespaceURI) {
|
|
@@ -1589,31 +1766,44 @@
|
|
|
1589
1766
|
|
|
1590
1767
|
normalize () {}
|
|
1591
1768
|
/**
|
|
1592
|
-
* Remove
|
|
1593
|
-
* @param {PseudoNode} childElement The child node
|
|
1594
|
-
* @returns {PseudoNode}
|
|
1769
|
+
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
1770
|
+
* @param {PseudoNode} childElement The child node to remove
|
|
1771
|
+
* @returns {PseudoNode} The removed node
|
|
1595
1772
|
* @throws {Error} When the node is not a child of this node
|
|
1596
1773
|
*/
|
|
1597
1774
|
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) {
|
|
1775
|
+
if (!childElement || childElement.parentNode !== this) {
|
|
1605
1776
|
throw new Error('The node to be removed is not a child of this node.')
|
|
1606
1777
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1778
|
+
const removed = childElement
|
|
1779
|
+
this.children.remove(removed.listLinker)
|
|
1780
|
+
removed.parent = null
|
|
1781
|
+
removed.listLinker = null
|
|
1782
|
+
return childElement
|
|
1609
1783
|
}
|
|
1610
1784
|
|
|
1611
1785
|
/**
|
|
1612
|
-
*
|
|
1613
|
-
* @
|
|
1786
|
+
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
1787
|
+
* @param {PseudoNode} newChild The node which takes the place
|
|
1788
|
+
* @param {PseudoNode} oldChild The child of this node to replace
|
|
1789
|
+
* @returns {PseudoNode} The replaced node
|
|
1790
|
+
* @throws {Error} When the old node is not a child of this node
|
|
1614
1791
|
*/
|
|
1615
1792
|
replaceChild (newChild, oldChild) {
|
|
1616
|
-
|
|
1793
|
+
if (!oldChild || oldChild.parentNode !== this) {
|
|
1794
|
+
throw new Error('The node to be replaced is not a child of this node.')
|
|
1795
|
+
}
|
|
1796
|
+
if (newChild === oldChild) {
|
|
1797
|
+
return oldChild
|
|
1798
|
+
}
|
|
1799
|
+
// The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
|
|
1800
|
+
let reference = oldChild.nextSibling
|
|
1801
|
+
if (reference === newChild) {
|
|
1802
|
+
reference = newChild.nextSibling
|
|
1803
|
+
}
|
|
1804
|
+
this.removeChild(oldChild)
|
|
1805
|
+
this.insertBefore(newChild, reference)
|
|
1806
|
+
return oldChild
|
|
1617
1807
|
}
|
|
1618
1808
|
}
|
|
1619
1809
|
exports.NodeService = NodeService
|
|
@@ -1630,7 +1820,7 @@
|
|
|
1630
1820
|
NodeService.DOCUMENT_TYPE_NODE = 10
|
|
1631
1821
|
NodeService.DOCUMENT_FRAGMENT_NODE = 11
|
|
1632
1822
|
NodeService.NOTATION_NODE = 12
|
|
1633
|
-
}, { '../factories/generateNodeList': 5, './EventTargetService': 13, '
|
|
1823
|
+
}, { '../factories/generateNodeList': 5, './EventTargetService': 13, 'collect-your-stuff/dist/collections/linked-tree-list/TreeLinker': 24 }],
|
|
1634
1824
|
17: [function (require, module, exports) {
|
|
1635
1825
|
'use strict'
|
|
1636
1826
|
|
|
@@ -1649,7 +1839,9 @@
|
|
|
1649
1839
|
* @param {*} [data=null] The data to be stored in this element.
|
|
1650
1840
|
*/
|
|
1651
1841
|
constructor (data = null) {
|
|
1842
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1652
1843
|
this.classType = ArrayElement
|
|
1844
|
+
/** The data stored in this element. */
|
|
1653
1845
|
this.data = null
|
|
1654
1846
|
this.data = data
|
|
1655
1847
|
}
|
|
@@ -1662,8 +1854,8 @@
|
|
|
1662
1854
|
*/
|
|
1663
1855
|
exports.ArrayElement = ArrayElement
|
|
1664
1856
|
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
|
|
1857
|
+
if (element === null || typeof element !== 'object') {
|
|
1858
|
+
// It is not an object (or it is null), so instantiate the Element with element as the data
|
|
1667
1859
|
return new classType(element)
|
|
1668
1860
|
}
|
|
1669
1861
|
if (element.classType) {
|
|
@@ -1719,14 +1911,32 @@
|
|
|
1719
1911
|
class Arrayable {
|
|
1720
1912
|
/**
|
|
1721
1913
|
* Create the new Arrayable instance, configure the Arrayable class.
|
|
1914
|
+
* @param {ArrayElement} [elementClass=ArrayElement] The class used to wrap given data as elements.
|
|
1722
1915
|
*/
|
|
1723
1916
|
constructor (elementClass = _ArrayElement.ArrayElement) {
|
|
1917
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1724
1918
|
this.classType = Arrayable
|
|
1919
|
+
/** The array which stores the elements of this Arrayable. */
|
|
1725
1920
|
this.innerList = []
|
|
1921
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
1726
1922
|
this.initialized = false
|
|
1727
1923
|
this.elementClass = elementClass
|
|
1728
1924
|
}
|
|
1729
1925
|
|
|
1926
|
+
/**
|
|
1927
|
+
* Find the position of an element which must be in this list.
|
|
1928
|
+
* @param {ArrayElement} node The element to find
|
|
1929
|
+
* @returns {number}
|
|
1930
|
+
* @throws {Error} When the element is not in this list
|
|
1931
|
+
*/
|
|
1932
|
+
indexOfElement (node) {
|
|
1933
|
+
const index = this.innerList.indexOf(node)
|
|
1934
|
+
if (index < 0) {
|
|
1935
|
+
throw new Error('The reference element is not in this list.')
|
|
1936
|
+
}
|
|
1937
|
+
return index
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1730
1940
|
/**
|
|
1731
1941
|
* Initialize the inner list, should only run once.
|
|
1732
1942
|
* @param {Array<ArrayElement>} initialList Give the array of elements to start in this Arrayable.
|
|
@@ -1743,7 +1953,7 @@
|
|
|
1743
1953
|
}
|
|
1744
1954
|
|
|
1745
1955
|
/**
|
|
1746
|
-
* Retrieve
|
|
1956
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
1747
1957
|
* @returns {Array<ArrayElement>}
|
|
1748
1958
|
*/
|
|
1749
1959
|
get list () {
|
|
@@ -1752,18 +1962,18 @@
|
|
|
1752
1962
|
|
|
1753
1963
|
/**
|
|
1754
1964
|
* Retrieve the first Element from the Arrayable
|
|
1755
|
-
* @returns {ArrayElement}
|
|
1965
|
+
* @returns {ArrayElement|null} The first element, or null when the Arrayable is empty
|
|
1756
1966
|
*/
|
|
1757
1967
|
get first () {
|
|
1758
|
-
return this.innerList[0]
|
|
1968
|
+
return this.length ? this.innerList[0] : null
|
|
1759
1969
|
}
|
|
1760
1970
|
|
|
1761
1971
|
/**
|
|
1762
1972
|
* Retrieve the last Element from the Arrayable
|
|
1763
|
-
* @returns {ArrayElement}
|
|
1973
|
+
* @returns {ArrayElement|null} The last element, or null when the Arrayable is empty
|
|
1764
1974
|
*/
|
|
1765
1975
|
get last () {
|
|
1766
|
-
return this.innerList[this.length - 1]
|
|
1976
|
+
return this.length ? this.innerList[this.length - 1] : null
|
|
1767
1977
|
}
|
|
1768
1978
|
|
|
1769
1979
|
/**
|
|
@@ -1776,25 +1986,29 @@
|
|
|
1776
1986
|
|
|
1777
1987
|
/**
|
|
1778
1988
|
* Insert a new node (or data) after a node.
|
|
1779
|
-
* @param {ArrayElement
|
|
1989
|
+
* @param {ArrayElement|null} node The existing node as reference, or null to insert at the start of the list
|
|
1780
1990
|
* @param {ArrayElement|*} newNode The new node to go after the existing node
|
|
1781
1991
|
* @returns {Arrayable}
|
|
1992
|
+
* @throws {Error} When the reference node is not in this list
|
|
1782
1993
|
*/
|
|
1783
1994
|
insertAfter (node, newNode) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1995
|
+
// With no reference element, the new one goes after nothing: at the start of the list
|
|
1996
|
+
const insertAt = node === null || typeof node === 'undefined' ? -1 : this.indexOfElement(node)
|
|
1997
|
+
this.innerList.splice(insertAt + 1, 0, this.elementClass.make(newNode, this.elementClass))
|
|
1786
1998
|
return this
|
|
1787
1999
|
}
|
|
1788
2000
|
|
|
1789
2001
|
/**
|
|
1790
2002
|
* Insert a new node (or data) before a node.
|
|
1791
|
-
* @param {ArrayElement
|
|
2003
|
+
* @param {ArrayElement|null} node The existing node as reference, or null to insert at the end of the list
|
|
1792
2004
|
* @param {ArrayElement|*} newNode The new node to go before the existing node
|
|
1793
2005
|
* @returns {Arrayable}
|
|
2006
|
+
* @throws {Error} When the reference node is not in this list
|
|
1794
2007
|
*/
|
|
1795
2008
|
insertBefore (node, newNode) {
|
|
1796
|
-
|
|
1797
|
-
this.
|
|
2009
|
+
// With no reference element, the new one goes before nothing: at the end of the list
|
|
2010
|
+
const insertAt = node === null || typeof node === 'undefined' ? this.length : this.indexOfElement(node)
|
|
2011
|
+
this.innerList.splice(insertAt, 0, this.elementClass.make(newNode, this.elementClass))
|
|
1798
2012
|
return this
|
|
1799
2013
|
}
|
|
1800
2014
|
|
|
@@ -1805,6 +2019,11 @@
|
|
|
1805
2019
|
* @returns {Arrayable}
|
|
1806
2020
|
*/
|
|
1807
2021
|
append (node, after = this.last) {
|
|
2022
|
+
if (after === this.last) {
|
|
2023
|
+
// Adding to the end does not need to search for where that is
|
|
2024
|
+
this.innerList.push(this.elementClass.make(node, this.elementClass))
|
|
2025
|
+
return this
|
|
2026
|
+
}
|
|
1808
2027
|
return this.insertAfter(after, node)
|
|
1809
2028
|
}
|
|
1810
2029
|
|
|
@@ -1815,16 +2034,24 @@
|
|
|
1815
2034
|
* @returns {Arrayable}
|
|
1816
2035
|
*/
|
|
1817
2036
|
prepend (node, before = this.first) {
|
|
2037
|
+
if (before === this.first) {
|
|
2038
|
+
// Adding to the start does not need to search for where that is
|
|
2039
|
+
this.innerList.unshift(this.elementClass.make(node, this.elementClass))
|
|
2040
|
+
return this
|
|
2041
|
+
}
|
|
1818
2042
|
return this.insertBefore(before, node)
|
|
1819
2043
|
}
|
|
1820
2044
|
|
|
1821
2045
|
/**
|
|
1822
2046
|
* Remove an element from this arrayable.
|
|
1823
2047
|
* @param {ArrayElement} node The node we wish to remove (and it will be returned after removal)
|
|
1824
|
-
* @return {ArrayElement}
|
|
2048
|
+
* @return {ArrayElement|null} The removed node, or null when it was not in this list (nothing is removed)
|
|
1825
2049
|
*/
|
|
1826
2050
|
remove (node) {
|
|
1827
2051
|
const deleteAt = this.innerList.indexOf(node)
|
|
2052
|
+
if (deleteAt < 0) {
|
|
2053
|
+
return null
|
|
2054
|
+
}
|
|
1828
2055
|
this.innerList.splice(deleteAt, 1)
|
|
1829
2056
|
return node
|
|
1830
2057
|
}
|
|
@@ -1904,7 +2131,7 @@
|
|
|
1904
2131
|
class DoubleLinker {
|
|
1905
2132
|
/**
|
|
1906
2133
|
* Create the new DoubleLinker instance, provide the data and optionally the next and prev references.
|
|
1907
|
-
* @param {Object} [nodeData={}]
|
|
2134
|
+
* @param {Object} [nodeData={}] The settings for the new linker.
|
|
1908
2135
|
* @param {*} [nodeData.data=null] The data to be stored in this linker
|
|
1909
2136
|
* @param {DoubleLinker|null} [nodeData.next=null] The reference to the next linker if any
|
|
1910
2137
|
* @param {DoubleLinker|null} [nodeData.prev=null] The reference to the previous linker if any
|
|
@@ -1914,9 +2141,13 @@
|
|
|
1914
2141
|
next = null,
|
|
1915
2142
|
prev = null
|
|
1916
2143
|
} = {}) {
|
|
2144
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1917
2145
|
this.classType = DoubleLinker
|
|
2146
|
+
/** The data stored in this linker. */
|
|
1918
2147
|
this.data = null
|
|
2148
|
+
/** The linker after this one, or null when this is the last. */
|
|
1919
2149
|
this.next = null
|
|
2150
|
+
/** The linker before this one, or null when this is the first. */
|
|
1920
2151
|
this.prev = null
|
|
1921
2152
|
this.data = data
|
|
1922
2153
|
this.next = next
|
|
@@ -1984,11 +2215,19 @@
|
|
|
1984
2215
|
class DoublyLinkedList {
|
|
1985
2216
|
/**
|
|
1986
2217
|
* Create the new DoublyLinkedList instance.
|
|
2218
|
+
* @param {DoubleLinker} [linkerClass=DoubleLinker] The class used to wrap given data as linkers.
|
|
1987
2219
|
*/
|
|
1988
2220
|
constructor (linkerClass = _DoubleLinker.DoubleLinker) {
|
|
2221
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
1989
2222
|
this.classType = DoublyLinkedList
|
|
2223
|
+
/** A linker of the list (null when the list is empty); the head is found by walking back from it. */
|
|
1990
2224
|
this.innerList = null
|
|
2225
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
1991
2226
|
this.initialized = false
|
|
2227
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2228
|
+
this.tailCache = null
|
|
2229
|
+
/** 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). */
|
|
2230
|
+
this.countCache = null
|
|
1992
2231
|
this.linkerClass = linkerClass
|
|
1993
2232
|
}
|
|
1994
2233
|
|
|
@@ -1998,11 +2237,12 @@
|
|
|
1998
2237
|
* @return {DoublyLinkedList}
|
|
1999
2238
|
*/
|
|
2000
2239
|
initialize (initialList) {
|
|
2240
|
+
// Borrowed from LinkedList, which types its return as a LinkedList although it returns whatever list called it
|
|
2001
2241
|
return _LinkedList.LinkedList.prototype.initialize.call(this, initialList)
|
|
2002
2242
|
}
|
|
2003
2243
|
|
|
2004
2244
|
/**
|
|
2005
|
-
* Retrieve
|
|
2245
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2006
2246
|
* @returns {DoubleLinker}
|
|
2007
2247
|
*/
|
|
2008
2248
|
get list () {
|
|
@@ -2014,91 +2254,122 @@
|
|
|
2014
2254
|
* @returns {DoubleLinker}
|
|
2015
2255
|
*/
|
|
2016
2256
|
get first () {
|
|
2017
|
-
|
|
2257
|
+
let head = this.innerList
|
|
2258
|
+
if (head === null) {
|
|
2259
|
+
return null
|
|
2260
|
+
}
|
|
2261
|
+
// innerList is normally the head already, walking back also finds anything linked on before it outside of this list
|
|
2262
|
+
while (head.prev !== null) {
|
|
2263
|
+
head = head.prev
|
|
2264
|
+
}
|
|
2265
|
+
this.innerList = head
|
|
2266
|
+
return head
|
|
2018
2267
|
}
|
|
2019
2268
|
|
|
2020
2269
|
/**
|
|
2021
|
-
* Retrieve the last DoubleLinker in the list.
|
|
2270
|
+
* Retrieve the last DoubleLinker in the list. The end is remembered, so this does not walk the list.
|
|
2022
2271
|
* @returns {DoubleLinker}
|
|
2023
2272
|
*/
|
|
2024
2273
|
get last () {
|
|
2025
|
-
|
|
2026
|
-
if (tail === null) {
|
|
2274
|
+
if (this.innerList === null) {
|
|
2027
2275
|
return null
|
|
2028
2276
|
}
|
|
2029
|
-
let
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2277
|
+
let tail = this.tailCache !== null ? this.tailCache : this.innerList
|
|
2278
|
+
// The remembered tail is normally the end already, walking on from it also finds anything linked on outside of this list
|
|
2279
|
+
while (tail.next !== null) {
|
|
2280
|
+
tail = tail.next
|
|
2033
2281
|
}
|
|
2282
|
+
this.tailCache = tail
|
|
2034
2283
|
return tail
|
|
2035
2284
|
}
|
|
2036
2285
|
|
|
2037
2286
|
/**
|
|
2038
|
-
* Return the length of the list.
|
|
2287
|
+
* 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
|
|
2288
|
+
* (call reset() after linkers were changed directly).
|
|
2039
2289
|
* @returns {number}
|
|
2040
2290
|
*/
|
|
2041
2291
|
get length () {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
while (current !== null) {
|
|
2045
|
-
++length
|
|
2046
|
-
current = current.next
|
|
2292
|
+
if (this.countCache === null) {
|
|
2293
|
+
this.reset()
|
|
2047
2294
|
}
|
|
2048
|
-
return
|
|
2295
|
+
return this.countCache
|
|
2049
2296
|
}
|
|
2050
2297
|
|
|
2051
2298
|
/**
|
|
2052
2299
|
* Insert a new node (or data) after a node.
|
|
2053
|
-
* @param {DoubleLinker|*} node The existing node as reference
|
|
2300
|
+
* @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
2301
|
* @param {DoubleLinker|*} newNode The new node to go after the existing node
|
|
2055
2302
|
* @returns {DoublyLinkedList}
|
|
2056
2303
|
*/
|
|
2057
2304
|
insertAfter (node, newNode) {
|
|
2058
|
-
newNode = this.linkerClass.make(newNode)
|
|
2059
|
-
if (node
|
|
2305
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2306
|
+
if (node === null || typeof node === 'undefined') {
|
|
2307
|
+
// After nothing means at the start of the list
|
|
2308
|
+
const head = this.first
|
|
2309
|
+
newNode.prev = null
|
|
2310
|
+
newNode.next = head
|
|
2311
|
+
if (head) {
|
|
2312
|
+
head.prev = newNode
|
|
2313
|
+
} else {
|
|
2314
|
+
this.tailCache = newNode
|
|
2315
|
+
}
|
|
2316
|
+
this.innerList = newNode
|
|
2317
|
+
} else {
|
|
2060
2318
|
// Ensure the next reference of this node is assigned to the new node
|
|
2061
2319
|
newNode.next = node.next
|
|
2062
2320
|
// Ensure this node is assigned as the prev reference of the new node
|
|
2063
2321
|
newNode.prev = node
|
|
2064
2322
|
// Then set this node's next reference to the new node
|
|
2065
2323
|
node.next = newNode
|
|
2324
|
+
if (newNode.next) {
|
|
2325
|
+
// Update the next reference to ensure circular reference for prev points to the new node
|
|
2326
|
+
newNode.next.prev = newNode
|
|
2327
|
+
} else {
|
|
2328
|
+
this.tailCache = newNode
|
|
2329
|
+
}
|
|
2066
2330
|
}
|
|
2067
|
-
if (
|
|
2068
|
-
|
|
2069
|
-
newNode.next.prev = newNode
|
|
2070
|
-
}
|
|
2071
|
-
if (!this.length) {
|
|
2072
|
-
this.innerList = newNode
|
|
2331
|
+
if (this.countCache !== null) {
|
|
2332
|
+
++this.countCache
|
|
2073
2333
|
}
|
|
2074
|
-
this.reset()
|
|
2075
2334
|
return this
|
|
2076
2335
|
}
|
|
2077
2336
|
|
|
2078
2337
|
/**
|
|
2079
2338
|
* Insert a new node (or data) before a node.
|
|
2080
|
-
* @param {DoubleLinker|*} node The existing node as reference
|
|
2339
|
+
* @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
2340
|
* @param {DoubleLinker|*} newNode The new node to go before the existing node
|
|
2082
2341
|
* @returns {DoublyLinkedList}
|
|
2083
2342
|
*/
|
|
2084
2343
|
insertBefore (node, newNode) {
|
|
2085
|
-
newNode = this.linkerClass.make(newNode)
|
|
2086
|
-
if (node
|
|
2344
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2345
|
+
if (node === null || typeof node === 'undefined') {
|
|
2346
|
+
// Before nothing means at the end of the list
|
|
2347
|
+
const tail = this.last
|
|
2348
|
+
newNode.next = null
|
|
2349
|
+
newNode.prev = tail
|
|
2350
|
+
if (tail === null) {
|
|
2351
|
+
this.innerList = newNode
|
|
2352
|
+
} else {
|
|
2353
|
+
tail.next = newNode
|
|
2354
|
+
}
|
|
2355
|
+
this.tailCache = newNode
|
|
2356
|
+
} else {
|
|
2087
2357
|
// The new node will reference this prev node as prev
|
|
2088
2358
|
newNode.prev = node.prev
|
|
2089
2359
|
// The new node will reference this node as next
|
|
2090
2360
|
newNode.next = node
|
|
2091
2361
|
// This prev will reference the new node
|
|
2092
2362
|
node.prev = newNode
|
|
2363
|
+
if (newNode.prev) {
|
|
2364
|
+
// Update the prev reference to ensure circular reference for next points to the new node
|
|
2365
|
+
newNode.prev.next = newNode
|
|
2366
|
+
} else {
|
|
2367
|
+
this.innerList = newNode
|
|
2368
|
+
}
|
|
2093
2369
|
}
|
|
2094
|
-
if (
|
|
2095
|
-
|
|
2096
|
-
newNode.prev.next = newNode
|
|
2370
|
+
if (this.countCache !== null) {
|
|
2371
|
+
++this.countCache
|
|
2097
2372
|
}
|
|
2098
|
-
if (!this.length) {
|
|
2099
|
-
this.innerList = newNode
|
|
2100
|
-
}
|
|
2101
|
-
this.reset()
|
|
2102
2373
|
return this
|
|
2103
2374
|
}
|
|
2104
2375
|
|
|
@@ -2128,7 +2399,7 @@
|
|
|
2128
2399
|
* @return {DoubleLinker}
|
|
2129
2400
|
*/
|
|
2130
2401
|
remove (node) {
|
|
2131
|
-
if (node === null) {
|
|
2402
|
+
if (node === null || typeof node === 'undefined') {
|
|
2132
2403
|
return null
|
|
2133
2404
|
}
|
|
2134
2405
|
if (node.prev) {
|
|
@@ -2144,35 +2415,47 @@
|
|
|
2144
2415
|
if (this.innerList === node) {
|
|
2145
2416
|
this.innerList = node.next || node.prev || null
|
|
2146
2417
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2418
|
+
if (this.tailCache === node) {
|
|
2419
|
+
this.tailCache = node.prev
|
|
2420
|
+
}
|
|
2421
|
+
if (this.innerList === null) {
|
|
2422
|
+
this.tailCache = null
|
|
2423
|
+
}
|
|
2424
|
+
if (this.countCache !== null) {
|
|
2425
|
+
--this.countCache
|
|
2426
|
+
}
|
|
2149
2427
|
return node
|
|
2150
2428
|
}
|
|
2151
2429
|
|
|
2152
2430
|
/**
|
|
2153
|
-
* Refresh all references and return head
|
|
2154
|
-
*
|
|
2431
|
+
* Refresh all references (the head, the end and the length) by walking the list once, and return the head. The list's
|
|
2432
|
+
* own methods keep these up to date, so this is only needed after linkers were changed directly.
|
|
2433
|
+
* @return {DoubleLinker|null}
|
|
2155
2434
|
*/
|
|
2156
2435
|
reset () {
|
|
2157
2436
|
// Start at the pointer for the list
|
|
2158
2437
|
let pointer = this.innerList
|
|
2159
2438
|
if (pointer === null) {
|
|
2439
|
+
this.countCache = 0
|
|
2440
|
+
this.tailCache = null
|
|
2160
2441
|
return null
|
|
2161
2442
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
pointer = next
|
|
2166
|
-
next = pointer.next
|
|
2443
|
+
// Follow references back to the beginning
|
|
2444
|
+
while (pointer.prev !== null) {
|
|
2445
|
+
pointer = pointer.prev
|
|
2167
2446
|
}
|
|
2168
|
-
|
|
2169
|
-
// From final reference, follow references back to the beginning
|
|
2170
|
-
while (prev !== null) {
|
|
2171
|
-
pointer = prev
|
|
2172
|
-
prev = pointer.prev
|
|
2173
|
-
}
|
|
2174
|
-
// All the live references should have been found, and we are pointing to the true head
|
|
2447
|
+
// We are pointing to the true head, now count along to the end to find the tail and the length
|
|
2175
2448
|
this.innerList = pointer
|
|
2449
|
+
let count = 0
|
|
2450
|
+
let tail = pointer
|
|
2451
|
+
let current = pointer
|
|
2452
|
+
while (current !== null) {
|
|
2453
|
+
++count
|
|
2454
|
+
tail = current
|
|
2455
|
+
current = current.next
|
|
2456
|
+
}
|
|
2457
|
+
this.countCache = count
|
|
2458
|
+
this.tailCache = tail
|
|
2176
2459
|
return pointer
|
|
2177
2460
|
}
|
|
2178
2461
|
|
|
@@ -2208,6 +2491,7 @@
|
|
|
2208
2491
|
* Be able to run forEach on this DoublyLinkedList to iterate over the DoubleLinker Items.
|
|
2209
2492
|
* @param {forEachCallback} callback The function to call for-each double linker
|
|
2210
2493
|
* @param {DoublyLinkedList} thisArg Optional, 'this' reference
|
|
2494
|
+
* @return {DoublyLinkedList} The list which was iterated.
|
|
2211
2495
|
*/
|
|
2212
2496
|
forEach (callback, thisArg = this) {
|
|
2213
2497
|
return _LinkedList.LinkedList.prototype.forEach.call(this, callback, thisArg)
|
|
@@ -2251,11 +2535,19 @@
|
|
|
2251
2535
|
class LinkedList {
|
|
2252
2536
|
/**
|
|
2253
2537
|
* Create the new LinkedList instance.
|
|
2538
|
+
* @param {Linker} [linkerClass=Linker] The class used to wrap given data as linkers.
|
|
2254
2539
|
*/
|
|
2255
2540
|
constructor (linkerClass = _Linker.Linker) {
|
|
2541
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2256
2542
|
this.classType = LinkedList
|
|
2543
|
+
/** The first linker of the list (null when the list is empty), from which the whole list is reached. */
|
|
2257
2544
|
this.innerList = null
|
|
2545
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
2258
2546
|
this.initialized = false
|
|
2547
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2548
|
+
this.tailCache = null
|
|
2549
|
+
/** 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). */
|
|
2550
|
+
this.countCache = null
|
|
2259
2551
|
this.linkerClass = linkerClass
|
|
2260
2552
|
}
|
|
2261
2553
|
|
|
@@ -2265,11 +2557,12 @@
|
|
|
2265
2557
|
* @return {LinkedList}
|
|
2266
2558
|
*/
|
|
2267
2559
|
initialize (initialList) {
|
|
2560
|
+
// Borrowed from Arrayable, which types its return as an Arrayable although it returns whatever list called it
|
|
2268
2561
|
return _Arrayable.Arrayable.prototype.initialize.call(this, initialList)
|
|
2269
2562
|
}
|
|
2270
2563
|
|
|
2271
2564
|
/**
|
|
2272
|
-
* Retrieve
|
|
2565
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2273
2566
|
* @returns {Linker}
|
|
2274
2567
|
*/
|
|
2275
2568
|
get list () {
|
|
@@ -2285,78 +2578,100 @@
|
|
|
2285
2578
|
}
|
|
2286
2579
|
|
|
2287
2580
|
/**
|
|
2288
|
-
* Retrieve the last Linker in the list.
|
|
2581
|
+
* Retrieve the last Linker in the list. The end is remembered, so this does not walk the list.
|
|
2289
2582
|
* @returns {Linker}
|
|
2290
2583
|
*/
|
|
2291
2584
|
get last () {
|
|
2292
|
-
|
|
2293
|
-
if (tail === null) {
|
|
2585
|
+
if (this.innerList === null) {
|
|
2294
2586
|
return null
|
|
2295
2587
|
}
|
|
2296
|
-
let
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2588
|
+
let tail = this.tailCache !== null ? this.tailCache : this.innerList
|
|
2589
|
+
// The remembered tail is normally the end already, walking on from it also finds anything linked on outside of this list
|
|
2590
|
+
while (tail.next !== null) {
|
|
2591
|
+
tail = tail.next
|
|
2300
2592
|
}
|
|
2593
|
+
this.tailCache = tail
|
|
2301
2594
|
return tail
|
|
2302
2595
|
}
|
|
2303
2596
|
|
|
2304
2597
|
/**
|
|
2305
|
-
* Return the length of the list.
|
|
2598
|
+
* 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
|
|
2599
|
+
* (call reset() after linkers were changed directly).
|
|
2306
2600
|
* @returns {number}
|
|
2307
2601
|
*/
|
|
2308
2602
|
get length () {
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
while (current !== null) {
|
|
2312
|
-
++length
|
|
2313
|
-
current = current.next
|
|
2603
|
+
if (this.countCache === null) {
|
|
2604
|
+
this.reset()
|
|
2314
2605
|
}
|
|
2315
|
-
return
|
|
2606
|
+
return this.countCache
|
|
2316
2607
|
}
|
|
2317
2608
|
|
|
2318
2609
|
/**
|
|
2319
2610
|
* Insert a new node (or data) after a node.
|
|
2320
|
-
* @param {Linker|*} node The existing node as reference
|
|
2611
|
+
* @param {Linker|*} node The existing node as reference, or null to insert at the start of the list
|
|
2321
2612
|
* @param {Linker|*} newNode The new node to go after the existing node
|
|
2322
2613
|
* @returns {LinkedList}
|
|
2323
2614
|
*/
|
|
2324
2615
|
insertAfter (node, newNode) {
|
|
2325
|
-
newNode = this.linkerClass.make(newNode)
|
|
2326
|
-
if (node
|
|
2327
|
-
//
|
|
2616
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2617
|
+
if (node === null || typeof node === 'undefined') {
|
|
2618
|
+
// After nothing means at the start of the list
|
|
2619
|
+
newNode.next = this.innerList
|
|
2620
|
+
if (this.innerList === null) {
|
|
2621
|
+
this.tailCache = newNode
|
|
2622
|
+
}
|
|
2623
|
+
this.innerList = newNode
|
|
2624
|
+
} else {
|
|
2328
2625
|
newNode.next = node.next
|
|
2329
|
-
// Then set this node's next reference to the new node
|
|
2330
2626
|
node.next = newNode
|
|
2627
|
+
if (newNode.next === null) {
|
|
2628
|
+
this.tailCache = newNode
|
|
2629
|
+
}
|
|
2331
2630
|
}
|
|
2332
|
-
if (
|
|
2333
|
-
this.
|
|
2631
|
+
if (this.countCache !== null) {
|
|
2632
|
+
++this.countCache
|
|
2334
2633
|
}
|
|
2335
2634
|
return this
|
|
2336
2635
|
}
|
|
2337
2636
|
|
|
2338
2637
|
/**
|
|
2339
2638
|
* Insert a new node (or data) before a node.
|
|
2340
|
-
* @param {Linker|*} node The existing node as reference
|
|
2639
|
+
* @param {Linker|*} node The existing node as reference, or null to insert at the end of the list
|
|
2341
2640
|
* @param {Linker|*} newNode The new node to go before the existing node
|
|
2342
2641
|
* @returns {LinkedList}
|
|
2642
|
+
* @throws {Error} When the reference node is not in this list
|
|
2343
2643
|
*/
|
|
2344
2644
|
insertBefore (node, newNode) {
|
|
2345
|
-
newNode = this.linkerClass.make(newNode)
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2645
|
+
newNode = this.linkerClass.make(newNode, this.linkerClass)
|
|
2646
|
+
if (node === null || typeof node === 'undefined') {
|
|
2647
|
+
// Before nothing means at the end of the list
|
|
2648
|
+
const tail = this.last
|
|
2649
|
+
newNode.next = null
|
|
2650
|
+
if (tail === null) {
|
|
2651
|
+
this.innerList = newNode
|
|
2652
|
+
} else {
|
|
2653
|
+
tail.next = newNode
|
|
2654
|
+
}
|
|
2655
|
+
this.tailCache = newNode
|
|
2656
|
+
} else {
|
|
2657
|
+
let prevNode = null
|
|
2658
|
+
let currentNode = this.first
|
|
2659
|
+
while (currentNode !== null && currentNode !== node) {
|
|
2660
|
+
prevNode = currentNode
|
|
2661
|
+
currentNode = currentNode.next
|
|
2662
|
+
}
|
|
2663
|
+
if (currentNode === null) {
|
|
2664
|
+
throw new Error('The reference node is not in this list.')
|
|
2665
|
+
}
|
|
2666
|
+
newNode.next = node
|
|
2667
|
+
if (prevNode) {
|
|
2668
|
+
prevNode.next = newNode
|
|
2669
|
+
} else {
|
|
2670
|
+
this.innerList = newNode
|
|
2671
|
+
}
|
|
2357
2672
|
}
|
|
2358
|
-
if (
|
|
2359
|
-
this.
|
|
2673
|
+
if (this.countCache !== null) {
|
|
2674
|
+
++this.countCache
|
|
2360
2675
|
}
|
|
2361
2676
|
return this
|
|
2362
2677
|
}
|
|
@@ -2384,26 +2699,58 @@
|
|
|
2384
2699
|
/**
|
|
2385
2700
|
* Remove a linker from this linked list.
|
|
2386
2701
|
* @param {Linker} node The node we wish to remove (and it will be returned after removal)
|
|
2387
|
-
* @return {Linker}
|
|
2702
|
+
* @return {Linker|null} The removed node, or null when it was not in this list (nothing is removed)
|
|
2388
2703
|
*/
|
|
2389
2704
|
remove (node) {
|
|
2705
|
+
if (node === null || typeof node === 'undefined') {
|
|
2706
|
+
return null
|
|
2707
|
+
}
|
|
2390
2708
|
let prevNode = null
|
|
2391
2709
|
let currentNode = this.first
|
|
2392
|
-
while (currentNode !== node) {
|
|
2710
|
+
while (currentNode !== null && currentNode !== node) {
|
|
2393
2711
|
prevNode = currentNode
|
|
2394
2712
|
currentNode = currentNode.next
|
|
2395
2713
|
}
|
|
2714
|
+
if (currentNode === null) {
|
|
2715
|
+
// The node is not in this list, so there is nothing to remove
|
|
2716
|
+
return null
|
|
2717
|
+
}
|
|
2396
2718
|
if (prevNode) {
|
|
2397
|
-
// Ensure the next reference of the previous node skips over the removed node
|
|
2398
2719
|
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
|
|
2720
|
+
} else {
|
|
2402
2721
|
this.innerList = node.next
|
|
2403
2722
|
}
|
|
2723
|
+
if (this.tailCache === node) {
|
|
2724
|
+
this.tailCache = prevNode
|
|
2725
|
+
}
|
|
2726
|
+
if (this.innerList === null) {
|
|
2727
|
+
this.tailCache = null
|
|
2728
|
+
}
|
|
2729
|
+
if (this.countCache !== null) {
|
|
2730
|
+
--this.countCache
|
|
2731
|
+
}
|
|
2404
2732
|
return node
|
|
2405
2733
|
}
|
|
2406
2734
|
|
|
2735
|
+
/**
|
|
2736
|
+
* Refresh the remembered end and length of the list by walking it once. The list's own methods keep these up to date,
|
|
2737
|
+
* so this is only needed after linkers were changed directly (for example by setting next on a linker).
|
|
2738
|
+
* @return {Linker|null} The first linker of the list
|
|
2739
|
+
*/
|
|
2740
|
+
reset () {
|
|
2741
|
+
let count = 0
|
|
2742
|
+
let tail = null
|
|
2743
|
+
let current = this.innerList
|
|
2744
|
+
while (current !== null) {
|
|
2745
|
+
++count
|
|
2746
|
+
tail = current
|
|
2747
|
+
current = current.next
|
|
2748
|
+
}
|
|
2749
|
+
this.countCache = count
|
|
2750
|
+
this.tailCache = tail
|
|
2751
|
+
return this.innerList
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2407
2754
|
/**
|
|
2408
2755
|
* Retrieve a Linker item from this list by numeric index, otherwise return null.
|
|
2409
2756
|
* @param {number} index The integer number for retrieving a node by position.
|
|
@@ -2486,7 +2833,7 @@
|
|
|
2486
2833
|
class Linker {
|
|
2487
2834
|
/**
|
|
2488
2835
|
* Create the new Linker instance, provide the data and optionally give the next Linker.
|
|
2489
|
-
* @param {Object} [nodeData={}]
|
|
2836
|
+
* @param {Object} [nodeData={}] The settings for the new linker.
|
|
2490
2837
|
* @param {*} [nodeData.data=null] The data to be stored in this linker
|
|
2491
2838
|
* @param {Linker|null} [nodeData.next=null] The reference to the next linker if any
|
|
2492
2839
|
*/
|
|
@@ -2494,8 +2841,11 @@
|
|
|
2494
2841
|
data = null,
|
|
2495
2842
|
next = null
|
|
2496
2843
|
} = {}) {
|
|
2844
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2497
2845
|
this.classType = Linker
|
|
2846
|
+
/** The data stored in this linker. */
|
|
2498
2847
|
this.data = null
|
|
2848
|
+
/** The linker after this one, or null when this is the last. */
|
|
2499
2849
|
this.next = null
|
|
2500
2850
|
this.data = data
|
|
2501
2851
|
this.next = next
|
|
@@ -2509,8 +2859,8 @@
|
|
|
2509
2859
|
*/
|
|
2510
2860
|
exports.Linker = Linker
|
|
2511
2861
|
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
|
|
2862
|
+
if (linker === null || typeof linker !== 'object') {
|
|
2863
|
+
// It is not an object (or it is null), so instantiate the Linker with element as the data
|
|
2514
2864
|
return new classType({
|
|
2515
2865
|
data: linker
|
|
2516
2866
|
})
|
|
@@ -2519,7 +2869,8 @@
|
|
|
2519
2869
|
// Already valid Linker, return as-is
|
|
2520
2870
|
return linker
|
|
2521
2871
|
}
|
|
2522
|
-
if (!linker
|
|
2872
|
+
if (!('data' in linker)) {
|
|
2873
|
+
// Not the settings for a linker (which would have data, even if it is falsy), so it is the data itself
|
|
2523
2874
|
linker = {
|
|
2524
2875
|
data: linker
|
|
2525
2876
|
}
|
|
@@ -2533,7 +2884,7 @@
|
|
|
2533
2884
|
* @param {IsLinker} [classType=Linker] Provide the type of IsLinker to use.
|
|
2534
2885
|
* @returns {{head: Linker, tail: Linker}}
|
|
2535
2886
|
*/
|
|
2536
|
-
Linker.fromArray = (values, classType = Linker) => values.reduce((references, linker) => {
|
|
2887
|
+
Linker.fromArray = (values = [], classType = Linker) => values.reduce((references, linker) => {
|
|
2537
2888
|
const newLinker = classType.make(linker, classType)
|
|
2538
2889
|
if (references.head === null) {
|
|
2539
2890
|
// Initialize the head and tail with the new node
|
|
@@ -2558,6 +2909,8 @@
|
|
|
2558
2909
|
value: true
|
|
2559
2910
|
})
|
|
2560
2911
|
exports.LinkedTreeList = void 0
|
|
2912
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
2913
|
+
require('core-js/modules/esnext.iterator.for-each.js')
|
|
2561
2914
|
const _TreeLinker = require('./TreeLinker')
|
|
2562
2915
|
const _TreeLinkerIterator = require('../../recipes/TreeLinkerIterator')
|
|
2563
2916
|
const _DoublyLinkedList = require('../doubly-linked-list/DoublyLinkedList')
|
|
@@ -2568,6 +2921,13 @@
|
|
|
2568
2921
|
* @memberOf module:collect-your-stuff
|
|
2569
2922
|
*/
|
|
2570
2923
|
|
|
2924
|
+
/**
|
|
2925
|
+
* Use one of the accessors of DoublyLinkedList (which keeps track of the head, tail and length) for a LinkedTreeList.
|
|
2926
|
+
* @param {string} name The accessor to use
|
|
2927
|
+
* @param {LinkedTreeList} list The list to use it on
|
|
2928
|
+
* @returns {*}
|
|
2929
|
+
*/
|
|
2930
|
+
const borrowedGetter = (name, list) => Object.getOwnPropertyDescriptor(_DoublyLinkedList.DoublyLinkedList.prototype, name).get.call(list)
|
|
2571
2931
|
/**
|
|
2572
2932
|
* LinkedTreeList represents a collection stored with a root and spreading in branching (tree) formation.
|
|
2573
2933
|
* @extends DoublyLinkedList
|
|
@@ -2575,11 +2935,21 @@
|
|
|
2575
2935
|
class LinkedTreeList {
|
|
2576
2936
|
/**
|
|
2577
2937
|
* Create the new LinkedTreeList instance, configure the list class.
|
|
2938
|
+
* @param {TreeLinker} [linkerClass=TreeLinker] The class used to wrap given data as tree linkers.
|
|
2578
2939
|
*/
|
|
2579
2940
|
constructor (linkerClass = _TreeLinker.TreeLinker) {
|
|
2941
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2580
2942
|
this.classType = LinkedTreeList
|
|
2943
|
+
/** A linker of the list (null when the list is empty); the head is found by walking back from it. */
|
|
2581
2944
|
this.innerList = null
|
|
2945
|
+
/** Whether the inner list has been initialized (it can only be initialized once). */
|
|
2582
2946
|
this.initialized = false
|
|
2947
|
+
/** The last linker, remembered so that adding to the end does not need to walk the whole list (null when not known yet). */
|
|
2948
|
+
this.tailCache = null
|
|
2949
|
+
/** 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). */
|
|
2950
|
+
this.countCache = null
|
|
2951
|
+
/** 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). */
|
|
2952
|
+
this.ownerNode = undefined
|
|
2583
2953
|
this.linkerClass = linkerClass
|
|
2584
2954
|
}
|
|
2585
2955
|
|
|
@@ -2599,7 +2969,7 @@
|
|
|
2599
2969
|
}
|
|
2600
2970
|
|
|
2601
2971
|
/**
|
|
2602
|
-
* Retrieve
|
|
2972
|
+
* Retrieve the innerList used (the list itself, not a copy).
|
|
2603
2973
|
* @returns {TreeLinker}
|
|
2604
2974
|
*/
|
|
2605
2975
|
get list () {
|
|
@@ -2611,57 +2981,46 @@
|
|
|
2611
2981
|
* @returns {TreeLinker}
|
|
2612
2982
|
*/
|
|
2613
2983
|
get first () {
|
|
2614
|
-
return this
|
|
2984
|
+
return borrowedGetter('first', this)
|
|
2615
2985
|
}
|
|
2616
2986
|
|
|
2617
2987
|
/**
|
|
2618
|
-
* Retrieve the last TreeLinker in the list.
|
|
2988
|
+
* Retrieve the last TreeLinker in the list. The end is remembered, so this does not walk the list.
|
|
2619
2989
|
* @returns {TreeLinker}
|
|
2620
2990
|
*/
|
|
2621
2991
|
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
|
|
2992
|
+
return borrowedGetter('last', this)
|
|
2632
2993
|
}
|
|
2633
2994
|
|
|
2634
2995
|
/**
|
|
2635
|
-
* Return the length of the list.
|
|
2996
|
+
* 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
|
|
2997
|
+
* (call reset() after linkers were changed directly).
|
|
2636
2998
|
* @returns {number}
|
|
2637
2999
|
*/
|
|
2638
3000
|
get length () {
|
|
2639
|
-
|
|
2640
|
-
let length = 0
|
|
2641
|
-
while (current !== null) {
|
|
2642
|
-
++length
|
|
2643
|
-
current = current.next
|
|
2644
|
-
}
|
|
2645
|
-
return length
|
|
3001
|
+
return borrowedGetter('length', this)
|
|
2646
3002
|
}
|
|
2647
3003
|
|
|
2648
3004
|
/**
|
|
2649
|
-
* Get the parent of this tree list
|
|
2650
|
-
*
|
|
3005
|
+
* Get the parent of this tree list: the node these linkers are the children of (remembered even while the list is
|
|
3006
|
+
* empty), or null for the linkers at the top of a tree.
|
|
3007
|
+
* @return {TreeLinker|null}
|
|
2651
3008
|
*/
|
|
2652
3009
|
get parent () {
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
return null
|
|
3010
|
+
if (this.ownerNode !== undefined) {
|
|
3011
|
+
return this.ownerNode
|
|
2656
3012
|
}
|
|
2657
|
-
|
|
3013
|
+
const first = this.first
|
|
3014
|
+
return first === null ? null : first.parent
|
|
2658
3015
|
}
|
|
2659
3016
|
|
|
2660
3017
|
/**
|
|
2661
|
-
* Set the parent of this tree list
|
|
2662
|
-
*
|
|
3018
|
+
* 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
|
|
3019
|
+
* children. Linkers added to the list later get this parent too.
|
|
3020
|
+
* @param {TreeLinker|null} parent The new node to use as the parent for this group of children
|
|
2663
3021
|
*/
|
|
2664
3022
|
set parent (parent) {
|
|
3023
|
+
this.ownerNode = parent
|
|
2665
3024
|
let current = this.first
|
|
2666
3025
|
while (current !== null) {
|
|
2667
3026
|
current.parent = parent
|
|
@@ -2691,34 +3050,57 @@
|
|
|
2691
3050
|
|
|
2692
3051
|
/**
|
|
2693
3052
|
* 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
|
|
3053
|
+
* @param {TreeLinker} item The TreeLinker node (one of the linkers of this list) that will be the parent of the children
|
|
3054
|
+
* @param {LinkedTreeList|null} [children=null] The LinkedTreeList which has the child nodes to use, or null to remove the children of the item
|
|
3055
|
+
* @throws {Error} When the item is not one of the linkers of this list
|
|
2696
3056
|
*/
|
|
2697
3057
|
setChildren (item, children = null) {
|
|
2698
|
-
|
|
2699
|
-
|
|
3058
|
+
// The item must be one of the linkers of this list (only the siblings are checked, not the whole tree)
|
|
3059
|
+
let isChild = false
|
|
3060
|
+
this.forEach(linker => {
|
|
3061
|
+
if (linker === item) {
|
|
3062
|
+
isChild = true
|
|
3063
|
+
}
|
|
3064
|
+
})
|
|
3065
|
+
if (!isChild) {
|
|
3066
|
+
throw new Error('The item is not one of the linkers of this list.')
|
|
3067
|
+
}
|
|
3068
|
+
if (children === null || typeof children === 'undefined') {
|
|
3069
|
+
item.children = null
|
|
3070
|
+
return
|
|
2700
3071
|
}
|
|
2701
3072
|
children.parent = item
|
|
2702
3073
|
}
|
|
2703
3074
|
|
|
2704
3075
|
/**
|
|
2705
|
-
*
|
|
2706
|
-
* @param {TreeLinker|*}
|
|
3076
|
+
* Make a linker of the given node (or data) and make this list's parent its parent.
|
|
3077
|
+
* @param {TreeLinker|*} newNode The node (or data) which is being added to this list
|
|
3078
|
+
* @returns {TreeLinker}
|
|
3079
|
+
*/
|
|
3080
|
+
adopt (newNode) {
|
|
3081
|
+
const linker = this.linkerClass.make(newNode, this.linkerClass)
|
|
3082
|
+
linker.parent = this.parent
|
|
3083
|
+
return linker
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* Insert a new node (or data) after a node. The new node gets the parent of this list.
|
|
3088
|
+
* @param {TreeLinker|*} node The existing node as reference, or null to insert at the start of the list
|
|
2707
3089
|
* @param {TreeLinker|*} newNode The new node to go after the existing node
|
|
2708
3090
|
* @returns {LinkedTreeList}
|
|
2709
3091
|
*/
|
|
2710
3092
|
insertAfter (node, newNode) {
|
|
2711
|
-
return _DoublyLinkedList.DoublyLinkedList.prototype.insertAfter.call(this, node, newNode)
|
|
3093
|
+
return _DoublyLinkedList.DoublyLinkedList.prototype.insertAfter.call(this, node, this.adopt(newNode))
|
|
2712
3094
|
}
|
|
2713
3095
|
|
|
2714
3096
|
/**
|
|
2715
|
-
* Insert a new node (or data) before a node.
|
|
2716
|
-
* @param {TreeLinker|*} node The existing node as reference
|
|
3097
|
+
* Insert a new node (or data) before a node. The new node gets the parent of this list.
|
|
3098
|
+
* @param {TreeLinker|*} node The existing node as reference, or null to insert at the end of the list
|
|
2717
3099
|
* @param {TreeLinker|*} newNode The new node to go before the existing node
|
|
2718
3100
|
* @returns {LinkedTreeList}
|
|
2719
3101
|
*/
|
|
2720
3102
|
insertBefore (node, newNode) {
|
|
2721
|
-
return _DoublyLinkedList.DoublyLinkedList.prototype.insertBefore.call(this, node, newNode)
|
|
3103
|
+
return _DoublyLinkedList.DoublyLinkedList.prototype.insertBefore.call(this, node, this.adopt(newNode))
|
|
2722
3104
|
}
|
|
2723
3105
|
|
|
2724
3106
|
/**
|
|
@@ -2742,16 +3124,24 @@
|
|
|
2742
3124
|
}
|
|
2743
3125
|
|
|
2744
3126
|
/**
|
|
2745
|
-
* Remove a linker from this linked list.
|
|
3127
|
+
* Remove a linker from this linked list. The removed node no longer has a parent.
|
|
2746
3128
|
* @param {TreeLinker} node The node we wish to remove (and it will be returned after removal)
|
|
2747
|
-
* @return {TreeLinker}
|
|
3129
|
+
* @return {TreeLinker|null} The removed node, or null when there was nothing to remove
|
|
2748
3130
|
*/
|
|
2749
3131
|
remove (node) {
|
|
2750
|
-
|
|
3132
|
+
const owner = this.parent
|
|
3133
|
+
const removed = _DoublyLinkedList.DoublyLinkedList.prototype.remove.call(this, node)
|
|
3134
|
+
if (removed && removed.parent === owner) {
|
|
3135
|
+
// Remember whose children these are (the list may now be empty), the removed node no longer has that parent
|
|
3136
|
+
this.ownerNode = owner
|
|
3137
|
+
removed.parent = null
|
|
3138
|
+
}
|
|
3139
|
+
return removed
|
|
2751
3140
|
}
|
|
2752
3141
|
|
|
2753
3142
|
/**
|
|
2754
|
-
* Refresh all references and return head
|
|
3143
|
+
* Refresh all references (the head, the end and the length) by walking the list once, and return the head. The
|
|
3144
|
+
* list's own methods keep these up to date, so this is only needed after linkers were changed directly.
|
|
2755
3145
|
* @return {TreeLinker}
|
|
2756
3146
|
*/
|
|
2757
3147
|
reset () {
|
|
@@ -2771,6 +3161,7 @@
|
|
|
2771
3161
|
* Be able to run forEach on this LinkedTreeList to iterate over the TreeLinker Items.
|
|
2772
3162
|
* @param {forEachCallback} callback The function to call for-each tree node
|
|
2773
3163
|
* @param {LinkedTreeList} thisArg Optional, 'this' reference
|
|
3164
|
+
* @return {LinkedTreeList} The list which was iterated.
|
|
2774
3165
|
*/
|
|
2775
3166
|
forEach (callback, thisArg = this) {
|
|
2776
3167
|
let index = 0
|
|
@@ -2784,12 +3175,14 @@
|
|
|
2784
3175
|
}
|
|
2785
3176
|
|
|
2786
3177
|
/**
|
|
2787
|
-
* Be able to iterate over this class.
|
|
3178
|
+
* Be able to iterate over this class: the linkers of this list and everything below them (left-first). It stays within
|
|
3179
|
+
* this list (it does not start at, or climb up to, the parents), use the parseTree service to parse a whole tree.
|
|
2788
3180
|
* @returns {Iterator}
|
|
2789
3181
|
*/
|
|
2790
3182
|
[Symbol.iterator] () {
|
|
2791
|
-
|
|
2792
|
-
|
|
3183
|
+
// The linkers of this list and everything below them, left-first. It stays within this list: it does not start at,
|
|
3184
|
+
// or climb up to, the parents (use the parseTree service to parse a whole tree)
|
|
3185
|
+
return new _TreeLinkerIterator.TreeLinkerIterator(this.first, this.parent)
|
|
2793
3186
|
}
|
|
2794
3187
|
}
|
|
2795
3188
|
/**
|
|
@@ -2804,7 +3197,7 @@
|
|
|
2804
3197
|
const list = new classType(linkerClass)
|
|
2805
3198
|
return list.initialize(linkerClass.fromArray(values).head)
|
|
2806
3199
|
}
|
|
2807
|
-
}, { '../../recipes/TreeLinkerIterator': 28, '../doubly-linked-list/DoublyLinkedList': 20, './TreeLinker': 24 }],
|
|
3200
|
+
}, { '../../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
3201
|
24: [function (require, module, exports) {
|
|
2809
3202
|
'use strict'
|
|
2810
3203
|
|
|
@@ -2823,7 +3216,7 @@
|
|
|
2823
3216
|
class TreeLinker {
|
|
2824
3217
|
/**
|
|
2825
3218
|
* Create the new TreeLinker instance, provide the data and optionally set references for next, prev, parent, or children.
|
|
2826
|
-
* @param {Object} [settings={}]
|
|
3219
|
+
* @param {Object} [settings={}] The settings for the new tree node.
|
|
2827
3220
|
* @param {*} [settings.data=null] The data to be stored in this tree node
|
|
2828
3221
|
* @param {TreeLinker} [settings.next=null] The reference to the next linker if any
|
|
2829
3222
|
* @param {TreeLinker} [settings.prev=null] The reference to the previous linker if any
|
|
@@ -2839,11 +3232,17 @@
|
|
|
2839
3232
|
parent = null,
|
|
2840
3233
|
listClass = _LinkedTreeList.LinkedTreeList
|
|
2841
3234
|
} = {}) {
|
|
3235
|
+
/** The class used to create this instance, so that it can be recognized as valid without an instanceof check. */
|
|
2842
3236
|
this.classType = TreeLinker
|
|
3237
|
+
/** The data stored in this tree node. */
|
|
2843
3238
|
this.data = null
|
|
3239
|
+
/** The sibling after this node, or null when this is the last child. */
|
|
2844
3240
|
this.next = null
|
|
3241
|
+
/** The sibling before this node, or null when this is the first child. */
|
|
2845
3242
|
this.prev = null
|
|
3243
|
+
/** The node this node is a child of, or null for a root node. */
|
|
2846
3244
|
this.parent = null
|
|
3245
|
+
/** The list of the children of this node, or null when it has none. */
|
|
2847
3246
|
this.children = null
|
|
2848
3247
|
this.data = data
|
|
2849
3248
|
this.next = next
|
|
@@ -2853,7 +3252,9 @@
|
|
|
2853
3252
|
}
|
|
2854
3253
|
|
|
2855
3254
|
/**
|
|
2856
|
-
* Create the children for this tree from an array.
|
|
3255
|
+
* Create the children for this tree from an array. Each child becomes a tree linker with this node as its parent: an
|
|
3256
|
+
* existing linker is kept as it is, an object with a data property gives the settings of the linker, and anything
|
|
3257
|
+
* else is the data of the linker.
|
|
2857
3258
|
* @param {Array|null} children Provide an array of data / linker references to be children of this tree node.
|
|
2858
3259
|
* @param {IsArrayable<IsTreeNode>} listClass Give the type of list to use for storing the children
|
|
2859
3260
|
* @return {LinkedTreeList|null}
|
|
@@ -2862,10 +3263,17 @@
|
|
|
2862
3263
|
if (children === null) {
|
|
2863
3264
|
return null
|
|
2864
3265
|
}
|
|
2865
|
-
//
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3266
|
+
// 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
|
|
3267
|
+
// given this node as its parent
|
|
3268
|
+
const nodes = children.map(child => {
|
|
3269
|
+
const linker = this.classType.make(child, this.classType)
|
|
3270
|
+
linker.parent = this
|
|
3271
|
+
return linker
|
|
3272
|
+
})
|
|
3273
|
+
// Creates a linked-tree-list to store the children, which remembers this node as its parent even when it is empty
|
|
3274
|
+
const list = listClass.fromArray(nodes, this.classType)
|
|
3275
|
+
list.parent = this
|
|
3276
|
+
return list
|
|
2869
3277
|
}
|
|
2870
3278
|
}
|
|
2871
3279
|
/**
|
|
@@ -2897,11 +3305,21 @@
|
|
|
2897
3305
|
* Class ArrayIterator returns the next value when using elements of array type list.
|
|
2898
3306
|
*/
|
|
2899
3307
|
class ArrayIterator {
|
|
3308
|
+
/**
|
|
3309
|
+
* Create an iterator over the given array.
|
|
3310
|
+
* @param {Array<IsElement>} innerList The elements to iterate over.
|
|
3311
|
+
* @param {number} [index=0] The position to start from.
|
|
3312
|
+
*/
|
|
2900
3313
|
constructor (innerList, index = 0) {
|
|
2901
3314
|
this.innerList = innerList
|
|
2902
3315
|
this.index = index
|
|
2903
3316
|
}
|
|
2904
3317
|
|
|
3318
|
+
/**
|
|
3319
|
+
* Get the next element, moving the iterator forward.
|
|
3320
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3321
|
+
* @return {IteratorResult<IsElement>} The next element, or done when there are no more.
|
|
3322
|
+
*/
|
|
2905
3323
|
next (value) {
|
|
2906
3324
|
if (this.index < this.innerList.length) {
|
|
2907
3325
|
return {
|
|
@@ -2928,10 +3346,19 @@
|
|
|
2928
3346
|
* Class DoubleLinkerIterator returns the next value when using linkers of linked type lists.
|
|
2929
3347
|
*/
|
|
2930
3348
|
class DoubleLinkerIterator {
|
|
3349
|
+
/**
|
|
3350
|
+
* Create an iterator starting at the given item.
|
|
3351
|
+
* @param {IsDoubleLinker} current The item to start from.
|
|
3352
|
+
*/
|
|
2931
3353
|
constructor (current) {
|
|
2932
3354
|
this.current = current
|
|
2933
3355
|
}
|
|
2934
3356
|
|
|
3357
|
+
/**
|
|
3358
|
+
* Get the current item and move on to the following one.
|
|
3359
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3360
|
+
* @return {IteratorResult<IsDoubleLinker>} The current item, or done when there are no more.
|
|
3361
|
+
*/
|
|
2935
3362
|
next (value) {
|
|
2936
3363
|
const result = {
|
|
2937
3364
|
value: this.current,
|
|
@@ -2954,10 +3381,19 @@
|
|
|
2954
3381
|
* Class LinkerIterator returns the next value when using linkers of linked type lists.
|
|
2955
3382
|
*/
|
|
2956
3383
|
class LinkerIterator {
|
|
3384
|
+
/**
|
|
3385
|
+
* Create an iterator starting at the given item.
|
|
3386
|
+
* @param {IsLinker} current The item to start from.
|
|
3387
|
+
*/
|
|
2957
3388
|
constructor (current) {
|
|
2958
3389
|
this.current = current
|
|
2959
3390
|
}
|
|
2960
3391
|
|
|
3392
|
+
/**
|
|
3393
|
+
* Get the current item and move on to the following one.
|
|
3394
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3395
|
+
* @return {IteratorResult<IsLinker>} The current item, or done when there are no more.
|
|
3396
|
+
*/
|
|
2961
3397
|
next (value) {
|
|
2962
3398
|
const result = {
|
|
2963
3399
|
value: this.current,
|
|
@@ -2981,16 +3417,27 @@
|
|
|
2981
3417
|
* Class TreeLinkerIterator returns the next value taking a left-first approach down a tree.
|
|
2982
3418
|
*/
|
|
2983
3419
|
class TreeLinkerIterator {
|
|
2984
|
-
|
|
3420
|
+
/**
|
|
3421
|
+
* Create an iterator starting at the given item.
|
|
3422
|
+
* @param {IsTreeNode} current The item to start from.
|
|
3423
|
+
* @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.
|
|
3424
|
+
*/
|
|
3425
|
+
constructor (current, boundaryParent) {
|
|
2985
3426
|
this.current = current
|
|
3427
|
+
this.boundaryParent = boundaryParent
|
|
2986
3428
|
}
|
|
2987
3429
|
|
|
3430
|
+
/**
|
|
3431
|
+
* Get the current item and move on to the following one (left-first, down each branch).
|
|
3432
|
+
* @param {*} [value] Not used, present to match the Iterator interface.
|
|
3433
|
+
* @return {IteratorResult<IsTreeNode>} The current item, or done when there are no more.
|
|
3434
|
+
*/
|
|
2988
3435
|
next (value) {
|
|
2989
3436
|
const result = {
|
|
2990
3437
|
value: this.current,
|
|
2991
3438
|
done: !this.current
|
|
2992
3439
|
}
|
|
2993
|
-
this.current = (0, _parseTreeNext.parseTreeNext)(this.current)
|
|
3440
|
+
this.current = (0, _parseTreeNext.parseTreeNext)(this.current, this.boundaryParent)
|
|
2994
3441
|
return result
|
|
2995
3442
|
}
|
|
2996
3443
|
}
|
|
@@ -3012,36 +3459,31 @@
|
|
|
3012
3459
|
* 5. Repeat 3
|
|
3013
3460
|
* 6. If no next child, return to parent and repeat 3
|
|
3014
3461
|
* 7. Stop at root (next is null and parent is null
|
|
3462
|
+
* A boundary can be given to parse only part of a tree: going back up to the parents stops at the boundary, so the
|
|
3463
|
+
* parsing stays within the nodes whose parent is the boundary (and everything below them).
|
|
3015
3464
|
* @param {IsTreeNode} treeNode Provide a node in a tree and get the next node (left-first approach)
|
|
3465
|
+
* @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
3466
|
* @returns {IsTreeNode|null}
|
|
3017
3467
|
*/
|
|
3018
|
-
const parseTreeNext = treeNode => {
|
|
3468
|
+
const parseTreeNext = (treeNode, boundaryParent) => {
|
|
3019
3469
|
if (!treeNode) {
|
|
3020
3470
|
return null
|
|
3021
3471
|
}
|
|
3022
|
-
let test = null
|
|
3023
3472
|
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
|
|
3473
|
+
return treeNode.children.first
|
|
3474
|
+
}
|
|
3475
|
+
if (treeNode.next) {
|
|
3476
|
+
return treeNode.next
|
|
3477
|
+
}
|
|
3478
|
+
// Nothing more below or beside this node, so go back up until there is a node which has a next (or the boundary)
|
|
3479
|
+
let parent = treeNode.parent
|
|
3480
|
+
while (parent && parent !== boundaryParent) {
|
|
3481
|
+
if (parent.next) {
|
|
3482
|
+
return parent.next
|
|
3483
|
+
}
|
|
3484
|
+
parent = parent.parent
|
|
3485
|
+
}
|
|
3486
|
+
return null
|
|
3045
3487
|
}
|
|
3046
3488
|
exports.parseTreeNext = parseTreeNext
|
|
3047
3489
|
}, {}],
|