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
|
@@ -4,6 +4,8 @@ require('core-js/modules/esnext.iterator.constructor.js')
|
|
|
4
4
|
require('core-js/modules/esnext.iterator.filter.js')
|
|
5
5
|
require('core-js/modules/esnext.iterator.find.js')
|
|
6
6
|
require('core-js/modules/esnext.iterator.for-each.js')
|
|
7
|
+
require('core-js/modules/esnext.iterator.map.js')
|
|
8
|
+
require('core-js/modules/esnext.iterator.some.js')
|
|
7
9
|
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
8
10
|
return mod && mod.__esModule
|
|
9
11
|
? mod
|
|
@@ -21,9 +23,13 @@ Object.defineProperty(exports, '__esModule', {
|
|
|
21
23
|
*/
|
|
22
24
|
const EventService_1 = require('./EventService')
|
|
23
25
|
const PseudoEventListener_1 = __importDefault(require('../classes/PseudoEventListener'))
|
|
26
|
+
const getParentNodes_1 = __importDefault(require('../functions/getParentNodes'))
|
|
24
27
|
const LinkedList_1 = require('collect-your-stuff/dist/collections/linked-list/LinkedList')
|
|
25
28
|
/**
|
|
26
29
|
* Simulate the behaviour of the EventTarget Class when there is no DOM available.
|
|
30
|
+
* Dispatching an event sends it through the tree the way the DOM does: down from the root to the target (capture
|
|
31
|
+
* listeners), to the target itself, then back up to the root (the listeners which are not capture listeners, when the
|
|
32
|
+
* event bubbles).
|
|
27
33
|
* @author Joshua Heagle <joshuaheagle@gmail.com>
|
|
28
34
|
* @class
|
|
29
35
|
* @property {Object.<string, Array.<PseudoEventListener>>} listeners
|
|
@@ -53,33 +59,51 @@ class EventTargetService {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/**
|
|
56
|
-
* Run
|
|
57
|
-
*
|
|
58
|
-
* and listeners
|
|
59
|
-
*
|
|
60
|
-
* @
|
|
62
|
+
* Run the listeners registered on this target for the type of the event which apply to the phase the event is in
|
|
63
|
+
* (at the target, the capture listeners run before the others). Listeners which are added while this runs do not run
|
|
64
|
+
* for this event, and listeners which are removed while it runs no longer do. Running stops as soon as immediate
|
|
65
|
+
* propagation is stopped. A listener which throws does not stop the others.
|
|
66
|
+
* @param {EventService} event The event, which is at a phase and has a current target
|
|
67
|
+
* @returns {Array<*>} The errors which the listeners threw
|
|
61
68
|
*/
|
|
62
69
|
runEvents (event) {
|
|
70
|
+
const errors = []
|
|
63
71
|
if (!(event.type in this.listeners)) {
|
|
64
|
-
return
|
|
72
|
+
return errors
|
|
65
73
|
}
|
|
66
|
-
const listeners = this.listeners[event.type]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for (const
|
|
70
|
-
const listener = linker.data
|
|
74
|
+
const listeners = Array.from(this.listeners[event.type]).map(linker => linker.data)
|
|
75
|
+
// At the target the capture listeners come first, otherwise the order is the order they were added
|
|
76
|
+
const ordered = event.eventPhase === EventService_1.EventService.AT_TARGET ? listeners.filter(listener => listener.capture).concat(listeners.filter(listener => !listener.capture)) : listeners
|
|
77
|
+
for (const listener of ordered) {
|
|
71
78
|
if (event.inner.immediatePropagationStopped) {
|
|
72
79
|
break
|
|
73
80
|
}
|
|
74
81
|
if (listener.rejectEvent(event)) {
|
|
75
82
|
continue
|
|
76
83
|
}
|
|
77
|
-
eventReturn = listener.handleEvent(event)
|
|
78
84
|
if (listener.once) {
|
|
79
|
-
|
|
85
|
+
this.removeListener(event.type, listener)
|
|
80
86
|
}
|
|
87
|
+
event.inner.inPassiveListener = listener.passive
|
|
88
|
+
try {
|
|
89
|
+
listener.handleEvent(event)
|
|
90
|
+
} catch (error) {
|
|
91
|
+
errors.push(error)
|
|
92
|
+
}
|
|
93
|
+
event.inner.inPassiveListener = false
|
|
81
94
|
}
|
|
82
|
-
return
|
|
95
|
+
return errors
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Take a listener out of the registered listeners, so that it does not run again.
|
|
100
|
+
* @param {string} type
|
|
101
|
+
* @param {PseudoEventListener} listener
|
|
102
|
+
*/
|
|
103
|
+
removeListener (type, listener) {
|
|
104
|
+
listener.removed = true
|
|
105
|
+
const registered = this.listeners[type]
|
|
106
|
+
Array.from(registered).filter(linker => linker.data === listener).forEach(linker => registered.remove(linker))
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
/**
|
|
@@ -92,48 +116,34 @@ class EventTargetService {
|
|
|
92
116
|
this.defaultEvent[type] = callback
|
|
93
117
|
}
|
|
94
118
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
startEvents (eventType) {
|
|
104
|
-
const event = new EventService_1.EventService(eventType)
|
|
105
|
-
event.inner.target = this;
|
|
106
|
-
[EventService_1.EventService.CAPTURING_PHASE, EventService_1.EventService.AT_TARGET, EventService_1.EventService.BUBBLING_PHASE].forEach(phase => {
|
|
107
|
-
let continueEvents = null
|
|
108
|
-
if (phase === EventService_1.EventService.AT_TARGET || !event.inner.propagationStopped) {
|
|
109
|
-
event.inner.eventPhase = phase
|
|
110
|
-
event.composedPath().forEach(target => {
|
|
111
|
-
event.inner.currentTarget = target
|
|
112
|
-
continueEvents = event.currentTarget.runEvents(event)
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
if (event.eventPhase === EventService_1.EventService.AT_TARGET && typeof continueEvents !== 'boolean' && this.defaultEvent[eventType]) {
|
|
116
|
-
this.runDefaultEvent(event)
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
return true
|
|
120
|
-
}
|
|
121
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Registers an event handler of a specific event type. Adding the same handler again for the same type and phase does
|
|
121
|
+
* nothing, like the DOM.
|
|
122
|
+
* @param {string} type The type of event to listen for
|
|
123
|
+
* @param {Function|Object} callback The function to call (or an object with a handleEvent function)
|
|
124
|
+
* @param {Object|boolean} [useCapture=false] Listen while the event travels down to the target (true), or an object with capture, once and passive
|
|
125
|
+
*/
|
|
122
126
|
addEventListener (type, callback, useCapture = false) {
|
|
123
127
|
let options = {
|
|
124
128
|
capture: false,
|
|
125
129
|
once: false,
|
|
126
130
|
passive: false
|
|
127
131
|
}
|
|
128
|
-
if (typeof useCapture === 'object') {
|
|
132
|
+
if (typeof useCapture === 'object' && useCapture !== null) {
|
|
129
133
|
// Originally useCapture was a single boolean flag, later optional other flags can be used
|
|
130
134
|
// Here we take all the given flags from the object and assign them as the options
|
|
131
135
|
options = Object.assign(options, useCapture)
|
|
132
136
|
} else {
|
|
133
|
-
options.capture = useCapture
|
|
137
|
+
options.capture = !!useCapture
|
|
134
138
|
}
|
|
135
|
-
const listener = new PseudoEventListener_1.default(type, options, (callback.handleEvent || callback).bind(this), callback)
|
|
136
139
|
const listeners = this.listenersFor(type)
|
|
140
|
+
const alreadyAdded = Array.from(listeners).some(linker => linker.data.callback === callback && linker.data.capture === options.capture)
|
|
141
|
+
if (alreadyAdded) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
// A function runs with this target as this, an object runs its handleEvent as itself
|
|
145
|
+
const handler = typeof callback === 'function' ? callback.bind(this) : callback.handleEvent.bind(callback)
|
|
146
|
+
const listener = new PseudoEventListener_1.default(type, options, handler, callback)
|
|
137
147
|
// Listeners run in the order they were added, except that listeners which are not defaults always come before the defaults
|
|
138
148
|
const firstDefault = Array.from(listeners).find(linker => linker.data.isDefault)
|
|
139
149
|
if (firstDefault && !listener.isDefault) {
|
|
@@ -143,20 +153,79 @@ class EventTargetService {
|
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Removes an event listener, the one which was added with the same type, handler and phase.
|
|
158
|
+
* @param {string} type The type of event
|
|
159
|
+
* @param {Function|Object} callback The handler which was added
|
|
160
|
+
* @param {Object|boolean} [options=false] Whether the listener was a capture listener (true), or an object with capture
|
|
161
|
+
*/
|
|
162
|
+
removeEventListener (type, callback, options = false) {
|
|
147
163
|
if (!(type in this.listeners)) {
|
|
148
164
|
return
|
|
149
165
|
}
|
|
150
|
-
const
|
|
151
|
-
Array.from(listeners).
|
|
166
|
+
const capture = typeof options === 'object' && options !== null ? !!options.capture : !!options
|
|
167
|
+
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))
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Dispatches an event to this target and through the tree: capture listeners of the ancestors from the root down,
|
|
172
|
+
* then the listeners of this target, then (when the event bubbles) the other listeners of the ancestors from the
|
|
173
|
+
* parent up to the root. stopPropagation() stops it reaching further targets, stopImmediatePropagation() also stops
|
|
174
|
+
* the remaining listeners of the current target. Afterwards, unless the default was prevented, the default action
|
|
175
|
+
* of this target (see setDefaultEvent) runs. The event can be dispatched again afterwards.
|
|
176
|
+
* @param {EventService} event The event to dispatch
|
|
177
|
+
* @returns {boolean} False when the event was cancelable and a listener prevented the default, otherwise true
|
|
178
|
+
* @throws {Error} When the event is already being dispatched, or (after the whole dispatch has finished) the error
|
|
179
|
+
* which a listener threw (an error with all of them in its errors property when several did)
|
|
180
|
+
*/
|
|
181
|
+
dispatchEvent (event) {
|
|
182
|
+
if (event.inner.dispatching) {
|
|
183
|
+
throw new Error('The event is already being dispatched.')
|
|
184
|
+
}
|
|
185
|
+
event.inner.dispatching = true
|
|
186
|
+
event.inner.target = this
|
|
187
|
+
// The ancestors, the root first, which can have listeners
|
|
188
|
+
const ancestors = (0, getParentNodes_1.default)(this).filter(node => node instanceof EventTargetService)
|
|
189
|
+
event.inner.path = [this].concat(ancestors.slice().reverse())
|
|
190
|
+
const errors = []
|
|
191
|
+
const visit = (target, phase) => {
|
|
192
|
+
event.inner.eventPhase = phase
|
|
193
|
+
event.inner.currentTarget = target
|
|
194
|
+
errors.push(...target.runEvents(event))
|
|
195
|
+
}
|
|
196
|
+
for (const ancestor of ancestors) {
|
|
197
|
+
if (event.inner.propagationStopped) {
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
visit(ancestor, EventService_1.EventService.CAPTURING_PHASE)
|
|
201
|
+
}
|
|
202
|
+
if (!event.inner.propagationStopped) {
|
|
203
|
+
visit(this, EventService_1.EventService.AT_TARGET)
|
|
204
|
+
}
|
|
205
|
+
if (event.bubbles) {
|
|
206
|
+
for (const ancestor of ancestors.slice().reverse()) {
|
|
207
|
+
if (event.inner.propagationStopped) {
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
visit(ancestor, EventService_1.EventService.BUBBLING_PHASE)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
event.inner.finishDispatch()
|
|
214
|
+
if (!event.defaultPrevented && typeof this.defaultEvent[event.type] === 'function') {
|
|
215
|
+
try {
|
|
216
|
+
this.defaultEvent[event.type](event)
|
|
217
|
+
} catch (error) {
|
|
218
|
+
errors.push(error)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (errors.length === 1) {
|
|
222
|
+
throw errors[0]
|
|
223
|
+
}
|
|
224
|
+
if (errors.length > 1) {
|
|
225
|
+
throw Object.assign(new Error(`${errors.length} listeners threw an error while dispatching the ${event.type} event.`), {
|
|
226
|
+
errors
|
|
227
|
+
})
|
|
158
228
|
}
|
|
159
|
-
this.runEvents(event)
|
|
160
229
|
return !event.defaultPrevented
|
|
161
230
|
}
|
|
162
231
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js");var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const EventService_1=require("./EventService"),PseudoEventListener_1=__importDefault(require("../classes/PseudoEventListener")),LinkedList_1=require("collect-your-stuff/dist/collections/linked-list/LinkedList");class EventTargetService{constructor(){this.listeners={},this.defaultEvent={}}listenersFor(e){return e in this.listeners||(this.listeners[e]=new LinkedList_1.LinkedList),this.listeners[e]}runEvents(e){if(!(e.type in this.listeners))return
|
|
1
|
+
"use strict";require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const EventService_1=require("./EventService"),PseudoEventListener_1=__importDefault(require("../classes/PseudoEventListener")),getParentNodes_1=__importDefault(require("../functions/getParentNodes")),LinkedList_1=require("collect-your-stuff/dist/collections/linked-list/LinkedList");class EventTargetService{constructor(){this.listeners={},this.defaultEvent={}}listenersFor(e){return e in this.listeners||(this.listeners[e]=new LinkedList_1.LinkedList),this.listeners[e]}runEvents(e){const t=[];if(!(e.type in this.listeners))return t;const r=Array.from(this.listeners[e.type]).map(e=>e.data),n=e.eventPhase===EventService_1.EventService.AT_TARGET?r.filter(e=>e.capture).concat(r.filter(e=>!e.capture)):r;for(const r of n){if(e.inner.immediatePropagationStopped)break;if(!r.rejectEvent(e)){r.once&&this.removeListener(e.type,r),e.inner.inPassiveListener=r.passive;try{r.handleEvent(e)}catch(e){t.push(e)}e.inner.inPassiveListener=!1}}return t}removeListener(e,t){t.removed=!0;const r=this.listeners[e];Array.from(r).filter(e=>e.data===t).forEach(e=>r.remove(e))}setDefaultEvent(e,t){this.listenersFor(e),this.defaultEvent[e]=t}addEventListener(e,t,r=!1){let n={capture:!1,once:!1,passive:!1};"object"==typeof r&&null!==r?n=Object.assign(n,r):n.capture=!!r;const i=this.listenersFor(e);if(Array.from(i).some(e=>e.data.callback===t&&e.data.capture===n.capture))return;const s="function"==typeof t?t.bind(this):t.handleEvent.bind(t),o=new PseudoEventListener_1.default(e,n,s,t),a=Array.from(i).find(e=>e.data.isDefault);a&&!o.isDefault?i.insertBefore(a,o):i.append(o)}removeEventListener(e,t,r=!1){if(!(e in this.listeners))return;const n="object"==typeof r&&null!==r?!!r.capture:!!r;Array.from(this.listeners[e]).map(e=>e.data).filter(e=>!e.isDefault&&e.callback===t&&e.capture===n).forEach(t=>this.removeListener(e,t))}dispatchEvent(e){if(e.inner.dispatching)throw new Error("The event is already being dispatched.");e.inner.dispatching=!0,e.inner.target=this;const t=(0,getParentNodes_1.default)(this).filter(e=>e instanceof EventTargetService);e.inner.path=[this].concat(t.slice().reverse());const r=[],n=(t,n)=>{e.inner.eventPhase=n,e.inner.currentTarget=t,r.push(...t.runEvents(e))};for(const r of t){if(e.inner.propagationStopped)break;n(r,EventService_1.EventService.CAPTURING_PHASE)}if(e.inner.propagationStopped||n(this,EventService_1.EventService.AT_TARGET),e.bubbles)for(const r of t.slice().reverse()){if(e.inner.propagationStopped)break;n(r,EventService_1.EventService.BUBBLING_PHASE)}if(e.inner.finishDispatch(),!e.defaultPrevented&&"function"==typeof this.defaultEvent[e.type])try{this.defaultEvent[e.type](e)}catch(e){r.push(e)}if(1===r.length)throw r[0];if(r.length>1)throw Object.assign(new Error(`${r.length} listeners threw an error while dispatching the ${e.type} event.`),{errors:r});return!e.defaultPrevented}}exports.default=EventTargetService;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PseudoNodeList } from '../classes/PseudoNodeList';
|
|
2
2
|
import { LinkedTreeList } from 'collect-your-stuff/dist/collections/linked-tree-list/LinkedTreeList';
|
|
3
|
+
import { TreeLinker } from 'collect-your-stuff/dist/collections/linked-tree-list/TreeLinker';
|
|
3
4
|
import { PseudoNode } from '../interfaces/PseudoNode';
|
|
4
5
|
import EventTargetService from './EventTargetService';
|
|
5
6
|
import { PseudoElement } from '../interfaces/PseudoElement';
|
|
@@ -33,8 +34,8 @@ export declare class NodeService extends EventTargetService implements PseudoNod
|
|
|
33
34
|
protected nodeNameValue: string;
|
|
34
35
|
private nodeValueStore;
|
|
35
36
|
private textContentStore;
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
/** The linker which holds this node in the children list of its parent, from which its siblings are found (null while it has no parent). */
|
|
38
|
+
protected listLinker: TreeLinker | null;
|
|
38
39
|
/**
|
|
39
40
|
*
|
|
40
41
|
* @constructor
|
|
@@ -57,11 +58,17 @@ export declare class NodeService extends EventTargetService implements PseudoNod
|
|
|
57
58
|
get textContent(): string | null;
|
|
58
59
|
set textContent(text: string | null);
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
-
* @param {PseudoNode} childNode
|
|
62
|
-
* @returns {PseudoNode}
|
|
61
|
+
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
62
|
+
* @param {PseudoNode} childNode The node to add
|
|
63
|
+
* @returns {PseudoNode} The added node
|
|
63
64
|
*/
|
|
64
65
|
appendChild(childNode: PseudoNode): PseudoNode;
|
|
66
|
+
/**
|
|
67
|
+
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
68
|
+
* (for example elements applying default events) can do so.
|
|
69
|
+
* @param {NodeService} child The node which was inserted
|
|
70
|
+
*/
|
|
71
|
+
protected childInserted(child: NodeService): void;
|
|
65
72
|
/**
|
|
66
73
|
* Not implemented yet.
|
|
67
74
|
* @throws {Error}
|
|
@@ -73,19 +80,24 @@ export declare class NodeService extends EventTargetService implements PseudoNod
|
|
|
73
80
|
*/
|
|
74
81
|
compareDocumentPosition(otherNode: PseudoNode): number;
|
|
75
82
|
/**
|
|
76
|
-
*
|
|
77
|
-
* @
|
|
83
|
+
* Check whether a node is this node or one of its descendants.
|
|
84
|
+
* @param {PseudoNode|null} otherNode The node to look for
|
|
85
|
+
* @returns {boolean}
|
|
78
86
|
*/
|
|
79
|
-
contains(otherNode: PseudoNode): boolean;
|
|
87
|
+
contains(otherNode: PseudoNode | null): boolean;
|
|
80
88
|
getRootNode(options?: {
|
|
81
89
|
composed: boolean;
|
|
82
90
|
}): PseudoNode;
|
|
83
91
|
hasChildNodes(): boolean;
|
|
84
92
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
93
|
+
* 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
|
|
94
|
+
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
95
|
+
* @param {PseudoNode} newNode The node to insert
|
|
96
|
+
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
97
|
+
* @returns {PseudoNode} The inserted node
|
|
98
|
+
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
87
99
|
*/
|
|
88
|
-
insertBefore(newNode: PseudoNode, referenceNode
|
|
100
|
+
insertBefore(newNode: PseudoNode, referenceNode?: PseudoNode | null): PseudoNode | PseudoDocumentFragment;
|
|
89
101
|
isDefaultNamespace(namespaceURI: string | null): boolean;
|
|
90
102
|
/**
|
|
91
103
|
* Not implemented yet.
|
|
@@ -97,15 +109,18 @@ export declare class NodeService extends EventTargetService implements PseudoNod
|
|
|
97
109
|
lookupNamespaceURI(prefix: string): string | null;
|
|
98
110
|
normalize(): void;
|
|
99
111
|
/**
|
|
100
|
-
* Remove
|
|
101
|
-
* @param {PseudoNode} childElement The child node
|
|
102
|
-
* @returns {PseudoNode}
|
|
112
|
+
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
113
|
+
* @param {PseudoNode} childElement The child node to remove
|
|
114
|
+
* @returns {PseudoNode} The removed node
|
|
103
115
|
* @throws {Error} When the node is not a child of this node
|
|
104
116
|
*/
|
|
105
117
|
removeChild(childElement: PseudoNode): PseudoNode;
|
|
106
118
|
/**
|
|
107
|
-
*
|
|
108
|
-
* @
|
|
119
|
+
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
120
|
+
* @param {PseudoNode} newChild The node which takes the place
|
|
121
|
+
* @param {PseudoNode} oldChild The child of this node to replace
|
|
122
|
+
* @returns {PseudoNode} The replaced node
|
|
123
|
+
* @throws {Error} When the old node is not a child of this node
|
|
109
124
|
*/
|
|
110
125
|
replaceChild(newChild: PseudoNode, oldChild: PseudoNode): PseudoNode;
|
|
111
126
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
require('core-js/modules/esnext.iterator.constructor.js')
|
|
4
|
-
require('core-js/modules/esnext.iterator.for-each.js')
|
|
5
3
|
const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
|
|
6
4
|
return mod && mod.__esModule
|
|
7
5
|
? mod
|
|
@@ -19,6 +17,7 @@ exports.NodeService = void 0
|
|
|
19
17
|
* @version 1.0.0
|
|
20
18
|
*/
|
|
21
19
|
const generateNodeList_1 = __importDefault(require('../factories/generateNodeList'))
|
|
20
|
+
const TreeLinker_1 = require('collect-your-stuff/dist/collections/linked-tree-list/TreeLinker')
|
|
22
21
|
const EventTargetService_1 = __importDefault(require('./EventTargetService'))
|
|
23
22
|
/**
|
|
24
23
|
* Simulate the behaviour of the Node Class when there is no DOM available.
|
|
@@ -41,8 +40,7 @@ class NodeService extends EventTargetService_1.default {
|
|
|
41
40
|
this.nodeNameValue = ''
|
|
42
41
|
this.children = (0, generateNodeList_1.default)()
|
|
43
42
|
this.parent = null
|
|
44
|
-
this.
|
|
45
|
-
this.prev = null
|
|
43
|
+
this.listLinker = null
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
get baseURI () {
|
|
@@ -66,7 +64,7 @@ class NodeService extends EventTargetService_1.default {
|
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
get nextSibling () {
|
|
69
|
-
return this.
|
|
67
|
+
return this.listLinker && this.listLinker.next ? this.listLinker.next.data : null
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
get nodeName () {
|
|
@@ -98,7 +96,7 @@ class NodeService extends EventTargetService_1.default {
|
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
get previousSibling () {
|
|
101
|
-
return this.
|
|
99
|
+
return this.listLinker && this.listLinker.prev ? this.listLinker.prev.data : null
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
get textContent () {
|
|
@@ -110,15 +108,20 @@ class NodeService extends EventTargetService_1.default {
|
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @param {PseudoNode} childNode
|
|
115
|
-
* @returns {PseudoNode}
|
|
111
|
+
* Add a node as the last child of this node (a node which is already in a tree is moved).
|
|
112
|
+
* @param {PseudoNode} childNode The node to add
|
|
113
|
+
* @returns {PseudoNode} The added node
|
|
116
114
|
*/
|
|
117
115
|
appendChild (childNode) {
|
|
118
|
-
this.
|
|
119
|
-
return childNode
|
|
116
|
+
return this.insertBefore(childNode, null)
|
|
120
117
|
}
|
|
121
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Called each time a node has been inserted as a child of this node, so that nodes which need to react to children
|
|
121
|
+
* (for example elements applying default events) can do so.
|
|
122
|
+
* @param {NodeService} child The node which was inserted
|
|
123
|
+
*/
|
|
124
|
+
childInserted (child) {}
|
|
122
125
|
/**
|
|
123
126
|
* Not implemented yet.
|
|
124
127
|
* @throws {Error}
|
|
@@ -136,11 +139,19 @@ class NodeService extends EventTargetService_1.default {
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @
|
|
142
|
+
* Check whether a node is this node or one of its descendants.
|
|
143
|
+
* @param {PseudoNode|null} otherNode The node to look for
|
|
144
|
+
* @returns {boolean}
|
|
141
145
|
*/
|
|
142
146
|
contains (otherNode) {
|
|
143
|
-
|
|
147
|
+
let current = otherNode
|
|
148
|
+
while (current) {
|
|
149
|
+
if (current === this) {
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
current = current.parentNode
|
|
153
|
+
}
|
|
154
|
+
return false
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
getRootNode (options = {
|
|
@@ -154,11 +165,44 @@ class NodeService extends EventTargetService_1.default {
|
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
168
|
+
* 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
|
|
169
|
+
* already in a tree is moved, and the children of a document fragment are moved in order.
|
|
170
|
+
* @param {PseudoNode} newNode The node to insert
|
|
171
|
+
* @param {PseudoNode|null} [referenceNode=null] The child of this node to insert before, or null to insert at the end
|
|
172
|
+
* @returns {PseudoNode} The inserted node
|
|
173
|
+
* @throws {Error} When the reference node is not a child of this node, or the new node is this node or contains it
|
|
159
174
|
*/
|
|
160
|
-
insertBefore (newNode, referenceNode) {
|
|
161
|
-
|
|
175
|
+
insertBefore (newNode, referenceNode = null) {
|
|
176
|
+
if (referenceNode !== null && referenceNode.parentNode !== this) {
|
|
177
|
+
throw new Error('The node before which the new node is to be inserted is not a child of this node.')
|
|
178
|
+
}
|
|
179
|
+
if (newNode === referenceNode) {
|
|
180
|
+
// Inserting a node before itself leaves it where it is
|
|
181
|
+
return newNode
|
|
182
|
+
}
|
|
183
|
+
if (typeof newNode.contains === 'function' && newNode.contains(this)) {
|
|
184
|
+
throw new Error('The new node cannot be inserted into itself or one of its own descendants.')
|
|
185
|
+
}
|
|
186
|
+
if (newNode.nodeType === NodeService.DOCUMENT_FRAGMENT_NODE) {
|
|
187
|
+
// The children of a fragment are inserted (moved) in order, and the fragment is left empty
|
|
188
|
+
while (newNode.firstChild) {
|
|
189
|
+
this.insertBefore(newNode.firstChild, referenceNode)
|
|
190
|
+
}
|
|
191
|
+
return newNode
|
|
192
|
+
}
|
|
193
|
+
if (newNode.parentNode) {
|
|
194
|
+
// A node can only be in one place, so it is moved from where it was
|
|
195
|
+
newNode.parentNode.removeChild(newNode)
|
|
196
|
+
}
|
|
197
|
+
const linker = new TreeLinker_1.TreeLinker({
|
|
198
|
+
data: newNode
|
|
199
|
+
})
|
|
200
|
+
this.children.insertBefore(referenceNode ? referenceNode.listLinker : null, linker)
|
|
201
|
+
const inserted = newNode
|
|
202
|
+
inserted.parent = this
|
|
203
|
+
inserted.listLinker = linker
|
|
204
|
+
this.childInserted(inserted)
|
|
205
|
+
return newNode
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
isDefaultNamespace (namespaceURI) {
|
|
@@ -187,31 +231,44 @@ class NodeService extends EventTargetService_1.default {
|
|
|
187
231
|
|
|
188
232
|
normalize () {}
|
|
189
233
|
/**
|
|
190
|
-
* Remove
|
|
191
|
-
* @param {PseudoNode} childElement The child node
|
|
192
|
-
* @returns {PseudoNode}
|
|
234
|
+
* Remove a child from this node, it no longer has a parent or siblings afterwards.
|
|
235
|
+
* @param {PseudoNode} childElement The child node to remove
|
|
236
|
+
* @returns {PseudoNode} The removed node
|
|
193
237
|
* @throws {Error} When the node is not a child of this node
|
|
194
238
|
*/
|
|
195
239
|
removeChild (childElement) {
|
|
196
|
-
|
|
197
|
-
this.children.forEach(linker => {
|
|
198
|
-
if (found === null && (linker === childElement || linker.data === childElement)) {
|
|
199
|
-
found = linker
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
if (found === null) {
|
|
240
|
+
if (!childElement || childElement.parentNode !== this) {
|
|
203
241
|
throw new Error('The node to be removed is not a child of this node.')
|
|
204
242
|
}
|
|
205
|
-
|
|
206
|
-
|
|
243
|
+
const removed = childElement
|
|
244
|
+
this.children.remove(removed.listLinker)
|
|
245
|
+
removed.parent = null
|
|
246
|
+
removed.listLinker = null
|
|
247
|
+
return childElement
|
|
207
248
|
}
|
|
208
249
|
|
|
209
250
|
/**
|
|
210
|
-
*
|
|
211
|
-
* @
|
|
251
|
+
* Replace a child of this node with another node (which is moved if it is already in a tree).
|
|
252
|
+
* @param {PseudoNode} newChild The node which takes the place
|
|
253
|
+
* @param {PseudoNode} oldChild The child of this node to replace
|
|
254
|
+
* @returns {PseudoNode} The replaced node
|
|
255
|
+
* @throws {Error} When the old node is not a child of this node
|
|
212
256
|
*/
|
|
213
257
|
replaceChild (newChild, oldChild) {
|
|
214
|
-
|
|
258
|
+
if (!oldChild || oldChild.parentNode !== this) {
|
|
259
|
+
throw new Error('The node to be replaced is not a child of this node.')
|
|
260
|
+
}
|
|
261
|
+
if (newChild === oldChild) {
|
|
262
|
+
return oldChild
|
|
263
|
+
}
|
|
264
|
+
// The new node goes where the old one was, which is before the old node's next sibling (unless that is the new node)
|
|
265
|
+
let reference = oldChild.nextSibling
|
|
266
|
+
if (reference === newChild) {
|
|
267
|
+
reference = newChild.nextSibling
|
|
268
|
+
}
|
|
269
|
+
this.removeChild(oldChild)
|
|
270
|
+
this.insertBefore(newChild, reference)
|
|
271
|
+
return oldChild
|
|
215
272
|
}
|
|
216
273
|
}
|
|
217
274
|
exports.NodeService = NodeService
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.NodeService=void 0;const generateNodeList_1=__importDefault(require("../factories/generateNodeList")),TreeLinker_1=require("collect-your-stuff/dist/collections/linked-tree-list/TreeLinker"),EventTargetService_1=__importDefault(require("./EventTargetService"));class NodeService extends EventTargetService_1.default{constructor(){super(),this.nodeValueStore="",this.textContentStore="",this.nodeNameValue="",this.children=(0,generateNodeList_1.default)(),this.parent=null,this.listLinker=null}get baseURI(){return window.location||"/"}get childNodes(){return this.children}get firstChild(){return this.children.first?this.children.first.data:null}get isConnected(){return!!this.parent}get lastChild(){return this.children.last?this.children.last.data:null}get nextSibling(){return this.listLinker&&this.listLinker.next?this.listLinker.next.data:null}get nodeName(){return this.nodeNameValue||""}get nodeType(){return NodeService.DEFAULT_NODE}get nodeValue(){return this.nodeValueStore}set nodeValue(e){this.nodeValueStore=e}get ownerDocument(){return null}get parentNode(){return this.parent}get parentElement(){return this.parent&&this.parent.nodeType===NodeService.ELEMENT_NODE?this.parent:null}get previousSibling(){return this.listLinker&&this.listLinker.prev?this.listLinker.prev.data:null}get textContent(){return this.textContentStore}set textContent(e){this.textContentStore=e}appendChild(e){return this.insertBefore(e,null)}childInserted(e){}cloneNode(e=!1){throw new Error(`NodeService.cloneNode(${e}) is not implemented yet.`)}compareDocumentPosition(e){throw new Error("NodeService.compareDocumentPosition() is not implemented yet.")}contains(e){let t=e;for(;t;){if(t===this)return!0;t=t.parentNode}return!1}getRootNode(e={composed:!1}){return this.parent?this.parent.getRootNode(e):this}hasChildNodes(){return this.children.length>0}insertBefore(e,t=null){if(null!==t&&t.parentNode!==this)throw new Error("The node before which the new node is to be inserted is not a child of this node.");if(e===t)return e;if("function"==typeof e.contains&&e.contains(this))throw new Error("The new node cannot be inserted into itself or one of its own descendants.");if(e.nodeType===NodeService.DOCUMENT_FRAGMENT_NODE){for(;e.firstChild;)this.insertBefore(e.firstChild,t);return e}e.parentNode&&e.parentNode.removeChild(e);const r=new TreeLinker_1.TreeLinker({data:e});this.children.insertBefore(t?t.listLinker:null,r);const i=e;return i.parent=this,i.listLinker=r,this.childInserted(i),e}isDefaultNamespace(e){return null===e}isEqualNode(e){throw new Error("NodeService.isEqualNode() is not implemented yet.")}isSameNode(e){return this===e}lookupPrefix(e){return null}lookupNamespaceURI(e){return null}normalize(){}removeChild(e){if(!e||e.parentNode!==this)throw new Error("The node to be removed is not a child of this node.");const t=e;return this.children.remove(t.listLinker),t.parent=null,t.listLinker=null,e}replaceChild(e,t){if(!t||t.parentNode!==this)throw new Error("The node to be replaced is not a child of this node.");if(e===t)return t;let r=t.nextSibling;return r===e&&(r=e.nextSibling),this.removeChild(t),this.insertBefore(e,r),t}}exports.NodeService=NodeService,NodeService.DEFAULT_NODE=0,NodeService.ELEMENT_NODE=1,NodeService.ATTRIBUTE_NODE=2,NodeService.TEXT_NODE=3,NodeService.CDATA_SECTION_NODE=4,NodeService.ENTITY_REFERENCE_NODE=5,NodeService.ENTITY_NODE=6,NodeService.PROCESSING_INSTRUCTION_NODE=7,NodeService.COMMENT_NODE=8,NodeService.DOCUMENT_NODE=9,NodeService.DOCUMENT_TYPE_NODE=10,NodeService.DOCUMENT_FRAGMENT_NODE=11,NodeService.NOTATION_NODE=12;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pseudo-dom",
|
|
3
3
|
"description": "Mock the DOM on the server side for managing state and testing",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"types": "dist/main.d.ts",
|
|
7
7
|
"author": "Joshua Heagle",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"watch:test": "gulp watchTest"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"collect-your-stuff": "^1.
|
|
21
|
+
"collect-your-stuff": "^2.1.0",
|
|
22
22
|
"core-js": "^3.50.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|