@twobirds/microcomponents 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +45 -20
  2. package/dist/MC.min.js +1 -1
  3. package/dist/autoload.min.js +1 -1
  4. package/dist/core/MC.d.ts +288 -237
  5. package/dist/core/MC.d.ts.map +1 -1
  6. package/dist/core/MC.js +438 -182
  7. package/dist/core/elements.d.ts +58 -231
  8. package/dist/core/elements.d.ts.map +1 -1
  9. package/dist/core/elements.js +99 -214
  10. package/dist/core/index.d.ts +75 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core/index.js +72 -0
  13. package/dist/core/mcGuards.d.ts +7 -7
  14. package/dist/core/mcGuards.d.ts.map +1 -1
  15. package/dist/core/mcGuards.js +1 -1
  16. package/dist/elements.min.js +1 -1
  17. package/dist/formValues.min.js +1 -1
  18. package/dist/mcGuards.min.js +1 -1
  19. package/dist/microcomponents.min.js +1 -1
  20. package/dist/observables.min.js +1 -1
  21. package/dist/state/formValues.d.ts +3 -1
  22. package/dist/state/formValues.d.ts.map +1 -1
  23. package/dist/state/formValues.js +3 -1
  24. package/dist/state/index.d.ts +7 -0
  25. package/dist/state/index.d.ts.map +1 -0
  26. package/dist/state/index.js +5 -0
  27. package/dist/state/observables.d.ts +7 -59
  28. package/dist/state/observables.d.ts.map +1 -1
  29. package/dist/state/observables.js +63 -1
  30. package/dist/state/placeholders.d.ts +1 -0
  31. package/dist/state/placeholders.d.ts.map +1 -1
  32. package/dist/state/placeholders.js +1 -0
  33. package/dist/types/index.d.ts +7 -0
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/dist/types/index.js +1 -0
  36. package/dist/types/mcTypes.d.ts +3 -3
  37. package/dist/types/mcTypes.js +1 -1
  38. package/dist/utils/autoload.d.ts +6 -2
  39. package/dist/utils/autoload.d.ts.map +1 -1
  40. package/dist/utils/autoload.js +7 -5
  41. package/dist/utils/events.d.ts +22 -79
  42. package/dist/utils/events.d.ts.map +1 -1
  43. package/dist/utils/events.js +63 -1
  44. package/dist/utils/helpers.d.ts +3 -2
  45. package/dist/utils/helpers.d.ts.map +1 -1
  46. package/dist/utils/helpers.js +3 -2
  47. package/dist/utils/index.d.ts +17 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +12 -0
  50. package/package.json +9 -11
