pseudo-dom 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +903 -43
  2. package/browser/pseudo-dom.js +1489 -376
  3. package/browser/pseudo-dom.min.js +1 -1
  4. package/dist/classes/PseudoEventListener.d.ts +25 -25
  5. package/dist/classes/PseudoEventListener.js +37 -34
  6. package/dist/classes/PseudoEventListener.min.js +1 -1
  7. package/dist/factories/createEvent.d.ts +28 -0
  8. package/dist/factories/createEvent.js +56 -0
  9. package/dist/factories/createEvent.min.js +1 -0
  10. package/dist/factories/eventDefaults.d.ts +31 -0
  11. package/dist/factories/eventDefaults.js +105 -0
  12. package/dist/factories/eventDefaults.min.js +1 -0
  13. package/dist/factories.d.ts +6 -0
  14. package/dist/factories.js +5 -1
  15. package/dist/factories.min.js +1 -1
  16. package/dist/functions/activeElement.d.ts +14 -0
  17. package/dist/functions/activeElement.js +33 -0
  18. package/dist/functions/activeElement.min.js +1 -0
  19. package/dist/functions/modifierState.d.ts +29 -0
  20. package/dist/functions/modifierState.js +41 -0
  21. package/dist/functions/modifierState.min.js +1 -0
  22. package/dist/interfaces/PseudoEventTarget.d.ts +2 -2
  23. package/dist/main.d.ts +32 -1
  24. package/dist/main.js +66 -1
  25. package/dist/main.min.js +1 -1
  26. package/dist/services/CustomEventService.d.ts +37 -0
  27. package/dist/services/CustomEventService.js +35 -0
  28. package/dist/services/CustomEventService.min.js +1 -0
  29. package/dist/services/ElementService.d.ts +1 -0
  30. package/dist/services/ElementService.js +17 -10
  31. package/dist/services/ElementService.min.js +1 -1
  32. package/dist/services/EventService.d.ts +18 -4
  33. package/dist/services/EventService.js +65 -31
  34. package/dist/services/EventService.min.js +1 -1
  35. package/dist/services/EventTargetService.d.ts +41 -9
  36. package/dist/services/EventTargetService.js +121 -52
  37. package/dist/services/EventTargetService.min.js +1 -1
  38. package/dist/services/FocusEventService.d.ts +32 -0
  39. package/dist/services/FocusEventService.js +35 -0
  40. package/dist/services/FocusEventService.min.js +1 -0
  41. package/dist/services/HTMLElementService.d.ts +20 -0
  42. package/dist/services/HTMLElementService.js +95 -0
  43. package/dist/services/HTMLElementService.min.js +1 -1
  44. package/dist/services/InputEventService.d.ts +41 -0
  45. package/dist/services/InputEventService.js +47 -0
  46. package/dist/services/InputEventService.min.js +1 -0
  47. package/dist/services/KeyboardEventService.d.ts +70 -0
  48. package/dist/services/KeyboardEventService.js +86 -0
  49. package/dist/services/KeyboardEventService.min.js +1 -0
  50. package/dist/services/MouseEventService.d.ts +80 -0
  51. package/dist/services/MouseEventService.js +108 -0
  52. package/dist/services/MouseEventService.min.js +1 -0
  53. package/dist/services/PointerEventService.d.ts +51 -0
  54. package/dist/services/PointerEventService.js +67 -0
  55. package/dist/services/PointerEventService.min.js +1 -0
  56. package/dist/services/UIEventService.d.ts +43 -0
  57. package/dist/services/UIEventService.js +42 -0
  58. package/dist/services/UIEventService.min.js +1 -0
  59. package/dist/simulate.d.ts +32 -0
  60. package/dist/simulate.js +115 -0
  61. package/dist/simulate.min.js +1 -0
  62. package/package.json +1 -1
