@aegisjsproject/callback-registry 1.0.0 → 1.0.2

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.
@@ -1,5 +1,605 @@
1
1
  'use strict';
2
2
 
3
+ const PREFIX = 'data-aegis-event-';
4
+ const EVENT_PREFIX = PREFIX + 'on-';
5
+ const EVENT_PREFIX_LENGTH = EVENT_PREFIX.length;
6
+ const DATA_PREFIX = 'aegisEventOn';
7
+ const DATA_PREFIX_LENGTH = DATA_PREFIX.length;
8
+ const signalSymbol = Symbol('aegis:signal');
9
+ const controllerSymbol = Symbol('aegis:controller');
10
+ const signalRegistry = new Map();
11
+ const controllerRegistry = new Map();
12
+
13
+ const once = PREFIX + 'once';
14
+ const passive = PREFIX + 'passive';
15
+ const capture = PREFIX + 'capture';
16
+ const signal = PREFIX + 'signal';
17
+ const controller = PREFIX + 'controller';
18
+ const onAbort = EVENT_PREFIX + 'abort';
19
+ const onBlur = EVENT_PREFIX + 'blur';
20
+ const onFocus = EVENT_PREFIX + 'focus';
21
+ const onCancel = EVENT_PREFIX + 'cancel';
22
+ const onAuxclick = EVENT_PREFIX + 'auxclick';
23
+ const onBeforeinput = EVENT_PREFIX + 'beforeinput';
24
+ const onBeforetoggle = EVENT_PREFIX + 'beforetoggle';
25
+ const onCanplay = EVENT_PREFIX + 'canplay';
26
+ const onCanplaythrough = EVENT_PREFIX + 'canplaythrough';
27
+ const onChange = EVENT_PREFIX + 'change';
28
+ const onClick = EVENT_PREFIX + 'click';
29
+ const onClose = EVENT_PREFIX + 'close';
30
+ const onContextmenu = EVENT_PREFIX + 'contextmenu';
31
+ const onCopy = EVENT_PREFIX + 'copy';
32
+ const onCuechange = EVENT_PREFIX + 'cuechange';
33
+ const onCut = EVENT_PREFIX + 'cut';
34
+ const onDblclick = EVENT_PREFIX + 'dblclick';
35
+ const onDrag = EVENT_PREFIX + 'drag';
36
+ const onDragend = EVENT_PREFIX + 'dragend';
37
+ const onDragenter = EVENT_PREFIX + 'dragenter';
38
+ const onDragexit = EVENT_PREFIX + 'dragexit';
39
+ const onDragleave = EVENT_PREFIX + 'dragleave';
40
+ const onDragover = EVENT_PREFIX + 'dragover';
41
+ const onDragstart = EVENT_PREFIX + 'dragstart';
42
+ const onDrop = EVENT_PREFIX + 'drop';
43
+ const onDurationchange = EVENT_PREFIX + 'durationchange';
44
+ const onEmptied = EVENT_PREFIX + 'emptied';
45
+ const onEnded = EVENT_PREFIX + 'ended';
46
+ const onFormdata = EVENT_PREFIX + 'formdata';
47
+ const onInput = EVENT_PREFIX + 'input';
48
+ const onInvalid = EVENT_PREFIX + 'invalid';
49
+ const onKeydown = EVENT_PREFIX + 'keydown';
50
+ const onKeypress = EVENT_PREFIX + 'keypress';
51
+ const onKeyup = EVENT_PREFIX + 'keyup';
52
+ const onLoad = EVENT_PREFIX + 'load';
53
+ const onLoadeddata = EVENT_PREFIX + 'loadeddata';
54
+ const onLoadedmetadata = EVENT_PREFIX + 'loadedmetadata';
55
+ const onLoadstart = EVENT_PREFIX + 'loadstart';
56
+ const onMousedown = EVENT_PREFIX + 'mousedown';
57
+ const onMouseenter = EVENT_PREFIX + 'mouseenter';
58
+ const onMouseleave = EVENT_PREFIX + 'mouseleave';
59
+ const onMousemove = EVENT_PREFIX + 'mousemove';
60
+ const onMouseout = EVENT_PREFIX + 'mouseout';
61
+ const onMouseover = EVENT_PREFIX + 'mouseover';
62
+ const onMouseup = EVENT_PREFIX + 'mouseup';
63
+ const onWheel = EVENT_PREFIX + 'wheel';
64
+ const onPaste = EVENT_PREFIX + 'paste';
65
+ const onPause = EVENT_PREFIX + 'pause';
66
+ const onPlay = EVENT_PREFIX + 'play';
67
+ const onPlaying = EVENT_PREFIX + 'playing';
68
+ const onProgress = EVENT_PREFIX + 'progress';
69
+ const onRatechange = EVENT_PREFIX + 'ratechange';
70
+ const onReset = EVENT_PREFIX + 'reset';
71
+ const onResize = EVENT_PREFIX + 'resize';
72
+ const onScroll = EVENT_PREFIX + 'scroll';
73
+ const onScrollend = EVENT_PREFIX + 'scrollend';
74
+ const onSecuritypolicyviolation = EVENT_PREFIX + 'securitypolicyviolation';
75
+ const onSeeked = EVENT_PREFIX + 'seeked';
76
+ const onSeeking = EVENT_PREFIX + 'seeking';
77
+ const onSelect = EVENT_PREFIX + 'select';
78
+ const onSlotchange = EVENT_PREFIX + 'slotchange';
79
+ const onStalled = EVENT_PREFIX + 'stalled';
80
+ const onSubmit = EVENT_PREFIX + 'submit';
81
+ const onSuspend = EVENT_PREFIX + 'suspend';
82
+ const onTimeupdate = EVENT_PREFIX + 'timeupdate';
83
+ const onVolumechange = EVENT_PREFIX + 'volumechange';
84
+ const onWaiting = EVENT_PREFIX + 'waiting';
85
+ const onSelectstart = EVENT_PREFIX + 'selectstart';
86
+ const onSelectionchange = EVENT_PREFIX + 'selectionchange';
87
+ const onToggle = EVENT_PREFIX + 'toggle';
88
+ const onPointercancel = EVENT_PREFIX + 'pointercancel';
89
+ const onPointerdown = EVENT_PREFIX + 'pointerdown';
90
+ const onPointerup = EVENT_PREFIX + 'pointerup';
91
+ const onPointermove = EVENT_PREFIX + 'pointermove';
92
+ const onPointerout = EVENT_PREFIX + 'pointerout';
93
+ const onPointerover = EVENT_PREFIX + 'pointerover';
94
+ const onPointerenter = EVENT_PREFIX + 'pointerenter';
95
+ const onPointerleave = EVENT_PREFIX + 'pointerleave';
96
+ const onGotpointercapture = EVENT_PREFIX + 'gotpointercapture';
97
+ const onLostpointercapture = EVENT_PREFIX + 'lostpointercapture';
98
+ const onMozfullscreenchange = EVENT_PREFIX + 'mozfullscreenchange';
99
+ const onMozfullscreenerror = EVENT_PREFIX + 'mozfullscreenerror';
100
+ const onAnimationcancel = EVENT_PREFIX + 'animationcancel';
101
+ const onAnimationend = EVENT_PREFIX + 'animationend';
102
+ const onAnimationiteration = EVENT_PREFIX + 'animationiteration';
103
+ const onAnimationstart = EVENT_PREFIX + 'animationstart';
104
+ const onTransitioncancel = EVENT_PREFIX + 'transitioncancel';
105
+ const onTransitionend = EVENT_PREFIX + 'transitionend';
106
+ const onTransitionrun = EVENT_PREFIX + 'transitionrun';
107
+ const onTransitionstart = EVENT_PREFIX + 'transitionstart';
108
+ const onWebkitanimationend = EVENT_PREFIX + 'webkitanimationend';
109
+ const onWebkitanimationiteration = EVENT_PREFIX + 'webkitanimationiteration';
110
+ const onWebkitanimationstart = EVENT_PREFIX + 'webkitanimationstart';
111
+ const onWebkittransitionend = EVENT_PREFIX + 'webkittransitionend';
112
+ const onError = EVENT_PREFIX + 'error';
113
+
114
+ const eventAttrs = [
115
+ onAbort,
116
+ onBlur,
117
+ onFocus,
118
+ onCancel,
119
+ onAuxclick,
120
+ onBeforeinput,
121
+ onBeforetoggle,
122
+ onCanplay,
123
+ onCanplaythrough,
124
+ onChange,
125
+ onClick,
126
+ onClose,
127
+ onContextmenu,
128
+ onCopy,
129
+ onCuechange,
130
+ onCut,
131
+ onDblclick,
132
+ onDrag,
133
+ onDragend,
134
+ onDragenter,
135
+ onDragexit,
136
+ onDragleave,
137
+ onDragover,
138
+ onDragstart,
139
+ onDrop,
140
+ onDurationchange,
141
+ onEmptied,
142
+ onEnded,
143
+ onFormdata,
144
+ onInput,
145
+ onInvalid,
146
+ onKeydown,
147
+ onKeypress,
148
+ onKeyup,
149
+ onLoad,
150
+ onLoadeddata,
151
+ onLoadedmetadata,
152
+ onLoadstart,
153
+ onMousedown,
154
+ onMouseenter,
155
+ onMouseleave,
156
+ onMousemove,
157
+ onMouseout,
158
+ onMouseover,
159
+ onMouseup,
160
+ onWheel,
161
+ onPaste,
162
+ onPause,
163
+ onPlay,
164
+ onPlaying,
165
+ onProgress,
166
+ onRatechange,
167
+ onReset,
168
+ onResize,
169
+ onScroll,
170
+ onScrollend,
171
+ onSecuritypolicyviolation,
172
+ onSeeked,
173
+ onSeeking,
174
+ onSelect,
175
+ onSlotchange,
176
+ onStalled,
177
+ onSubmit,
178
+ onSuspend,
179
+ onTimeupdate,
180
+ onVolumechange,
181
+ onWaiting,
182
+ onSelectstart,
183
+ onSelectionchange,
184
+ onToggle,
185
+ onPointercancel,
186
+ onPointerdown,
187
+ onPointerup,
188
+ onPointermove,
189
+ onPointerout,
190
+ onPointerover,
191
+ onPointerenter,
192
+ onPointerleave,
193
+ onGotpointercapture,
194
+ onLostpointercapture,
195
+ onMozfullscreenchange,
196
+ onMozfullscreenerror,
197
+ onAnimationcancel,
198
+ onAnimationend,
199
+ onAnimationiteration,
200
+ onAnimationstart,
201
+ onTransitioncancel,
202
+ onTransitionend,
203
+ onTransitionrun,
204
+ onTransitionstart,
205
+ onWebkitanimationend,
206
+ onWebkitanimationiteration,
207
+ onWebkitanimationstart,
208
+ onWebkittransitionend,
209
+ onError,
210
+ ];
211
+
212
+ let selector = eventAttrs.map(attr => `[${CSS.escape(attr)}]`).join(', ');
213
+
214
+ const attrToProp = attr => `on${attr[EVENT_PREFIX_LENGTH].toUpperCase()}${attr.substring(EVENT_PREFIX_LENGTH + 1)}`;
215
+
216
+ const eventToProp = event => EVENT_PREFIX + event;
217
+
218
+ const hasEventAttribute = event => eventAttrs.includes(EVENT_PREFIX + event);
219
+
220
+ const isEventDataAttr = ([name]) => name.startsWith(DATA_PREFIX);
221
+
222
+ function _addListeners(el, { signal, attrFilter = EVENTS } = {}) {
223
+ const dataset = el.dataset;
224
+
225
+ for (const [attr, val] of Object.entries(dataset).filter(isEventDataAttr)) {
226
+ try {
227
+ const event = 'on' + attr.substring(DATA_PREFIX_LENGTH);
228
+
229
+ if (attrFilter.hasOwnProperty(event) && hasCallback(val)) {
230
+ el.addEventListener(event.substring(2).toLowerCase(), getCallback(val), {
231
+ passive: dataset.hasOwnProperty('aegisEventPassive'),
232
+ capture: dataset.hasOwnProperty('aegisEventCapture'),
233
+ once: dataset.hasOwnProperty('aegisEventOnce'),
234
+ signal: dataset.hasOwnProperty('aegisEventSignal') ? getSignal(dataset.aegisEventSignal) : signal,
235
+ });
236
+ }
237
+ } catch(err) {
238
+ reportError(err);
239
+ }
240
+ }
241
+ }
242
+
243
+ const observer = new MutationObserver(records => {
244
+ records.forEach(record => {
245
+ switch(record.type) {
246
+ case 'childList':
247
+ [...record.addedNodes]
248
+ .filter(node => node.nodeType === Node.ELEMENT_NODE)
249
+ .forEach(node => attachListeners(node));
250
+ break;
251
+
252
+ case 'attributes':
253
+ if (typeof record.oldValue === 'string' && hasCallback(record.oldValue)) {
254
+ record.target.removeEventListener(
255
+ record.attributeName.substring(EVENT_PREFIX_LENGTH),
256
+ getCallback(record.oldValue), {
257
+ once: record.target.hasAttribute(once),
258
+ capture: record.target.hasAttribute(capture),
259
+ passive: record.target.hasAttribute(passive),
260
+ }
261
+ );
262
+ }
263
+
264
+ if (
265
+ record.target.hasAttribute(record.attributeName)
266
+ && hasCallback(record.target.getAttribute(record.attributeName))
267
+ ) {
268
+ record.target.addEventListener(
269
+ record.attributeName.substring(EVENT_PREFIX_LENGTH),
270
+ getCallback(record.target.getAttribute(record.attributeName)), {
271
+ once: record.target.hasAttribute(once),
272
+ capture: record.target.hasAttribute(capture),
273
+ passive: record.target.hasAttribute(passive),
274
+ signal: record.target.hasAttribute(signal) ? getSignal(record.target.getAttribute(signal)) : undefined,
275
+ }
276
+ );
277
+ }
278
+ break;
279
+ }
280
+ });
281
+ });
282
+
283
+ const EVENTS = {
284
+ onAbort,
285
+ onBlur,
286
+ onFocus,
287
+ onCancel,
288
+ onAuxclick,
289
+ onBeforeinput,
290
+ onBeforetoggle,
291
+ onCanplay,
292
+ onCanplaythrough,
293
+ onChange,
294
+ onClick,
295
+ onClose,
296
+ onContextmenu,
297
+ onCopy,
298
+ onCuechange,
299
+ onCut,
300
+ onDblclick,
301
+ onDrag,
302
+ onDragend,
303
+ onDragenter,
304
+ onDragexit,
305
+ onDragleave,
306
+ onDragover,
307
+ onDragstart,
308
+ onDrop,
309
+ onDurationchange,
310
+ onEmptied,
311
+ onEnded,
312
+ onFormdata,
313
+ onInput,
314
+ onInvalid,
315
+ onKeydown,
316
+ onKeypress,
317
+ onKeyup,
318
+ onLoad,
319
+ onLoadeddata,
320
+ onLoadedmetadata,
321
+ onLoadstart,
322
+ onMousedown,
323
+ onMouseenter,
324
+ onMouseleave,
325
+ onMousemove,
326
+ onMouseout,
327
+ onMouseover,
328
+ onMouseup,
329
+ onWheel,
330
+ onPaste,
331
+ onPause,
332
+ onPlay,
333
+ onPlaying,
334
+ onProgress,
335
+ onRatechange,
336
+ onReset,
337
+ onResize,
338
+ onScroll,
339
+ onScrollend,
340
+ onSecuritypolicyviolation,
341
+ onSeeked,
342
+ onSeeking,
343
+ onSelect,
344
+ onSlotchange,
345
+ onStalled,
346
+ onSubmit,
347
+ onSuspend,
348
+ onTimeupdate,
349
+ onVolumechange,
350
+ onWaiting,
351
+ onSelectstart,
352
+ onSelectionchange,
353
+ onToggle,
354
+ onPointercancel,
355
+ onPointerdown,
356
+ onPointerup,
357
+ onPointermove,
358
+ onPointerout,
359
+ onPointerover,
360
+ onPointerenter,
361
+ onPointerleave,
362
+ onGotpointercapture,
363
+ onLostpointercapture,
364
+ onMozfullscreenchange,
365
+ onMozfullscreenerror,
366
+ onAnimationcancel,
367
+ onAnimationend,
368
+ onAnimationiteration,
369
+ onAnimationstart,
370
+ onTransitioncancel,
371
+ onTransitionend,
372
+ onTransitionrun,
373
+ onTransitionstart,
374
+ onWebkitanimationend,
375
+ onWebkitanimationiteration,
376
+ onWebkitanimationstart,
377
+ onWebkittransitionend,
378
+ onError,
379
+ once,
380
+ passive,
381
+ capture,
382
+ };
383
+
384
+ /**
385
+ * Register an attribute to observe for adding/removing event listeners
386
+ *
387
+ * @param {string} attr Name of the attribute to observe
388
+ * @param {object} options
389
+ * @param {boolean} [options.addListeners=false] Whether or not to automatically add listeners
390
+ * @param {Document|Element} [options.base=document.body] Root node to observe
391
+ * @param {AbortSignal} [options.signal] An abort signal to remove any listeners when aborted
392
+ * @returns {string} The resulting `data-*` attribute name
393
+ */
394
+ function registerEventAttribute(attr, {
395
+ addListeners = false,
396
+ base = document.body,
397
+ signal,
398
+ } = {}) {
399
+ const fullAttr = EVENT_PREFIX + attr.toLowerCase();
400
+
401
+ if (! eventAttrs.includes(fullAttr)) {
402
+ const sel = `[${CSS.escape(fullAttr)}]`;
403
+ const prop = attrToProp(fullAttr);
404
+ eventAttrs.push(fullAttr);
405
+ EVENTS[prop] = fullAttr;
406
+ selector += `, ${sel}`;
407
+
408
+ if (addListeners) {
409
+ requestAnimationFrame(() => {
410
+ const config = { attrFilter: { [prop]: sel }, signal };
411
+ [base, ...base.querySelectorAll(sel)].forEach(el => _addListeners(el, config));
412
+ });
413
+ }
414
+ }
415
+
416
+ return fullAttr;
417
+ }
418
+
419
+ /**
420
+ * Registers an `AbortController` in the controller registry and returns the key for it
421
+ *
422
+ * @param {AbortController} controller
423
+ * @returns {string} The randomly generated key with which the controller is registered
424
+ * @throws {TypeError} If controller is not an `AbortController`
425
+ * @throws {Error} Any `reason` if controller is already aborted
426
+ */
427
+ function registerController(controller) {
428
+ if (! (controller instanceof AbortController)) {
429
+ throw new TypeError('Controller is not an `AbortSignal.');
430
+ } else if (controller.signal.aborted) {
431
+ throw controller.signal.reason;
432
+ } else if (typeof controller.signal[controllerSymbol] === 'string') {
433
+ return controller.signal[controllerSymbol];
434
+ } else {
435
+ const key = 'aegis:event:controller:' + crypto.randomUUID();
436
+ Object.defineProperty(controller.signal, controllerSymbol, { value: key, writable: false, enumerable: false });
437
+ controllerRegistry.set(key, controller);
438
+
439
+ controller.signal.addEventListener('abort', unregisterController, { once: true });
440
+
441
+ return key;
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Removes a controller from the registry
447
+ *
448
+ * @param {AbortController|AbortSignal|string} key The registed key or the controller or signal it corresponds to
449
+ * @returns {boolean} Whether or not the controller was successfully unregistered
450
+ */
451
+ function unregisterController(key) {
452
+ if (key instanceof AbortController) {
453
+ return controllerRegistry.delete(key.signal[controllerSymbol]);
454
+ } else if (key instanceof AbortSignal) {
455
+ return controllerRegistry.delete(key[controllerSymbol]);
456
+ } else {
457
+ return controllerRegistry.delete(key);
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Creates and registers an `AbortController` in the controller registry and returns the key for it
463
+ *
464
+ * @param {object} options
465
+ * @param {AbortSignal} [options.signal] An optional `AbortSignal` to externally abort the controller with
466
+ * @returns {string} The randomly generated key with which the controller is registered
467
+ */
468
+ function createController({ signal } = {}) {
469
+ const controller = new AbortController();
470
+
471
+ if (signal instanceof AbortSignal) {
472
+ signal.addEventListener('abort', ({ target }) => controller.abort(target.reason), { signal: controller.signal});
473
+ }
474
+
475
+ return registerController(controller);
476
+ }
477
+
478
+ /**
479
+ * Get a registetd controller from the registry
480
+ *
481
+ * @param {string} key Generated key with which the controller was registered
482
+ * @returns {AbortController|void} Any registered controller, if any
483
+ */
484
+ const getController = key => controllerRegistry.get(key);
485
+
486
+ function abortController(key, reason) {
487
+ const controller = getController(key);
488
+
489
+ if (! (controller instanceof AbortController)) {
490
+ return false;
491
+ } else if (typeof reason === 'string') {
492
+ controller.abort(new Error(reason));
493
+ return true;
494
+ } else {
495
+ controller.abort(reason);
496
+ return true;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Register an `AbortSignal` to be used in declarative HTML as a value for `data-aegis-event-signal`
502
+ *
503
+ * @param {AbortSignal} signal The signal to register
504
+ * @returns {string} The registered key
505
+ * @throws {TypeError} Thrown if not an `AbortSignal`
506
+ */
507
+ function registerSignal(signal) {
508
+ if (! (signal instanceof AbortSignal)) {
509
+ throw new TypeError('Signal must be an `AbortSignal`.');
510
+ } else if (typeof signal[signalSymbol] === 'string') {
511
+ return signal[signalSymbol];
512
+ } else {
513
+ const key = 'aegis:event:signal:' + crypto.randomUUID();
514
+ Object.defineProperty(signal, signalSymbol, { value: key, writable: false, enumerable: false });
515
+ signalRegistry.set(key, signal);
516
+ signal.addEventListener('abort', ({ target }) => unregisterSignal(target[signalSymbol]), { once: true });
517
+
518
+ return key;
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Gets and `AbortSignal` from the registry
524
+ *
525
+ * @param {string} key The registered key for the signal
526
+ * @returns {AbortSignal|void} The corresponding `AbortSignal`, if any
527
+ */
528
+ const getSignal = key => signalRegistry.get(key);
529
+
530
+ /**
531
+ * Removes an `AbortSignal` from the registry
532
+ *
533
+ * @param {AbortSignal|string} signal An `AbortSignal` or the registered key for one
534
+ * @returns {boolean} Whether or not the signal was sucessfully unregistered
535
+ * @throws {TypeError} Throws if `signal` is not an `AbortSignal` or the key for a registered signal
536
+ */
537
+ function unregisterSignal(signal) {
538
+ if (signal instanceof AbortSignal) {
539
+ return signalRegistry.delete(signal[signalSymbol]);
540
+ } else if (typeof signal === 'string') {
541
+ return signalRegistry.delete(signal);
542
+ } else {
543
+ throw new TypeError('Signal must be an `AbortSignal` or registered key/attribute.');
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Add listeners to an element and its children, matching a generated query based on registered attributes
549
+ *
550
+ * @param {Element|Document} target Root node to add listeners from
551
+ * @param {object} options
552
+ * @param {AbortSignal} [options.signal] Optional signal to remove event listeners
553
+ * @returns {Element|Document} Returns the passed target node
554
+ */
555
+ function attachListeners(target, { signal } = {}) {
556
+ const nodes = target instanceof Element && target.matches(selector)
557
+ ? [target, ...target.querySelectorAll(selector)]
558
+ : target.querySelectorAll(selector);
559
+
560
+ nodes.forEach(el => _addListeners(el, { signal }));
561
+
562
+ return target;
563
+ }
564
+
565
+ /**
566
+ * Add a node to the `MutationObserver` to observe attributes and add/remove event listeners
567
+ *
568
+ * @param {Document|Element} root Element to observe attributes on
569
+ */
570
+ function observeEvents(root = document) {
571
+ attachListeners(root);
572
+
573
+ observer.observe(root, {
574
+ subtree: true,
575
+ childList:true,
576
+ attributes: true,
577
+ attributeOldValue: true,
578
+ attributeFilter: eventAttrs,
579
+ });
580
+ }
581
+
582
+ /**
583
+ * Disconnects the `MutationObserver`, disabling observing of all attribute changes
584
+ *
585
+ * @returns {void}
586
+ */
587
+ const disconnectEventsObserver = () => observer.disconnect();
588
+
589
+ /**
590
+ * Register a global error handler callback
591
+ *
592
+ * @param {Function} callback Callback to register as a global error handler
593
+ * @param {EventInit} config Typical event listener config object
594
+ */
595
+ function setGlobalErrorHandler(callback, { capture, once, passive, signal } = {}) {
596
+ if (callback instanceof Function) {
597
+ globalThis.addEventListener('error', callback, { capture, once, passive, signal });
598
+ } else {
599
+ throw new TypeError('Callback is not a function.');
600
+ }
601
+ }
602
+
3
603
  let _isRegistrationOpen = true;
4
604
 
5
605
  const $$ = (selector, base = document) => base.querySelectorAll(selector);
@@ -17,6 +617,7 @@ const FUNCS = {
17
617
  back: 'aegis:navigate:back',
18
618
  forward: 'aegis:navigate:forward',
19
619
  reload: 'aegis:navigate:reload',
620
+ close: 'aegis:navigate:close',
20
621
  link: 'aegis:navigate:go',
21
622
  popup: 'aegis:navigate:popup',
22
623
  },
@@ -34,6 +635,9 @@ const FUNCS = {
34
635
  disable: 'aegis:ui:disable',
35
636
  scrollTo: 'aegis:ui:scrollTo',
36
637
  prevent: 'aegis:ui:prevent',
638
+ revokeObjectURL: 'aegis:ui:revokeObjectURL',
639
+ cancelAnimationFrame: 'aegis:ui:cancelAnimationFrame',
640
+ abortController: 'aegis:ui:controller:abort',
37
641
  },
38
642
  };
39
643
 
@@ -42,9 +646,10 @@ const registry = new Map([
42
646
  [FUNCS.debug.warn, console.warn],
43
647
  [FUNCS.debug.error, console.error],
44
648
  [FUNCS.debug.info, console.info],
45
- [FUNCS.navigate.back, history.back],
46
- [FUNCS.navigate.forward, history.forward],
649
+ [FUNCS.navigate.back, () => history.back()],
650
+ [FUNCS.navigate.forward, () => history.forward()],
47
651
  [FUNCS.navigate.reload, () => history.go(0)],
652
+ [FUNCS.navigate.close, () => globalThis.close()],
48
653
  [FUNCS.navigate.link, event => {
49
654
  if (event.isTrusted) {
50
655
  event.preventDefault();
@@ -83,6 +688,11 @@ const registry = new Map([
83
688
  });
84
689
  }
85
690
  }],
691
+ [FUNCS.ui.revokeObjectURL, ({ currentTarget }) => URL.revokeObjectURL(currentTarget.src)],
692
+ [FUNCS.ui.cancelAnimationFrame, ({ currentTarget }) => cancelAnimationFrame(parseInt(currentTarget.dataset.animationFrame))],
693
+ [FUNCS.ui.clearInterval, ({ currentTarget }) => clearInterval(parseInt(currentTarget.dataset.clearInterval))],
694
+ [FUNCS.ui.clearTimeout, ({ currentTarget }) => clearTimeout(parseInt(currentTarget.dataset.timeout))],
695
+ [FUNCS.ui.abortController, ({ currentTarget }) => abortController(currentTarget.dataset.aegisEventController, currentTarget.dataset.aegisControllerReason)],
86
696
  [FUNCS.ui.showModal, ({ currentTarget }) => {
87
697
  const target = $(currentTarget.dataset.showModalSelector);
88
698
 
@@ -170,7 +780,7 @@ const unregisterCallback = name => _isRegistrationOpen && registry.delete(name);
170
780
  /**
171
781
  * Remove all callbacks from the registry
172
782
  *
173
- * @returns {undefined}
783
+ * @returns {void}
174
784
  */
175
785
  const clearRegistry = () => registry.clear();
176
786
 
@@ -240,289 +850,169 @@ function getHost(target) {
240
850
  }
241
851
  }
242
852
 
243
- const EVENT_PREFIX = 'data-aegis-event-on-';
244
- const EVENT_PREFIX_LENGTH = EVENT_PREFIX.length;
245
- const DATA_PREFIX = 'aegisEventOn';
246
- const DATA_PREFIX_LENGTH = DATA_PREFIX.length;
247
-
248
- const once = 'data-aegis-event-once',
249
- passive = 'data-aegis-event-passive',
250
- capture = 'data-aegis-event-capture';
251
-
252
- const eventAttrs = [
253
- EVENT_PREFIX + 'abort',
254
- EVENT_PREFIX + 'blur',
255
- EVENT_PREFIX + 'focus',
256
- EVENT_PREFIX + 'cancel',
257
- EVENT_PREFIX + 'auxclick',
258
- EVENT_PREFIX + 'beforeinput',
259
- EVENT_PREFIX + 'beforetoggle',
260
- EVENT_PREFIX + 'canplay',
261
- EVENT_PREFIX + 'canplaythrough',
262
- EVENT_PREFIX + 'change',
263
- EVENT_PREFIX + 'click',
264
- EVENT_PREFIX + 'close',
265
- EVENT_PREFIX + 'contextmenu',
266
- EVENT_PREFIX + 'copy',
267
- EVENT_PREFIX + 'cuechange',
268
- EVENT_PREFIX + 'cut',
269
- EVENT_PREFIX + 'dblclick',
270
- EVENT_PREFIX + 'drag',
271
- EVENT_PREFIX + 'dragend',
272
- EVENT_PREFIX + 'dragenter',
273
- EVENT_PREFIX + 'dragexit',
274
- EVENT_PREFIX + 'dragleave',
275
- EVENT_PREFIX + 'dragover',
276
- EVENT_PREFIX + 'dragstart',
277
- EVENT_PREFIX + 'drop',
278
- EVENT_PREFIX + 'durationchange',
279
- EVENT_PREFIX + 'emptied',
280
- EVENT_PREFIX + 'ended',
281
- EVENT_PREFIX + 'formdata',
282
- EVENT_PREFIX + 'input',
283
- EVENT_PREFIX + 'invalid',
284
- EVENT_PREFIX + 'keydown',
285
- EVENT_PREFIX + 'keypress',
286
- EVENT_PREFIX + 'keyup',
287
- EVENT_PREFIX + 'load',
288
- EVENT_PREFIX + 'loadeddata',
289
- EVENT_PREFIX + 'loadedmetadata',
290
- EVENT_PREFIX + 'loadstart',
291
- EVENT_PREFIX + 'mousedown',
292
- EVENT_PREFIX + 'mouseenter',
293
- EVENT_PREFIX + 'mouseleave',
294
- EVENT_PREFIX + 'mousemove',
295
- EVENT_PREFIX + 'mouseout',
296
- EVENT_PREFIX + 'mouseover',
297
- EVENT_PREFIX + 'mouseup',
298
- EVENT_PREFIX + 'wheel',
299
- EVENT_PREFIX + 'paste',
300
- EVENT_PREFIX + 'pause',
301
- EVENT_PREFIX + 'play',
302
- EVENT_PREFIX + 'playing',
303
- EVENT_PREFIX + 'progress',
304
- EVENT_PREFIX + 'ratechange',
305
- EVENT_PREFIX + 'reset',
306
- EVENT_PREFIX + 'resize',
307
- EVENT_PREFIX + 'scroll',
308
- EVENT_PREFIX + 'scrollend',
309
- EVENT_PREFIX + 'securitypolicyviolation',
310
- EVENT_PREFIX + 'seeked',
311
- EVENT_PREFIX + 'seeking',
312
- EVENT_PREFIX + 'select',
313
- EVENT_PREFIX + 'slotchange',
314
- EVENT_PREFIX + 'stalled',
315
- EVENT_PREFIX + 'submit',
316
- EVENT_PREFIX + 'suspend',
317
- EVENT_PREFIX + 'timeupdate',
318
- EVENT_PREFIX + 'volumechange',
319
- EVENT_PREFIX + 'waiting',
320
- EVENT_PREFIX + 'selectstart',
321
- EVENT_PREFIX + 'selectionchange',
322
- EVENT_PREFIX + 'toggle',
323
- EVENT_PREFIX + 'pointercancel',
324
- EVENT_PREFIX + 'pointerdown',
325
- EVENT_PREFIX + 'pointerup',
326
- EVENT_PREFIX + 'pointermove',
327
- EVENT_PREFIX + 'pointerout',
328
- EVENT_PREFIX + 'pointerover',
329
- EVENT_PREFIX + 'pointerenter',
330
- EVENT_PREFIX + 'pointerleave',
331
- EVENT_PREFIX + 'gotpointercapture',
332
- EVENT_PREFIX + 'lostpointercapture',
333
- EVENT_PREFIX + 'mozfullscreenchange',
334
- EVENT_PREFIX + 'mozfullscreenerror',
335
- EVENT_PREFIX + 'animationcancel',
336
- EVENT_PREFIX + 'animationend',
337
- EVENT_PREFIX + 'animationiteration',
338
- EVENT_PREFIX + 'animationstart',
339
- EVENT_PREFIX + 'transitioncancel',
340
- EVENT_PREFIX + 'transitionend',
341
- EVENT_PREFIX + 'transitionrun',
342
- EVENT_PREFIX + 'transitionstart',
343
- EVENT_PREFIX + 'webkitanimationend',
344
- EVENT_PREFIX + 'webkitanimationiteration',
345
- EVENT_PREFIX + 'webkitanimationstart',
346
- EVENT_PREFIX + 'webkittransitionend',
347
- EVENT_PREFIX + 'error',
348
- ];
349
-
350
- let selector = eventAttrs.map(attr => `[${CSS.escape(attr)}]`).join(', ');
351
-
352
- const attrToProp = attr => `on${attr[EVENT_PREFIX_LENGTH].toUpperCase()}${attr.substring(EVENT_PREFIX_LENGTH + 1)}`;
353
-
354
- const attrEntriesMap = attr => [attrToProp(attr), attr];
355
-
356
- const isEventDataAttr = ([name]) => name.startsWith(DATA_PREFIX);
357
-
358
- const DATA_EVENTS = Object.fromEntries([...eventAttrs].map(attrEntriesMap));
359
-
360
- function _addListeners(el, { signal, attrFilter = EVENTS } = {}) {
361
- const dataset = el.dataset;
362
-
363
- for (const [attr, val] of Object.entries(dataset).filter(isEventDataAttr)) {
364
- try {
365
- const event = 'on' + attr.substring(DATA_PREFIX_LENGTH);
366
-
367
- if (attrFilter.hasOwnProperty(event) && hasCallback(val)) {
368
- el.addEventListener(event.substring(2).toLowerCase(), getCallback(val), {
369
- passive: dataset.hasOwnProperty('aegisEventPassive'),
370
- capture: dataset.hasOwnProperty('aegisEventCapture'),
371
- once: dataset.hasOwnProperty('aegisEventOnce'),
372
- signal,
373
- });
374
- }
375
- } catch(err) {
376
- console.error(err);
853
+ function on(event, callback, { capture: capture$1 = false, passive: passive$1 = false, once: once$1 = false, signal: signal$1 } = {}) {
854
+ if (callback instanceof Function) {
855
+ return on(event, createCallback(callback), { capture: capture$1, passive: passive$1, once: once$1, signal: signal$1 });
856
+ } else if (typeof callback !== 'string' || callback.length === 0) {
857
+ throw new TypeError('Callback must be a function or a registered callback string.');
858
+ } else if (typeof event !== 'string' || event.length === 0) {
859
+ throw new TypeError('Event must be a non-empty string.');
860
+ } else {
861
+ if (! hasEventAttribute(event)) {
862
+ registerEventAttribute(event);
377
863
  }
378
- }
379
- }
380
864
 
381
- const observer = new MutationObserver(records => {
382
- records.forEach(record => {
383
- switch(record.type) {
384
- case 'childList':
385
- [...record.addedNodes]
386
- .filter(node => node.nodeType === Node.ELEMENT_NODE)
387
- .forEach(node => attachListeners(node));
388
- break;
865
+ const parts = [[eventToProp(event), callback]];
389
866
 
390
- case 'attributes':
391
- if (typeof record.oldValue === 'string' && hasCallback(record.oldValue)) {
392
- record.target.removeEventListener(
393
- record.attributeName.substring(EVENT_PREFIX_LENGTH),
394
- getCallback(record.oldValue), {
395
- once: record.target.hasAttribute(once),
396
- capture: record.target.hasAttribute(capture),
397
- passive: record.target.hasAttribute(passive),
398
- }
399
- );
400
- }
401
-
402
- if (
403
- record.target.hasAttribute(record.attributeName)
404
- && hasCallback(record.target.getAttribute(record.attributeName))
405
- ) {
406
- record.target.addEventListener(
407
- record.attributeName.substring(EVENT_PREFIX_LENGTH),
408
- getCallback(record.target.getAttribute(record.attributeName)), {
409
- once: record.target.hasAttribute(once),
410
- capture: record.target.hasAttribute(capture),
411
- passive: record.target.hasAttribute(passive),
412
- }
413
- );
414
- }
415
- break;
867
+ if (capture$1) {
868
+ parts.push([capture, '']);
416
869
  }
417
- });
418
- });
419
870
 
420
- const EVENTS = { ...DATA_EVENTS, once, passive, capture };
421
-
422
- /**
423
- * Register an attribute to observe for adding/removing event listeners
424
- *
425
- * @param {string} attr Name of the attribute to observe
426
- * @param {object} options
427
- * @param {boolean} [options.addListeners=false] Whether or not to automatically add listeners
428
- * @param {Document|Element} [options.base=document.body] Root node to observe
429
- * @param {AbortSignal} [options.signal] An abort signal to remove any listeners when aborted
430
- * @returns {string} The resulting `data-*` attribute name
431
- */
432
- function registerEventAttribute(attr, {
433
- addListeners = false,
434
- base = document.body,
435
- signal,
436
- } = {}) {
437
- const fullAttr = EVENT_PREFIX + attr.toLowerCase();
438
-
439
- if (! eventAttrs.includes(fullAttr)) {
440
- const sel = `[${CSS.escape(fullAttr)}]`;
441
- const prop = attrToProp(fullAttr);
442
- eventAttrs.push(fullAttr);
443
- EVENTS[prop] = fullAttr;
444
- selector += `, ${sel}`;
445
-
446
- if (addListeners) {
447
- requestAnimationFrame(() => {
448
- const config = { attrFilter: { [prop]: sel }, signal };
449
- [base, ...base.querySelectorAll(sel)].forEach(el => _addListeners(el, config));
450
- });
871
+ if (passive$1) {
872
+ parts.push([passive, '']);
451
873
  }
452
- }
453
-
454
- return fullAttr;
455
- }
456
-
457
- /**
458
- * Add listeners to an element and its children, matching a generated query based on registered attributes
459
- *
460
- * @param {Element|Document} target Root node to add listeners from
461
- * @param {object} options
462
- * @param {AbortSignal} [options.signal] Optional signal to remove event listeners
463
- * @returns {Element|Document} Returns the passed target node
464
- */
465
- function attachListeners(target, { signal } = {}) {
466
- const nodes = target instanceof Element && target.matches(selector)
467
- ? [target, ...target.querySelectorAll(selector)]
468
- : target.querySelectorAll(selector);
469
874
 
470
- nodes.forEach(el => _addListeners(el, { signal }));
471
-
472
- return target;
473
- }
474
- /**
475
- * Add a node to the `MutationObserver` to observe attributes and add/remove event listeners
476
- *
477
- * @param {Document|Element} root Element to observe attributes on
478
- */
479
- function observeEvents(root = document) {
480
- attachListeners(root);
481
- observer.observe(root, {
482
- subtree: true,
483
- childList:true,
484
- attributes: true,
485
- attributeOldValue: true,
486
- attributeFilter: eventAttrs,
487
- });
488
- }
875
+ if (once$1) {
876
+ parts.push([once, '']);
877
+ }
489
878
 
490
- /**
491
- * Disconnects the `MutationObserver`, disabling observing of all attribute changes
492
- *
493
- * @returns {void}
494
- */
495
- const disconnectEventsObserver = () => observer.disconnect();
879
+ if (signal$1 instanceof AbortSignal) {
880
+ parts.push([signal, registerSignal(signal$1)]);
881
+ } else if (typeof signal$1 === 'string') {
882
+ parts.push([signal, signal$1]);
883
+ }
496
884
 
497
- /**
498
- * Register a global error handler callback
499
- *
500
- * @param {Function} callback Callback to register as a global error handler
501
- * @param {EventInit} config Typical event listener config object
502
- */
503
- function setGlobalErrorHandler(callback, { capture, once, passive, signal } = {}) {
504
- if (callback instanceof Function) {
505
- globalThis.addEventListener('error', callback, { capture, once, passive, signal });
506
- } else {
507
- throw new TypeError('Callback is not a function.');
885
+ return parts.map(([prop, val]) => `${prop}="${val}"`).join(' ');
508
886
  }
509
887
  }
510
888
 
511
889
  exports.EVENTS = EVENTS;
512
890
  exports.FUNCS = FUNCS;
891
+ exports.abortController = abortController;
513
892
  exports.attachListeners = attachListeners;
514
893
  exports.callCallback = callCallback;
894
+ exports.capture = capture;
515
895
  exports.clearRegistry = clearRegistry;
516
896
  exports.closeRegistration = closeRegistration;
897
+ exports.controller = controller;
517
898
  exports.createCallback = createCallback;
899
+ exports.createController = createController;
518
900
  exports.disconnectEventsObserver = disconnectEventsObserver;
901
+ exports.eventAttrs = eventAttrs;
902
+ exports.eventToProp = eventToProp;
519
903
  exports.getCallback = getCallback;
904
+ exports.getController = getController;
520
905
  exports.getHost = getHost;
906
+ exports.getSignal = getSignal;
521
907
  exports.hasCallback = hasCallback;
908
+ exports.hasEventAttribute = hasEventAttribute;
522
909
  exports.isRegistrationOpen = isRegistrationOpen;
523
910
  exports.listCallbacks = listCallbacks;
524
911
  exports.observeEvents = observeEvents;
912
+ exports.on = on;
913
+ exports.onAbort = onAbort;
914
+ exports.onAnimationcancel = onAnimationcancel;
915
+ exports.onAnimationend = onAnimationend;
916
+ exports.onAnimationiteration = onAnimationiteration;
917
+ exports.onAnimationstart = onAnimationstart;
918
+ exports.onAuxclick = onAuxclick;
919
+ exports.onBeforeinput = onBeforeinput;
920
+ exports.onBeforetoggle = onBeforetoggle;
921
+ exports.onBlur = onBlur;
922
+ exports.onCancel = onCancel;
923
+ exports.onCanplay = onCanplay;
924
+ exports.onCanplaythrough = onCanplaythrough;
925
+ exports.onChange = onChange;
926
+ exports.onClick = onClick;
927
+ exports.onClose = onClose;
928
+ exports.onContextmenu = onContextmenu;
929
+ exports.onCopy = onCopy;
930
+ exports.onCuechange = onCuechange;
931
+ exports.onCut = onCut;
932
+ exports.onDblclick = onDblclick;
933
+ exports.onDrag = onDrag;
934
+ exports.onDragend = onDragend;
935
+ exports.onDragenter = onDragenter;
936
+ exports.onDragexit = onDragexit;
937
+ exports.onDragleave = onDragleave;
938
+ exports.onDragover = onDragover;
939
+ exports.onDragstart = onDragstart;
940
+ exports.onDrop = onDrop;
941
+ exports.onDurationchange = onDurationchange;
942
+ exports.onEmptied = onEmptied;
943
+ exports.onEnded = onEnded;
944
+ exports.onError = onError;
945
+ exports.onFocus = onFocus;
946
+ exports.onFormdata = onFormdata;
947
+ exports.onGotpointercapture = onGotpointercapture;
948
+ exports.onInput = onInput;
949
+ exports.onInvalid = onInvalid;
950
+ exports.onKeydown = onKeydown;
951
+ exports.onKeypress = onKeypress;
952
+ exports.onKeyup = onKeyup;
953
+ exports.onLoad = onLoad;
954
+ exports.onLoadeddata = onLoadeddata;
955
+ exports.onLoadedmetadata = onLoadedmetadata;
956
+ exports.onLoadstart = onLoadstart;
957
+ exports.onLostpointercapture = onLostpointercapture;
958
+ exports.onMousedown = onMousedown;
959
+ exports.onMouseenter = onMouseenter;
960
+ exports.onMouseleave = onMouseleave;
961
+ exports.onMousemove = onMousemove;
962
+ exports.onMouseout = onMouseout;
963
+ exports.onMouseover = onMouseover;
964
+ exports.onMouseup = onMouseup;
965
+ exports.onMozfullscreenchange = onMozfullscreenchange;
966
+ exports.onMozfullscreenerror = onMozfullscreenerror;
967
+ exports.onPaste = onPaste;
968
+ exports.onPause = onPause;
969
+ exports.onPlay = onPlay;
970
+ exports.onPlaying = onPlaying;
971
+ exports.onPointercancel = onPointercancel;
972
+ exports.onPointerdown = onPointerdown;
973
+ exports.onPointerenter = onPointerenter;
974
+ exports.onPointerleave = onPointerleave;
975
+ exports.onPointermove = onPointermove;
976
+ exports.onPointerout = onPointerout;
977
+ exports.onPointerover = onPointerover;
978
+ exports.onPointerup = onPointerup;
979
+ exports.onProgress = onProgress;
980
+ exports.onRatechange = onRatechange;
981
+ exports.onReset = onReset;
982
+ exports.onResize = onResize;
983
+ exports.onScroll = onScroll;
984
+ exports.onScrollend = onScrollend;
985
+ exports.onSecuritypolicyviolation = onSecuritypolicyviolation;
986
+ exports.onSeeked = onSeeked;
987
+ exports.onSeeking = onSeeking;
988
+ exports.onSelect = onSelect;
989
+ exports.onSelectionchange = onSelectionchange;
990
+ exports.onSelectstart = onSelectstart;
991
+ exports.onSlotchange = onSlotchange;
992
+ exports.onStalled = onStalled;
993
+ exports.onSubmit = onSubmit;
994
+ exports.onSuspend = onSuspend;
995
+ exports.onTimeupdate = onTimeupdate;
996
+ exports.onToggle = onToggle;
997
+ exports.onTransitioncancel = onTransitioncancel;
998
+ exports.onTransitionend = onTransitionend;
999
+ exports.onTransitionrun = onTransitionrun;
1000
+ exports.onTransitionstart = onTransitionstart;
1001
+ exports.onVolumechange = onVolumechange;
1002
+ exports.onWaiting = onWaiting;
1003
+ exports.onWebkitanimationend = onWebkitanimationend;
1004
+ exports.onWebkitanimationiteration = onWebkitanimationiteration;
1005
+ exports.onWebkitanimationstart = onWebkitanimationstart;
1006
+ exports.onWebkittransitionend = onWebkittransitionend;
1007
+ exports.onWheel = onWheel;
1008
+ exports.once = once;
1009
+ exports.passive = passive;
525
1010
  exports.registerCallback = registerCallback;
1011
+ exports.registerController = registerController;
526
1012
  exports.registerEventAttribute = registerEventAttribute;
1013
+ exports.registerSignal = registerSignal;
527
1014
  exports.setGlobalErrorHandler = setGlobalErrorHandler;
1015
+ exports.signal = signal;
528
1016
  exports.unregisterCallback = unregisterCallback;
1017
+ exports.unregisterController = unregisterController;
1018
+ exports.unregisterSignal = unregisterSignal;