package/dist/core/MC.js CHANGED
@@ -1,164 +1,8 @@
1
- /**
2
- @module
3
- ## Overview
4
-
5
- MicroComponents (MC) is a lightweight pattern for creating self-contained, event-driven, and composable UI components. It provides two core base classes: {@link MC} and {@link DC}, which differ primarily in their target attachment strategy and runtime environment requirements.
6
-
7
- In general, your code extends from either of these classes depending on whether you need DOM interaction or pure logic encapsulation.
8
-
9
- ---
10
-
11
- ## MC vs DC: Server vs Client
12
-
13
- The key distinction between the two base classes is their execution context:
14
-
15
- - **MC (MicroComponent)** – A general-purpose component that works in **any JavaScript environment**, including server-side runtimes (Node.js, Deno, etc.). It does not require a DOM and can be used for business logic, state management, or as a lightweight container. The `target` parameter is optional.
16
-
17
- - **DC (DOM Component)** – A DOM-centric component that operates wherever a real ( browser ) or simulated ( jsdom, etc. ) DOM exists. It attaches itself to an {@link HTMLElement} and provides event handling, DOM traversal, and lifecycle management. The `target` parameter is required and must be an HTMLElement.
18
-
19
- **HINT:** DCs can be attached to **any** DOM node ( HTMLElement ). That is you can connect them to <div>, <span> and the like. With a trick you can even define custom elements that convert themselves to e.g. a plain <div>.
20
-
21
- ### When to use which?
22
-
23
- | Scenario | Recommended Class |
24
- |----------|-------------------|
25
- | Server-side rendering or logic | MC |
26
- | Pure data processing without DOM | MC |
27
- | Interactive UI elements in the browser | DC |
28
- | Event-driven components attached to HTML nodes | DC |
29
-
30
- ---
31
-
32
- ## The `target` Property: Backreference Concept
33
-
34
- Both MC and DC store a reference to their **host object** via the internal `_target` property (exposed as the `target` getter). This is a *backreference* — it points back to the object that contains or hosts this component instance.
35
-
36
- - For **DC instances**, `this.target` refers to the {@link HTMLElement} in the DOM (or testing DOM such as jsdom) to which the component is attached.
37
- - For **MC instances**, `this.target` can be any arbitrary object, allowing flexible composition patterns.
38
-
39
- The reference is held via a `WeakRef` ([MDN WeakRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef)) internally to avoid circular references and prevent memory leaks.
40
-
41
- ---
42
-
43
- ## DOM Integration: The `_mc` Property and `data-mc` Attribute
44
-
45
- When a DC is attached to an element via {@linkcode DC.add}, two things happen:
46
-
47
- 1. The element gains a `_mc` property (of type {@link McHost}) that holds references to all attached microcomponents, keyed by their unique identifiers.
48
- 2. The element receives a `data-mc` attribute, signaling that it hosts at least one microcomponent. This attribute is used internally for DOM queries and selectors.
49
-
50
- This `_mc` property is the **only** interference with the native DOM structure, ensuring compatibility with other frameworks or libraries.
51
-
52
- ---
53
-
54
- ## Lifecycle & Automatic Event Handler Attachment
55
-
56
- DC instances automatically trigger an `"Init"` lifecycle event asynchronously after construction. Additionally, methods prefixed with `on` or `one` (e.g., `onClick`, `oneUpdate`) are auto-attached as event listeners on the target element, supporting both native HTML events and custom microcomponent events.
57
-
58
- ---
59
-
60
-
61
- @example Understanding target in DC
62
-
63
- javascript:
64
- ```javascript
65
- import { DC } from '@twobirds/microcomponents';
66
-
67
- class MyButton extends DC {
68
- constructor(target: HTMLElement) {
69
- super(target);
70
- }
71
-
72
- logTarget() {
73
- // this.target is the <button> element itself
74
- console.log(this.target.tagName); // "BUTTON"
75
- }
76
- }
77
-
78
- const btn = document.querySelector('button');
79
- if (btn) {
80
- const host = DC.add(btn, 'myButton', new MyButton(btn));
81
- host._mc.myButton.logTarget();
82
- }
83
- ```
84
-
85
- @example Understanding target in MC
86
-
87
-
88
- ```javascript
89
- import { MC } from '@twobirds/microcomponents';
90
-
91
- class Counter extends MC {
92
- private count = 0;
93
-
94
- constructor(target?: object) {
95
- super(target);
96
- }
97
-
98
- increment() {
99
- this.count++;
100
- }
101
-
102
- getCount() {
103
- return this.count;
104
- }
105
- }
106
-
107
- // Works without any DOM — usable on server or client
108
- const counter = new Counter();
109
- counter.increment();
110
- console.log(counter.getCount()); // 1
111
- ```
112
-
113
- @example Usage
114
-
115
- Below is a complete example demonstrating how to create, attach, and interact with a custom microcomponent:
116
-
117
- html before:
118
- ```html
119
- <div id="container"></div>
120
- ```
121
-
122
- javascript:
123
- ```javascript
124
- import { DC } from '@twobirds/microcomponents';
125
-
126
- // 1. Create a custom micro component class extending DC
127
- class HelloWorld extends DC {
128
- constructor(target: HTMLElement) {
129
- super(target);
130
- }
131
-
132
- greet() {
133
- console.log('Hello World');
134
- }
135
- }
136
-
137
- // 2. Get an element from the DOM
138
- const element = document.getElementById('container')!;
139
-
140
- // 3. Attach the micro component instance to the target element
141
- DC.add(element, 'helloWorld', new HelloWorld(element));
142
-
143
- // 4. Invoke methods on the attached component
144
- element._mc.helloWorld.greet(); // "Hello World"
145
- ```
146
-
147
- html after:
148
- ```html
149
- <div id="container" data-mc></div>
150
- ```
151
-
152
- The `data-mc` attribute indicates that this element contains at least one microcomponent. The `_mc` property provides direct programmatic access to the attached instances.
153
-
154
- ---
155
-
156
- */
157
1
  'use strict';