@@ -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 each of the listeners registered on this target for the type of the event.
57
- * Listeners which do not apply to the event's phase are skipped, running stops once immediate propagation is stopped,
58
- * and listeners added or removed while running do not change which ones run for this event.
59
- * @param {EventService} event
60
- * @returns {*} true when there was nothing registered, otherwise the last value returned from a handler (null when none ran)
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 true
72
+ return errors
65
73
  }
66
- const listeners = this.listeners[event.type]
67
- let eventReturn = null
68
- // Work from a copy of the linkers so that removing a listener (for example a once listener) does not disturb the walk
69
- for (const linker of Array.from(listeners)) {
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
- listeners.remove(linker)
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 eventReturn
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
- runDefaultEvent (event) {
96
- if (event.defaultPrevented) {
97
- return false
98
- }
99
- this.defaultEvent[event.type](event)
100
- return true
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
- removeEventListener (type, callback) {
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 listeners = this.listeners[type]
151
- Array.from(listeners).filter(linker => !linker.data.isDefault && linker.data.callback === callback).forEach(linker => listeners.remove(linker))
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
- dispatchEvent (event, target = this) {
155
- event.inner.target = target
156
- if (!(event.type in this.listeners)) {
157
- return true
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!0;const t=this.listeners[e.type];let r=null;for(const n of Array.from(t)){const s=n.data;if(e.inner.immediatePropagationStopped)break;s.rejectEvent(e)||(r=s.handleEvent(e),s.once&&t.remove(n))}return r}setDefaultEvent(e,t){this.listenersFor(e),this.defaultEvent[e]=t}runDefaultEvent(e){return!e.defaultPrevented&&(this.defaultEvent[e.type](e),!0)}startEvents(e){const t=new EventService_1.EventService(e);return t.inner.target=this,[EventService_1.EventService.CAPTURING_PHASE,EventService_1.EventService.AT_TARGET,EventService_1.EventService.BUBBLING_PHASE].forEach(r=>{let n=null;r!==EventService_1.EventService.AT_TARGET&&t.inner.propagationStopped||(t.inner.eventPhase=r,t.composedPath().forEach(e=>{t.inner.currentTarget=e,n=t.currentTarget.runEvents(t)})),t.eventPhase===EventService_1.EventService.AT_TARGET&&"boolean"!=typeof n&&this.defaultEvent[e]&&this.runDefaultEvent(t)}),!0}addEventListener(e,t,r=!1){let n={capture:!1,once:!1,passive:!1};"object"==typeof r?n=Object.assign(n,r):n.capture=r;const s=new PseudoEventListener_1.default(e,n,(t.handleEvent||t).bind(this),t),i=this.listenersFor(e),o=Array.from(i).find(e=>e.data.isDefault);o&&!s.isDefault?i.insertBefore(o,s):i.append(s)}removeEventListener(e,t){if(!(e in this.listeners))return;const r=this.listeners[e];Array.from(r).filter(e=>!e.data.isDefault&&e.data.callback===t).forEach(e=>r.remove(e))}dispatchEvent(e,t=this){return e.inner.target=t,!(e.type in this.listeners)||(this.runEvents(e),!e.defaultPrevented)}}exports.default=EventTargetService;
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;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @file Substitute for the DOM FocusEvent Class.
3
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
4
+ * @version 1.0.0
5
+ */
6
+ import { UIEventInit, UIEventService } from './UIEventService';
7
+ import { PseudoEventTarget } from '../interfaces/PseudoEventTarget';
8
+ /**
9
+ * The options for creating a focus event, on top of the ones every UI event has.
10
+ * @typedef {Object} FocusEventInit
11
+ * @property {PseudoEventTarget|null} [relatedTarget=null] The other target involved (the one losing or gaining the focus)
12
+ */
13
+ export type FocusEventInit = UIEventInit & {
14
+ relatedTarget?: PseudoEventTarget | null;
15
+ };
16
+ /**
17
+ * Simulate the behaviour of the FocusEvent Class when there is no DOM available.
18
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
19
+ * @class
20
+ * @augments UIEventService
21
+ * @property {PseudoEventTarget|null} relatedTarget
22
+ */
23
+ export declare class FocusEventService extends UIEventService {
24
+ private readonly related;
25
+ /**
26
+ * @param {string} [typeArg=''] The type of the event
27
+ * @param {FocusEventInit} [init={}] The options for the event
28
+ * @constructor
29
+ */
30
+ constructor(typeArg?: string, init?: FocusEventInit);
31
+ get relatedTarget(): PseudoEventTarget | null;
32
+ }
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ })
6
+ exports.FocusEventService = void 0
7
+ /**
8
+ * @file Substitute for the DOM FocusEvent Class.
9
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
10
+ * @version 1.0.0
11
+ */
12
+ const UIEventService_1 = require('./UIEventService')
13
+ /**
14
+ * Simulate the behaviour of the FocusEvent Class when there is no DOM available.
15
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
16
+ * @class
17
+ * @augments UIEventService
18
+ * @property {PseudoEventTarget|null} relatedTarget
19
+ */
20
+ class FocusEventService extends UIEventService_1.UIEventService {
21
+ /**
22
+ * @param {string} [typeArg=''] The type of the event
23
+ * @param {FocusEventInit} [init={}] The options for the event
24
+ * @constructor
25
+ */
26
+ constructor (typeArg = '', init = {}) {
27
+ super(typeArg, init)
28
+ this.related = init.relatedTarget || null
29
+ }
30
+
31
+ get relatedTarget () {
32
+ return this.related
33
+ }
34
+ }
35
+ exports.FocusEventService = FocusEventService
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.FocusEventService=void 0;const UIEventService_1=require("./UIEventService");class FocusEventService extends UIEventService_1.UIEventService{constructor(e="",t={}){super(e,t),this.related=t.relatedTarget||null}get relatedTarget(){return this.related}}exports.FocusEventService=FocusEventService;
@@ -34,4 +34,24 @@ export declare class HTMLElementService extends ElementService implements Partia
34
34
  parent?: PseudoNode | null;
35
35
  children?: Array<any>;
36
36
  });
