@twobirds/microcomponents 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -16
- package/dist/MC.d.ts +2 -10
- package/dist/MC.d.ts.map +1 -1
- package/dist/MC.js +11 -61
- package/dist/MC.min.js +63 -60
- package/dist/MC.ts +268 -112
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +1 -2
- package/dist/elements.min.js +1 -1
- package/dist/elements.ts +26 -6
- package/dist/events.d.ts +11 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +55 -0
- package/dist/events.ts +158 -0
- package/dist/formValues.d.ts.map +1 -1
- package/dist/formValues.js +4 -6
- package/dist/formValues.min.js +1 -1
- package/dist/formValues.ts +168 -10
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +3 -1
- package/dist/helpers.ts +82 -1
- package/dist/main.mjs +1 -0
- package/dist/microcomponents.d.ts.map +1 -1
- package/dist/microcomponents.min.js +1 -1
- package/dist/observables.d.ts +1 -1
- package/dist/observables.d.ts.map +1 -1
- package/dist/observables.js +14 -14
- package/dist/observables.min.js +1 -1
- package/dist/observables.ts +132 -21
- package/package.json +7 -7
package/dist/MC.ts
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import { LooseObject } from './helpers.js';
|
|
4
|
+
import { trigger, on, one, off, BubbleController } from './events.js';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
Determines whether the current runtime environment is a web browser.
|
|
8
|
+
|
|
9
|
+
This function works by attempting to access `this` within an anonymous function
|
|
10
|
+
and checking if it refers to the global `window` object. It returns `true`
|
|
11
|
+
when executed in a browser context, and `false` otherwise (e.g., Node.js).
|
|
12
|
+
|
|
13
|
+
Note: While modern environments typically provide more reliable detection mechanisms,
|
|
14
|
+
this fallback remains compatible across older runtimes where other detection techniques
|
|
15
|
+
may be unreliable.
|
|
16
|
+
|
|
17
|
+
@returns {boolean} `true` if the code is running in a browser environment, `false` otherwise.
|
|
18
|
+
*/
|
|
5
19
|
const isBrowser = new Function('try {return this===window;}catch(e){ return false;}');
|
|
6
20
|
|
|
21
|
+
/**
|
|
22
|
+
Represents a DOM element that has been enhanced with microcomponent functionality.
|
|
23
|
+
|
|
24
|
+
@template HTMLElement - The base HTML element type
|
|
25
|
+
*/
|
|
7
26
|
export interface HTMLMcElement extends HTMLElement {
|
|
27
|
+
/**
|
|
28
|
+
Hash object containing inner microcomponents, keyed by their unique identifiers (strings).
|
|
29
|
+
Each value is a reference to the corresponding `MicroComponent` instance or its configuration,
|
|
30
|
+
depending on the implementation.
|
|
31
|
+
|
|
32
|
+
This property allows direct access to nested microcomponents for programmatic interaction
|
|
33
|
+
(e.g., invoking methods or accessing state), and supports internal lifecycle management
|
|
34
|
+
such as mounting/unmounting or updating.
|
|
35
|
+
|
|
36
|
+
@type {LooseObject<MicroComponent>}
|
|
37
|
+
@readonly
|
|
38
|
+
*/
|
|
8
39
|
_mc: LooseObject;
|
|
9
40
|
}
|
|
10
41
|
|
|
@@ -12,7 +43,18 @@ function isDOM(element: { [key: string]: any }) {
|
|
|
12
43
|
return element && element instanceof HTMLElement;
|
|
13
44
|
}
|
|
14
45
|
|
|
15
|
-
|
|
46
|
+
/**
|
|
47
|
+
Adds a microcomponent instance to a target DOM node under the specified key.
|
|
48
|
+
|
|
49
|
+
This function initializes or updates the internal `_mc` collection on the target element,
|
|
50
|
+
which stores references to child microcomponents. It also adds the `data-mc` attribute
|
|
51
|
+
if missing, indicating that this DOM node hosts at least one microcomponent.
|
|
52
|
+
|
|
53
|
+
@param {HTMLElement} target - The DOM element to attach the microcomponent to.
|
|
54
|
+
@param {string} key - The property name/key under which the element will be stored in `_mc`.
|
|
55
|
+
@param {any} [element={}] - The microcomponent instance or configuration object to attach.
|
|
56
|
+
@returns {HTMLMcElement} The target DOM node, now enhanced with the attached microcomponent.
|
|
57
|
+
*/
|
|
16
58
|
function add(target: HTMLElement, key: string, element: any = {}): HTMLMcElement {
|
|
17
59
|
let node = target as HTMLMcElement;
|
|
18
60
|
|
|
@@ -37,7 +79,15 @@ function add(target: HTMLElement, key: string, element: any = {}): HTMLMcElement
|
|
|
37
79
|
return node;
|
|
38
80
|
}
|
|
39
81
|
|
|
40
|
-
|
|
82
|
+
/**
|
|
83
|
+
Removes a named microcomponent from the internal `_mc` collection of a target DOM node.
|
|
84
|
+
|
|
85
|
+
This function deletes the specified key from the element's `_mc` hash object and, if the
|
|
86
|
+
collection becomes empty, removes the `data-mc` attribute from the DOM node.
|
|
87
|
+
|
|
88
|
+
@param {HTMLElement} target - The DOM element whose microcomponent should be removed.
|
|
89
|
+
@param {string} key - The property name/key corresponding to the microcomponent to remove.
|
|
90
|
+
*/
|
|
41
91
|
function remove(target: HTMLElement, key: string) {
|
|
42
92
|
if (!key) return; // silent fail
|
|
43
93
|
|
|
@@ -55,7 +105,15 @@ function remove(target: HTMLElement, key: string) {
|
|
|
55
105
|
return;
|
|
56
106
|
}
|
|
57
107
|
|
|
58
|
-
|
|
108
|
+
/**
|
|
109
|
+
Runs the "Init" lifecycle event on a microcomponent instance asynchronously.
|
|
110
|
+
|
|
111
|
+
This function schedules the `trigger('Init')` call within a zero-delay timeout,
|
|
112
|
+
ensuring that all synchronous initialization is complete before the lifecycle
|
|
113
|
+
hook executes. It skips execution for the base `DC` class itself.
|
|
114
|
+
|
|
115
|
+
@param {DC} element - The microcomponent instance on which to trigger the Init event.
|
|
116
|
+
*/
|
|
59
117
|
function init(element: DC) {
|
|
60
118
|
//console.warn('init(): args', ...arguments);
|
|
61
119
|
setTimeout(() => {
|
|
@@ -64,99 +122,15 @@ function init(element: DC) {
|
|
|
64
122
|
}, 0);
|
|
65
123
|
}
|
|
66
124
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/*
|
|
73
|
-
CustomEvent Section...
|
|
74
|
-
*/
|
|
75
|
-
function off(domNode: Node, eventName: string, handler: EventListenerOrEventListenerObject) {
|
|
76
|
-
domNode.removeEventListener(getMcEventName(eventName), handler);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function on(
|
|
80
|
-
domNode: Node,
|
|
81
|
-
eventName: string,
|
|
82
|
-
handler: EventListenerOrEventListenerObject,
|
|
83
|
-
once: boolean = false
|
|
84
|
-
): EventListenerOrEventListenerObject {
|
|
85
|
-
domNode.addEventListener(getMcEventName(eventName), handler, { capture: false, once });
|
|
86
|
-
return handler;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function one(
|
|
90
|
-
domNode: Node,
|
|
91
|
-
eventName: string,
|
|
92
|
-
handler: EventListenerOrEventListenerObject
|
|
93
|
-
): EventListenerOrEventListenerObject {
|
|
94
|
-
on(domNode, eventName, handler, true);
|
|
95
|
-
return handler;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
class BubbleController {
|
|
99
|
-
stopped = false;
|
|
100
|
-
constructor() {}
|
|
101
|
-
stop() {
|
|
102
|
-
this.stopped = true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// create and dispatch custom event
|
|
107
|
-
function trigger(
|
|
108
|
-
domNode: HTMLElement,
|
|
109
|
-
event: string,
|
|
110
|
-
data: any = {},
|
|
111
|
-
bubble: string = 'l', // direction of bubbling
|
|
112
|
-
controller?: BubbleController
|
|
113
|
-
) {
|
|
114
|
-
//console.warn('trigger args', ...arguments);
|
|
115
|
-
|
|
116
|
-
const eventName = /^_mc/.test(event) ? event : getMcEventName(event),
|
|
117
|
-
bubbleController = controller ? controller : new BubbleController(),
|
|
118
|
-
ev = new CustomEvent(eventName, {
|
|
119
|
-
detail: { data, control: bubbleController },
|
|
120
|
-
bubbles: false,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (bubbleController.stopped) return;
|
|
124
|
-
|
|
125
|
-
if (/l/.test(bubble)) {
|
|
126
|
-
domNode.dispatchEvent(ev);
|
|
127
|
-
ev.stopImmediatePropagation();
|
|
128
|
-
|
|
129
|
-
if (bubbleController.stopped) return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// bubble if up or down is indicated
|
|
133
|
-
if (/[ud]/.test(bubble)) {
|
|
134
|
-
// add missing 'l', or else no handlers are executed
|
|
135
|
-
if (bubble.indexOf('l') === -1) {
|
|
136
|
-
bubble += 'l';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
setTimeout(() => {
|
|
140
|
-
// bubble up towards document.body
|
|
141
|
-
if (bubble.indexOf('u') > -1) {
|
|
142
|
-
let parentMc: HTMLMcElement | null | undefined =
|
|
143
|
-
domNode.parentElement?.closest('[data-mc]');
|
|
144
|
-
if (parentMc) {
|
|
145
|
-
parentMc?._mc.trigger(eventName, data, bubble, bubbleController);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
125
|
+
/**
|
|
126
|
+
Attaches event listeners to DOM elements based on method naming conventions.
|
|
148
127
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (childNodes) {
|
|
153
|
-
childNodes.forEach((_mc: DC) => _mc.trigger(eventName, data, bubble)); // event multiplex - dont carry bubble controller
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}, 0);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
128
|
+
Scans the prototype of the provided element and attaches listeners for methods
|
|
129
|
+
prefixed with `on` or `one`, corresponding to either native HTML events (e.g.,
|
|
130
|
+
'onclick') or microcomponent-prefixed custom events (e.g., '_mcUpdate').
|
|
159
131
|
|
|
132
|
+
@param {MC|DC} element - The instance whose prototype should be scanned for event handlers.
|
|
133
|
+
*/
|
|
160
134
|
function autoAttachListeners(element: MC | DC) {
|
|
161
135
|
if (!isBrowser() || element.constructor === DC) return;
|
|
162
136
|
|
|
@@ -193,6 +167,17 @@ function autoAttachListeners(element: MC | DC) {
|
|
|
193
167
|
}
|
|
194
168
|
}
|
|
195
169
|
|
|
170
|
+
/**
|
|
171
|
+
Retrieves immediate child microcomponents of a DOM node.
|
|
172
|
+
|
|
173
|
+
Queries the DOM for descendant elements marked with `data-mc` that are direct
|
|
174
|
+
children of the provided element and returns their associated `_mc` instances.
|
|
175
|
+
Temporarily sets an internal attribute on the node to prevent recursive matches
|
|
176
|
+
within nested subtrees.
|
|
177
|
+
|
|
178
|
+
@param {HTMLElement} domNode - The DOM element whose child microcomponents to find.
|
|
179
|
+
@returns {DC[]|null} An array of child microcomponent instances, or null if none found.
|
|
180
|
+
*/
|
|
196
181
|
function getMcChildNodes(domNode: HTMLElement): DC[] | null {
|
|
197
182
|
const id = Math.random().toString().replace('.', '');
|
|
198
183
|
const selector = '[data-mc]:not([temp_id="' + id + '"] [data-mc] [data-mc])';
|
|
@@ -204,9 +189,24 @@ function getMcChildNodes(domNode: HTMLElement): DC[] | null {
|
|
|
204
189
|
return mcArray.length ? (mcArray as DC[]) : null;
|
|
205
190
|
}
|
|
206
191
|
|
|
192
|
+
/**
|
|
193
|
+
The base class for all microcomponents.
|
|
194
|
+
|
|
195
|
+
Implements a `WeakRef` to the target DOM element or other data, avoiding
|
|
196
|
+
circular references. Automatically triggers the "Init" lifecycle event and
|
|
197
|
+
attaches listeners via `autoAttachListeners` upon construction.
|
|
198
|
+
*/
|
|
207
199
|
class McBase {
|
|
208
200
|
private _target: WeakRef<any>; // avoid circular reference
|
|
209
201
|
|
|
202
|
+
/**
|
|
203
|
+
The constructor for the base `McBase` class.
|
|
204
|
+
|
|
205
|
+
Initializes a weak reference to the target and triggers auto-attachment
|
|
206
|
+
of listeners and initialization lifecycle events.
|
|
207
|
+
|
|
208
|
+
@param {any} [target={}] - The object or element this microcomponent represents.
|
|
209
|
+
*/
|
|
210
210
|
constructor(target?: { [key: string]: any }) {
|
|
211
211
|
let element = this as unknown as MC | DC;
|
|
212
212
|
|
|
@@ -219,28 +219,73 @@ class McBase {
|
|
|
219
219
|
init(element as DC); // async lifecycle event
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
Returns the current target object (DOM node or other data) referenced by this microcomponent.
|
|
224
|
+
|
|
225
|
+
Uses a `WeakRef` internally to avoid circular references, and safely dereferences it here.
|
|
226
|
+
|
|
227
|
+
@returns {any} The target object, or undefined if the reference has been garbage collected.
|
|
228
|
+
*/
|
|
222
229
|
get target(): any {
|
|
223
230
|
return this._target.deref();
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
|
|
234
|
+
/**
|
|
235
|
+
A general-purpose base microcomponent that does not require a DOM target.
|
|
236
|
+
|
|
237
|
+
Inherits from `McBase`, but is primarily used as a lightweight container or
|
|
238
|
+
for logic not directly tied to DOM elements.
|
|
239
|
+
*/
|
|
227
240
|
class MC extends McBase {
|
|
241
|
+
/**
|
|
242
|
+
Constructs an instance of the general-purpose microcomponent.
|
|
243
|
+
|
|
244
|
+
@param {any} [target=undefined] - Optional target object or data.
|
|
245
|
+
*/
|
|
228
246
|
constructor(target?: any) {
|
|
229
247
|
super(target);
|
|
230
248
|
}
|
|
231
249
|
}
|
|
232
250
|
|
|
251
|
+
/**
|
|
252
|
+
A DOM-centric microcomponent that operates on a specific HTML element.
|
|
253
|
+
|
|
254
|
+
Extends `McBase` and associates itself with an actual DOM node via the
|
|
255
|
+
`_target` reference. Provides methods for event handling, DOM traversal,
|
|
256
|
+
and interaction with sibling or ancestor microcomponents.
|
|
257
|
+
*/
|
|
233
258
|
class DC extends McBase {
|
|
234
259
|
constructor(target: HTMLElement) {
|
|
235
260
|
super(target);
|
|
236
261
|
//console.warn('DC constructor:', arguments);
|
|
237
262
|
}
|
|
238
263
|
|
|
264
|
+
/**
|
|
265
|
+
Static helper to attach a microcomponent instance to a DOM element.
|
|
266
|
+
|
|
267
|
+
Wrapper around the `add()` utility function, providing a class-level API
|
|
268
|
+
for adding child microcomponents to a target node.
|
|
269
|
+
|
|
270
|
+
@param {HTMLElement} target - The DOM element to which to attach the microcomponent.
|
|
271
|
+
@param {string} key - The property name/key under which to store the instance in `_mc`.
|
|
272
|
+
@param {any} [element={}] - The microcomponent instance or config object to attach.
|
|
273
|
+
@returns {HTMLMcElement} The target DOM element with the attached microcomponent.
|
|
274
|
+
*/
|
|
239
275
|
static add(target: HTMLElement, key: string, element: any = {}): HTMLMcElement {
|
|
240
276
|
//console.warn('static DC.add() args', arguments);
|
|
241
277
|
return add(target, key, element);
|
|
242
278
|
}
|
|
243
279
|
|
|
280
|
+
/**
|
|
281
|
+
Static helper to remove a named microcomponent from a DOM element.
|
|
282
|
+
|
|
283
|
+
Wrapper around the `remove()` utility function, providing a class-level API
|
|
284
|
+
for removing child microcomponents.
|
|
285
|
+
|
|
286
|
+
@param {HTMLElement} target - The DOM element from which to remove the microcomponent.
|
|
287
|
+
@param {string} key - The property name/key corresponding to the microcomponent to remove.
|
|
288
|
+
*/
|
|
244
289
|
static remove(target: HTMLElement, key: string) {
|
|
245
290
|
remove(target, key);
|
|
246
291
|
}
|
|
@@ -250,6 +295,15 @@ class DC extends McBase {
|
|
|
250
295
|
off(this.target, eventName, handler);
|
|
251
296
|
}
|
|
252
297
|
|
|
298
|
+
/**
|
|
299
|
+
Attaches a persistent event listener to the DOM node.
|
|
300
|
+
|
|
301
|
+
This is an instance method wrapper for `on()`, using the internal `_target` DOM reference.
|
|
302
|
+
|
|
303
|
+
@param {string} eventName - The name of the event (e.g., 'click', 'update').
|
|
304
|
+
@param {EventListenerOrEventListenerObject} handler - The callback function or object to invoke.
|
|
305
|
+
@returns {EventListenerOrEventListenerObject} The attached handler reference.
|
|
306
|
+
*/
|
|
253
307
|
on(
|
|
254
308
|
eventName: string,
|
|
255
309
|
handler: EventListenerOrEventListenerObject
|
|
@@ -257,6 +311,15 @@ class DC extends McBase {
|
|
|
257
311
|
return on(this.target, eventName, handler);
|
|
258
312
|
}
|
|
259
313
|
|
|
314
|
+
/**
|
|
315
|
+
Attaches a one-time event listener to the DOM node.
|
|
316
|
+
|
|
317
|
+
This is an instance method wrapper for `one()`, using the internal `_target` DOM reference.
|
|
318
|
+
|
|
319
|
+
@param {string} eventName - The name of the event (e.g., 'click', 'update').
|
|
320
|
+
@param {EventListenerOrEventListenerObject} handler - The callback function or object to invoke once.
|
|
321
|
+
@returns {EventListenerOrEventListenerObject} The attached one-time handler reference.
|
|
322
|
+
*/
|
|
260
323
|
one(
|
|
261
324
|
eventName: string,
|
|
262
325
|
handler: EventListenerOrEventListenerObject
|
|
@@ -264,13 +327,30 @@ class DC extends McBase {
|
|
|
264
327
|
return one(this.target, eventName, handler);
|
|
265
328
|
}
|
|
266
329
|
|
|
330
|
+
/**
|
|
331
|
+
Triggers a custom event on the DOM node and optionally bubbles it.
|
|
332
|
+
|
|
333
|
+
This is an instance method wrapper for `trigger()`, using the internal `_target` DOM reference.
|
|
334
|
+
|
|
335
|
+
@param {string} eventName - The name of the event (will be prefixed if needed).
|
|
336
|
+
@param {any} [data={}] - Additional data payload attached to `event.detail.data`.
|
|
337
|
+
@param {string} [bubble='l'] - Bubbling direction ('l', 'u', 'd' or combinations).
|
|
338
|
+
@param {BubbleController} [controller] - Optional controller for managing bubbling behavior.
|
|
339
|
+
*/
|
|
267
340
|
trigger(eventName: string, data: any = {}, bubble: string = 'l', controller?: BubbleController) {
|
|
268
341
|
//console.warn('MC.trigger args', ...arguments, this);
|
|
269
342
|
|
|
270
343
|
trigger(this.target, eventName, data, bubble, controller);
|
|
271
344
|
}
|
|
272
345
|
|
|
273
|
-
|
|
346
|
+
/**
|
|
347
|
+
Returns an array of all ancestor microcomponents up the DOM tree.
|
|
348
|
+
|
|
349
|
+
Traverses upward from this element's target, collecting all ancestor
|
|
350
|
+
elements marked with `data-mc` and returning their `_mc` instances.
|
|
351
|
+
|
|
352
|
+
@returns {DC[]|null} An array of ancestor microcomponent instances, or null if none found.
|
|
353
|
+
*/
|
|
274
354
|
ancestors(): DC[] | null {
|
|
275
355
|
let currentNode: HTMLElement | null = (this as DC)?.target as HTMLMcElement,
|
|
276
356
|
result: DC[] = [];
|
|
@@ -286,18 +366,41 @@ class DC extends McBase {
|
|
|
286
366
|
return result.length ? result : null;
|
|
287
367
|
}
|
|
288
368
|
|
|
369
|
+
/**
|
|
370
|
+
Returns the closest ancestor microcomponent or null if none found.
|
|
371
|
+
|
|
372
|
+
This method is equivalent to calling `ancestors()[0]`, but more concise.
|
|
373
|
+
|
|
374
|
+
@returns {DC|null} The nearest ancestor microcomponent, or null if not found.
|
|
375
|
+
*/
|
|
289
376
|
closest(): DC | null {
|
|
290
377
|
let target: HTMLElement = (this as DC).target;
|
|
291
378
|
if (!isDOM(target)) return null;
|
|
292
379
|
return ((target.closest('[data-mc]') as HTMLMcElement)?._mc as DC) || null;
|
|
293
380
|
}
|
|
294
381
|
|
|
382
|
+
/**
|
|
383
|
+
Returns an array of direct child microcomponents.
|
|
384
|
+
|
|
385
|
+
Calls `getMcChildNodes()` internally to find immediate descendants only,
|
|
386
|
+
excluding nested grandchildren and deeper levels.
|
|
387
|
+
|
|
388
|
+
@returns {DC[]|null} An array of child microcomponent instances, or null if none found.
|
|
389
|
+
*/
|
|
295
390
|
children(): DC[] | null {
|
|
296
391
|
let target: any = (this as any)?.target;
|
|
297
392
|
if (!isDOM(target)) return null;
|
|
298
393
|
return getMcChildNodes(target);
|
|
299
394
|
}
|
|
300
395
|
|
|
396
|
+
/**
|
|
397
|
+
Returns an array of all descendant microcomponents recursively.
|
|
398
|
+
|
|
399
|
+
Uses a `querySelectorAll` query to collect every nested element marked with
|
|
400
|
+
`data-mc`, returning their associated `_mc` instances.
|
|
401
|
+
|
|
402
|
+
@returns {DC[]|null} An array of descendant microcomponent instances, or null if none found.
|
|
403
|
+
*/
|
|
301
404
|
descendants(): DC[] | null {
|
|
302
405
|
const target: any = (this as DC)?.target;
|
|
303
406
|
if (!isDOM(target)) return [];
|
|
@@ -314,8 +417,21 @@ Autoload Section...
|
|
|
314
417
|
|
|
315
418
|
const loadingDCs = new Set<string>();
|
|
316
419
|
|
|
420
|
+
/**
|
|
421
|
+
Creates a closure that attaches DOM elements with the specified tag name to
|
|
422
|
+
their corresponding microcomponent instances.
|
|
423
|
+
|
|
424
|
+
Used internally by `makeLoadScript()` when dynamically loading external
|
|
425
|
+
custom element definitions. The returned function is invoked after the
|
|
426
|
+
element definition becomes available, and performs:
|
|
427
|
+
- Querying of all matching elements in the DOM
|
|
428
|
+
- Instantiation of new `DC` instances
|
|
429
|
+
- Attachment to each DOM node via `DC.add()`
|
|
430
|
+
|
|
431
|
+
@param {string} element - The tag name (custom element name) of the microcomponent to instantiate.
|
|
432
|
+
@returns {Function} A closure that performs the attachment logic when invoked.
|
|
433
|
+
*/
|
|
317
434
|
function attachDC(element: string): Function {
|
|
318
|
-
//@ts-ignore
|
|
319
435
|
return () => {
|
|
320
436
|
const elements: HTMLElement[] = Array.from(
|
|
321
437
|
document.querySelectorAll(`[data-mc*="${element}"]`)
|
|
@@ -342,7 +458,7 @@ function attachDC(element: string): Function {
|
|
|
342
458
|
}
|
|
343
459
|
}
|
|
344
460
|
|
|
345
|
-
//@ts-ignore
|
|
461
|
+
//@ts-ignore: override - node may be null
|
|
346
462
|
const new_mc = node
|
|
347
463
|
.getAttribute('data-mc')
|
|
348
464
|
.split(' ')
|
|
@@ -350,16 +466,21 @@ function attachDC(element: string): Function {
|
|
|
350
466
|
.join(' ')
|
|
351
467
|
.trim();
|
|
352
468
|
|
|
353
|
-
//@ts-ignore
|
|
354
|
-
node.setAttribute(
|
|
355
|
-
'data-mc',
|
|
356
|
-
//@ts-ignore
|
|
357
|
-
new_mc
|
|
358
|
-
);
|
|
469
|
+
//@ts-ignore: override - node may be null
|
|
470
|
+
node.setAttribute('data-mc', new_mc);
|
|
359
471
|
});
|
|
360
472
|
};
|
|
361
473
|
}
|
|
362
474
|
|
|
475
|
+
/**
|
|
476
|
+
Dynamically loads and registers a custom element definition from an external JS file.
|
|
477
|
+
|
|
478
|
+
Constructs the script URL based on kebab-case naming (e.g., `my-element` → `./my/element.js`).
|
|
479
|
+
Registers a handler to be executed once the element is defined, then appends the
|
|
480
|
+
`<script>` tag to `<head>`. Handles both successful loading and error cases.
|
|
481
|
+
|
|
482
|
+
@param {string} element - The custom element tag name (e.g., 'mc-button', 'my-widget').
|
|
483
|
+
*/
|
|
363
484
|
function makeLoadScript(element: string) {
|
|
364
485
|
if (loadingDCs.has(element)) return; // already loading
|
|
365
486
|
|
|
@@ -379,7 +500,7 @@ function makeLoadScript(element: string) {
|
|
|
379
500
|
|
|
380
501
|
const loadedPromise = customElements.whenDefined(element);
|
|
381
502
|
|
|
382
|
-
//@ts-ignore
|
|
503
|
+
//@ts-ignore: attachDC will return correct type
|
|
383
504
|
loadedPromise.then(attachDC(element));
|
|
384
505
|
|
|
385
506
|
se.addEventListener('load', (ev: Event) => {
|
|
@@ -395,6 +516,18 @@ function makeLoadScript(element: string) {
|
|
|
395
516
|
document.head.append(se);
|
|
396
517
|
}
|
|
397
518
|
|
|
519
|
+
/**
|
|
520
|
+
Scans the DOM for undefined custom elements and pending microcomponents,
|
|
521
|
+
then loads their definitions if necessary.
|
|
522
|
+
|
|
523
|
+
This is the main driver function for the autoload mechanism. It performs:
|
|
524
|
+
1. Finds all undefined custom elements in the document
|
|
525
|
+
2. Checks whether they are already loading or defined
|
|
526
|
+
3. Calls `makeLoadScript()` to fetch missing definitions
|
|
527
|
+
4. Invokes `attachDC()` where element definitions exist but haven't been attached yet
|
|
528
|
+
|
|
529
|
+
It is called both immediately after enabling autoload and on each DOM mutation.
|
|
530
|
+
*/
|
|
398
531
|
function loadUndefinedMCs() {
|
|
399
532
|
// load unresolved custom elements
|
|
400
533
|
[...document.querySelectorAll(':not(:defined)')]
|
|
@@ -429,17 +562,34 @@ function loadUndefinedMCs() {
|
|
|
429
562
|
});
|
|
430
563
|
}
|
|
431
564
|
|
|
432
|
-
|
|
433
|
-
|
|
565
|
+
/**
|
|
566
|
+
Enables or disables automatic detection and instantiation of microcomponents.
|
|
434
567
|
|
|
568
|
+
When enabled, the system uses a `MutationObserver` to watch for newly-added
|
|
569
|
+
DOM nodes. For each such node:
|
|
570
|
+
- If it contains custom elements that are not yet defined, attempts to load them
|
|
571
|
+
- If it references undefined microcomponent names in its `data-mc` attribute,
|
|
572
|
+
those definitions are also loaded and attached.
|
|
573
|
+
|
|
574
|
+
When disabled, the observer is disconnected and all internal tracking state is reset.
|
|
575
|
+
|
|
576
|
+
@param {boolean} [enable=true] - Whether to enable or disable auto-loading behavior.
|
|
577
|
+
@returns {boolean} The new autoload state (true if enabled, false otherwise).
|
|
578
|
+
*/
|
|
579
|
+
let observer: MutationObserver | null = null;
|
|
435
580
|
function autoload(enable: boolean = true): boolean {
|
|
436
581
|
if (!enable) {
|
|
437
|
-
observer
|
|
438
|
-
|
|
439
|
-
|
|
582
|
+
if (observer) {
|
|
583
|
+
observer.disconnect();
|
|
584
|
+
observer = null;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
440
587
|
}
|
|
441
588
|
|
|
442
|
-
|
|
589
|
+
const wasEnabled = !!observer;
|
|
590
|
+
if (wasEnabled) return true;
|
|
591
|
+
|
|
592
|
+
// Initialize observer only once
|
|
443
593
|
observer = new MutationObserver(loadUndefinedMCs);
|
|
444
594
|
|
|
445
595
|
setTimeout(() => {
|
|
@@ -449,9 +599,15 @@ function autoload(enable: boolean = true): boolean {
|
|
|
449
599
|
|
|
450
600
|
(window as LooseObject).autoload = (window as LooseObject).autoload || autoload;
|
|
451
601
|
|
|
452
|
-
return
|
|
602
|
+
return true;
|
|
453
603
|
}
|
|
454
604
|
|
|
605
|
+
/**
|
|
606
|
+
The internal `loadingDCs` set, exposed for external inspection.
|
|
607
|
+
|
|
608
|
+
This property is attached to the `autoload()` function itself and contains
|
|
609
|
+
the names of microcomponent classes currently being loaded (or queued to load).
|
|
610
|
+
*/
|
|
455
611
|
(autoload as LooseObject).state = loadingDCs;
|
|
456
612
|
|
|
457
|
-
export {
|
|
613
|
+
export { MC, DC, McBase, autoload };
|
package/dist/elements.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["elements.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAwBA,wBAAgB,QAAQ,CACvB,OAAO,EAAE,MAAM,EACf,eAAe,EAAE;IAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;CAAE,GAAG,QAAQ,EACzD,kBAAkB,GAAE,MAAM,EAAO,QA6EjC;AAOD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAE3E"}
|
package/dist/elements.js
CHANGED
package/dist/elements.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var t={d:(e,n)=>{for(var r in n)t.o(n,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};
|
|
1
|
+
var t={d:(e,n)=>{for(var r in n)t.o(n,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};function n(t){return"_mc"+t[0].toUpperCase()+t.substring(1)}function r(t,e,r,c=!1){return t.addEventListener(n(e),r,{capture:!1,once:c}),r}t.d(e,{c:()=>l,t:()=>u});class c{stopped=!1;constructor(){}stop(){this.stopped=!0}}const o=new Function("try {return this===window;}catch(e){ return false;}");function s(t){return t&&t instanceof HTMLElement}class a{_target;constructor(t){var e;this._target=new WeakRef(t||{}),function(t){if(!o()||t.constructor===i)return;const e=t.target;if(e instanceof HTMLElement){let n=t;Object.getOwnPropertyNames(n.constructor.prototype).filter((t=>/^(on[A-Z]|one[A-Z])/.test(t)&&"function"==typeof n.constructor.prototype[t])).forEach((t=>{let c=/^one/.test(t),o=t.replace(/^on[e]{0,1}/,""),s=o.toLowerCase();if("on"+s in e){let r=e=>{n[t](e)};e.addEventListener(s,r)}else r(e,o,(e=>{n[t](e)}),c)}))}}(this),e=this,setTimeout((()=>{e.constructor!==i&&e.trigger("Init")}),0)}get target(){return this._target.deref()}}class i extends a{constructor(t){super(t)}static add(t,e,n={}){return function(t,e,n={}){let r=t;r._mc||(r._mc=new i(t));const c=r._mc;return e.length&&!c[e]&&(c[e]=n,t instanceof HTMLElement&&!t.getAttribute("data-mc")&&t.setAttribute("data-mc","")),r}(t,e,n)}static remove(t,e){!function(t,e){if(!e)return;const n=t?._mc||{};n&&n[e]&&delete n[e],!Object.keys(n).length&&t instanceof HTMLElement&&t.hasAttribute("data-mc")&&t.removeAttribute("data-mc")}(t,e)}off(t,e){!function(t,e,r){t.removeEventListener(n(e),r)}(this.target,t,e)}on(t,e){return r(this.target,t,e)}one(t,e){return function(t,e,n){return r(t,e,n,!0),n}(this.target,t,e)}trigger(t,e={},r="l",o){!function(t,e,r={},o="l",s){const a=/^_mc/.test(e)?e:n(e),i=s||new c,l=new CustomEvent(a,{detail:{data:r,control:i},bubbles:!1});i.stopped||/l/.test(o)&&(t.dispatchEvent(l),l.stopImmediatePropagation(),i.stopped)||/[ud]/.test(o)&&(-1===o.indexOf("l")&&(o+="l"),setTimeout((()=>{if(o.indexOf("u")>-1){let e=t.parentElement?.closest("[data-mc]");e&&e?._mc.trigger(a,r,o,i)}if(o.indexOf("d")>-1){let e=t._mc.children();e&&e.forEach((t=>t.trigger(a,r,o)))}}),0))}(this.target,t,e,r,o)}ancestors(){let t=this?.target,e=[];if(!s(t))return[];for(;t=t.closest("[data-mc]");)t?._mc&&e.push(t?._mc);return e.length?e:null}closest(){let t=this.target;return s(t)&&t.closest("[data-mc]")?._mc||null}children(){let t=this?.target;return s(t)?function(t){const e=Math.random().toString().replace(".",""),n='[data-mc]:not([temp_id="'+e+'"] [data-mc] [data-mc])';t.setAttribute("temp_id",e);const r=[...t.querySelectorAll(n)].filter((t=>!!t._mc)).map((t=>t._mc));return t.removeAttribute("temp_id"),r.length?r:null}(t):null}descendants(){const t=this?.target;if(!s(t))return[];const e=[...t.querySelectorAll("[data-mc]")].filter((t=>!!t._mc)).map((t=>t._mc));return e.length?e:null}}function l(t,e,n=[]){if(customElements.get(t))return void console.warn(`IGNORED: Custom element "${t}" is already defined!`);if(!t.includes("-"))throw new Error('createCustomElement: tagName must contain at least one hyphen ("-").');let r=t.split("-").map((t=>t.charAt(0).toUpperCase()+t.slice(1))).join("");e.prototype instanceof a?customElements.define(t,function(t,e){return Object.defineProperty(e,"name",{value:t})}(r,class extends HTMLElement{constructor(t){super(),i.add(this,r,new e(this,t))}connectedCallback(){this.dispatchEvent(new CustomEvent("_mcConnected"))}connectedMoveCallback(){this.dispatchEvent(new CustomEvent("_mcConnectedMove"))}disconnectedCallback(){this.dispatchEvent(new CustomEvent("_mcDisconnected"))}adoptedCallback(){this.dispatchEvent(new CustomEvent("_mcAdopted"))}attributeChangedCallback(t,e,n){this.dispatchEvent(new CustomEvent("_mcAttributeChanged",{detail:{name:t,oldValue:e,newValue:n}}))}formResetCallback(){this.dispatchEvent(new CustomEvent("_mcFormReset"))}static get observedAttributes(){return n}})):customElements.define(t,e)}function u(t){return customElements.get(t)}new Set;var m=e.c,d=e.t;export{m as defineCE,d as getCE};
|
package/dist/elements.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
import { DC, McBase } from './MC.js';
|
|
4
1
|
import { kebabToPascal } from './helpers.js';
|
|
5
|
-
|
|
2
|
+
import { McBase, DC } from './MC.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a named class constructor with the specified name.
|
|
6
|
+
* @param name - The desired name for the class.
|
|
7
|
+
* @param ClassBody - The class constructor to which the name will be assigned.
|
|
8
|
+
* @returns The class constructor with the name property set.
|
|
9
|
+
*/
|
|
6
10
|
function createNamedClass(name: string, ClassBody: { new (...args: any[]): any }) {
|
|
7
11
|
return Object.defineProperty(ClassBody, 'name', { value: name });
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Defines a custom HTML element with optional observed attributes and micro component integration.
|
|
16
|
+
* The class provided in DefinitionClass will be added to the ._mc hash object appended to the custom element instance.
|
|
17
|
+
* Automatically assigns a "PascalCase" class name derived from the <kebab-case> tagName.
|
|
18
|
+
* If the provided definition class extends McBase (indicating a DC or MC class extension), it wraps the constructor to register
|
|
19
|
+
* the element instance with DC (DomComponent).
|
|
20
|
+
*
|
|
21
|
+
* @param tagName - The HTML tag name for the custom element (must contain at least one hyphen).
|
|
22
|
+
* @param DefinitionClass - The constructor function or class that defines the element's behavior.
|
|
23
|
+
* @param observedAttributes - An optional array of attribute names to observe for changes.
|
|
24
|
+
*/
|
|
10
25
|
export function defineCE(
|
|
11
26
|
tagName: string,
|
|
12
27
|
DefinitionClass: { new (...args: any[]): any } | Function,
|
|
@@ -30,7 +45,7 @@ export function defineCE(
|
|
|
30
45
|
//console.warn('pure class', tagName, DefinitionClass);
|
|
31
46
|
customElements.define(
|
|
32
47
|
tagName,
|
|
33
|
-
// @ts-ignore
|
|
48
|
+
// @ts-ignore: DefinitionClass exists
|
|
34
49
|
DefinitionClass
|
|
35
50
|
);
|
|
36
51
|
return;
|
|
@@ -46,7 +61,7 @@ export function defineCE(
|
|
|
46
61
|
constructor(data?: any) {
|
|
47
62
|
super();
|
|
48
63
|
// console.log('[named class constructor]', tagName, name, this, data);
|
|
49
|
-
// @ts-ignore
|
|
64
|
+
// @ts-ignore: DefinitionClass exists
|
|
50
65
|
DC.add(this, name, new DefinitionClass(this, data));
|
|
51
66
|
}
|
|
52
67
|
|
|
@@ -89,6 +104,11 @@ export function defineCE(
|
|
|
89
104
|
);
|
|
90
105
|
}
|
|
91
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Retrieves a custom element constructor by tag name.
|
|
109
|
+
* @param tagName - The HTML tag name to look up.
|
|
110
|
+
* @returns The CustomElementConstructor if defined, or undefined if not found.
|
|
111
|
+
*/
|
|
92
112
|
export function getCE(tagName: string): CustomElementConstructor | undefined {
|
|
93
113
|
return customElements.get(tagName);
|
|
94
114
|
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function getMcEventName(eventName: string): string;
|
|
2
|
+
export declare function off(domNode: Node, eventName: string, handler: EventListenerOrEventListenerObject): void;
|
|
3
|
+
export declare function on(domNode: Node, eventName: string, handler: EventListenerOrEventListenerObject, once?: boolean): EventListenerOrEventListenerObject;
|
|
4
|
+
export declare function one(domNode: Node, eventName: string, handler: EventListenerOrEventListenerObject): EventListenerOrEventListenerObject;
|
|
5
|
+
export declare class BubbleController {
|
|
6
|
+
stopped: boolean;
|
|
7
|
+
constructor();
|
|
8
|
+
stop(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare function trigger(domNode: HTMLElement, event: string, data?: any, bubble?: string, controller?: BubbleController): void;
|
|
11
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAaA,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAUD,wBAAgB,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kCAAkC,QAEhG;AAWD,wBAAgB,EAAE,CACjB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kCAAkC,EAC3C,IAAI,GAAE,OAAe,GACnB,kCAAkC,CAGpC;AAYD,wBAAgB,GAAG,CAClB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kCAAkC,GACzC,kCAAkC,CAGpC;AAUD,qBAAa,gBAAgB;IAC5B,OAAO,UAAS;;IAQhB,IAAI;CAGJ;AAmBD,wBAAgB,OAAO,CACtB,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,GAAQ,EACd,MAAM,GAAE,MAAY,EACpB,UAAU,CAAC,EAAE,gBAAgB,QA8C7B"}
|