@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.
@@ -0,0 +1,601 @@
1
+ {
2
+ "libraryVersion": "0.5.0",
3
+ "exportedCount": 41,
4
+ "entries": {
5
+ "MC": {
6
+ "name": "MC",
7
+ "kind": "class",
8
+ "file": "src/core/MC.ts",
9
+ "signatures": [
10
+ "class MC<TTarget extends object = object> extends McBase<TTarget> {"
11
+ ],
12
+ "description": "A general-purpose base microcomponent that does not require a DOM target.\n\nInherits from `McBase`, but is primarily used as a lightweight container or\nfor logic not directly tied to DOM elements."
13
+ },
14
+ "DC": {
15
+ "name": "DC",
16
+ "kind": "class",
17
+ "file": "src/core/MC.ts",
18
+ "signatures": [
19
+ "class DC< T extends HTMLElement = HTMLElement, EM extends Record<string, unknown> = Record<never, never>, > extends McBase<T> implements McController<T> {"
20
+ ],
21
+ "description": "A DOM-centric microcomponent that operates on a specific HTML element.\n\nExtends McBase and associates itself with an actual DOM node via the\n`_target` reference. Provides methods for event handling, DOM traversal,\nand interaction with sibling or ancestor microcomponents.\n\n\n## Static Configuration Properties\n\nDC classes support three static properties that configure custom elements\nbefore any instance is constructed:\n\n### `static tag?: string`\n\nReplaces the default target element with a semantic HTML element, or removes it entirely.\n\n- **Non-empty string** (e.g., `'header'`) — the original custom element is replaced\nwith the specified built-in element. The original tag name is added as a CSS class.\n- **Empty string `''`** — the target element is removed from the DOM entirely.\nUseful for components that only inject global styles or act as style containers.\n\nProduces `<header class=\"shop-header\">` instead of `<shop-header>`.\n\nThe element self-destructs; only side effects (e.g., injected styles) remain.\n\n### `static assets: string[]`\n\nDeclares companion files for auto-loading by convention. Asset names are camelCase\nand translated into file extensions: `'targetHtml'` → `.target.html`, `'json'` → `.json`.\n\nThe path is derived from the custom element tag segments, and the basename is the\nlast segment. For `<cart-container-checkout>`, assets load from `/cart/container/checkout.*`.\n\nWhen assets finish loading, an `'assets'` event fires so that `oneAssets(ev)` can\nprocess them. Everything ending in `Json` arrives parsed; all other files arrive as text.\n\n*One-time global styles**: Assets named `'css'` and `'targetCss'` inject CSS into the\nglobal document scope via `injectCSS(document, ...)`. Because these are idempotent\nglobal side effects, they are processed only once — after the first instance loads them,\nthe entries are removed from both `static assets` and `static _assets`. Subsequent\ninstances skip loading and re-processing them entirely.\n\n*CSS file roles**:\n\n- `.css` — design tokens only (CSS custom-property presets). Injected into the document\nscope once, like `targetCss`.\n- `.target.css` — layout rules for the light DOM. Also document-scoped and one-time.\n- `.shadow.css` — layout rules for the shadow DOM. Injected per instance into the\ncomponent's own shadow root, so each instance gets its own copy.\n\nEvery declared asset — CSS, HTML, JSON — is inlined into `static _assets` at build time by\nthe load-assets plugin, so a bundled build performs no fetches at all.\n\nIn a bundled build the plain `.css` files are additionally concatenated into a single\n`bundle.css` by the bundling script. That file is a convenience listing of the available\nvariables: copy the ones you want to change into your own stylesheet and override them\nthere. It is not required at runtime, and it cannot drift from the inlined presets — both\nare read from the same source files.\n\nThe runtime decides whether an asset still needs fetching with `!ctor._assets[key]`, so an\ninlined entry must never be an empty string: that would trigger a re-fetch of a file that\ndoes not exist in a bundled build.\n\n`configJson` arrives already parsed into an object.\n\n### `static attributes?: object`\n\nSets HTML attributes on every instance automatically.\n\nEvery `<product-card>` carries `role=\"listitem\"`.\n\n### Combined usage\n\nAll three can be combined in one class:\n\nProduces `<section role=\"list\" aria-label=\"products\">`.\n\n\n## Event Handler Conventions\n\nMethods whose names start with `on` or `one` are automatically wired as event listeners\non the target element. The prefix determines persistence:\n\n- **`on<EventName>`** — persistent listener (fires every time)\n- **`one<EventName>`** — one-time listener (fires once, then detaches)\n\n### Web Component Lifecycle Events\n\nWhen a DC is registered via defineCE, the custom element dispatches these\ninternal events that can be caught with handler methods:\n\n| Method | When it fires |\n|--------|---------------|\n| `onConnected()` | Element was inserted into the DOM (`connectedCallback`) |\n| `onDisconnected()` | Element was removed from the DOM (`disconnectedCallback`) |\n| `onAdopted()` | Element was moved to a new document (`adoptedCallback`) |\n| `onAttributeChanged()` | An observed attribute changed (`attributeChangedCallback`) |\n| `oneInit()` | Constructor finished; all synchronous setup complete (microtask) |\n| `oneAssets()` | Constructor finished; all assets loaded and available; ev contains assets object |\n\n\n## Construction Patterns\n\n### Template usage (declarative)\n\n```ts\nclass MyElement extends DC {}\ndefineCE('my-element', MyElement);\n```\n\nThe registered element is then used in HTML as `<my-element></my-element>`.\n\n### Programmatic construction with data\n\nWhen constructing programmatically via getCE, pass only the `data` payload —\nthe target element is created automatically by the custom element constructor.\n\n### Data from HTML attributes\n\nUsed in HTML as `<my-element my-data='{\"name\":\"John\"}'></my-element>`.\n\n\n## Plugin Extension Points\n\nThe prototype of `DC` (inherited from McBase) is extended at runtime by optional\nplugins. See the McBase documentation for details on State.observePlugin and\nState.formValuePlugin.\n\n`trigger()` checks the payload against the registered `CartUpdate` shape, and the `on()`\nhandler receives a fully typed `ev`.",
22
+ "examples": [
23
+ {
24
+ "title": "static tag — replace with semantic element",
25
+ "code": "class ShopHeader extends DC {\nstatic tag = 'header';\n}\ndefineCE('shop-header', ShopHeader);"
26
+ },
27
+ {
28
+ "title": "static tag — remove element entirely",
29
+ "code": "class GlobalStyles extends DC {\nstatic tag = '';\n}\ndefineCE('global-styles', GlobalStyles);"
30
+ },
31
+ {
32
+ "title": "static assets with autoload",
33
+ "code": "class CartCheckout extends DC {\nstatic assets = ['targetHtml', 'targetCss', 'configJson'];\n\noneAssets(ev) {\nconst { configJson } = ev.detail.data;\nconsole.log(configJson);\n}\n}\ndefineCE('cart-checkout', CartCheckout);"
34
+ },
35
+ {
36
+ "title": "static attributes",
37
+ "code": "class ProductCard extends DC {\nstatic attributes = { role: 'listitem' };\n}\ndefineCE('product-card', ProductCard);"
38
+ },
39
+ {
40
+ "title": "All static properties together",
41
+ "code": "class ShopCatalog extends DC {\nstatic tag = 'section';\nstatic attributes = { role: 'list', 'aria-label': 'products' };\nstatic assets = ['targetHtml', 'targetCss'];\n}\ndefineCE('shop-catalog', ShopCatalog);"
42
+ },
43
+ {
44
+ "title": "Event handlers in action",
45
+ "code": "class MyElement extends DC {\nonConnected() {\nconsole.log('entered the DOM');\n}\n\nonDisconnected() {\nconsole.log('left the DOM');\n}\n\nonClick() {\nthis.trigger('something', 'payload');\n}\n\noneSomething(ev) {\nconsole.log('first time only:', ev.detail.data);\n}\n}\ndefineCE('my-element', MyElement);"
46
+ },
47
+ {
48
+ "title": "Programmatic construction",
49
+ "code": "class MyElement extends DC {\ndata = {};\n\nconstructor(target, data) {\nsuper(target);\nthis.data = data;\n}\n}\ndefineCE('my-element', MyElement);\n\nconst Ctor = getCE('my-element');\ndocument.body.append(new Ctor({ name: 'John' }));"
50
+ },
51
+ {
52
+ "title": "Reading data from an attribute",
53
+ "code": "class MyElement extends DC {\ndata = {};\n\nconstructor(target) {\nsuper(target);\nthis.data = JSON.parse(target.getAttribute('my-data'));\n}\n}\ndefineCE('my-element', MyElement);"
54
+ },
55
+ {
56
+ "title": "Typed event map",
57
+ "code": "class Cart extends DC<HTMLFormElement, { CartUpdate: { count: number } }> {\ncheckout() {\nthis.trigger('CartUpdate', { count: 2 }, 'u');\nthis.on('CartUpdate', (ev) => ev.detail.data.count);\n}\n}"
58
+ }
59
+ ]
60
+ },
61
+ "McBase": {
62
+ "name": "McBase",
63
+ "kind": "class",
64
+ "file": "src/core/MC.ts",
65
+ "signatures": [
66
+ "class McBase<TTarget extends object = object> {"
67
+ ],
68
+ "description": "The base class for all microcomponents — shared root of both MC and DC.\n\nHolds a `WeakRef` to the target (a DOM element for `DC`, or any plain object for `MC`)\nto avoid circular references. For `DC` subclasses it also bootstraps two automatic\nbehaviours:\n\n1. **Event listener auto-attachment** — methods whose names start with `on` or `one`\n(e.g. `onClick`, `oneInit`) are wired to the target element via on / one.\n2. **\"Init\" lifecycle event** — dispatched asynchronously in a microtask so that all\nsynchronous constructor work finishes first.\n\nBoth mechanisms run only when the instance is a `DC` (checked with `instanceof DC`).\nPlain `MC` instances skip them entirely, keeping server-side usage lightweight.\n\n### Plugin extension points\n\nThe prototype of `McBase` is the attachment point for optional plugins that curry\nadditional instance methods onto every microcomponent. Two built-in plugins modify\nthis class at runtime:\n\n- **State.observePlugin** — adds `notify()`, `subscribe()`, and `bind()` so that\nobservable properties can be controlled directly on the component instance with\nfluent chaining. Call `observePlugin(DC)` once at module level to activate.\n\n- **State.formValuePlugin** — adds `formValues()` for two-way binding between DOM\nform controls and an observable property. Also pulls in all methods from\nState.observePlugin, so you can chain `.subscribe()` and `.bind()` after\n`.formValues()`. Call `formValuePlugin(DC)` once at module level to activate.\n\nBoth plugins are idempotent — calling them multiple times has no side effects. They\nwork by assigning methods onto `McBase.prototype` (reached via\n`Object.getPrototypeOf(Class.prototype)`) and use TypeScript module augmentation\n(`declare module`) so the added signatures appear in type-checking automatically.\n\n`observePlugin(DC)` adds `notify()`, `subscribe()`, `bind()` to `McBase.prototype`;\n`formValuePlugin(DC)` adds `formValues()` and re-runs `observePlugin`. `oneInit()`\nruns once after the constructor.",
69
+ "examples": [
70
+ {
71
+ "title": "Extending with plugins at runtime",
72
+ "code": "import { DC, observePlugin, formValuePlugin } from '@twobirds/microcomponents';\n\nobservePlugin(DC);\nformValuePlugin(DC);\n\nclass MyForm extends DC {\ndata = { name: '' };\n\noneInit() {\nthis\n.formValues(this.target, 'data')\n.subscribe('data', (v) => console.log(v))\n.bind('data', this.target);\n}\n}"
73
+ }
74
+ ]
75
+ },
76
+ "defineCE": {
77
+ "name": "defineCE",
78
+ "kind": "function",
79
+ "group": "Elements",
80
+ "file": "src/core/elements.ts",
81
+ "signatures": [
82
+ "export function defineCE< Tag extends `${string}-${string}`, T extends object, D, E extends keyof HTMLElementTagNameMap | undefined = undefined, >( tagName: Tag, DefinitionClass: new (target: TargetOf<E>, data?: D) => T, observedAttributes?: string[], extend?: E",
83
+ "export function defineCE< Tag extends `${string}-${string}`, T extends object, E extends keyof HTMLElementTagNameMap | undefined = undefined, >( tagName: Tag, DefinitionClass: new (target: TargetOf<E>) => T, observedAttributes?: string[], extend?: E ): McElementConstructor<T, undefined, E, McPascalCase<Tag>> | undefined;",
84
+ "export function defineCE< Tag extends `${string}-${string}`, T extends object, D, E extends keyof HTMLElementTagNameMap | undefined = undefined, >( tagName: Tag, DefinitionClass: new (target: TargetOf<E>, data: D) => T, observedAttributes?: string[], extend?: E",
85
+ "export function defineCE< Tag extends `${string}-${string}`, T extends object, D = unknown, E extends keyof HTMLElementTagNameMap | undefined = undefined, >( tagName: Tag, DefinitionClass: new (target: TargetOf<E>, data?: D) => T, observedAttributes: string[] = [], extend?: E"
86
+ ],
87
+ "description": "### Examples\n\n#### Define custom element in registry\n\n```ts\nclass MyElement extends DC {}\ndefineCE('my-element', MyElement);\n```\n\nThe registered element is then used in HTML as `<my-element></my-element>`.\n\n#### Define custom element in registry with observed attributes\n\n```ts\nclass MyElement extends DC {\nonAttributeChanged(ev){\nconst newValue = ev.detail.data.newValue;\n}\n}\ndefineCE('my-element', MyElement, ['observed']);\n```\n\nUsed in HTML as `<my-element observed></my-element>`. When the `observed` attribute\nchanges, `attributeChanged` is triggered, which calls `onAttributeChanged` with the\nchange data in the payload.\n\nDefines a custom HTML element with optional observed attributes and micro component integration.\nThe class provided in DefinitionClass will be added to the ._mc hash object appended to the custom element instance.\nAutomatically assigns a \"PascalCase\" class name derived from the <kebab-case> tagName.\nIf the provided definition class extends McBase (indicating a DC or MC class extension), it wraps the constructor to register\nthe element instance with DC (DomComponent)."
88
+ },
89
+ "getCE": {
90
+ "name": "getCE",
91
+ "kind": "function",
92
+ "group": "Elements",
93
+ "file": "src/core/elements.ts",
94
+ "signatures": [
95
+ "export function getCE<K extends keyof McElementTagNameMap & string>( tagName: K ): McElementTagNameMap[K] | undefined;",
96
+ "export function getCE(tagName: string): CustomElementConstructor | undefined;",
97
+ "export function getCE(tagName: string) {"
98
+ ],
99
+ "description": "Retrieves a custom element constructor by tag name.\n\nTags registered on the global McElementTagNameMap (via declaration\nmerging) come back as their declared constructor type — payload and `_mc`\nentry included; any other string falls back to the plain\n`CustomElementConstructor`.",
100
+ "examples": [
101
+ {
102
+ "title": "",
103
+ "code": "const MyElementClass = getCE('my-element');\nif (MyElementClass) {\ndocument.body.append(new MyElementClass());\n}"
104
+ }
105
+ ]
106
+ },
107
+ "listCE": {
108
+ "name": "listCE",
109
+ "kind": "function",
110
+ "group": "Elements",
111
+ "file": "src/core/elements.ts",
112
+ "signatures": [
113
+ "export function listCE(): string[] {"
114
+ ],
115
+ "description": "Returns an array of all tag names registered via defineCE.",
116
+ "examples": [
117
+ {
118
+ "title": "",
119
+ "code": "defineCE('my-element', MyElement);\ndefineCE('another-widget', AnotherWidget);\nconsole.log(listCE());"
120
+ }
121
+ ]
122
+ },
123
+ "TargetOf": {
124
+ "name": "TargetOf",
125
+ "kind": "type alias",
126
+ "group": "Types",
127
+ "file": "src/core/elements.ts",
128
+ "signatures": [
129
+ "export type TargetOf<E> = E extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[E] : HTMLElement;"
130
+ ],
131
+ "description": "Resolves the host element type for a given `extend` tag: the matching built-in\nelement type when extending one (e.g. `'button'` → `HTMLButtonElement`),\nplain `HTMLElement` otherwise.\n\n`Btn` resolves to `HTMLButtonElement`, `Plain` to plain `HTMLElement`.",
132
+ "examples": [
133
+ {
134
+ "title": "",
135
+ "code": "type Btn = TargetOf<'button'>;\ntype Plain = TargetOf<'div'>;"
136
+ }
137
+ ]
138
+ },
139
+ "McPascalCase": {
140
+ "name": "McPascalCase",
141
+ "kind": "type alias",
142
+ "group": "Types",
143
+ "file": "src/core/elements.ts",
144
+ "signatures": [
145
+ "export type McPascalCase<S extends string> = S extends `${infer Head}-${infer Rest}` ? `${Capitalize<Head>}${McPascalCase<Rest>}` : Capitalize<S>;"
146
+ ],
147
+ "description": "The type-level mirror of `kebabToPascal()` in helpers.ts: `'user-card'` →\n`'UserCard'`. Lets defineCE type the `_mc` registry entry under the\nexact key the runtime stores it at."
148
+ },
149
+ "McElementConstructor": {
150
+ "name": "McElementConstructor",
151
+ "kind": "type alias",
152
+ "group": "Types",
153
+ "file": "src/core/elements.ts",
154
+ "signatures": [
155
+ "export type McElementConstructor< T extends object = object, D = unknown, E extends keyof HTMLElementTagNameMap | undefined = undefined, N extends string = string, > = new (data?: D) => McHost<TargetOf<E>, string extends N ? Record<never, never> : Record<N, T>>;"
156
+ ],
157
+ "description": "The custom element constructor returned by defineCE. Constructing it\nprogrammatically accepts an optional `data` payload, forwarded as the second\nargument to the microcomponent class. The resulting element exposes its\nmicrocomponent instance via `._mc`, keyed by the PascalCase form of the tag\nname (`N`); with the default `N = string` no entry is claimed."
158
+ },
159
+ "isMcHost": {
160
+ "name": "isMcHost",
161
+ "kind": "function",
162
+ "group": "Helpers",
163
+ "file": "src/core/mcGuards.ts",
164
+ "signatures": [
165
+ "export function isMcHost<T extends HTMLElement, K extends keyof McRegistry & string>( element: T, key: K ): element is McHost<T, Pick<McRegistry, K>>;",
166
+ "export function isMcHost<K extends keyof McRegistry & string>( element: unknown, key: K ): element is McHost<HTMLElement, Pick<McRegistry, K>>;",
167
+ "export function isMcHost<T extends HTMLElement>(element: T): element is McHost<T>;",
168
+ "export function isMcHost(element: unknown): element is McHost;",
169
+ "export function isMcHost(element: unknown, key?: string): boolean {"
170
+ ],
171
+ "description": "Checks whether the given value is a microcomponent host element (McHost).\n\nBare form: narrows to the controller API only — no registry entries are claimed,\nbecause different elements carry different `_mc` records and the type system\ncannot know which keys a specific element holds.\n\nKeyed form: additionally checks `key in element._mc` at runtime and narrows the\nentry to the type bound on the global `McRegistry` interface — *non-optional*,\nbecause presence was just verified. The binding is an app-wide naming\nconvention (key name → component type); where type identity must be proven or\nkey names may collide, use `DC.get(el, key, Class)` instead.",
172
+ "examples": [
173
+ {
174
+ "title": "",
175
+ "code": "declare global {\ninterface McRegistry {\ncart: Cart;\n}\n}\n\nconst el = document.querySelector('form');\nif (el && isMcHost(el)) {\nel._mc.trigger('Ping');\n}\nif (el && isMcHost(el, 'cart')) {\nel._mc.cart.addItem(item);\n}"
176
+ }
177
+ ]
178
+ },
179
+ "observe": {
180
+ "name": "observe",
181
+ "kind": "function",
182
+ "file": "src/state/observables.ts",
183
+ "signatures": [
184
+ "export function observe<T extends object, K extends keyof T>( target: T, propertyName: K ): ObservedKey<T, K>;",
185
+ "export function observe<T extends object>(target: T): Observed<T>;",
186
+ "export function observe<S extends object>(target: Unobserved<S>): S;",
187
+ "export function observe(target: object, propertyName?: string): object {"
188
+ ],
189
+ "description": "Makes a target object's properties observable by replacing them with wrapped values.\n\nWraps each property of `target` that is not already observable (and meets criteria)\nin an observable wrapper. Also sets up deep change detection for objects and\ndebounced notifications when the data changes.\n\nThere are two sides to using it — pick per call site:\n\n*Declarative (typed class fields).** Declare the field as `Observable<T>` up\nfront, then let `observe(this, 'field')` install the live wrapper in the\nconstructor. The declaration needs the double cast once (`0 as unknown as\nObservable<number>`): the field starts life as a plain value and only becomes\nthe wrapper when `observe()` runs. In exchange, every later use is fully typed —\narithmetic, `subscribe`, `bind` — with no casts anywhere else.\n\n*Imperative (wrapping existing data).** Hand any object to `observe()` after\nthe fact — no declarations, the return type is inferred: every property comes\nback as `Observable<PropertyType>`.\n\nIncrementing `state.count` triggers the subscribed callback.\n\nPlain arithmetic compiles because the declaration carries the observable type.\n\nThe seed object is validated against the schema, the subscription callback is fully\ntyped, and plain assignment is legal because the declared setter type permits it.",
190
+ "examples": [
191
+ {
192
+ "title": "Imperative usage — wrap an existing plain object:",
193
+ "code": "const state = observe({ count: 0, label: 'hi' });\nstate.count.subscribe((v) => console.log('count is now', v));\nstate.count++;"
194
+ },
195
+ {
196
+ "title": "Declarative usage — typed class field with double cast:",
197
+ "code": "class Counter {\nclicks: Observable<number> = 0 as unknown as Observable<number>;\n\nconstructor() {\nobserve(this, 'clicks');\n}\n\nbump() {\nthis.clicks++;\n}\n}"
198
+ },
199
+ {
200
+ "title": "Schema-first usage — divergent read/write types via hand-declared interface:",
201
+ "code": "interface CounterState {\nget count(): Observable<number>;\nset count(v: number);\n}\n\nconst state = observe<CounterState>({ count: 0 });\nstate.count.subscribe((v) => console.log(v));\nstate.count = 5;"
202
+ }
203
+ ]
204
+ },
205
+ "observePlugin": {
206
+ "name": "observePlugin",
207
+ "kind": "function",
208
+ "file": "src/state/observables.ts",
209
+ "signatures": [
210
+ "export function observePlugin(Class: any) {"
211
+ ],
212
+ "description": "Attaches convenience methods `notify()`, `subscribe()`, and `bind()` to the prototype of a\nmicrocomponent class so that observable properties can be controlled directly on the instance.\n\nAll three methods return the component instance, so you can chain them fluently.\n\nCall it once at module level before defining any component classes. The plugin is idempotent —\nsubsequent calls are no-ops.",
213
+ "examples": [
214
+ {
215
+ "title": "",
216
+ "code": "class Counter extends DC {\ndata = { clicks: 1 };\n\noneAssets() {\nthis\n.subscribe('data', (v) => console.log('data →', v))\n.bind('data', this.target);\n}\n}"
217
+ }
218
+ ]
219
+ },
220
+ "isAlreadyObserved": {
221
+ "name": "isAlreadyObserved",
222
+ "kind": "function",
223
+ "file": "src/state/observables.ts",
224
+ "signatures": [
225
+ "export function isAlreadyObserved(value: unknown): boolean {"
226
+ ],
227
+ "description": "Whether a value is already an observable wrapper — `subscribe` is reachable on its prototype chain.\nUsed by observe to skip re-wrapping and to detect the \"not yet observed\" state on reassignment.\n\nThe wrapped property reports `true`, a plain primitive reports `false`.",
228
+ "examples": [
229
+ {
230
+ "title": "",
231
+ "code": "const state = observe({ count: 0 });\nconsole.log(isAlreadyObserved(state.count));\nconsole.log(isAlreadyObserved(42));"
232
+ }
233
+ ]
234
+ },
235
+ "Observable": {
236
+ "name": "Observable",
237
+ "kind": "type alias",
238
+ "file": "src/state/observables.ts",
239
+ "signatures": [
240
+ "export type Observable<T = unknown> = T & {"
241
+ ],
242
+ "description": "Interface representing an observable object that supports subscription, notification, and DOM binding.\n\nObservable objects track registered callbacks and invoke them when their internal state changes.\nThey can also be bound to a DOM target to automatically update placeholder values in the view layer.\n\nThe subscription fires on the increment, logging `count is now 1`.",
243
+ "examples": [
244
+ {
245
+ "title": "",
246
+ "code": "const state = observe({ count: 0 });\nstate.count.subscribe((v) => console.log('count is now', v));\nstate.count++;"
247
+ }
248
+ ]
249
+ },
250
+ "Callback": {
251
+ "name": "Callback",
252
+ "kind": "type alias",
253
+ "file": "src/state/observables.ts",
254
+ "signatures": [
255
+ "export type Callback<T = unknown> = (value: T) => void;"
256
+ ],
257
+ "description": "A callback invoked with the current observable value when notified.",
258
+ "examples": [
259
+ {
260
+ "title": "",
261
+ "code": "const cb: Callback<number> = (v) => console.log('new value:', v);\nobservable.subscribe(cb);"
262
+ }
263
+ ]
264
+ },
265
+ "Observed": {
266
+ "name": "Observed",
267
+ "kind": "type alias",
268
+ "file": "src/state/observables.ts",
269
+ "signatures": [
270
+ "export type Observed<T> = { [K in keyof T]: Observable<T[K]> };"
271
+ ],
272
+ "description": "Every property of `T` mapped to an observable of its value.\n\nKNOWN LIMIT: the runtime property *setter* accepts a plain value\n(`state.count = 5` re-wraps it), but this mapped type cannot say so — mapped\ntypes cannot declare divergent read/write (get/set) types, so the wrapper type\ngoverns writes too and plain assignment to a primitive observable is rejected.\nTracked upstream as https://github.com/microsoft/TypeScript/issues/43826\n(variant accessors for mapped types); when it lands, the write type here\nbecomes `T[K] | Observable<T[K]>` and the limit disappears.\n\nUntil then, use one of the supported write paths:\n- the `observed` variant, a single container whose `.value` gives \ntyped reads and writes today (`state.value.count = 5`, deep\nwrites included) — the limitation never applies;\n- `state.count++` (compiles today; TS does not re-check the `++` write-back);\n- the schema-first `observe<S>()` overload with a hand-declared get/set\ninterface, where the divergence IS expressible (and plain `=` becomes\nlegal) — see Unobserved.\n\nEvery property is mapped to an observable: `State` is\n`{ count: Observable<number>, name: Observable<string> }`.",
273
+ "examples": [
274
+ {
275
+ "title": "",
276
+ "code": "type State = Observed<{ count: number; name: string }>;"
277
+ }
278
+ ]
279
+ },
280
+ "ObservedKey": {
281
+ "name": "ObservedKey",
282
+ "kind": "type alias",
283
+ "file": "src/state/observables.ts",
284
+ "signatures": [
285
+ "export type ObservedKey<T, K extends keyof T> = {"
286
+ ],
287
+ "description": "Only `K` of `T` mapped to an observable; other properties keep their type.\nCarries the same write limitation as Observed for the mapped key.\n\nOnly `count` is mapped to an observable: `State` is\n`{ count: Observable<number>, label: string }`.",
288
+ "examples": [
289
+ {
290
+ "title": "",
291
+ "code": "type State = ObservedKey<{ count: number; label: string }, 'count'>;"
292
+ }
293
+ ]
294
+ },
295
+ "Unobserved": {
296
+ "name": "Unobserved",
297
+ "kind": "type alias",
298
+ "file": "src/state/observables.ts",
299
+ "signatures": [
300
+ "export type Unobserved<S> = {"
301
+ ],
302
+ "description": "The inverse of a hand-declared observable state interface: strips the\n`Observable` wrapper from every property, yielding the plain seed shape.\n`Unobserved<CounterState>` → `{ count: number }`.\n\nExtraction goes through the `subscribe` callback parameter, so it works for\nboth accessor-declared members (`get count(): Observable<number>`) and plain\n`Observable<T>` properties; non-observable members pass through unchanged.\n\nUsed by the schema-first `observe<S>()` overload to validate the seed object\nagainst the declared interface — see that overload's example.\n\n`Seed` strips the wrapper, yielding the plain seed shape `{ count: number }`. The\n`observe<CounterState>()` overload validates the seed object against the schema.",
303
+ "examples": [
304
+ {
305
+ "title": "",
306
+ "code": "interface CounterState {\nget count(): Observable<number>;\nset count(v: number);\n}\ntype Seed = Unobserved<CounterState>;\nconst state = observe<CounterState>({ count: 0 });"
307
+ }
308
+ ]
309
+ },
310
+ "formValues": {
311
+ "name": "formValues",
312
+ "kind": "function",
313
+ "file": "src/state/formValues.ts",
314
+ "signatures": [
315
+ "export function formValues<T extends object>( targetObject: T, propertyName: keyof T & string, dom: HTMLElement | ShadowRoot ) {"
316
+ ],
317
+ "description": "Attaches a form-values observable to a target object property, enabling two-way binding\nbetween DOM form controls and the object's state.\n\nScans the given DOM subtree for `input`, `select`, and `textarea` elements with a `name`\nattribute and creates live getters/setters on the resulting observable. Input events trigger\nautomatic notifications so subscribers react to user changes.\n\nReading `state.formData.username` reflects the live `<input name=\"username\">` value;\nassigning `state.formData.role` writes straight back to the `<select>` control.",
318
+ "examples": [
319
+ {
320
+ "title": "Bind a plain object to an HTML form element",
321
+ "code": "import { formValues } from '@twobirds/microcomponents/formValues';\n\nconst state = {};\nformValues(state, 'formData', document.getElementById('myForm'));\nconsole.log(state.formData.username);\nstate.formData.role = 'admin';"
322
+ },
323
+ {
324
+ "title": "Subscribe to form changes inside a component",
325
+ "code": "import { DC, defineCE, formValues } from '@twobirds/microcomponents';\n\nclass MyForm extends DC {\nstatic assets = ['targetHtml'];\n\noneAssets() {\nformValues(this, 'formData', this.target);\nthis.formData.subscribe((data) => console.log('form changed:', data));\n}\n}\n\ndefineCE('my-form', MyForm);"
326
+ }
327
+ ]
328
+ },
329
+ "formValuePlugin": {
330
+ "name": "formValuePlugin",
331
+ "kind": "function",
332
+ "file": "src/state/formValues.ts",
333
+ "signatures": [
334
+ "export function formValuePlugin(Class: any) {"
335
+ ],
336
+ "description": "Attaches a convenience method `formValues()` to the prototype of a microcomponent class so that\nform controls can be bound directly on the instance without importing the standalone function.\n\nThis plugin also pulls in all observable methods from observePlugin — `notify()`,\n`subscribe()`, and `bind()` — so you can chain them fluently after binding form data.\n\nCall it once at module level before defining any component classes. The plugin is idempotent —\nsubsequent calls are no-ops.",
337
+ "examples": [
338
+ {
339
+ "title": "",
340
+ "code": "import { DC, defineCE, formValuePlugin } from '@twobirds/microcomponents';\n\nformValuePlugin(DC);\n\nclass CheckoutForm extends DC {\nstatic assets = ['targetHtml'];\n\noneAssets() {\nthis\n.formValues(this.target, 'formData')\n.subscribe('formData', (data) => console.log(data))\n.bind('formData', this.target);\n}\n}\n\ndefineCE('checkout-form', CheckoutForm);"
341
+ }
342
+ ]
343
+ },
344
+ "getMcEventName": {
345
+ "name": "getMcEventName",
346
+ "kind": "function",
347
+ "file": "src/utils/events.ts",
348
+ "signatures": [
349
+ "export function getMcEventName(eventName: string): string {"
350
+ ],
351
+ "description": "Converts a standard event name to its microcomponent-prefixed variant.\n\nThis utility is used internally to namespace custom events (e.g., 'Click' → '_mcClick').",
352
+ "examples": [
353
+ {
354
+ "title": "",
355
+ "code": "console.log(getMcEventName('update'));"
356
+ }
357
+ ]
358
+ },
359
+ "trueEventName": {
360
+ "name": "trueEventName",
361
+ "kind": "function",
362
+ "file": "src/utils/events.ts",
363
+ "signatures": [
364
+ "export function trueEventName(target: HTMLElement, eventName: string): string {"
365
+ ],
366
+ "description": "Returns the correct event name to listen for on a DOM element.\n\nIf `eventName` (case-insensitive) matches a native HTML event handler property\n(`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native\nname so that standard DOM events like `'click'` are listened to correctly.\nOtherwise, prefixes with `_mc` for microcomponent custom events.\n\n`'Click'` resolves to the native `'click'` because the button has an `onclick` handler\nproperty; `'Update'` has no native counterpart, so it becomes the `_mc`-prefixed custom\nevent name.",
367
+ "examples": [
368
+ {
369
+ "title": "",
370
+ "code": "const btn = document.createElement('button');\nconsole.log(trueEventName(btn, 'Click'));\nconsole.log(trueEventName(btn, 'Update'));"
371
+ }
372
+ ]
373
+ },
374
+ "off": {
375
+ "name": "off",
376
+ "kind": "function",
377
+ "file": "src/utils/events.ts",
378
+ "signatures": [
379
+ "export function off<D = unknown>( target: TargetSelect, eventName: string, handler: McEventHandler<D> ): void;",
380
+ "export function off( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject ): void;",
381
+ "export function off( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject | McEventHandler<never> ) {"
382
+ ],
383
+ "description": "Removes a previously attached event handler from the DOM node. See on and one.\n\nUses internal microcomponent-prefixed naming for event handlers.\nThis is primarily used to interface with external frameworks or vanilla JS environments.\nFor communication between frontend microcomponents, use the `DC.off()` method instead.",
384
+ "examples": [
385
+ {
386
+ "title": "",
387
+ "code": "const handler = on(myElement, 'click', () => console.log('clicked'));\noff(myElement, 'click', handler);"
388
+ }
389
+ ]
390
+ },
391
+ "on": {
392
+ "name": "on",
393
+ "kind": "function",
394
+ "file": "src/utils/events.ts",
395
+ "signatures": [
396
+ "export function on<D = unknown>( target: TargetSelect, eventName: string, handler: McEventHandler<D>, once?: boolean ): McEventHandler<D>;",
397
+ "export function on( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject, once?: boolean ): EventListenerOrEventListenerObject;",
398
+ "export function on( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject | McEventHandler<never>, once: boolean = false ) {"
399
+ ],
400
+ "description": "Attaches a persistent event listener that will fire every time the specified event occurs.\n\nThis is primarily used to interface with external frameworks or vanilla JS environments.\nFor communication between frontend microcomponents, use the `DC.on()` method instead.\n\nThe handler receives a `CustomEvent<McDetail<D>>`: the payload sits under\n`ev.detail.data`, the bubble-control handle under `ev.detail.control`.",
401
+ "examples": [
402
+ {
403
+ "title": "",
404
+ "code": "const handler = on<{ value: number }>(myElement, 'update', (ev) => {\nconsole.log(ev.detail.data.value);\n});\noff(myElement, 'update', handler);"
405
+ }
406
+ ]
407
+ },
408
+ "one": {
409
+ "name": "one",
410
+ "kind": "function",
411
+ "file": "src/utils/events.ts",
412
+ "signatures": [
413
+ "export function one<D = unknown>( target: TargetSelect, eventName: string, handler: McEventHandler<D> ): McEventHandler<D>;",
414
+ "export function one( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject ): EventListenerOrEventListenerObject;",
415
+ "export function one( target: TargetSelect, eventName: string, handler: EventListenerOrEventListenerObject | McEventHandler<never> ) {"
416
+ ],
417
+ "description": "Attaches an event listener that will fire only once.\n\nInternally calls on with the `once` flag set to true. The handler\nis automatically removed after its first invocation.\nThis is primarily used to interface with external frameworks or vanilla JS environments.\nFor communication between frontend microcomponents, use the `DC.one()` method instead.\n\nThe listener fires only once and detaches automatically.",
418
+ "examples": [
419
+ {
420
+ "title": "",
421
+ "code": "one<{ ready: boolean }>(myElement, 'dataReady', (ev) => console.log('received:', ev.detail.data));"
422
+ }
423
+ ]
424
+ },
425
+ "trigger": {
426
+ "name": "trigger",
427
+ "kind": "function",
428
+ "file": "src/utils/events.ts",
429
+ "signatures": [
430
+ "export function trigger<D = unknown>( target: HTMLElement | string | undefined | null, event: string, data: D = {} as D, bubble: BubbleDirections = 'l', // direction of bubbling controller?: BubbleController ) {"
431
+ ],
432
+ "description": "Creates and dispatches a custom event on the specified DOM node.\n\nThe event is namespaced with an `_mc` prefix by default, unless already prefixed.\nSupports bidirectional bubbling (up/down the DOM tree) based on the `bubble` parameter:\n- 'l': local dispatch only\n- 'u': bubble up towards ancestors\n- 'd': bubble down to descendants\n\nThe dispatched event is a `CustomEvent<McDetail<D>>`: handlers find the payload\nunder `ev.detail.data` and can call `ev.detail.control.stop()` to cancel bubbling.\n\nThe first dispatch fires locally only; the second bubbles upward to ancestor components.",
433
+ "examples": [
434
+ {
435
+ "title": "",
436
+ "code": "trigger(myElement, 'update', { value: 42 }, 'l');\ntrigger(myElement, 'dataReady', { ready: true }, 'u');"
437
+ }
438
+ ]
439
+ },
440
+ "BubbleController": {
441
+ "name": "BubbleController",
442
+ "kind": "class",
443
+ "file": "src/utils/events.ts",
444
+ "signatures": [
445
+ "export class BubbleController implements McBubbleController {"
446
+ ],
447
+ "description": "A controller object used to manage and potentially abort event bubbling.\n\nThe `trigger()` function internally creates instance of this class,\nallowing the an eventual handler to call `ev.detail.control.stop()` to\nprevent further propagation in either direction (up/down).\n\nThis is only used internally.",
448
+ "examples": [
449
+ {
450
+ "title": "",
451
+ "code": "trigger(myElement, 'update', { value: 42 }, 'u');\n\nonUpdate(ev) {\n ev.detail.control.stop();\n}"
452
+ }
453
+ ]
454
+ },
455
+ "McBubbleController": {
456
+ "name": "McBubbleController",
457
+ "kind": "interface",
458
+ "file": "src/types/mcTypes.ts",
459
+ "signatures": [
460
+ "export interface McBubbleController {"
461
+ ],
462
+ "description": "The bubble control handle passed to event handlers; `BubbleController` in events.ts implements it. */"
463
+ },
464
+ "BubbleDirection": {
465
+ "name": "BubbleDirection",
466
+ "kind": "type alias",
467
+ "file": "src/types/mcTypes.ts",
468
+ "signatures": [
469
+ "export type BubbleDirection = 'l' | 'u' | 'd';"
470
+ ],
471
+ "description": "One bubbling direction: `l` local dispatch, `u` up to ancestors, `d` down to descendants. */"
472
+ },
473
+ "BubbleDirections": {
474
+ "name": "BubbleDirections",
475
+ "kind": "type alias",
476
+ "file": "src/types/mcTypes.ts",
477
+ "signatures": [
478
+ "export type BubbleDirections = | BubbleDirection | `${BubbleDirection}${BubbleDirection}` | `${BubbleDirection}${BubbleDirection}${BubbleDirection}`;"
479
+ ],
480
+ "description": "The `bubble` argument of `trigger()`: one to three BubbleDirection letters\ncombined — e.g. `'l'`, `'lu'`, `'lud'` — to dispatch locally and/or propagate up\nand down in one call."
481
+ },
482
+ "McDetail": {
483
+ "name": "McDetail",
484
+ "kind": "interface",
485
+ "file": "src/types/mcTypes.ts",
486
+ "signatures": [
487
+ "export interface McDetail<D = unknown> {"
488
+ ],
489
+ "description": "The `detail` of a microcomponent event: the payload under `data`, the bubble\ncontrol handle under `control`."
490
+ },
491
+ "McEventHandler": {
492
+ "name": "McEventHandler",
493
+ "kind": "type alias",
494
+ "file": "src/types/mcTypes.ts",
495
+ "signatures": [
496
+ "export type McEventHandler<D = unknown> = (ev: McEvent<D>) => void;"
497
+ ],
498
+ "description": "The function `on()` / `one()` accept as an event handler: it receives an\nMcEvent carrying payload `D`."
499
+ },
500
+ "McEvent": {
501
+ "name": "McEvent",
502
+ "kind": "type alias",
503
+ "file": "src/types/mcTypes.ts",
504
+ "signatures": [
505
+ "export type McEvent<D = unknown> = CustomEvent<McDetail<D>>;"
506
+ ],
507
+ "description": "The event object passed to `on()` / `one()` handlers — a `CustomEvent` carrying\npayload `D`. Short for `CustomEvent<McDetail<D>>`.\n\nAnnotate a handler with `McEvent<D>`, or skip the annotation when the event name\nis in the component's event map (`EM` on `DC`) and let it be inferred.\n\n`ev.detail.data.count` is typed `number`; `ev.detail.control.stop()` halts further bubbling.",
508
+ "examples": [
509
+ {
510
+ "title": "",
511
+ "code": "cart.on('CartUpdate', (ev: McEvent<{ count: number }>) => {\nconsole.log(ev.detail.data.count);\nev.detail.control.stop();\n});"
512
+ }
513
+ ]
514
+ },
515
+ "McPayloadOf": {
516
+ "name": "McPayloadOf",
517
+ "kind": "type alias",
518
+ "file": "src/types/mcTypes.ts",
519
+ "signatures": [
520
+ "export type McPayloadOf<EM, K extends string, D = unknown> = K extends keyof EM ? EM[K] : D;"
521
+ ],
522
+ "description": "The payload type of event `K`: its entry in the event map `EM`, or the fallback\n`D` when `K` is not in the map."
523
+ },
524
+ "McController": {
525
+ "name": "McController",
526
+ "kind": "interface",
527
+ "file": "src/types/mcTypes.ts",
528
+ "signatures": [
529
+ "export interface McController<T extends HTMLElement = HTMLElement> {"
530
+ ],
531
+ "description": "The public surface of a DOM microcomponent controller — what `DC` exposes per\ninstance. The registry types reference this instead of the `DC` class so the\nfile stays import-free (`DC implements McController`).\n\n`trigger`'s `bubble` is `string` here, not `BubbleDirections`, because this file\ncannot import from events.ts; `DC` narrows it."
532
+ },
533
+ "McNativeEventHandlers": {
534
+ "name": "McNativeEventHandlers",
535
+ "kind": "type alias",
536
+ "file": "src/types/mcTypes.ts",
537
+ "signatures": [
538
+ "export type McNativeEventHandlers = {"
539
+ ],
540
+ "description": "Opt-in type check for native-event handler methods (`onClick`, `onePointerdown`,\n…). Add it with `implements` and each `on<Event>` / `one<Event>` method is\nchecked against the correct DOM event type; other methods are left alone.\nType-only — nothing exists at runtime.\n\n`onClick` is checked against the native `'click'` event type. Declaring\n`onClick(ev: KeyboardEvent)` instead is a compile error.",
541
+ "examples": [
542
+ {
543
+ "title": "",
544
+ "code": "class Panel extends DC implements McNativeEventHandlers {\nonClick(ev: MouseEvent) {}\n}"
545
+ }
546
+ ]
547
+ },
548
+ "McHost": {
549
+ "name": "McHost",
550
+ "kind": "type alias",
551
+ "file": "src/types/mcTypes.ts",
552
+ "signatures": [
553
+ "export type McHost< T extends HTMLElement = HTMLElement, R extends Record<string, unknown> = Record<never, never>, > = T & {"
554
+ ],
555
+ "description": "A DOM element that hosts microcomponents: the element type `T` plus an `_mc`\nregistry. `R` holds the known entries (`key → element type`); there is no string\nindex, so an unregistered key is a compile error. Chained `add()` calls merge\ntheir entries into `R`.\n\n`withA._mc.a.a` is typed `number`. Accessing `withA._mc.b` is a compile error\nbecause that key was never registered.",
556
+ "examples": [
557
+ {
558
+ "title": "",
559
+ "code": "const withA = DC.add(form, 'a', { a: 0 });\nconsole.log(withA._mc.a.a);"
560
+ }
561
+ ]
562
+ },
563
+ "McHostOf": {
564
+ "name": "McHostOf",
565
+ "kind": "type alias",
566
+ "file": "src/types/mcTypes.ts",
567
+ "signatures": [
568
+ "export type McHostOf<T extends HTMLElement> = T extends { _mc: McController<infer U> } ? U : T;"
569
+ ],
570
+ "description": "The element type behind a target: unwraps `McHost<U, R>` back to `U`, identity\nfor a plain element. Keeps chained `add()` calls from nesting."
571
+ },
572
+ "McEntriesOf": {
573
+ "name": "McEntriesOf",
574
+ "kind": "type alias",
575
+ "file": "src/types/mcTypes.ts",
576
+ "signatures": [
577
+ "export type McEntriesOf<T> = T extends { _mc: infer M } ? {"
578
+ ],
579
+ "description": "The known `_mc` entries of a target as a flat `key → element type` record: the\n`add()` entries with the controller members stripped out, empty for a plain\nelement. This is what lets chained `add()` calls stay flat instead of nesting."
580
+ },
581
+ "McMerged": {
582
+ "name": "McMerged",
583
+ "kind": "type alias",
584
+ "file": "src/types/mcTypes.ts",
585
+ "signatures": [
586
+ "export type McMerged<A, B> = A & B extends infer U ? { [K in keyof U]: U[K] } : never;"
587
+ ],
588
+ "description": "Merges two records into one flat record, so registry entries show as a single object. */"
589
+ },
590
+ "McRemoved": {
591
+ "name": "McRemoved",
592
+ "kind": "type alias",
593
+ "file": "src/types/mcTypes.ts",
594
+ "signatures": [
595
+ "export type McRemoved<T extends HTMLElement, K extends string> = T extends { _mc: McController } ? { _mc: Record<K, never> } : unknown;"
596
+ ],
597
+ "description": "The type `remove()` asserts onto the caller's reference: on an enhanced target the\nremoved key is poisoned to `never` (member access becomes a compile error); a\nplain target is left untouched. A later `add()` re-registers the key cleanly."
598
+ }
599
+ },
600
+ "undocumented": []
601
+ }