@twobirds/microcomponents 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -16
  2. package/dist/cli/bundle.d.ts +106 -0
  3. package/dist/cli/bundle.d.ts.map +1 -0
  4. package/dist/cli/bundler.d.ts +44 -0
  5. package/dist/cli/bundler.d.ts.map +1 -0
  6. package/dist/cli/create.d.ts.map +1 -1
  7. package/dist/cli/dev.d.ts +87 -0
  8. package/dist/cli/dev.d.ts.map +1 -0
  9. package/dist/cli/dispatch.d.ts +6 -2
  10. package/dist/cli/dispatch.d.ts.map +1 -1
  11. package/dist/cli/index.js +1807 -1
  12. package/dist/cli/inliner.d.ts +42 -0
  13. package/dist/cli/inliner.d.ts.map +1 -0
  14. package/dist/cli/preview.d.ts +48 -0
  15. package/dist/cli/preview.d.ts.map +1 -0
  16. package/dist/core/elements.js +1294 -75
  17. package/dist/core/mcGuards.js +5 -8
  18. package/dist/mcp/api.json +2 -17
  19. package/dist/mcp/concepts/project-layout.md +9 -8
  20. package/dist/mcp/index.js +450 -230
  21. package/dist/microcomponents.js +2882 -9
  22. package/dist/state/formValues.js +2236 -281
  23. package/dist/state/observables.js +1794 -342
  24. package/dist/utils/events.js +171 -197
  25. package/package.json +7 -8
  26. package/dist/cli/create.js +0 -653
  27. package/dist/cli/dispatch.js +0 -316
  28. package/dist/cli/scaffold.js +0 -109
  29. package/dist/cli/writeScaffold.js +0 -114
  30. package/dist/core/MC.js +0 -837
  31. package/dist/core/index.js +0 -72
  32. package/dist/state/index.js +0 -5
  33. package/dist/state/placeholders.js +0 -187
  34. package/dist/types/index.js +0 -1
  35. package/dist/types/mcTypes.js +0 -9
  36. package/dist/utils/autoload.js +0 -160
  37. package/dist/utils/helpers.js +0 -532
  38. package/dist/utils/index.js +0 -12
  39. package/dist/utils/resolveComponentPath.js +0 -39
  40. package/dist/vite/index.d.ts +0 -7
  41. package/dist/vite/index.d.ts.map +0 -1
  42. package/dist/vite/index.js +0 -6
  43. package/dist/vite/loadAssets.d.ts +0 -36
  44. package/dist/vite/loadAssets.d.ts.map +0 -1
  45. package/dist/vite/loadAssets.js +0 -221
@@ -1,3 +1,4 @@
1
+ //#region src/utils/events.ts
1
2
  /**
2
3
  @module Events
3
4
 
@@ -18,10 +19,10 @@ The core feature is the ability to explicitly control event flow direction when
18
19
  html:
19
20
  ```html
20
21
  <div class="grandparent">
21
- <div class="parent">
22
- <div class="child">
23
- </div>
24
- </div>
22
+ <div class="parent">
23
+ <div class="child">
24
+ </div>
25
+ </div>
25
26
  </div>
26
27
  ```
27
28
 
@@ -48,52 +49,50 @@ The `dataReady` event bubbles upward from the innermost `div`, but `parentHandle
48
49
  calls `ev.detail.control.stop()`, so the `grandParent` handler is never reached.
49
50
  Handlers are detached with `off()` once they are no longer needed.
50
51
  */
