@twobirds/microcomponents 0.2.1 → 0.2.3
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 +45 -20
- package/dist/MC.min.js +1 -1
- package/dist/autoload.min.js +1 -1
- package/dist/core/MC.d.ts +289 -238
- package/dist/core/MC.d.ts.map +1 -1
- package/dist/core/MC.js +445 -184
- package/dist/core/elements.d.ts +58 -231
- package/dist/core/elements.d.ts.map +1 -1
- package/dist/core/elements.js +99 -214
- package/dist/core/index.d.ts +75 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +72 -0
- package/dist/core/mcGuards.d.ts +7 -7
- package/dist/core/mcGuards.d.ts.map +1 -1
- package/dist/core/mcGuards.js +1 -1
- package/dist/elements.min.js +1 -1
- package/dist/formValues.min.js +1 -1
- package/dist/mcGuards.min.js +1 -1
- package/dist/microcomponents.min.js +1 -1
- package/dist/observables.min.js +1 -1
- package/dist/state/formValues.d.ts +113 -66
- package/dist/state/formValues.d.ts.map +1 -1
- package/dist/state/formValues.js +134 -68
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +5 -0
- package/dist/state/observables.d.ts +66 -133
- package/dist/state/observables.d.ts.map +1 -1
- package/dist/state/observables.js +185 -83
- package/dist/state/placeholders.d.ts +1 -0
- package/dist/state/placeholders.d.ts.map +1 -1
- package/dist/state/placeholders.js +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcTypes.d.ts +3 -3
- package/dist/types/mcTypes.js +1 -1
- package/dist/utils/autoload.d.ts +6 -2
- package/dist/utils/autoload.d.ts.map +1 -1
- package/dist/utils/autoload.js +7 -5
- package/dist/utils/events.d.ts +22 -79
- package/dist/utils/events.d.ts.map +1 -1
- package/dist/utils/events.js +63 -1
- package/dist/utils/helpers.d.ts +10 -9
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +26 -14
- package/dist/utils/index.d.ts +17 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +12 -0
- package/package.json +13 -15
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 {
|
|
161
|
-
export {
|
|
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
|
|
@@ -223,7 +67,7 @@ signature encodes that invariant instead of re-checking it at runtime.
|
|
|
223
67
|
*/
|
|
224
68
|
function init(element) {
|
|
225
69
|
queueMicrotask(() => {
|
|
226
|
-
element.trigger('Init');
|
|
70
|
+
element.trigger('Init', null);
|
|
227
71
|
});
|
|
228
72
|
}
|
|
229
73
|
/**
|
|
@@ -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.
|
|
147
|
+
|
|
148
|
+
### Plugin extension points
|
|
291
149
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
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
|
-
|
|
466
|
+
|
|
467
|
+
@example Typed event map
|
|
468
|
+
|
|
367
469
|
```ts
|
|
368
470
|
class Cart extends DC<HTMLFormElement, { CartUpdate: { count: number } }> {
|
|
369
471
|
checkout() {
|
|
@@ -372,49 +474,130 @@ 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 !== '') {
|
|
386
521
|
const element = document.createElement(ctor.tag);
|
|
387
522
|
element.classList.add(tagName);
|
|
388
523
|
DC.add(element, ctor.name, new ctor(element));
|
|
389
|
-
this.target.
|
|
524
|
+
if (tagName.includes('-') && !this.target.isConnected) {
|
|
525
|
+
this.on('connected', () => this.target.replaceWith(element));
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
this.target.replaceWith(element);
|
|
529
|
+
}
|
|
390
530
|
return;
|
|
391
531
|
}
|
|
392
|
-
// work on
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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)
|
|
397
556
|
.then((res) => {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
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);
|
|
401
564
|
});
|
|
402
|
-
// - in the component, have a method onRender(){ ... }
|
|
403
|
-
// - no explicit async needed in component code
|
|
404
565
|
}
|
|
566
|
+
else if ([...Object.keys(ctor._assets)].length) {
|
|
567
|
+
queueMicrotask(() => that.trigger('assets', ctor._assets));
|
|
568
|
+
}
|
|
569
|
+
// remove empty tag
|
|
405
570
|
if (ctor.tag === '') {
|
|
406
571
|
this.target.remove();
|
|
407
572
|
return;
|
|
408
573
|
}
|
|
409
574
|
;
|
|
410
|
-
// work on attributes
|
|
411
|
-
const attributes = ctor.attributes;
|
|
412
|
-
if (ctor.attributes) {
|
|
413
|
-
Object.keys(ctor.attributes).forEach((key) => {
|
|
414
|
-
this.target.setAttribute(key, attributes[key]);
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
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
|
+
*/
|
|
418
601
|
static add(target, key, element = {}) {
|
|
419
602
|
return add(target, key, element);
|
|
420
603
|
}
|
|
@@ -455,17 +638,50 @@ class DC extends McBase {
|
|
|
455
638
|
```
|
|
456
639
|
*/
|
|
457
640
|
static get(element, key, elementMcClass) {
|
|
458
|
-
if (!
|
|
641
|
+
if (!isMcHost(element))
|
|
459
642
|
return undefined;
|
|
460
643
|
const entry = element._mc[key];
|
|
461
644
|
return entry instanceof elementMcClass ? entry : undefined;
|
|
462
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
|
+
*/
|
|
463
654
|
off(eventName, handler) {
|
|
464
655
|
off(this.target, eventName, handler);
|
|
465
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
|
+
*/
|
|
466
672
|
on(eventName, handler) {
|
|
467
673
|
return on(this.target, eventName, handler);
|
|
468
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
|
+
*/
|
|
469
685
|
one(eventName, handler) {
|
|
470
686
|
return one(this.target, eventName, handler);
|
|
471
687
|
}
|
|
@@ -486,6 +702,18 @@ class DC extends McBase {
|
|
|
486
702
|
trigger(eventName, data = {}, bubble = 'l', controller) {
|
|
487
703
|
trigger(this.target, eventName, data, bubble, controller);
|
|
488
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
|
+
*/
|
|
489
717
|
ancestors(key) {
|
|
490
718
|
const result = [];
|
|
491
719
|
let current = findMcAncestor(this.target, key);
|
|
@@ -495,13 +723,46 @@ class DC extends McBase {
|
|
|
495
723
|
}
|
|
496
724
|
return result;
|
|
497
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
|
+
*/
|
|
498
739
|
closest(key) {
|
|
499
740
|
return findMcAncestor(this.target, key);
|
|
500
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
|
+
*/
|
|
501
752
|
children(key) {
|
|
502
753
|
const kids = getMcChildNodes(this.target);
|
|
503
754
|
return key === undefined ? kids : kids.filter((mc) => key in mc);
|
|
504
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
|
+
*/
|
|
505
766
|
descendants(key) {
|
|
506
767
|
const hosts = Array.from(this.target.querySelectorAll('[data-mc]'));
|
|
507
768
|
const mcArray = hosts
|