158
2
  import { loadAssets, processAssets } from '../utils/helpers.js';
159
3
  import { trigger, on, one, off } from '../utils/events.js';
160
- import { isMcDOM } from './mcGuards.js';
161
- export { isMcDOM } from './mcGuards.js';
4
+ import { isMcHost } from './mcGuards.js';
5
+ export { isMcHost } from './mcGuards.js';
162
6
  function add(targetHTMLElement, key, element = {}) {
163
7
  const node = targetHTMLElement;
164
8
  // create the _mc property if necessary ('in' is tested on the unwidened
@@ -287,11 +131,63 @@ function findMcAncestor(from, key) {
287
131
  return null;
288
132
  }
289
133
  /**
290
- The base class for all microcomponents.
134
+ The base class for all microcomponents — shared root of both {@link MC} and {@link DC}.
135
+
136
+ Holds a `WeakRef` to the target (a DOM element for `DC`, or any plain object for `MC`)
137
+ to avoid circular references. For `DC` subclasses it also bootstraps two automatic
138
+ behaviours:
139
+
140
+ 1. **Event listener auto-attachment** — methods whose names start with `on` or `one`
141
+ (e.g. `onClick`, `oneInit`) are wired to the target element via {@link on} / {@link one}.
142
+ 2. **"Init" lifecycle event** — dispatched asynchronously in a microtask so that all
143
+ synchronous constructor work finishes first.
144
+
145
+ Both mechanisms run only when the instance is a `DC` (checked with `instanceof DC`).
146
+ Plain `MC` instances skip them entirely, keeping server-side usage lightweight.
291
147
 
292
- Implements a `WeakRef` to the target DOM element or other data, avoiding
293
- circular references. Automatically triggers the "Init" lifecycle event and
294
- attaches listeners via `autoAttachListeners` upon construction.
148
+ ### Plugin extension points
149
+
150
+ The prototype of `McBase` is the attachment point for optional plugins that curry
151
+ additional instance methods onto every microcomponent. Two built-in plugins modify
152
+ this class at runtime:
153
+
154
+ - **{@link State.observePlugin}** — adds `notify()`, `subscribe()`, and `bind()` so that
155
+ observable properties can be controlled directly on the component instance with
156
+ fluent chaining. Call `observePlugin(DC)` once at module level to activate.
157
+
158
+ - **{@link State.formValuePlugin}** — adds `formValues()` for two-way binding between DOM
159
+ form controls and an observable property. Also pulls in all methods from
160
+ {@link State.observePlugin}, so you can chain `.subscribe()` and `.bind()` after
161
+ `.formValues()`. Call `formValuePlugin(DC)` once at module level to activate.
162
+
163
+ Both plugins are idempotent — calling them multiple times has no side effects. They
164
+ work by assigning methods onto `McBase.prototype` (reached via
165
+ `Object.getPrototypeOf(Class.prototype)`) and use TypeScript module augmentation
166
+ (`declare module`) so the added signatures appear in type-checking automatically.
167
+
168
+ @template TTarget - The concrete type of the wrapped target; preserved by the
169
+ {@link McBase.target} getter.
170
+
171
+ @example Extending with plugins at runtime
172
+
173
+ ```ts
174
+ import { DC, observePlugin, formValuePlugin } from '@twobirds/microcomponents';
175
+
176
+ observePlugin(DC); // adds notify(), subscribe(), bind() to McBase.prototype
177
+ formValuePlugin(DC); // adds formValues() (and re-runs observePlugin)
178
+
179
+ class MyForm extends DC {
180
+ data = { name: '' };
181
+
182
+ oneInit() { // runs once after the constructor
183
+ this
184
+ .formValues(this.target, 'data')
185
+ .subscribe('data', (v) => console.log(v))
186
+ .bind('data', this.target);
187
+ }
188
+ }
189
+ ```
190
+ @group Micro Components
295
191
  */
296
192
  class McBase {
297
193
  _target; // avoid circular reference
@@ -339,6 +235,7 @@ for logic not directly tied to DOM elements.
339
235
 
340
236
  @template TTarget - The concrete type of the wrapped target data; preserved by
341
237
  the `target` getter, so `new MC({ some: 'data' }).target.some` stays typed.
238
+ @group Micro Components
342
239
  */
343
240
  class MC extends McBase {
344
241
  /**
@@ -351,19 +248,224 @@ class MC extends McBase {
351
248
  }
352
249
  }
353
250
  /**
251
+ @class DC
252
+ ---
253
+
354
254
  A DOM-centric microcomponent that operates on a specific HTML element.
355
255
 
356
- Extends `McBase` and associates itself with an actual DOM node via the
256
+ Extends {@link McBase} and associates itself with an actual DOM node via the
357
257
  `_target` reference. Provides methods for event handling, DOM traversal,
358
258
  and interaction with sibling or ancestor microcomponents.
359
259
 
260
+ ---
261
+
262
+ ## Static Configuration Properties
263
+
264
+ DC classes support three static properties that configure custom elements
265
+ before any instance is constructed:
266
+
267
+ ### `static tag?: string`
268
+
269
+ Replaces the default target element with a semantic HTML element, or removes it entirely.
270
+
271
+ - **Non-empty string** (e.g., `'header'`) — the original custom element is replaced
272
+ with the specified built-in element. The original tag name is added as a CSS class.
273
+ - **Empty string `''`** — the target element is removed from the DOM entirely.
274
+ Useful for components that only inject global styles or act as style containers.
275
+
276
+ @example static tag — replace with semantic element
277
+
278
+ ```ts
279
+ class ShopHeader extends DC {
280
+ static tag = 'header';
281
+ }
282
+ defineCE('shop-header', ShopHeader);
283
+ ```
284
+
285
+ Produces `<header class="shop-header">` instead of `<shop-header>`.
286
+
287
+ @example static tag — remove element entirely
288
+
289
+ ```ts
290
+ class GlobalStyles extends DC {
291
+ static tag = '';
292
+ }
293
+ defineCE('global-styles', GlobalStyles);
294
+ ```
295
+
296
+ The element self-destructs; only side effects (e.g., injected styles) remain.
297
+
298
+ ### `static assets: string[]`
299
+
300
+ Declares companion files for auto-loading by convention. Asset names are camelCase
301
+ and translated into file extensions: `'targetHtml'` → `.target.html`, `'json'` → `.json`.
302
+
303
+ The path is derived from the custom element tag segments, and the basename is the
304
+ last segment. For `<cart-container-checkout>`, assets load from `/cart/container/checkout.*`.
305
+
306
+ When assets finish loading, an `'assets'` event fires so that `oneAssets(ev)` can
307
+ process them. Everything ending in `Json` arrives parsed; all other files arrive as text.
308
+
309
+ @example static assets with autoload
310
+
311
+ ```ts
312
+ class CartCheckout extends DC {
313
+ static assets = ['targetHtml', 'targetCss', 'configJson'];
314
+
315
+ oneAssets(ev) {
316
+ const { configJson } = ev.detail.data;
317
+ console.log(configJson); // parsed JSON object
318
+ }
319
+ }
320
+ defineCE('cart-checkout', CartCheckout);
321
+ ```
322
+
323
+ ### `static attributes?: object`
324
+
325
+ Sets HTML attributes on every instance automatically.
326
+
327
+ @example static attributes
328
+
329
+ ```ts
330
+ class ProductCard extends DC {
331
+ static attributes = { role: 'listitem' };
332
+ }
333
+ defineCE('product-card', ProductCard);
334
+ ```
335
+
336
+ Every `<product-card>` carries `role="listitem"`.
337
+
338
+ ### Combined usage
339
+
340
+ All three can be combined in one class:
341
+
342
+ @example All static properties together
343
+
344
+ ```ts
345
+ class ShopCatalog extends DC {
346
+ static tag = 'section';
347
+ static attributes = { role: 'list', 'aria-label': 'products' };
348
+ static assets = ['targetHtml', 'targetCss'];
349
+ }
350
+ defineCE('shop-catalog', ShopCatalog);
351
+ ```
352
+
353
+ Produces `<section role="list" aria-label="products">`.
354
+
355
+ ---
356
+
357
+ ## Event Handler Conventions
358
+
359
+ Methods whose names start with `on` or `one` are automatically wired as event listeners
360
+ on the target element. The prefix determines persistence:
361
+
362
+ - **`on<EventName>`** — persistent listener (fires every time)
363
+ - **`one<EventName>`** — one-time listener (fires once, then detaches)
364
+
365
+ ### Web Component Lifecycle Events
366
+
367
+ When a DC is registered via {@link defineCE}, the custom element dispatches these
368
+ internal events that can be caught with handler methods:
369
+
370
+ | Method | When it fires |
371
+ |--------|---------------|
372
+ | `onConnected()` | Element was inserted into the DOM (`connectedCallback`) |
373
+ | `onDisconnected()` | Element was removed from the DOM (`disconnectedCallback`) |
374
+ | `onAdopted()` | Element was moved to a new document (`adoptedCallback`) |
375
+ | `onAttributeChanged()` | An observed attribute changed (`attributeChangedCallback`) |
376
+ | `oneInit()` | Constructor finished; all synchronous setup complete (microtask) |
377
+
378
+ @example Event handlers in action
379
+
380
+ ```ts
381
+ class MyElement extends DC {
382
+ onConnected() {
383
+ console.log('entered the DOM');
384
+ }
385
+
386
+ onDisconnected() {
387
+ console.log('left the DOM');
388
+ }
389
+
390
+ onClick() {
391
+ this.trigger('something', 'payload');
392
+ }
393
+
394
+ oneSomething(ev) {
395
+ console.log('first time only:', ev.detail.data);
396
+ }
397
+ }
398
+ defineCE('my-element', MyElement);
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Construction Patterns
404
+
405
+ ### Template usage (declarative)
406
+
407
+ ```ts
408
+ class MyElement extends DC {}
409
+ defineCE('my-element', MyElement);
410
+ // Use in HTML: <my-element></my-element>
411
+ ```
412
+
413
+ ### Programmatic construction with data
414
+
415
+ When constructing programmatically via {@link getCE}, pass only the `data` payload —
416
+ the target element is created automatically by the custom element constructor.
417
+
418
+ @example Programmatic construction
419
+
420
+ ```ts
421
+ class MyElement extends DC {
422
+ data = {};
423
+
424
+ constructor(target, data) {
425
+ super(target);
426
+ this.data = data;
427
+ }
428
+ }
429
+ defineCE('my-element', MyElement);
430
+
431
+ const Ctor = getCE('my-element');
432
+ document.body.append(new Ctor({ name: 'John' }));
433
+ ```
434
+
435
+ ### Data from HTML attributes
436
+
437
+ @example Reading data from an attribute
438
+
439
+ ```ts
440
+ class MyElement extends DC {
441
+ data = {};
442
+
443
+ constructor(target) {
444
+ super(target);
445
+ this.data = JSON.parse(target.getAttribute('my-data'));
446
+ }
447
+ }
448
+ defineCE('my-element', MyElement);
449
+ // Use in HTML: <my-element my-data='{"name":"John"}'></my-element>
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Plugin Extension Points
455
+
456
+ The prototype of `DC` (inherited from {@link McBase}) is extended at runtime by optional
457
+ plugins. See the {@link McBase} documentation for details on {@link State.observePlugin} and
458
+ {@link State.formValuePlugin}.
459
+
360
460
  @template T - The concrete element type of the host element.
361
461
  @template EM - The component's custom event map (`event name → payload type`).
362
462
  Declaring it registers events on the type level: `trigger()` enforces the
363
463
  payload for registered names, and `on()`/`one()` handlers receive
364
464
  `CustomEvent<McDetail<Payload>>` with no annotation needed. Unregistered
365
465
  names keep the open `unknown`-payload behavior.
366
- @example
466
+
467
+ @example Typed event map
468
+
367
469
  ```ts
368
470
  class Cart extends DC<HTMLFormElement, { CartUpdate: { count: number } }> {
369
471
  checkout() {
@@ -372,14 +474,47 @@ class Cart extends DC<HTMLFormElement, { CartUpdate: { count: number } }> {
372
474
  }
373
475
  }
374
476
  ```
477
+ @group Micro Components
375
478
  */
376
479
  class DC extends McBase {
480
+ /**
481
+ * Replaces the target element with a semantic HTML tag, or removes it entirely.
482
+ * Set to `'header'` to swap `<my-element>` for `<header>`, or `''` to self-destruct.
483
+ */
377
484
  static tag;
485
+ /**
486
+ * Result container for assets (e.g., `['targetHtml', 'json']`).
487
+ * Asset names (keys) are camelCase and translated into file extensions: `'targetHtml'` → `.target.html`.
488
+ */
489
+ static _assets = {};
490
+ /**
491
+ * Declares companion files for auto-loading by convention (e.g., `['targetHtml', 'json']`).
492
+ * Asset names are camelCase and translated into file extensions: `'targetHtml'` → `.target.html`.
493
+ */
378
494
  static assets = [];
495
+ /**
496
+ * Sets HTML attributes on every instance automatically.
497
+ * Example: `{ role: 'listitem', 'aria-label': 'nav' }`.
498
+ */
379
499
  static attributes = {};
500
+ /**
501
+ * Constructs a new DC instance and applies static configuration.
502
+ *
503
+ * The constructor performs these steps in order:
504
+ * 1. Calls {@link McBase} to establish the target reference and auto-attach listeners.
505
+ * 2. If `static tag` is set and differs from the current element, replaces the target
506
+ * with a new built-in element (or removes it if `tag === ''`).
507
+ * 3. If `static assets` has entries, initiates asynchronous loading, fires an `'assets'` event when complete handled by `oneAssets(ev)`.
508
+ * 4. Applies any `static attributes` to the target element.
509
+ *
510
+ * @param target - The host DOM element this component operates on.
511
+ */
380
512
  constructor(target) {
381
513
  super(target);
382
514
  const ctor = this.constructor;
515
+ // do not run on '_mc' property
516
+ if (ctor === DC)
517
+ return;
383
518
  const tagName = this.target.tagName.toLowerCase();
384
519
  // work on tag:
385
520
  if (ctor['tag'] !== undefined && tagName !== ctor.tag && ctor.tag !== '') {
@@ -394,32 +529,75 @@ class DC extends McBase {
394
529
  }
395
530
  return;
396
531
  }
397
- // work on assets:
398
- if (ctor.assets.length) {
399
- // - initiate loading...
400
- loadAssets(tagName, ctor.assets)
401
- // - returns a promise, .then( ()=>{ this.trigger('render') } )
532
+ // work on attributes
533
+ const attributes = ctor.attributes;
534
+ if (ctor.attributes) {
535
+ Object.keys(ctor.attributes).forEach((key) => {
536
+ this.target.setAttribute(key, attributes[key]);
537
+ });
538
+ }
539
+ /*
540
+ work on assets
541
+ */
542
+ // if predefined assets exist get list of not pre-defined ones
543
+ const remainingAssets = ctor.assets.filter((key) => !ctor._assets[key]);
544
+ // oxlint-disable-next-line typescript/no-this-alias
545
+ const that = this;
546
+ // process existing assets
547
+ if (ctor.hasOwnProperty('_assets')) {
548
+ processAssets(this, ctor._assets);
549
+ }
550
+ else if (ctor.assets.length && !ctor.hasOwnProperty('_assets')) {
551
+ ctor._assets = {};
552
+ }
553
+ // autoload and process remaining assets
554
+ if (remainingAssets.length) {
555
+ loadAssets(tagName, remainingAssets)
402
556
  .then((res) => {
403
- const assets = Object.fromEntries(ctor.assets.map((key, i) => [key, res[i]]));
404
- processAssets(this, assets);
405
- this.trigger('assets', assets);
557
+ const loadedAssets = {};
558
+ remainingAssets.forEach((key, i) => {
559
+ loadedAssets[key] = res[i];
560
+ });
561
+ processAssets(this, loadedAssets);
562
+ ctor._assets = Object.assign({}, ctor._assets, loadedAssets);
563
+ that.trigger('assets', ctor._assets);
406
564
  });
407
- // - in the component, have a method onRender(){ ... }
408
- // - no explicit async needed in component code
409
565
  }
566
+ else if ([...Object.keys(ctor._assets)].length) {
567
+ queueMicrotask(() => that.trigger('assets', ctor._assets));
568
+ }
569
+ // remove empty tag
410
570
  if (ctor.tag === '') {
411
571
  this.target.remove();
412
572
  return;
413
573
  }
414
574
  ;
415
- // work on attributes
416
- const attributes = ctor.attributes;
417
- if (ctor.attributes) {
418
- Object.keys(ctor.attributes).forEach((key) => {
419
- this.target.setAttribute(key, attributes[key]);
420
- });
421
- }
422
575
  }
576
+ /**
577
+ Returns the loaded assets for this component instance.
578
+
579
+ Assets are loaded asynchronously when `static assets` is defined on the class.
580
+ This method returns the resolved asset map keyed by filename, or `undefined`
581
+ if assets have not yet been loaded.
582
+
583
+ @returns A record mapping asset keys to their content strings, or undefined.
584
+ */
585
+ getAssets() {
586
+ return this.constructor._assets || {};
587
+ }
588
+ /**
589
+ Static helper to attach a microcomponent instance to a DOM element.
590
+
591
+ Wrapper around the `add()` utility function, providing a class-level API
592
+ for adding child microcomponents to a target node.
593
+
594
+ @template T - The concrete element type of the target; preserved in the return type.
595
+ @template K - The literal registration key; becomes a known entry of the returned `_mc` registry.
596
+ @template E - The type of the attached element; retrievable as `result._mc[key]`.
597
+ @param target - The DOM element to which to attach the microcomponent.
598
+ @param key - The property name/key under which to store the instance in `_mc`.
599
+ @returns The target DOM element with the attached microcomponent.
600
+ */
423
601
  static add(target, key, element = {}) {
424
602
  return add(target, key, element);
425
603
  }
@@ -460,17 +638,50 @@ class DC extends McBase {
460
638
  ```
461
639
  */
462
640
  static get(element, key, elementMcClass) {
463
- if (!isMcDOM(element))
641
+ if (!isMcHost(element))
464
642
  return undefined;
465
643
  const entry = element._mc[key];
466
644
  return entry instanceof elementMcClass ? entry : undefined;
467
645
  }
646
+ /**
647
+ Detaches an event listener from the DOM node.
648
+
649
+ This is an instance method wrapper for `off()`, using the internal `_target` DOM reference.
650
+
651
+ @param eventName - The name of the event (e.g., 'click', 'update').
652
+ @param handler - The previously attached callback function or object to remove.
653
+ */
468
654
  off(eventName, handler) {
469
655
  off(this.target, eventName, handler);
470
656
  }
657
+ /**
658
+ Attaches a persistent event listener to the DOM node.
659
+
660
+ This is an instance method wrapper for `on()`, using the internal `_target` DOM reference.
661
+
662
+ The handler receives a `CustomEvent<McDetail<Payload>>`. For event names
663
+ registered in the component's event map `EM` the payload is resolved from the
664
+ map — an unannotated arrow handler is fully typed. For unregistered names the
665
+ payload defaults to `unknown` and can be pinned via the type argument
666
+ (`on<Payload>('Name', handler)`) or the handler's own annotation.
667
+
668
+ @param eventName - The name of the event (e.g., 'click', 'update').
669
+ @param handler - The callback function or object to invoke.
670
+ @returns The attached handler reference, unchanged — usable directly with {@link off}.
671
+ */
471
672
  on(eventName, handler) {
472
673
  return on(this.target, eventName, handler);
473
674
  }
675
+ /**
676
+ Attaches a one-time event listener to the DOM node.
677
+
678
+ This is an instance method wrapper for `one()`, using the internal `_target` DOM reference.
679
+ Payload typing follows the same rules as {@link DC.on}.
680
+
681
+ @param eventName - The name of the event (e.g., 'click', 'update').
682
+ @param handler - The callback function or object to invoke once.
683
+ @returns The attached one-time handler reference, unchanged — usable directly with {@link off}.
684
+ */
474
685
  one(eventName, handler) {
475
686
  return one(this.target, eventName, handler);
476
687
  }
@@ -491,6 +702,18 @@ class DC extends McBase {
491
702
  trigger(eventName, data = {}, bubble = 'l', controller) {
492
703
  trigger(this.target, eventName, data, bubble, controller);
493
704
  }
705
+ /**
706
+ Returns an array of all ancestor microcomponents up the DOM tree.
707
+
708
+ Traverses upward from this element's target, collecting the `_mc` instances
709
+ of ancestor elements marked with `data-mc`. The keyed form only collects
710
+ hosts whose registry carries `key`, typed from the global {@link McRegistry}
711
+ binding — non-optional, since presence is checked during the walk.
712
+
713
+ @template R - Known registry entries of the results; pass a record to assert
714
+ a site-specific shape: `ancestors<{ cart: Cart }>()`. Defaults to none.
715
+ @returns An array of ancestor microcomponent instances (empty if none found).
716
+ */
494
717
  ancestors(key) {
495
718
  const result = [];
496
719
  let current = findMcAncestor(this.target, key);
@@ -500,13 +723,46 @@ class DC extends McBase {
500
723
  }
501
724
  return result;
502
725
  }
726
+ /**
727
+ Returns the closest ancestor microcomponent or null if none found.
728
+
729
+ This method is equivalent to calling `ancestors()[0]`, but more concise.
730
+ The keyed form walks up to the nearest host whose registry carries `key`,
731
+ typed from the global {@link McRegistry} binding — non-optional, since
732
+ presence is checked during the walk.
733
+
734
+ @template R - Known registry entries of the result; pass a record to assert
735
+ a site-specific shape: `closest<{ cart: Cart }>()?.cart.addItem(...)`.
736
+ Defaults to none.
737
+ @returns The nearest matching ancestor's `_mc`, or null if not found.
738
+ */
503
739
  closest(key) {
504
740
  return findMcAncestor(this.target, key);
505
741
  }
742
+ /**
743
+ Returns an array of direct child microcomponents.
744
+
745
+ Calls `getMcChildNodes()` internally to find immediate descendants only,
746
+ excluding nested grandchildren and deeper levels. The keyed form filters to
747
+ hosts whose registry carries `key`; see {@link DC.closest}.
748
+
749
+ @template R - Known registry entries of the results; see {@link DC.closest}.
750
+ @returns An array of child microcomponent instances (empty if none found).
751
+ */
506
752
  children(key) {
507
753
  const kids = getMcChildNodes(this.target);
508
754
  return key === undefined ? kids : kids.filter((mc) => key in mc);
509
755
  }
756
+ /**
757
+ Returns an array of all descendant microcomponents recursively.
758
+
759
+ Uses a `querySelectorAll` query to collect every nested element marked with
760
+ `data-mc`, returning their associated `_mc` instances. The keyed form
761
+ filters to hosts whose registry carries `key`; see {@link DC.closest}.
762
+
763
+ @template R - Known registry entries of the results; see {@link DC.closest}.
764
+ @returns An array of descendant microcomponent instances (empty if none found).
765
+ */
510
766
  descendants(key) {
511
767
  const hosts = Array.from(this.target.querySelectorAll('[data-mc]'));
512
768
  const mcArray = hosts