37
+ /**
38
+ * Whether this element can have the focus: form controls and links which are not disabled, and anything with a tabindex.
39
+ * @returns {boolean}
40
+ */
41
+ get canFocus(): boolean;
42
+ /**
43
+ * Click the element: a click event is sent to it, which bubbles and can be cancelled, like one from a user but a
44
+ * script made it (so it is not trusted). A disabled element does nothing.
45
+ */
46
+ click(): void;
47
+ /**
48
+ * Give the element the focus. The element which had it gets blur then focusout, and this one gets focus then
49
+ * focusin (blur and focus do not bubble, focusin and focusout do). Nothing happens when the element cannot have the
50
+ * focus or already has it.
51
+ */
52
+ focus(): void;
53
+ /**
54
+ * Take the focus away from the element, when it has it: it gets blur then focusout.
55
+ */
56
+ blur(): void;
37
57
  }
@@ -1,10 +1,19 @@
1
1
  'use strict'
2
2
 
3
+ const __importDefault = void 0 && (void 0).__importDefault || function (mod) {
4
+ return mod && mod.__esModule
5
+ ? mod
6
+ : {
7
+ default: mod
8
+ }
9
+ }
3
10
  Object.defineProperty(exports, '__esModule', {
4
11
  value: true
5
12
  })
6
13
  exports.HTMLElementService = void 0
7
14
  const ElementService_1 = require('./ElementService')
