@aegisjsproject/callback-registry 1.0.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/callbacks.cjs ADDED
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ let _isRegistrationOpen = true;
4
+
5
+ const $$ = (selector, base = document) => base.querySelectorAll(selector);
6
+
7
+ const $ = (selector, base = document) => base.querySelector(selector);
8
+
9
+ const FUNCS = {
10
+ debug: {
11
+ log: 'aegis:debug:log',
12
+ info: 'aegis:debug:info',
13
+ warn: 'aegis:debug:warn',
14
+ error: 'aegis:debug:error',
15
+ },
16
+ navigate: {
17
+ back: 'aegis:navigate:back',
18
+ forward: 'aegis:navigate:forward',
19
+ reload: 'aegis:navigate:reload',
20
+ link: 'aegis:navigate:go',
21
+ popup: 'aegis:navigate:popup',
22
+ },
23
+ ui: {
24
+ print: 'aegis:ui:print',
25
+ remove: 'aegis:ui:remove',
26
+ hide: 'aegis:ui:hide',
27
+ unhide: 'aegis:ui:unhide',
28
+ showModal: 'aegis:ui:showModal',
29
+ closeModal: 'aegis:ui:closeModal',
30
+ showPopover: 'aegis:ui:showPopover',
31
+ hidePopover: 'aegis:ui:hidePopover',
32
+ togglePopover: 'aegis:ui:togglePopover',
33
+ enable: 'aegis:ui:enable',
34
+ disable: 'aegis:ui:disable',
35
+ scrollTo: 'aegis:ui:scrollTo',
36
+ prevent: 'aegis:ui:prevent',
37
+ },
38
+ };
39
+
40
+ const registry = new Map([
41
+ [FUNCS.debug.log, console.log],
42
+ [FUNCS.debug.warn, console.warn],
43
+ [FUNCS.debug.error, console.error],
44
+ [FUNCS.debug.info, console.info],
45
+ [FUNCS.navigate.back, history.back],
46
+ [FUNCS.navigate.forward, history.forward],
47
+ [FUNCS.navigate.reload, () => history.go(0)],
48
+ [FUNCS.navigate.link, event => {
49
+ if (event.isTrusted) {
50
+ event.preventDefault();
51
+ location.href = event.currentTarget.dataset.url;
52
+ }
53
+ }],
54
+ [FUNCS.navigate.popup, event => {
55
+ if (event.isTrusted) {
56
+ event.preventDefault();
57
+ globalThis.open(event.currentTarget.dataset.url);
58
+ }
59
+ }],
60
+ [FUNCS.ui.hide, ({ currentTarget }) => {
61
+ $$(currentTarget.dataset.hideSelector).forEach(el => el.hidden = true);
62
+ }],
63
+ [FUNCS.ui.unhide, ({ currentTarget }) => {
64
+ $$(currentTarget.dataset.unhideSelector).forEach(el => el.hidden = false);
65
+ }],
66
+ [FUNCS.ui.disable, ({ currentTarget }) => {
67
+ $$(currentTarget.dataset.disableSelector).forEach(el => el.disabled = true);
68
+ }],
69
+ [FUNCS.ui.enable, ({ currentTarget }) => {
70
+ $$(currentTarget.dataset.enableSelector).forEach(el => el.disabled = false);
71
+ }],
72
+ [FUNCS.ui.remove, ({ currentTarget }) => {
73
+ $$(currentTarget.dataset.removeSelector).forEach(el => el.remove());
74
+ }],
75
+ [FUNCS.ui.scrollTo, ({ currentTarget }) => {
76
+ const target = $(currentTarget.dataset.scrollToSelector);
77
+
78
+ if (target instanceof Element) {
79
+ target.scrollIntoView({
80
+ behavior: matchMedia('(prefers-reduced-motion: reduce)').matches
81
+ ? 'instant'
82
+ : 'smooth',
83
+ });
84
+ }
85
+ }],
86
+ [FUNCS.ui.showModal, ({ currentTarget }) => {
87
+ const target = $(currentTarget.dataset.showModalSelector);
88
+
89
+ if (target instanceof HTMLDialogElement) {
90
+ target.showModal();
91
+ }
92
+ }],
93
+ [FUNCS.ui.closeModal, ({ currentTarget }) => {
94
+ const target = $(currentTarget.dataset.closeModalSelector);
95
+
96
+ if (target instanceof HTMLDialogElement) {
97
+ target.close();
98
+ }
99
+ }],
100
+ [FUNCS.ui.showPopover, ({ currentTarget }) => {
101
+ const target = $(currentTarget.dataset.showPopoverSelector);
102
+
103
+ if (target instanceof HTMLElement) {
104
+ target.showPopover();
105
+ }
106
+ }],
107
+ [FUNCS.ui.hidePopover, ({ currentTarget }) => {
108
+ const target = $(currentTarget.dataset.hidePopoverSelector);
109
+
110
+ if (target instanceof HTMLElement) {
111
+ target.hidePopover();
112
+ }
113
+ }],
114
+ [FUNCS.ui.togglePopover, ({ currentTarget }) => {
115
+ const target = $(currentTarget.dataset.togglePopoverSelector);
116
+
117
+ if (target instanceof HTMLElement) {
118
+ target.togglePopover();
119
+ }
120
+ }],
121
+ [FUNCS.ui.print, () => globalThis.print()],
122
+ [FUNCS.ui.prevent, event => event.preventDefault()],
123
+ ]);
124
+
125
+ /**
126
+ * Check if callback registry is open
127
+ *
128
+ * @returns {boolean} Whether or not callback registry is open
129
+ */
130
+ const isRegistrationOpen = () => _isRegistrationOpen;
131
+
132
+ /**
133
+ * Close callback registry
134
+ *
135
+ * @returns {boolean} Whether or not the callback was succesfully removed
136
+ */
137
+ const closeRegistration = () => _isRegistrationOpen = false;
138
+
139
+ /**
140
+ * Get an array of registered callbacks
141
+ *
142
+ * @returns {Array} A frozen array listing keys to all registered callbacks
143
+ */
144
+ const listCallbacks = () => Object.freeze(Array.from(registry.keys()));
145
+
146
+ /**
147
+ * Check if a callback is registered
148
+ *
149
+ * @param {string} name The name/key to check for in callback registry
150
+ * @returns {boolean} Whether or not a callback is registered
151
+ */
152
+ const hasCallback = name => registry.has(name);
153
+
154
+ /**
155
+ * Get a callback from the registry by name/key
156
+ *
157
+ * @param {string} name The name/key of the callback to get
158
+ * @returns {Function|undefined} The corresponding function registered under that name/key
159
+ */
160
+ const getCallback = name => registry.get(name);
161
+
162
+ /**
163
+ * Remove a callback from the registry
164
+ *
165
+ * @param {string} name The name/key of the callback to get
166
+ * @returns {boolean} Whether or not the callback was successfully unregisterd
167
+ */
168
+ const unregisterCallback = name => _isRegistrationOpen && registry.delete(name);
169
+
170
+ /**
171
+ * Remove all callbacks from the registry
172
+ *
173
+ * @returns {undefined}
174
+ */
175
+ const clearRegistry = () => registry.clear();
176
+
177
+ /**
178
+ * Create a registered callback with a randomly generated name
179
+ *
180
+ * @param {Function} callback Callback function to register
181
+ * @returns {string} The automatically generated key/name of the registered callback
182
+ */
183
+ const createCallback = (callback) => registerCallback('aegis:callback:' + crypto.randomUUID(), callback);
184
+
185
+ /**
186
+ * Call a callback fromt the registry by name/key
187
+ *
188
+ * @param {string} name The name/key of the registered function
189
+ * @param {...any} args Any arguments to pass along to the function
190
+ * @returns {any} Whatever the return value of the function is
191
+ * @throws {Error} Throws if callback is not found or any error resulting from calling the function
192
+ */
193
+ function callCallback(name, ...args) {
194
+ if (registry.has(name)) {
195
+ return registry.get(name).apply(this || globalThis, args);
196
+ } else {
197
+ throw new Error(`No ${name} function registered.`);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Register a named callback in registry
203
+ *
204
+ * @param {string} name The name/key to register the callback under
205
+ * @param {Function} callback The callback value to register
206
+ * @returns {string} The registered name/key
207
+ */
208
+ function registerCallback(name, callback) {
209
+ if (typeof name !== 'string' || name.length === 0) {
210
+ throw new TypeError('Callback name must be a string.');
211
+ } if (! (callback instanceof Function)) {
212
+ throw new TypeError('Callback must be a function.');
213
+ } else if (! _isRegistrationOpen) {
214
+ throw new TypeError('Cannot register new callbacks because registry is closed.');
215
+ } else if (registry.has(name)) {
216
+ throw new Error(`Handler "${name}" is already registered.`);
217
+ } else {
218
+ registry.set(name, callback);
219
+ return name;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Get the host/root node of a given thing.
225
+ *
226
+ * @param {Event|Document|Element|ShadowRoot} target Source thing to search for host of
227
+ * @returns {Document|Element|null} The host/root node, or null
228
+ */
229
+ function getHost(target) {
230
+ if (target instanceof Event) {
231
+ return getHost(target.currentTarget);
232
+ } else if (target instanceof Document) {
233
+ return target;
234
+ } else if (target instanceof Element) {
235
+ return getHost(target.getRootNode());
236
+ } else if (target instanceof ShadowRoot) {
237
+ return target.host;
238
+ } else {
239
+ return null;
240
+ }
241
+ }
242
+
243
+ exports.FUNCS = FUNCS;
244
+ exports.callCallback = callCallback;
245
+ exports.clearRegistry = clearRegistry;
246
+ exports.closeRegistration = closeRegistration;
247
+ exports.createCallback = createCallback;
248
+ exports.getCallback = getCallback;
249
+ exports.getHost = getHost;
250
+ exports.hasCallback = hasCallback;
251
+ exports.isRegistrationOpen = isRegistrationOpen;
252
+ exports.listCallbacks = listCallbacks;
253
+ exports.registerCallback = registerCallback;
254
+ exports.unregisterCallback = unregisterCallback;
package/callbacks.js ADDED
@@ -0,0 +1,239 @@
1
+ let _isRegistrationOpen = true;
2
+
3
+ const $$ = (selector, base = document) => base.querySelectorAll(selector);
4
+
5
+ const $ = (selector, base = document) => base.querySelector(selector);
6
+
7
+ export const FUNCS = {
8
+ debug: {
9
+ log: 'aegis:debug:log',
10
+ info: 'aegis:debug:info',
11
+ warn: 'aegis:debug:warn',
12
+ error: 'aegis:debug:error',
13
+ },
14
+ navigate: {
15
+ back: 'aegis:navigate:back',
16
+ forward: 'aegis:navigate:forward',
17
+ reload: 'aegis:navigate:reload',
18
+ link: 'aegis:navigate:go',
19
+ popup: 'aegis:navigate:popup',
20
+ },
21
+ ui: {
22
+ print: 'aegis:ui:print',
23
+ remove: 'aegis:ui:remove',
24
+ hide: 'aegis:ui:hide',
25
+ unhide: 'aegis:ui:unhide',
26
+ showModal: 'aegis:ui:showModal',
27
+ closeModal: 'aegis:ui:closeModal',
28
+ showPopover: 'aegis:ui:showPopover',
29
+ hidePopover: 'aegis:ui:hidePopover',
30
+ togglePopover: 'aegis:ui:togglePopover',
31
+ enable: 'aegis:ui:enable',
32
+ disable: 'aegis:ui:disable',
33
+ scrollTo: 'aegis:ui:scrollTo',
34
+ prevent: 'aegis:ui:prevent',
35
+ },
36
+ };
37
+
38
+ const registry = new Map([
39
+ [FUNCS.debug.log, console.log],
40
+ [FUNCS.debug.warn, console.warn],
41
+ [FUNCS.debug.error, console.error],
42
+ [FUNCS.debug.info, console.info],
43
+ [FUNCS.navigate.back, history.back],
44
+ [FUNCS.navigate.forward, history.forward],
45
+ [FUNCS.navigate.reload, () => history.go(0)],
46
+ [FUNCS.navigate.link, event => {
47
+ if (event.isTrusted) {
48
+ event.preventDefault();
49
+ location.href = event.currentTarget.dataset.url;
50
+ }
51
+ }],
52
+ [FUNCS.navigate.popup, event => {
53
+ if (event.isTrusted) {
54
+ event.preventDefault();
55
+ globalThis.open(event.currentTarget.dataset.url);
56
+ }
57
+ }],
58
+ [FUNCS.ui.hide, ({ currentTarget }) => {
59
+ $$(currentTarget.dataset.hideSelector).forEach(el => el.hidden = true);
60
+ }],
61
+ [FUNCS.ui.unhide, ({ currentTarget }) => {
62
+ $$(currentTarget.dataset.unhideSelector).forEach(el => el.hidden = false);
63
+ }],
64
+ [FUNCS.ui.disable, ({ currentTarget }) => {
65
+ $$(currentTarget.dataset.disableSelector).forEach(el => el.disabled = true);
66
+ }],
67
+ [FUNCS.ui.enable, ({ currentTarget }) => {
68
+ $$(currentTarget.dataset.enableSelector).forEach(el => el.disabled = false);
69
+ }],
70
+ [FUNCS.ui.remove, ({ currentTarget }) => {
71
+ $$(currentTarget.dataset.removeSelector).forEach(el => el.remove());
72
+ }],
73
+ [FUNCS.ui.scrollTo, ({ currentTarget }) => {
74
+ const target = $(currentTarget.dataset.scrollToSelector);
75
+
76
+ if (target instanceof Element) {
77
+ target.scrollIntoView({
78
+ behavior: matchMedia('(prefers-reduced-motion: reduce)').matches
79
+ ? 'instant'
80
+ : 'smooth',
81
+ });
82
+ }
83
+ }],
84
+ [FUNCS.ui.showModal, ({ currentTarget }) => {
85
+ const target = $(currentTarget.dataset.showModalSelector);
86
+
87
+ if (target instanceof HTMLDialogElement) {
88
+ target.showModal();
89
+ }
90
+ }],
91
+ [FUNCS.ui.closeModal, ({ currentTarget }) => {
92
+ const target = $(currentTarget.dataset.closeModalSelector);
93
+
94
+ if (target instanceof HTMLDialogElement) {
95
+ target.close();
96
+ }
97
+ }],
98
+ [FUNCS.ui.showPopover, ({ currentTarget }) => {
99
+ const target = $(currentTarget.dataset.showPopoverSelector);
100
+
101
+ if (target instanceof HTMLElement) {
102
+ target.showPopover();
103
+ }
104
+ }],
105
+ [FUNCS.ui.hidePopover, ({ currentTarget }) => {
106
+ const target = $(currentTarget.dataset.hidePopoverSelector);
107
+
108
+ if (target instanceof HTMLElement) {
109
+ target.hidePopover();
110
+ }
111
+ }],
112
+ [FUNCS.ui.togglePopover, ({ currentTarget }) => {
113
+ const target = $(currentTarget.dataset.togglePopoverSelector);
114
+
115
+ if (target instanceof HTMLElement) {
116
+ target.togglePopover();
117
+ }
118
+ }],
119
+ [FUNCS.ui.print, () => globalThis.print()],
120
+ [FUNCS.ui.prevent, event => event.preventDefault()],
121
+ ]);
122
+
123
+ /**
124
+ * Check if callback registry is open
125
+ *
126
+ * @returns {boolean} Whether or not callback registry is open
127
+ */
128
+ export const isRegistrationOpen = () => _isRegistrationOpen;
129
+
130
+ /**
131
+ * Close callback registry
132
+ *
133
+ * @returns {boolean} Whether or not the callback was succesfully removed
134
+ */
135
+ export const closeRegistration = () => _isRegistrationOpen = false;
136
+
137
+ /**
138
+ * Get an array of registered callbacks
139
+ *
140
+ * @returns {Array} A frozen array listing keys to all registered callbacks
141
+ */
142
+ export const listCallbacks = () => Object.freeze(Array.from(registry.keys()));
143
+
144
+ /**
145
+ * Check if a callback is registered
146
+ *
147
+ * @param {string} name The name/key to check for in callback registry
148
+ * @returns {boolean} Whether or not a callback is registered
149
+ */
150
+ export const hasCallback = name => registry.has(name);
151
+
152
+ /**
153
+ * Get a callback from the registry by name/key
154
+ *
155
+ * @param {string} name The name/key of the callback to get
156
+ * @returns {Function|undefined} The corresponding function registered under that name/key
157
+ */
158
+ export const getCallback = name => registry.get(name);
159
+
160
+ /**
161
+ * Remove a callback from the registry
162
+ *
163
+ * @param {string} name The name/key of the callback to get
164
+ * @returns {boolean} Whether or not the callback was successfully unregisterd
165
+ */
166
+ export const unregisterCallback = name => _isRegistrationOpen && registry.delete(name);
167
+
168
+ /**
169
+ * Remove all callbacks from the registry
170
+ *
171
+ * @returns {undefined}
172
+ */
173
+ export const clearRegistry = () => registry.clear();
174
+
175
+ /**
176
+ * Create a registered callback with a randomly generated name
177
+ *
178
+ * @param {Function} callback Callback function to register
179
+ * @returns {string} The automatically generated key/name of the registered callback
180
+ */
181
+ export const createCallback = (callback) => registerCallback('aegis:callback:' + crypto.randomUUID(), callback);
182
+
183
+ /**
184
+ * Call a callback fromt the registry by name/key
185
+ *
186
+ * @param {string} name The name/key of the registered function
187
+ * @param {...any} args Any arguments to pass along to the function
188
+ * @returns {any} Whatever the return value of the function is
189
+ * @throws {Error} Throws if callback is not found or any error resulting from calling the function
190
+ */
191
+ export function callCallback(name, ...args) {
192
+ if (registry.has(name)) {
193
+ return registry.get(name).apply(this || globalThis, args);
194
+ } else {
195
+ throw new Error(`No ${name} function registered.`);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Register a named callback in registry
201
+ *
202
+ * @param {string} name The name/key to register the callback under
203
+ * @param {Function} callback The callback value to register
204
+ * @returns {string} The registered name/key
205
+ */
206
+ export function registerCallback(name, callback) {
207
+ if (typeof name !== 'string' || name.length === 0) {
208
+ throw new TypeError('Callback name must be a string.');
209
+ } if (! (callback instanceof Function)) {
210
+ throw new TypeError('Callback must be a function.');
211
+ } else if (! _isRegistrationOpen) {
212
+ throw new TypeError('Cannot register new callbacks because registry is closed.');
213
+ } else if (registry.has(name)) {
214
+ throw new Error(`Handler "${name}" is already registered.`);
215
+ } else {
216
+ registry.set(name, callback);
217
+ return name;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Get the host/root node of a given thing.
223
+ *
224
+ * @param {Event|Document|Element|ShadowRoot} target Source thing to search for host of
225
+ * @returns {Document|Element|null} The host/root node, or null
226
+ */
227
+ export function getHost(target) {
228
+ if (target instanceof Event) {
229
+ return getHost(target.currentTarget);
230
+ } else if (target instanceof Document) {
231
+ return target;
232
+ } else if (target instanceof Element) {
233
+ return getHost(target.getRootNode());
234
+ } else if (target instanceof ShadowRoot) {
235
+ return target.host;
236
+ } else {
237
+ return null;
238
+ }
239
+ }