@twobirds/microcomponents 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -20
- package/dist/cli/create.js +1 -1
- package/dist/cli/dispatch.d.ts.map +1 -1
- package/dist/cli/dispatch.js +52 -16
- package/dist/cli/writeScaffold.d.ts +35 -6
- package/dist/cli/writeScaffold.d.ts.map +1 -1
- package/dist/cli/writeScaffold.js +75 -21
- package/dist/mcp/api.json +601 -0
- package/dist/mcp/concepts/custom-element-registry.md +92 -0
- package/dist/mcp/concepts/event-system.md +45 -0
- package/dist/mcp/concepts/implementation-priority.md +10 -0
- package/dist/mcp/concepts/mc-types.md +349 -0
- package/dist/mcp/concepts/microcomponent-architecture.md +31 -0
- package/dist/mcp/concepts/state-management.md +18 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +264 -0
- package/package.json +17 -9
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
All functionality in Micro Components is registered in the browser's custom element registry, regardless of its purpose. This includes:
|
|
2
|
+
|
|
3
|
+
- Real web components that render visible DOM elements
|
|
4
|
+
- Logic-only components that execute code in constructor() and oneInit(), then self-remove
|
|
5
|
+
- CSS-only components that attach stylesheets to the document and remove themselves
|
|
6
|
+
|
|
7
|
+
When a component should not render anything visually, it declares `static tag = '';`. This tells defineCE to register the element but never produce visible markup. The component still runs its lifecycle callbacks (constructor → connectedCallback → oneInit), allowing it to execute setup logic or inject global styles before optionally removing itself from the DOM.
|
|
8
|
+
|
|
9
|
+
This unified registration model means every piece of functionality — visual, logical, or stylistic — follows the same pattern: extend DC, define behavior, register with defineCE().
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Tag Name to File Path Mapping
|
|
14
|
+
|
|
15
|
+
Because `autoload()` resolves an undefined tag to a script URL by converting kebab-case to path segments (`<shop-header>` → `./shop/header.js`), the tag name _is_ the file path. The kebab-case segments become the directory path and the last segment becomes the filename. The directory structure in `dist/` must mirror this segmentation for autoloading to work.
|
|
16
|
+
|
|
17
|
+
| Tag | Source File | Companion Files |
|
|
18
|
+
| --------------------------- | -------------------------------- | --------------------------------------------------------------------------- |
|
|
19
|
+
| `<shop-header>` | `src/shop/header.ts` | `shop/header.target.html`, `shop/header.target.css` |
|
|
20
|
+
| `<shop-sidebar>` | `src/shop/sidebar.ts` | `shop/sidebar.target.html`, `shop/sidebar.target.css` |
|
|
21
|
+
| `<cart-container-articles>` | `src/cart/container/articles.ts` | `cart/container/articles.shadow.html`, `cart/container/articles.shadow.css` |
|
|
22
|
+
|
|
23
|
+
This is the same contract that makes one-class-per-file mandatory (see [microcomponent-architecture](microcomponent-architecture.md)): one tag → one script → one class.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Frontend Nesting in Folder Structure
|
|
28
|
+
|
|
29
|
+
When a component is visually and structurally nested inside another (a composed parent projecting child web components — see [mc-types](mc-types.md) §1c), the folder hierarchy reflects that nesting. A child's tag name includes its parent's prefix, and its file resides in a subdirectory of the parent. This applies recursively to grandchildren.
|
|
30
|
+
|
|
31
|
+
| Parent Tag | Child Tag | Source File |
|
|
32
|
+
| ------------------ | --------------------------- | -------------------------------- |
|
|
33
|
+
| `<app-header>` | `<app-header-left>` | `src/app/header/left.ts` |
|
|
34
|
+
| `<app-header>` | `<app-header-right>` | `src/app/header/right.ts` |
|
|
35
|
+
| `<cart-container>` | `<cart-container-articles>` | `src/cart/container/articles.ts` |
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
src/
|
|
39
|
+
app/
|
|
40
|
+
header.ts ← <app-header>
|
|
41
|
+
header.css
|
|
42
|
+
left.ts ← <app-header-left> (child of header)
|
|
43
|
+
left.css
|
|
44
|
+
right.ts ← <app-header-right> (child of header)
|
|
45
|
+
right.css
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The DOM hierarchy is therefore immediately visible in the file system.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## General-Purpose Components in Side Folders
|
|
53
|
+
|
|
54
|
+
Components serving cross-cutting concerns — used across unrelated parts of the frontend rather than belonging to one visual hierarchy — reside in dedicated side folders organized by purpose. These are not nested under any UI component but exist at a parallel level. They follow the same tag-to-file mapping and are imported directly by any component that needs them, regardless of where that component lives in the UI hierarchy.
|
|
55
|
+
|
|
56
|
+
| Purpose | Folder | Example Tags |
|
|
57
|
+
| -------------------- | ------------- | --------------------------------------------- |
|
|
58
|
+
| Server communication | `transport/` | `<transport-request>`, `<transport-response>` |
|
|
59
|
+
| Authentication | `auth/` | `<auth-guard>`, `<auth-token>` |
|
|
60
|
+
| State management | `store/` | `<store-cart>`, `<store-user>` |
|
|
61
|
+
| Validation | `validation/` | `<validation-email>`, `<validation-required>` |
|
|
62
|
+
| Utilities | `helpers/` | `<helper-formatter>`, `<helper-locale>` |
|
|
63
|
+
|
|
64
|
+
### Diversification Within General-Purpose Folders
|
|
65
|
+
|
|
66
|
+
General-purpose folders can contain subdirectories for more specific variations within a category, flowing from broad purpose to narrow specialization. Subfolders represent functional specializations, not visual nesting.
|
|
67
|
+
|
|
68
|
+
| Purpose | Folder Structure | Example Tags |
|
|
69
|
+
| ------------------------ | ------------------ | ------------------------------------------------------- |
|
|
70
|
+
| Transport → HTTP methods | `transport/http/` | `<transport-http-get>`, `<transport-http-post>` |
|
|
71
|
+
| Transport → WebSocket | `transport/ws/` | `<transport-ws-client>`, `<transport-ws-handler>` |
|
|
72
|
+
| Validation → Form fields | `validation/form/` | `<validation-form-email>`, `<validation-form-password>` |
|
|
73
|
+
| Store → Persistence | `store/persist/` | `<store-persist-local>`, `<store-persist-session>` |
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
src/
|
|
77
|
+
transport/
|
|
78
|
+
request.ts ← <transport-request> (general)
|
|
79
|
+
response.ts ← <transport-response> (general)
|
|
80
|
+
http/ ← HTTP-specific diversification
|
|
81
|
+
get.ts ← <transport-http-get>
|
|
82
|
+
post.ts ← <transport-http-post>
|
|
83
|
+
ws/ ← WebSocket-specific diversification
|
|
84
|
+
client.ts ← <transport-ws-client>
|
|
85
|
+
validation/
|
|
86
|
+
email.ts ← <validation-email> (general)
|
|
87
|
+
form/ ← Form-specific diversification
|
|
88
|
+
email.ts ← <validation-form-email>
|
|
89
|
+
password.ts ← <validation-form-password>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The same tag-to-file mapping applies: `<transport-http-get>` maps to `src/transport/http/get.ts`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Events in Micro Components use CustomEvent with directional bubbling:
|
|
2
|
+
|
|
3
|
+
Handler conventions (auto-attached):
|
|
4
|
+
|
|
5
|
+
- on<EventName>() - persistent listener (fires every time)
|
|
6
|
+
- one<EventName>() - one-time listener (fires once, then detaches)
|
|
7
|
+
|
|
8
|
+
Lifecycle events when registered via defineCE:
|
|
9
|
+
|
|
10
|
+
- oneInit() - constructor finished; all synchronous setup complete
|
|
11
|
+
- onConnected() - element inserted into DOM
|
|
12
|
+
- onDisconnected() - element removed from DOM
|
|
13
|
+
- onAdopted() - element moved to new document
|
|
14
|
+
- onAttributeChanged() - observed attribute changed
|
|
15
|
+
|
|
16
|
+
Bubble directions in trigger():
|
|
17
|
+
|
|
18
|
+
- 'l' = local dispatch only (default)
|
|
19
|
+
- 'u' = bubble up towards ancestors
|
|
20
|
+
- 'd' = bubble down to descendants
|
|
21
|
+
- Can combine: 'lu', 'ld', 'lud'
|
|
22
|
+
|
|
23
|
+
Only use bubbling when there is a specific reason for it. Usually directly addressing the target is better, because of the speed advantage using querySelectorAll().
|
|
24
|
+
|
|
25
|
+
## Payload & propagation
|
|
26
|
+
|
|
27
|
+
MC-triggered custom events and lifecycle events are `McEvent<D>` — a `CustomEvent` whose `detail` is `{ data, control }`. The payload is always read from `ev.detail.data`, never `ev.data`.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
cart.on('CartUpdate', (ev: McEvent<{ count: number }>) => {
|
|
31
|
+
const count = ev.detail.data.count; // payload lives under detail.data
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To stop a bubbling event, call `stop()` on the bubble controller carried in the same detail object:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
cart.on('CartUpdate', (ev: McEvent<{ count: number }>) => {
|
|
39
|
+
ev.detail.control.stop(); // halt 'u'/'d' propagation
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
When the event name is part of the component's event map (`EM` on `DC`), the payload type is inferred and the `McEvent<…>` annotation can be omitted; annotate it explicitly only for ad-hoc events.
|
|
44
|
+
|
|
45
|
+
Native DOM events (`click`, `input`, `submit`, …) are **not** `McEvent` — they carry their natural payload types (`MouseEvent`, `InputEvent`, …) and have no `detail.data` / `detail.control`. Only events dispatched through `trigger()` (and the lifecycle events emitted by `defineCE`) follow the `McEvent` shape above.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
When implementing functionality, follow this strict priority chain. Each tier must be exhausted before descending to the next:
|
|
2
|
+
|
|
3
|
+
**Tier 1 — MC Framework Automatic Functionality (mandatory when inside the framework)**
|
|
4
|
+
Always use built-in DC/MC conventions first. This includes auto-attaching event handlers (`onClick()`, `oneInit()`, etc.), lifecycle callbacks, static properties (`tag`, `assets`, `attributes`), and plugin methods (`.subscribe()` `.bind()` `.formValues()`). These mechanisms are the framework's intended way of wiring behavior — never bypass them with manual alternatives when working inside a DC or MC class.
|
|
5
|
+
|
|
6
|
+
**Tier 2 — Exported Library Functions (when outside the framework)**
|
|
7
|
+
When using Micro Components alongside another framework, in plain JavaScript, or in non-DC/MC contexts, use the library's exported utility functions: `on()`, `one()`, `trigger()`, `observe()`, `formValues()`, `autoload()`, etc. These are the official public API for accessing MC functionality without extending DC or MC.
|
|
8
|
+
|
|
9
|
+
**Tier 3 — Pure TypeScript / Vanilla JavaScript (last resort only)**
|
|
10
|
+
Only fall back to native browser APIs (`addEventListener`, `querySelector`, manual DOM manipulation, raw `CustomEvent`, etc.) when neither Tier 1 nor Tier 2 provides a solution. This is the least preferred approach and should be avoided whenever any MC mechanism can accomplish the task.
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Micro Component Types
|
|
2
|
+
|
|
3
|
+
Every piece of functionality in Micro Components is registered in the custom element
|
|
4
|
+
registry — visual, logical, or stylistic. What differs is the **base class**, the
|
|
5
|
+
**registration pattern**, and **what (if anything) is rendered**.
|
|
6
|
+
|
|
7
|
+
This document defines each type, its purpose, and when to reach for it.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Class Hierarchy
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
McBase shared root — WeakRef target, auto-listener attachment, Init lifecycle
|
|
15
|
+
├── MC general-purpose, no DOM requirement
|
|
16
|
+
└── DC DOM-centric, operates on an HTMLElement
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`McBase` is never extended directly. Choose `MC` for DOM-free logic and `DC` for
|
|
20
|
+
anything that touches an element.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
| Type | Base | Registry | Instantiation | Renders | Typical Use |
|
|
27
|
+
| -------------------- | ----------------- | ---------- | --------------------------------------------- | -------- | ------------------------ |
|
|
28
|
+
| Visual Component | `DC` | `defineCE` | `<tag-name>` in markup, or `new Ctor(data)` | Yes | UI elements |
|
|
29
|
+
| Logic-Only Component | `DC` (`tag = ''`) | `defineCE` | `getCE()` → `DC.add(host, key, new Ctor())` | No | Router, auth, stores |
|
|
30
|
+
| Style-Only Component | `DC` (`tag = ''`) | `defineCE` | `<tag-name>` in markup, or side-effect import | CSS only | Global stylesheets |
|
|
31
|
+
| Pure Logic Component | `MC` | — | `new Mc(data)` — direct import | No | Data processing, helpers |
|
|
32
|
+
| Plugin | function | — | `plugin(DC)` — called, never instantiated | No | Prototype extensions |
|
|
33
|
+
| Utility Function | function | — | called directly | No | Standalone API |
|
|
34
|
+
|
|
35
|
+
Registered `DC` types have three instantiation paths:
|
|
36
|
+
|
|
37
|
+
- **Declarative** — the tag appears in markup and `autoload` loads and constructs it. No
|
|
38
|
+
import statement is needed.
|
|
39
|
+
- **Programmatic** — `getCE('tag-name')` returns the constructor and `new Ctor(data)`
|
|
40
|
+
creates the element. Only the `data` payload is passed; the host element is created by
|
|
41
|
+
the custom element constructor and arrives as the first argument of the `DC`
|
|
42
|
+
constructor.
|
|
43
|
+
- **Attached** — a logic-only component is constructed with `new Ctor()` and bound to an
|
|
44
|
+
existing host via `DC.add(host, key, instance)`. It never appears in markup itself.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 1. Visual Components
|
|
49
|
+
|
|
50
|
+
**Base:** `DC` · **Registry:** `defineCE` · **Renders:** visible DOM
|
|
51
|
+
|
|
52
|
+
The default type. Any element the user can see and interact with is a `DC`. It owns a
|
|
53
|
+
host element, receives lifecycle events, and wires its own event handlers by naming
|
|
54
|
+
convention.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
class ShopHeader extends DC {
|
|
58
|
+
static tag = 'header';
|
|
59
|
+
static attributes = { role: 'banner' };
|
|
60
|
+
static assets = ['targetHtml', 'targetCss'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
defineCE('shop-header', ShopHeader);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Visual components split into three structural sub-patterns.
|
|
67
|
+
|
|
68
|
+
### 1a. Target-Based (flat content)
|
|
69
|
+
|
|
70
|
+
Markup is injected into `this.target.innerHTML`. Styles are scoped by tag or class and
|
|
71
|
+
adopted into the **document** via `injectCSS(document, ...)`.
|
|
72
|
+
|
|
73
|
+
Use when the internal structure is simple and flat — a card, a cart line, a label.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
src/shop/product/card.ts
|
|
77
|
+
src/shop/product/card.target.html
|
|
78
|
+
src/shop/product/card.target.css
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 1b. Shadow-DOM Based (complex structure)
|
|
82
|
+
|
|
83
|
+
Markup is injected into `shadowRoot.innerHTML`. Internal styles are adopted into the
|
|
84
|
+
**shadow root** via `injectCSS(shadowRoot, ...)`.
|
|
85
|
+
|
|
86
|
+
Use when the component has several distinct regions or needs style isolation.
|
|
87
|
+
|
|
88
|
+
- _Simple shadow_ — one unnamed slot; light-DOM content slots in automatically.
|
|
89
|
+
- _Complex shadow_ — multiple named slots; light-DOM content uses `slot="…"`.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
src/cart/container/articles.ts
|
|
93
|
+
src/cart/container/articles.shadow.html
|
|
94
|
+
src/cart/container/articles.shadow.css
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 1c. Composed Components (parents)
|
|
98
|
+
|
|
99
|
+
A shadow-DOM component that projects **child web components** from the light DOM through
|
|
100
|
+
named `<slot name="…">` elements. Child elements are never embedded in the shadow
|
|
101
|
+
template — projection keeps them in the light DOM so `autoload` can discover and
|
|
102
|
+
register them.
|
|
103
|
+
|
|
104
|
+
External design of the composed unit is driven by namespaced CSS custom properties
|
|
105
|
+
(`--mcui-btn-*`, `--mcui-card-*`), with defaults declared in the companion `.css` file.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<cart-container>
|
|
109
|
+
<cart-container-articles slot="articles"></cart-container-articles>
|
|
110
|
+
<cart-container-checkout slot="checkout"></cart-container-checkout>
|
|
111
|
+
</cart-container>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 1d. Leaf Components
|
|
115
|
+
|
|
116
|
+
Self-contained visual units that project no child custom elements — a button, a badge,
|
|
117
|
+
a single cart line. They are the smallest building block and are usually styled through
|
|
118
|
+
the namespaced variables of their composed parent.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Companion Files
|
|
123
|
+
|
|
124
|
+
Each custom element (`.ts`/`.js`) ships with optional companion files sharing the same
|
|
125
|
+
base name. The suffix encodes the structural sub-pattern (§1a–1b) and the styling target.
|
|
126
|
+
|
|
127
|
+
| Suffix | Purpose | Styling Target |
|
|
128
|
+
| -------------- | --------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
129
|
+
| `.css` | CSS custom properties (variable defaults) only — no structural styles | N/A (token definitions) |
|
|
130
|
+
| `.target.css` | Structural style rules for target DOM (`innerHTML`) | Global document scope via `injectCSS(document, ...)` |
|
|
131
|
+
| `.shadow.css` | Structural style rules for shadow DOM | Shadow root via `injectCSS(shadowRoot, ...)` |
|
|
132
|
+
| `.target.html` | HTML template injected into `this.target.innerHTML` | N/A (markup) |
|
|
133
|
+
| `.shadow.html` | HTML template injected into `shadowRoot.innerHTML` | N/A (markup) |
|
|
134
|
+
| `.json` | Static data files imported by the component | N/A (data) |
|
|
135
|
+
|
|
136
|
+
Companion files follow the same directory structure as their source file. A component at
|
|
137
|
+
`src/shop/sidebar.ts` has companions at `src/shop/sidebar.target.html`,
|
|
138
|
+
`src/shop/sidebar.target.css`, etc. Templates, styles, and data are loaded via
|
|
139
|
+
`loadAssets()` / `processAssets()`; keep the `.ts` source focused on logic only —
|
|
140
|
+
constructor, lifecycle, event handlers, state.
|
|
141
|
+
|
|
142
|
+
### Inline Templates — Escape Hatch
|
|
143
|
+
|
|
144
|
+
A simple `const template` string constant is allowed **only** when the component has no
|
|
145
|
+
advanced handling: no reactive state (`observe`/`bind`/`subscribe`), no dynamic DOM
|
|
146
|
+
mutations after render, and no separate stylesheet to ship. This is useful for one-time
|
|
147
|
+
placeholder replacement during programmatic creation:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const template = '<h1>{title}</h1><button click="this|onGo">Go</button>';
|
|
151
|
+
// ...
|
|
152
|
+
processAssets(this, { targetHtml: parse(template, data) });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`processAssets` still runs `bindTemplateEvents`, so declarative `click='this|onHandler'`
|
|
156
|
+
bindings work for inline strings too. As soon as the component needs reactive data, a
|
|
157
|
+
stylesheet, or autoload-discoverable assets, move the markup into a `.target.html` /
|
|
158
|
+
`.shadow.html` companion file.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Theming via CSS Custom Properties
|
|
163
|
+
|
|
164
|
+
Components **never hard-code colors**; they read tokens. Four themes — `custom-dark` /
|
|
165
|
+
`custom-light` / `dark` / `light` — are pure token sets on `:root` (switched by a `theme`
|
|
166
|
+
attribute), implemented as style-only components (§3). The `custom-*` themes merge into
|
|
167
|
+
the standard settings, preserving `dark` and `light` defaults.
|
|
168
|
+
|
|
169
|
+
External design of a composed unit is driven by namespaced CSS custom properties
|
|
170
|
+
(`--mcui-btn-*`, `--mcui-card-*`), with defaults declared in the companion `.css` file.
|
|
171
|
+
Variable names always reflect the element namespace to avoid collisions — a
|
|
172
|
+
`shop-header` component defines `--shop-header-bg`, `--shop-header-color`, etc. in
|
|
173
|
+
`shop-header.css` and consumes them in `shop-header.target.css` / `shop-header.shadow.css`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 2. Logic-Only Components
|
|
178
|
+
|
|
179
|
+
**Base:** `DC` with `static tag = ''` · **Registry:** `defineCE` · **Renders:** nothing
|
|
180
|
+
|
|
181
|
+
Functionality that must be discoverable through the registry and attachable to other
|
|
182
|
+
elements, but produces no markup. `static tag = ''` tells `defineCE` to register the
|
|
183
|
+
element while the constructor removes the host from the DOM. The full lifecycle still
|
|
184
|
+
runs — `constructor` → `connectedCallback` → `oneInit` — so setup logic executes
|
|
185
|
+
normally.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
class Router extends DC {
|
|
189
|
+
static tag = '';
|
|
190
|
+
|
|
191
|
+
constructor(target: HTMLElement) {
|
|
192
|
+
super(target);
|
|
193
|
+
// pure setup, no markup
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
defineCE('app-router', Router);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Retrieved from the registry and attached to a host with `DC.add()`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const Router = getCE('app-router')!;
|
|
204
|
+
DC.add(target, 'router', new Router());
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Use for: routing, history management, auth guards, state stores, validation engines,
|
|
208
|
+
translation services — anything that should live in the central repository and be
|
|
209
|
+
composable onto existing elements rather than inherited through a class chain.
|
|
210
|
+
|
|
211
|
+
These components are pulled in through **barrel imports** so they register by
|
|
212
|
+
side-effect without appearing in the DOM.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 3. Style-Only Components
|
|
217
|
+
|
|
218
|
+
**Base:** `DC` with `static tag = ''` · **Registry:** `defineCE` · **Renders:** CSS only
|
|
219
|
+
|
|
220
|
+
Structurally identical to a logic-only component, but its side effect is a stylesheet.
|
|
221
|
+
It injects global rules and then self-removes; the styles persist regardless of the
|
|
222
|
+
element's removal.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
class PageCockpit extends DC {
|
|
226
|
+
static tag = '';
|
|
227
|
+
static assets = ['css'];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
defineCE('page-cockpit', PageCockpit);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Consumption options: place the tag in a template, side-effect import the `.css.ts`
|
|
234
|
+
module, `@import` the raw `.css` into a bundle, or import it with `?raw` and apply it
|
|
235
|
+
manually.
|
|
236
|
+
|
|
237
|
+
Use for: global `body`/`:root` token sets, theme definitions, reset stylesheets.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 4. Pure Logic Components
|
|
242
|
+
|
|
243
|
+
**Base:** `MC` · **Registry:** none · **Renders:** nothing
|
|
244
|
+
|
|
245
|
+
`MC` is the DOM-free branch of the hierarchy. It skips listener auto-attachment and the
|
|
246
|
+
`Init` lifecycle entirely, which keeps it lightweight for server-side or non-DOM use.
|
|
247
|
+
|
|
248
|
+
It is **not** registered with `defineCE`. It is imported directly and attached as a
|
|
249
|
+
property on the component that needs it:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import { PriceCalculator } from '../shop/helpers.js';
|
|
253
|
+
|
|
254
|
+
class CartSummary extends DC {
|
|
255
|
+
constructor(target: HTMLElement) {
|
|
256
|
+
super(target);
|
|
257
|
+
this.calculator = new PriceCalculator();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Use for: reusable process-flow helpers, calculations, parsers, formatters — logic that
|
|
263
|
+
is genuinely independent of the DOM. If the logic needs registry discoverability or
|
|
264
|
+
lifecycle events, use a logic-only `DC` instead.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 5. Plugins
|
|
269
|
+
|
|
270
|
+
**Base:** none (functions) · **Registry:** none · **Renders:** nothing
|
|
271
|
+
|
|
272
|
+
Plugins are functions that curry additional instance methods onto `McBase.prototype`,
|
|
273
|
+
making them available on every `DC` and `MC` instance. They are called once at module
|
|
274
|
+
level and are idempotent.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
observePlugin(DC); // adds notify(), subscribe(), bind()
|
|
278
|
+
formValuePlugin(DC); // adds formValues() — also pulls in observePlugin
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Use for: cross-cutting behavior that every component should be able to chain — state
|
|
282
|
+
observation, two-way form binding. A plugin is the right shape when the capability is
|
|
283
|
+
orthogonal to what the component _is_ rather than part of its identity.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 6. Utility Functions
|
|
288
|
+
|
|
289
|
+
**Base:** none (plain exports) · **Registry:** none · **Renders:** nothing
|
|
290
|
+
|
|
291
|
+
The public API for using Micro Components **outside** a `DC`/`MC` class — in plain
|
|
292
|
+
JavaScript, alongside another framework, or in a non-component context.
|
|
293
|
+
|
|
294
|
+
| Group | Functions |
|
|
295
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
296
|
+
| Events | `on`, `one`, `off`, `trigger` |
|
|
297
|
+
| State | `observe`, `formValues` |
|
|
298
|
+
| Assets & styles | `loadAssets`, `processAssets`, `injectCSS`, `htmlToElements` |
|
|
299
|
+
| Templates | `parse`, `bindTemplateEvents` |
|
|
300
|
+
| Registry | `defineCE`, `getCE`, `listCE` |
|
|
301
|
+
| Loading | `autoload` |
|
|
302
|
+
| Helpers | `debounce`, `deepEqual`, `flattenObject`, `sortObject`, `nameSpace`, `kebabToPascal`, `isNativeObject`, `isBrowser` |
|
|
303
|
+
|
|
304
|
+
Inside a `DC`, prefer the instance equivalents (`this.on()`, `this.trigger()`,
|
|
305
|
+
`this.subscribe()`) — see [implementation-priority](implementation-priority.md).
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Not an MC: Plain Custom Elements
|
|
310
|
+
|
|
311
|
+
`defineCE` also accepts a class that does **not** extend `McBase`. In that case the class
|
|
312
|
+
itself becomes the element and no `_mc` registry is attached. This is an escape hatch for
|
|
313
|
+
interop with existing custom elements — it is not a Micro Component and gains none of the
|
|
314
|
+
framework's conventions.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Decision Flow
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
Does it need to be discoverable via getCE() / attachable via DC.add()?
|
|
322
|
+
│
|
|
323
|
+
├── No → Is it DOM-free logic?
|
|
324
|
+
│ ├── Yes → MC (pure logic)
|
|
325
|
+
│ └── No → Is it a cross-cutting capability for all components?
|
|
326
|
+
│ ├── Yes → Plugin
|
|
327
|
+
│ └── No → Utility function
|
|
328
|
+
│
|
|
329
|
+
└── Yes → Does it render visible DOM?
|
|
330
|
+
├── Yes → Visual DC
|
|
331
|
+
│ ├── Flat content → target-based
|
|
332
|
+
│ ├── Regions / isolation → shadow-based
|
|
333
|
+
│ ├── Projects children → composed (named slots)
|
|
334
|
+
│ └── No children → leaf
|
|
335
|
+
│
|
|
336
|
+
└── No → static tag = ''
|
|
337
|
+
├── Injects styles → style-only DC
|
|
338
|
+
└── Runs logic → logic-only DC
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Related
|
|
344
|
+
|
|
345
|
+
- [microcomponent-architecture](microcomponent-architecture.md) — composition model, `DC.add`
|
|
346
|
+
- [custom-element-registry](custom-element-registry.md) — unified registration
|
|
347
|
+
- [event-system](event-system.md) — handler conventions and lifecycle events
|
|
348
|
+
- [state-management](state-management.md) — observables and form binding
|
|
349
|
+
- [implementation-priority](implementation-priority.md) — which API tier to use
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Micro Components are function blocks attached to DOM nodes. They use composition over inheritance and work like building blocks:
|
|
2
|
+
|
|
3
|
+
Key principles:
|
|
4
|
+
|
|
5
|
+
- Every visible element is a micro component extending DC
|
|
6
|
+
- Pure logic (not DOM) extends MC
|
|
7
|
+
- Register custom elements with defineCE()
|
|
8
|
+
- Attach additional DOM logic with DC.add()
|
|
9
|
+
- Minimal DOM interference: only _mc property on host elements
|
|
10
|
+
- Works alongside any frontend framework
|
|
11
|
+
- One class per file — see below
|
|
12
|
+
|
|
13
|
+
## One Class Per File
|
|
14
|
+
|
|
15
|
+
Every `.ts` or `.js` file contains exactly **one class**, regardless of which type it is:
|
|
16
|
+
visual, logic-only, style-only, or pure logic. This applies to `DC` subclasses, `MC`
|
|
17
|
+
subclasses, and any other class defined in `src/`.
|
|
18
|
+
|
|
19
|
+
Why:
|
|
20
|
+
|
|
21
|
+
- **Autoload resolution** — `autoload()` maps a tag name to a single script path
|
|
22
|
+
(`<shop-header>` → `./shop/header.js`). One class per file keeps that mapping
|
|
23
|
+
unambiguous.
|
|
24
|
+
- **Companion files** — templates, styles, and data share the class's basename
|
|
25
|
+
(`header.target.html`, `header.css`). A second class in the same file would have no
|
|
26
|
+
place to put its own assets.
|
|
27
|
+
- **Registry clarity** — `defineCE()` is called once per file, so file, class, and
|
|
28
|
+
registered tag stay in a 1:1 relationship.
|
|
29
|
+
|
|
30
|
+
Non-class entities that belong to the class — interfaces, types, constants, plain
|
|
31
|
+
functions — may live alongside it. A class that is reusable on its own gets its own file.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
State management uses observable objects with reactive binding. Two usage patterns exist:
|
|
2
|
+
|
|
3
|
+
**Standalone functions** (outside MC ecosystem):
|
|
4
|
+
|
|
5
|
+
- observe(obj) — wraps properties in Observable wrappers; you manage subscribe/bind/notify manually
|
|
6
|
+
- formValues(obj, prop, dom) — binds form controls to an observable property on the object
|
|
7
|
+
|
|
8
|
+
**Plugins** (inside DC custom elements):
|
|
9
|
+
|
|
10
|
+
- observePlugin(DC) — adds .notify(), .subscribe(), .bind() instance methods that implicitly create observables on demand
|
|
11
|
+
- formValuePlugin(DC) — adds .formValues() instance method; also pulls in all observePlugin methods
|
|
12
|
+
|
|
13
|
+
Key behaviors:
|
|
14
|
+
|
|
15
|
+
- subscribe(callback) registers change listeners
|
|
16
|
+
- bind(target) connects objects to DOM placeholders {propertyName} — only objects can be bound (primitives are ignored)
|
|
17
|
+
- Deep change detection on nested objects with debounced notifications via microtask queue
|
|
18
|
+
- The plugin methods take the property _name_ rather than the value, and are typed as `keyof this & string`: an undeclared or typo'd property is a compile error, and the `subscribe` callback parameter is inferred from the declared class field (`this.subscribe('count', (v) => v.toFixed(1))` — `v` is `number` without an annotation)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":""}
|