15
+ const createEvent_1 = __importDefault(require('../factories/createEvent'))
16
+ const activeElement_1 = require('../functions/activeElement')
8
17
  /**
9
18
  * Simulate the behaviour of the HTMLElement Class when there is no DOM available.
10
19
  * @author Joshua Heagle <joshuaheagle@gmail.com>
@@ -64,5 +73,91 @@ class HTMLElementService extends ElementService_1.ElementService {
64
73
  children
65
74
  })
66
75
  }
76
+
77
+ /**
78
+ * Whether this element can have the focus: form controls and links which are not disabled, and anything with a tabindex.
79
+ * @returns {boolean}
80
+ */
81
+ get canFocus () {
82
+ if (this.hasAttribute('disabled')) {
83
+ return false
84
+ }
85
+ switch (this.tagName) {
86
+ case 'button':
87
+ case 'select':
88
+ case 'textarea':
89
+ return true
90
+ case 'input':
91
+ return String(this.getAttribute('type') || '').toLowerCase() !== 'hidden'
92
+ case 'a':
93
+ return this.hasAttribute('href') || this.hasAttribute('tabindex')
94
+ default:
95
+ return this.hasAttribute('tabindex')
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Click the element: a click event is sent to it, which bubbles and can be cancelled, like one from a user but a
101
+ * script made it (so it is not trusted). A disabled element does nothing.
102
+ */
103
+ click () {
104
+ if (this.hasAttribute('disabled')) {
105
+ return
106
+ }
107
+ this.dispatchEvent((0, createEvent_1.default)('click', {}, {
108
+ browser: true
109
+ }))
110
+ }
111
+
112
+ /**
113
+ * Give the element the focus. The element which had it gets blur then focusout, and this one gets focus then
114
+ * focusin (blur and focus do not bubble, focusin and focusout do). Nothing happens when the element cannot have the
115
+ * focus or already has it.
116
+ */
117
+ focus () {
118
+ const root = this.getRootNode()
119
+ const previous = (0, activeElement_1.getActiveElement)(root)
120
+ if (!this.canFocus || previous === this) {
121
+ return
122
+ }
123
+ const send = (target, type, relatedTarget) => {
124
+ target.dispatchEvent((0, createEvent_1.default)(type, {
125
+ relatedTarget
126
+ }, {
127
+ browser: true,
128
+ trusted: true
129
+ }))
130
+ }
131
+ if (previous) {
132
+ send(previous, 'blur', this)
133
+ send(previous, 'focusout', this)
134
+ }
135
+ (0, activeElement_1.setActiveElement)(root, this)
136
+ send(this, 'focus', previous)
137
+ send(this, 'focusin', previous)
138
+ }
139
+
140
+ /**
141
+ * Take the focus away from the element, when it has it: it gets blur then focusout.
142
+ */
143
+ blur () {
144
+ const root = this.getRootNode()
145
+ if ((0, activeElement_1.getActiveElement)(root) !== this) {
146
+ return
147
+ }
148
+ (0, activeElement_1.setActiveElement)(root, null)
149
+ this.dispatchEvent((0, createEvent_1.default)('blur', {
150
+ relatedTarget: null
151
+ }, {
152
+ browser: true,
153
+ trusted: true
154
+ }))
155
+ this.dispatchEvent((0, createEvent_1.default)('focusout', {
156
+ relatedTarget: null
157
+ }, {
158
+ browser: true,
159
+ trusted: true
160
+ }))
161
+ }
67
162
  }
68
163
  exports.HTMLElementService = HTMLElementService
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.HTMLElementService=void 0;const ElementService_1=require("./ElementService");class HTMLElementService extends ElementService_1.ElementService{constructor({tagName:e="",parent:t=null,children:n=[]}={}){super({tagName:e,attributes:[{name:"hidden",value:!1},{name:"offsetHeight",value:0},{name:"offsetLeft",value:0},{name:"offsetParent",value:null},{name:"offsetTop",value:0},{name:"offsetWidth",value:0},{name:"style",value:{}},{name:"title",value:""}],parent:t,children:n})}}exports.HTMLElementService=HTMLElementService;
1
+ "use strict";var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.HTMLElementService=void 0;const ElementService_1=require("./ElementService"),createEvent_1=__importDefault(require("../factories/createEvent")),activeElement_1=require("../functions/activeElement");class HTMLElementService extends ElementService_1.ElementService{constructor({tagName:e="",parent:t=null,children:r=[]}={}){super({tagName:e,attributes:[{name:"hidden",value:!1},{name:"offsetHeight",value:0},{name:"offsetLeft",value:0},{name:"offsetParent",value:null},{name:"offsetTop",value:0},{name:"offsetWidth",value:0},{name:"style",value:{}},{name:"title",value:""}],parent:t,children:r})}get canFocus(){if(this.hasAttribute("disabled"))return!1;switch(this.tagName){case"button":case"select":case"textarea":return!0;case"input":return"hidden"!==String(this.getAttribute("type")||"").toLowerCase();case"a":return this.hasAttribute("href")||this.hasAttribute("tabindex");default:return this.hasAttribute("tabindex")}}click(){this.hasAttribute("disabled")||this.dispatchEvent((0,createEvent_1.default)("click",{},{browser:!0}))}focus(){const e=this.getRootNode(),t=(0,activeElement_1.getActiveElement)(e);if(!this.canFocus||t===this)return;const r=(e,t,r)=>{e.dispatchEvent((0,createEvent_1.default)(t,{relatedTarget:r},{browser:!0,trusted:!0}))};t&&(r(t,"blur",this),r(t,"focusout",this)),(0,activeElement_1.setActiveElement)(e,this),r(this,"focus",t),r(this,"focusin",t)}blur(){const e=this.getRootNode();(0,activeElement_1.getActiveElement)(e)===this&&((0,activeElement_1.setActiveElement)(e,null),this.dispatchEvent((0,createEvent_1.default)("blur",{relatedTarget:null},{browser:!0,trusted:!0})),this.dispatchEvent((0,createEvent_1.default)("focusout",{relatedTarget:null},{browser:!0,trusted:!0})))}}exports.HTMLElementService=HTMLElementService;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @file Substitute for the DOM InputEvent Class.
3
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
4
+ * @version 1.0.0
5
+ */
6
+ import { UIEventInit, UIEventService } from './UIEventService';
7
+ /**
8
+ * The options for creating an input event, on top of the ones every UI event has.
9
+ * @typedef {Object} InputEventInit
10
+ * @property {string|null} [data=null] The characters which were entered
11
+ * @property {string} [inputType=''] What kind of change it was, such as insertText
12
+ * @property {boolean} [isComposing=false]
13
+ */
14
+ export type InputEventInit = UIEventInit & {
15
+ data?: string | null;
16
+ inputType?: string;
17
+ isComposing?: boolean;
18
+ };
19
+ /**
20
+ * Simulate the behaviour of the InputEvent Class when there is no DOM available.
21
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
22
+ * @class
23
+ * @augments UIEventService
24
+ * @property {string|null} data
25
+ * @property {string} inputType
26
+ * @property {boolean} isComposing
27
+ */
28
+ export declare class InputEventService extends UIEventService {
29
+ private readonly inputData;
30
+ private readonly kind;
31
+ private readonly composing;
32
+ /**
33
+ * @param {string} [typeArg=''] The type of the event
34
+ * @param {InputEventInit} [init={}] The options for the event
35
+ * @constructor
36
+ */
37
+ constructor(typeArg?: string, init?: InputEventInit);
38
+ get data(): string | null;
39
+ get inputType(): string;
40
+ get isComposing(): boolean;
41
+ }
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ })
6
+ exports.InputEventService = void 0
7
+ /**
8
+ * @file Substitute for the DOM InputEvent Class.
9
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
10
+ * @version 1.0.0
11
+ */
12
+ const UIEventService_1 = require('./UIEventService')
13
+ /**
14
+ * Simulate the behaviour of the InputEvent Class when there is no DOM available.
15
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
16
+ * @class
17
+ * @augments UIEventService
18
+ * @property {string|null} data
19
+ * @property {string} inputType
20
+ * @property {boolean} isComposing
21
+ */
22
+ class InputEventService extends UIEventService_1.UIEventService {
23
+ /**
24
+ * @param {string} [typeArg=''] The type of the event
25
+ * @param {InputEventInit} [init={}] The options for the event
26
+ * @constructor
27
+ */
28
+ constructor (typeArg = '', init = {}) {
29
+ super(typeArg, init)
30
+ this.inputData = typeof init.data === 'string' ? init.data : null
31
+ this.kind = init.inputType || ''
32
+ this.composing = !!init.isComposing
33
+ }
34
+
35
+ get data () {
36
+ return this.inputData
37
+ }
38
+
39
+ get inputType () {
40
+ return this.kind
41
+ }
42
+
43
+ get isComposing () {
44
+ return this.composing
45
+ }
46
+ }
47
+ exports.InputEventService = InputEventService
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.InputEventService=void 0;const UIEventService_1=require("./UIEventService");class InputEventService extends UIEventService_1.UIEventService{constructor(e="",t={}){super(e,t),this.inputData="string"==typeof t.data?t.data:null,this.kind=t.inputType||"",this.composing=!!t.isComposing}get data(){return this.inputData}get inputType(){return this.kind}get isComposing(){return this.composing}}exports.InputEventService=InputEventService;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @file Substitute for the DOM KeyboardEvent Class.
3
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
4
+ * @version 1.0.0
5
+ */
6
+ import { UIEventInit, UIEventService } from './UIEventService';
7
+ /**
8
+ * The options for creating a keyboard event, on top of the ones every UI event has.
9
+ * @typedef {Object} KeyboardEventInit
10
+ * @property {string} [key=''] The value of the key, such as a or Enter
11
+ * @property {string} [code=''] The physical key, such as KeyA or Enter
12
+ * @property {number} [location=0] Where on the keyboard the key is (0 standard, 1 left, 2 right, 3 numeric pad)
13
+ * @property {boolean} [repeat=false] The key is being held down
14
+ * @property {boolean} [isComposing=false]
15
+ * @property {boolean} [ctrlKey=false]
16
+ * @property {boolean} [shiftKey=false]
17
+ * @property {boolean} [altKey=false]
18
+ * @property {boolean} [metaKey=false]
19
+ */
20
+ export type KeyboardEventInit = UIEventInit & {
21
+ key?: string;
22
+ code?: string;
23
+ location?: number;
24
+ repeat?: boolean;
25
+ isComposing?: boolean;
26
+ ctrlKey?: boolean;
27
+ shiftKey?: boolean;
28
+ altKey?: boolean;
29
+ metaKey?: boolean;
30
+ };
31
+ /**
32
+ * Simulate the behaviour of the KeyboardEvent Class when there is no DOM available.
33
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
34
+ * @class
35
+ * @augments UIEventService
36
+ * @property {string} key
37
+ * @property {string} code
38
+ * @property {number} location
39
+ * @property {boolean} repeat
40
+ * @property {boolean} isComposing
41
+ */
42
+ export declare class KeyboardEventService extends UIEventService {
43
+ private readonly keyValue;
44
+ private readonly keyCode;
45
+ private readonly keyLocation;
46
+ private readonly held;
47
+ private readonly composing;
48
+ private readonly modifiers;
49
+ /**
50
+ * @param {string} [typeArg=''] The type of the event
51
+ * @param {KeyboardEventInit} [init={}] The options for the event
52
+ * @constructor
53
+ */
54
+ constructor(typeArg?: string, init?: KeyboardEventInit);
55
+ get key(): string;
56
+ get code(): string;
57
+ get location(): number;
58
+ get repeat(): boolean;
59
+ get isComposing(): boolean;
60
+ get ctrlKey(): boolean;
61
+ get shiftKey(): boolean;
62
+ get altKey(): boolean;
63
+ get metaKey(): boolean;
64
+ /**
65
+ * Whether a modifier key was held down when the event happened.
66
+ * @param {string} key Control, Shift, Alt or Meta
67
+ * @returns {boolean}
68
+ */
69
+ getModifierState(key: string): boolean;
70
+ }