51
- 'use strict';
52
52
  /**
53
- * Converts a standard event name to its microcomponent-prefixed variant.
54
- *
55
- * This utility is used internally to namespace custom events (e.g., 'Click' → '_mcClick').
56
- *
57
- * @param eventName - The original event name (e.g., 'click', 'update').
58
- * @returns The microcomponent-prefixed event name.
59
- *
60
- * @example
61
- * ```ts
62
- * console.log(getMcEventName('update'));
63
- * ```
64
- */
65
- export function getMcEventName(eventName) {
66
- return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
53
+ * Converts a standard event name to its microcomponent-prefixed variant.
54
+ *
55
+ * This utility is used internally to namespace custom events (e.g., 'Click' → '_mcClick').
56
+ *
57
+ * @param eventName - The original event name (e.g., 'click', 'update').
58
+ * @returns The microcomponent-prefixed event name.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * console.log(getMcEventName('update'));
63
+ * ```
64
+ */
65
+ function getMcEventName(eventName) {
66
+ return "_mc" + eventName[0].toUpperCase() + eventName.substring(1);
67
67
  }
68
68
  /**
69
- * Returns the correct event name to listen for on a DOM element.
70
- *
71
- * If `eventName` (case-insensitive) matches a native HTML event handler property
72
- * (`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native
73
- * name so that standard DOM events like `'click'` are listened to correctly.
74
- * Otherwise, prefixes with `_mc` for microcomponent custom events.
75
- *
76
- * @hidden
77
- * @param target - The DOM element whose native event support is checked.
78
- * @param eventName - The event name as provided by the caller (e.g., `'click'`, `'Update'`).
79
- * @returns The resolved event name: lowercase native or `_mc`-prefixed custom.
80
- *
81
- * @example
82
- * ```ts
83
- * const btn = document.createElement('button');
84
- * console.log(trueEventName(btn, 'Click'));
85
- * console.log(trueEventName(btn, 'Update'));
86
- * ```
87
- *
88
- * `'Click'` resolves to the native `'click'` because the button has an `onclick` handler
89
- * property; `'Update'` has no native counterpart, so it becomes the `_mc`-prefixed custom
90
- * event name.
91
- */
92
- export function trueEventName(target, eventName) {
93
- const lower = eventName.toLowerCase();
94
- if ('on' + lower in target)
95
- return lower;
96
- return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
69
+ * Returns the correct event name to listen for on a DOM element.
70
+ *
71
+ * If `eventName` (case-insensitive) matches a native HTML event handler property
72
+ * (`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native
73
+ * name so that standard DOM events like `'click'` are listened to correctly.
74
+ * Otherwise, prefixes with `_mc` for microcomponent custom events.
75
+ *
76
+ * @hidden
77
+ * @param target - The DOM element whose native event support is checked.
78
+ * @param eventName - The event name as provided by the caller (e.g., `'click'`, `'Update'`).
79
+ * @returns The resolved event name: lowercase native or `_mc`-prefixed custom.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const btn = document.createElement('button');
84
+ * console.log(trueEventName(btn, 'Click'));
85
+ * console.log(trueEventName(btn, 'Update'));
86
+ * ```
87
+ *
88
+ * `'Click'` resolves to the native `'click'` because the button has an `onclick` handler
89
+ * property; `'Update'` has no native counterpart, so it becomes the `_mc`-prefixed custom
90
+ * event name.
91
+ */
92
+ function trueEventName(target, eventName) {
93
+ const lower = eventName.toLowerCase();
94
+ if ("on" + lower in target) return lower;
95
+ return "_mc" + eventName[0].toUpperCase() + eventName.substring(1);
97
96
  }
98
97
  /**
99
98
  Removes a previously attached event handler from the DOM node. See {@link on} and {@link one}.
@@ -113,19 +112,14 @@ off(myElement, 'click', handler);
113
112
  ```
114
113
  @group Events
115
114
  */
116
- export function off(target, eventName, handler) {
117
- /* v8 ignore next */
118
- if (!target)
119
- return;
120
- if (typeof target === 'string') {
121
- document
122
- .querySelectorAll(target)
123
- .forEach((el) => off(el, eventName, handler));
124
- return;
125
- }
126
- // McEventHandler<D> narrows the runtime EventListener contract type-side only;
127
- // the cast re-widens it at the DOM boundary.
128
- target.removeEventListener(trueEventName(target, eventName), handler);
115
+ function off(target, eventName, handler) {
116
+ /* v8 ignore next */
117
+ if (!target) return;
118
+ if (typeof target === "string") {
119
+ document.querySelectorAll(target).forEach((el) => off(el, eventName, handler));
120
+ return;
121
+ }
122
+ target.removeEventListener(trueEventName(target, eventName), handler);
129
123
  }
130
124
  /**
131
125
  Attaches a persistent event listener that will fire every time the specified event occurs.
@@ -145,27 +139,24 @@ The handler receives a `CustomEvent<McDetail<D>>`: the payload sits under
145
139
  @example
146
140
  ```ts
147
141
  const handler = on<{ value: number }>(myElement, 'update', (ev) => {
148
- console.log(ev.detail.data.value);
142
+ console.log(ev.detail.data.value);
149
143
  });
150
144
  off(myElement, 'update', handler);
151
145
  ```
152
146
  @group Events
153
147
  */
154
- export function on(target, eventName, handler, once = false) {
155
- /* v8 ignore next */
156
- if (!target)
157
- return;
158
- if (typeof target === 'string') {
159
- document
160
- .querySelectorAll(target)
161
- .forEach((el) => on(el, eventName, handler));
162
- return;
163
- }
164
- target.addEventListener(trueEventName(target, eventName), handler, {
165
- capture: false,
166
- once,
167
- });
168
- return handler;
148
+ function on(target, eventName, handler, once = false) {
149
+ /* v8 ignore next */
150
+ if (!target) return;
151
+ if (typeof target === "string") {
152
+ document.querySelectorAll(target).forEach((el) => on(el, eventName, handler));
153
+ return;
154
+ }
155
+ target.addEventListener(trueEventName(target, eventName), handler, {
156
+ capture: false,
157
+ once
158
+ });
159
+ return handler;
169
160
  }
170
161
  /**
171
162
  Attaches an event listener that will fire only once.
@@ -188,128 +179,111 @@ one<{ ready: boolean }>(myElement, 'dataReady', (ev) => console.log('received:',
188
179
  The listener fires only once and detaches automatically.
189
180
  @group Events
190
181
  */
191
- export function one(target, eventName, handler) {
192
- /* v8 ignore next */
193
- if (!target)
194
- return;
195
- if (typeof target === 'string') {
196
- document
197
- .querySelectorAll(target)
198
- .forEach((el) => one(el, eventName, handler));
199
- return;
200
- }
201
- on(target, eventName, handler, true); // true indicates once
202
- return handler;
182
+ function one(target, eventName, handler) {
183
+ /* v8 ignore next */
184
+ if (!target) return;
185
+ if (typeof target === "string") {
186
+ document.querySelectorAll(target).forEach((el) => one(el, eventName, handler));
187
+ return;
188
+ }
189
+ on(target, eventName, handler, true);
190
+ return handler;
203
191
  }
204
192
  /**
205
- * @hidden
206
- * A controller object used to manage and potentially abort event bubbling.
207
- *
208
- * The `trigger()` function internally creates instance of this class,
209
- * allowing the an eventual handler to call `ev.detail.control.stop()` to
210
- * prevent further propagation in either direction (up/down).
211
- *
212
- * This is only used internally.
213
- * @example
214
- * ```ts
215
- * trigger(myElement, 'update', { value: 42 }, 'u');
216
- *
217
- * onUpdate(ev) {
218
- * ev.detail.control.stop();
219
- * }
220
- * ```
221
- */
222
- export class BubbleController {
223
- stopped = false;
224
- constructor() { }
225
- /**
226
- * Signals that event bubbling should be halted.
227
- *
228
- * After calling this method, subsequent `trigger()` calls will abort
229
- * before dispatching the event to any targets.
230
- *
231
- * @example
232
- * ```ts
233
- * trigger(myElement, 'update', { value: 42 }, 'u');
234
- *
235
- * onUpdate(ev) {
236
- * ev.detail.control.stop();
237
- * }
238
- * ```
239
- */
240
- stop() {
241
- this.stopped = true;
242
- }
243
- }
193
+ * @hidden
194
+ * A controller object used to manage and potentially abort event bubbling.
195
+ *
196
+ * The `trigger()` function internally creates instance of this class,
197
+ * allowing the an eventual handler to call `ev.detail.control.stop()` to
198
+ * prevent further propagation in either direction (up/down).
199
+ *
200
+ * This is only used internally.
201
+ * @example
202
+ * ```ts
203
+ * trigger(myElement, 'update', { value: 42 }, 'u');
204
+ *
205
+ * onUpdate(ev) {
206
+ * ev.detail.control.stop();
207
+ * }
208
+ * ```
209
+ */
210
+ var BubbleController = class {
211
+ stopped = false;
212
+ constructor() {}
213
+ /**
214
+ * Signals that event bubbling should be halted.
215
+ *
216
+ * After calling this method, subsequent `trigger()` calls will abort
217
+ * before dispatching the event to any targets.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * trigger(myElement, 'update', { value: 42 }, 'u');
222
+ *
223
+ * onUpdate(ev) {
224
+ * ev.detail.control.stop();
225
+ * }
226
+ * ```
227
+ */
228
+ stop() {
229
+ this.stopped = true;
230
+ }
231
+ };
244
232
  /**
245
- * Creates and dispatches a custom event on the specified DOM node.
246
- *
247
- * The event is namespaced with an `_mc` prefix by default, unless already prefixed.
248
- * Supports bidirectional bubbling (up/down the DOM tree) based on the `bubble` parameter:
249
- * - 'l': local dispatch only
250
- * - 'u': bubble up towards ancestors
251
- * - 'd': bubble down to descendants
252
- *
253
- * The dispatched event is a `CustomEvent<McDetail<D>>`: handlers find the payload
254
- * under `ev.detail.data` and can call `ev.detail.control.stop()` to cancel bubbling.
255
- *
256
- * Prefer a direct target over bubbling: name the receiving element in `target` and keep the
257
- * default `'l'`, so one `querySelectorAll` reaches it in a single step. Reserve `'u'`/`'d'`
258
- * for receivers that cannot be named in advance, since they walk the tree and re-dispatch the
259
- * event at every level. Pass a typed object as `data` rather than a bare string, so the
260
- * handler reads named fields from `ev.detail.data` and the flow stays readable as it grows.
261
- *
262
- * @param target - The DOM element on which to trigger the event, or a querySelectorAll(selector) selector string.
263
- * @param event - The name of the event (will be prefixed if needed).
264
- * @param data - Additional data payload attached to `event.detail.data`.
265
- * @param bubble - Bubbling direction ('l', 'u', 'd' or combinations).
266
- * @param controller - Optional controller for managing bubbling behavior.
267
- *
268
- * @example
269
- * ```ts
270
- * trigger(myElement, 'update', { value: 42 }, 'l');
271
- * trigger(myElement, 'dataReady', { ready: true }, 'u');
272
- * ```
273
- *
274
- * The first dispatch fires locally only; the second bubbles upward to ancestor components.
275
- */
276
- export function trigger(target, event, data = {}, bubble = 'l', // direction of bubbling
277
- controller) {
278
- /* v8 ignore next */
279
- if (!target)
280
- return;
281
- if (typeof target === 'string') {
282
- document
283
- .querySelectorAll(target)
284
- .forEach((el) => trigger(el, event, data, bubble));
285
- return;
286
- }
287
- const eventName = /^_mc/.test(event) ? event : trueEventName(target, event), bubbleController = controller ? controller : new BubbleController(), ev = new CustomEvent(eventName, {
288
- detail: { data, control: bubbleController },
289
- bubbles: false,
290
- });
291
- if (bubbleController.stopped) {
292
- return;
293
- }
294
- if (/l/.test(bubble)) {
295
- target.dispatchEvent(ev);
296
- if (bubbleController.stopped) {
297
- return;
298
- }
299
- }
300
- // bubble if up or down is indicated
301
- if (/[ud]/.test(bubble)) {
302
- queueMicrotask(() => {
303
- // bubble up towards document.body
304
- if (bubble.indexOf('u') > -1) {
305
- trigger(target.parentElement, eventName, data, 'lu', bubbleController);
306
- }
307
- // bubble down towards DOM children
308
- if (bubble.indexOf('d') > -1) {
309
- for (const child of target.children) {
310
- trigger(child, eventName, data, 'ld'); // event multiplex - dont carry bubble controller
311
- }
312
- }
313
- });
314
- }
233
+ * Creates and dispatches a custom event on the specified DOM node.
234
+ *
235
+ * The event is namespaced with an `_mc` prefix by default, unless already prefixed.
236
+ * Supports bidirectional bubbling (up/down the DOM tree) based on the `bubble` parameter:
237
+ * - 'l': local dispatch only
238
+ * - 'u': bubble up towards ancestors
239
+ * - 'd': bubble down to descendants
240
+ *
241
+ * The dispatched event is a `CustomEvent<McDetail<D>>`: handlers find the payload
242
+ * under `ev.detail.data` and can call `ev.detail.control.stop()` to cancel bubbling.
243
+ *
244
+ * Prefer a direct target over bubbling: name the receiving element in `target` and keep the
245
+ * default `'l'`, so one `querySelectorAll` reaches it in a single step. Reserve `'u'`/`'d'`
246
+ * for receivers that cannot be named in advance, since they walk the tree and re-dispatch the
247
+ * event at every level. Pass a typed object as `data` rather than a bare string, so the
248
+ * handler reads named fields from `ev.detail.data` and the flow stays readable as it grows.
249
+ *
250
+ * @param target - The DOM element on which to trigger the event, or a querySelectorAll(selector) selector string.
251
+ * @param event - The name of the event (will be prefixed if needed).
252
+ * @param data - Additional data payload attached to `event.detail.data`.
253
+ * @param bubble - Bubbling direction ('l', 'u', 'd' or combinations).
254
+ * @param controller - Optional controller for managing bubbling behavior.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * trigger(myElement, 'update', { value: 42 }, 'l');
259
+ * trigger(myElement, 'dataReady', { ready: true }, 'u');
260
+ * ```
261
+ *
262
+ * The first dispatch fires locally only; the second bubbles upward to ancestor components.
263
+ */
264
+ function trigger(target, event, data = {}, bubble = "l", controller) {
265
+ /* v8 ignore next */
266
+ if (!target) return;
267
+ if (typeof target === "string") {
268
+ document.querySelectorAll(target).forEach((el) => trigger(el, event, data, bubble));
269
+ return;
270
+ }
271
+ const eventName = /^_mc/.test(event) ? event : trueEventName(target, event), bubbleController = controller ? controller : new BubbleController(), ev = new CustomEvent(eventName, {
272
+ detail: {
273
+ data,
274
+ control: bubbleController
275
+ },
276
+ bubbles: false
277
+ });
278
+ if (bubbleController.stopped) return;
279
+ if (/l/.test(bubble)) {
280
+ target.dispatchEvent(ev);
281
+ if (bubbleController.stopped) return;
282
+ }
283
+ if (/[ud]/.test(bubble)) queueMicrotask(() => {
284
+ if (bubble.indexOf("u") > -1) trigger(target.parentElement, eventName, data, "lu", bubbleController);
285
+ if (bubble.indexOf("d") > -1) for (const child of target.children) trigger(child, eventName, data, "ld");
286
+ });
315
287
  }
288
+ //#endregion
289
+ export { BubbleController, getMcEventName, off, on, one, trigger, trueEventName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twobirds/microcomponents",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Micro Components",
5
5
  "keywords": [
6
6
  "PWA",
@@ -73,17 +73,13 @@
73
73
  "types": "./dist/mcp/index.d.ts",
74
74
  "import": "./dist/mcp/index.js"
75
75
  },
76
- "./vite": {
77
- "types": "./dist/vite/index.d.ts",
78
- "import": "./dist/vite/index.js"
79
- },
80
76
  "./package.json": "./package.json"
81
77
  },
82
78
  "scripts": {
83
79
  "format": "oxfmt .",
84
80
  "lint": "oxlint \"src\"",
85
81
  "fix": "oxfmt . && oxlint \"src\" --fix",
86
- "check": "tsc --noEmit -p tsconfig.lib.json && tsc --noEmit -p tsconfig.cli.json && tsc --noEmit -p tsconfig.mcp.json && tsc -p tsconfig.test.json && tsc -p demo/web-test/tsconfig.demo.json && tsc --noEmit -p demo/shop/tsconfig.demo.json && tsc --noEmit -p demo/todo_mvc/tsconfig.demo.json && tsc -p tsconfig.node.json",
82
+ "check": "tsc --noEmit -p tsconfig.lib.json && tsc --noEmit -p tsconfig.cli.json && tsc --noEmit -p tsconfig.mcp.json && tsc -p tsconfig.test.json && tsc -p demo/web-test/tsconfig.demo.json && tsc --noEmit -p demo/shop/tsconfig.demo.json && tsc --noEmit -p demo/todo_mvc/tsconfig.demo.json && tsc --noEmit -p demo/docs/tsconfig.demo.json && tsc -p tsconfig.node.json",
87
83
  "check:pack": "publint && attw --pack . --profile esm-only",
88
84
  "test": "vitest",
89
85
  "test:run": "vitest run --reporter=verbose",
@@ -95,11 +91,14 @@
95
91
  "demo:todo": "vite --config demo/todo_mvc/vite.config.ts",
96
92
  "demo:todo:build": "tsx demo/build-demo.ts todo_mvc",
97
93
  "demo:todo:serve": "vite preview --outDir public/todo_mvc --port 5175 --host",
94
+ "demo:docs": "vite --config demo/docs/vite.config.ts",
95
+ "demo:docs:build": "tsx demo/docs/vite.docs.bundle.ts",
96
+ "demo:docs:serve": "vite preview --outDir public --port 5176 --host",
98
97
  "typedoc": "typedoc --displayBasePath ./typedoc/",
99
98
  "bundle": "tsx vite.build.ts",
100
99
  "clean": "rm -rf dist public build coverage",
101
- "build": "pnpm run clean && tsc -p tsconfig.lib.json && tsc -p tsconfig.cli.json && chmod +x dist/cli/index.js && pnpm run mcp:build && pnpm run test:coverage && pnpm run bundle && pnpm run check:pack && pnpm run typedoc && pnpm run demo:build && pnpm run demo:shop:build && pnpm run demo:todo:build && find src -name '*.js' -delete",
102
- "mcp:build": "tsc -p tsconfig.mcp.json && node mcp-docs-generate.ts && cp -r docs/concepts dist/mcp/concepts && chmod +x dist/mcp/index.js",
100
+ "build": "pnpm run clean && tsc --emitDeclarationOnly -p tsconfig.lib.json && tsc --emitDeclarationOnly -p tsconfig.cli.json && tsc --emitDeclarationOnly -p tsconfig.mcp.json && pnpm run bundle && chmod +x dist/cli/index.js dist/mcp/index.js && pnpm run mcp:docs && cp -r docs/concepts dist/mcp/concepts && pnpm run test:coverage && pnpm run check:pack && pnpm run typedoc && pnpm run demo:build && pnpm run demo:shop:build && pnpm run demo:todo:build && pnpm run demo:docs:build",
101
+ "mcp:build": "node mcp-docs-generate.ts && cp -r docs/concepts dist/mcp/concepts",
103
102
  "demo:build": "tsx demo/web-test/vite.demo.ts",
104
103
  "dev": "vite preview --outDir build/web-test --port 10000 --host",
105
104
  "doc": "vite preview --outDir public/typedoc --port 11000 --host",