@twobirds/microcomponents 0.2.0 → 0.2.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.
@@ -57,7 +57,6 @@ off( grandParent, 'dataReady', grandParentHandler );
57
57
  ```
58
58
  */
59
59
  'use strict';
60
- import { trueEventName } from '../utils/helpers.js';
61
60
  /**
62
61
  * Converts a standard event name to its microcomponent-prefixed variant.
63
62
  *
@@ -74,6 +73,32 @@ import { trueEventName } from '../utils/helpers.js';
74
73
  export function getMcEventName(eventName) {
75
74
  return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
76
75
  }
76
+ /**
77
+ * Returns the correct event name to listen for on a DOM element.
78
+ *
79
+ * If `eventName` (case-insensitive) matches a native HTML event handler property
80
+ * (`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native
81
+ * name so that standard DOM events like `'click'` are listened to correctly.
82
+ * Otherwise, prefixes with `_mc` for microcomponent custom events.
83
+ *
84
+ * @hidden
85
+ * @param target - The DOM element whose native event support is checked.
86
+ * @param eventName - The event name as provided by the caller (e.g., `'click'`, `'Update'`).
87
+ * @returns The resolved event name: lowercase native or `_mc`-prefixed custom.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const btn = document.createElement('button');
92
+ * trueEventName(btn, 'Click'); // 'click' — native handler exists
93
+ * trueEventName(btn, 'Update'); // '_mcUpdate' — custom event
94
+ * ```
95
+ */
96
+ export function trueEventName(target, eventName) {
97
+ const lower = eventName.toLowerCase();
98
+ if ('on' + lower in target)
99
+ return lower;
100
+ return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
101
+ }
77
102
  export function off(target, eventName, handler) {
78
103
  /* v8 ignore next */
79
104
  if (!target)
@@ -5,6 +5,7 @@ Small runtime-agnostic utilities shared across the library: environment
5
5
  detection, object flattening/comparison, debouncing, accessor copying, and
6
6
  kebab-to-PascalCase conversion.
7
7
  */
8
+ import type { McController } from '../types/mcTypes.js';
8
9
  /**
9
10
  * Determines whether the current runtime environment is a web browser.
10
11
  *
@@ -147,6 +148,7 @@ export declare function debounce<A extends unknown[]>(func: (...args: A) => unkn
147
148
  /**
148
149
  * Parses an HTML string and returns an array of DOM elements.
149
150
  * Handles self-closing tags (XHTML style) by converting them to full closing tags first.
151
+ * @hidden
150
152
  * @param html - The HTML string to parse.
151
153
  * @returns An array of HTMLElement objects representing the parsed HTML nodes.
152
154
  *
@@ -157,29 +159,10 @@ export declare function debounce<A extends unknown[]>(func: (...args: A) => unkn
157
159
  * ```
158
160
  */
159
161
  export declare function htmlToElements(html: string): HTMLElement[];
160
- /**
161
- * Returns the correct event name to listen for on a DOM element.
162
- *
163
- * If `eventName` (case-insensitive) matches a native HTML event handler property
164
- * (`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native
165
- * name so that standard DOM events like `'click'` are listened to correctly.
166
- * Otherwise, prefixes with `_mc` for microcomponent custom events.
167
- *
168
- * @param target - The DOM element whose native event support is checked.
169
- * @param eventName - The event name as provided by the caller (e.g., `'click'`, `'Update'`).
170
- * @returns The resolved event name: lowercase native or `_mc`-prefixed custom.
171
- *
172
- * @example
173
- * ```ts
174
- * const btn = document.createElement('button');
175
- * trueEventName(btn, 'Click'); // 'click' — native handler exists
176
- * trueEventName(btn, 'Update'); // '_mcUpdate' — custom event
177
- * ```
178
- */
179
- export declare function trueEventName(target: HTMLElement, eventName: string): string;
180
162
  /**
181
163
  * Copies all getter and setter properties from a source object to a target object.
182
164
  * Does not copy methods or regular data properties—only accessor properties (get/set).
165
+ * @hidden
183
166
  * @param source - The source object with getters and/or setters to copy.
184
167
  * @param target - The target object to which the accessors will be copied.
185
168
  *
@@ -192,4 +175,106 @@ export declare function trueEventName(target: HTMLElement, eventName: string): s
192
175
  * ```
193
176
  */
194
177
  export declare function copyGettersSetters(source: object, target: object): void;
178
+ /**
179
+ * Injects a CSS string into the given scope (document or shadow root) as an adopted stylesheet.
180
+ * Enables css-in-js so that every custom element is self-contained and importable.
181
+ *
182
+ * @hidden
183
+ * @param target - The scope to inject styles into: `document` for global styles, or a `ShadowRoot` for encapsulated component internals.
184
+ * @param css - The CSS string to inject.
185
+ * @returns The created `CSSStyleSheet` instance that was adopted into the target scope.
186
+ *
187
+ * When targeting **document**: selectors like `:host`, `::slotted()`, and shadow-internal classes are **not allowed** — they have no effect outside a shadow root. Use only global selectors, element tags, or layout presets.
188
+ * When targeting **shadowRoot**: use `:host` for the custom element itself, standard selectors (`.inner`, `#id`) for internal structure, and `::slotted()` for projected light-DOM content.
189
+ *
190
+ * @example Inject global styles into the document:
191
+ * ```ts
192
+ * import { injectCSS } from '@twobirds/mc/utils/helpers';
193
+ * injectCSS(document, 'body { display: block; }');
194
+ * ```
195
+ *
196
+ * @example Inject global styles for a micro component into the document:
197
+ * ```ts
198
+ * import { injectCSS } from '@twobirds/mc/utils/helpers';
199
+ * injectCSS(document, 'my-microcomponent { display: block; }');
200
+ * ```
201
+ *
202
+ * @example Inject scoped styles into a shadow root:
203
+ * ```ts
204
+ * const shadow = element.attachShadow({ mode: 'open' });
205
+ * injectCSS(shadow, ':host { color: var(--text); } .inner { padding: 8px; }');
206
+ * ```
207
+ * Styles can also be added using a compound innerHTML statement: `shadow.innerHTML = \`<style>${style}</style>${template}\``
208
+ */
209
+ export declare function injectCSS(target: Document | ShadowRoot, css: string): CSSStyleSheet;
210
+ /**
211
+ * Processes a record of asset strings and applies them to the given target element.
212
+ * Supports injecting CSS into document or shadow root, and setting innerHTML for
213
+ * both target and shadow DOM. Automatically attaches a shadow root if any key starts
214
+ * with `'shadow'`.
215
+ *
216
+ * Recognized keys: `'css'`, `'targetCss'` (document-level styles), `'shadowCss'`
217
+ * (shadow-root styles), `'shadowHtml'` (shadow innerHTML), `'targetHtml'` (target innerHTML).
218
+ *
219
+ * @param target - The HTMLElement to apply assets onto.
220
+ * @param assets - A record mapping asset type keys to their string content.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * processAssets(this, {
225
+ * css: 'body { margin: 0; }',
226
+ * shadowCss: ':host { display: block; }',
227
+ * shadowHtml: '<slot></slot>'
228
+ * });
229
+ * ```
230
+ */
231
+ export declare function processAssets(instance: McController, assets: Record<string, string>): void;
232
+ /**
233
+ * Loads companion asset files (CSS, HTML) for a given component basename from the server.
234
+ *
235
+ * Each entry in `assets` encodes an optional variant and extension in camelCase, e.g. `'shadowCss'` → `{name}.shadow.css`,
236
+ * `'targetHtml'` → `{name}.target.html`. Plain entries like `'css'` or `'html'` resolve to
237
+ * `{name}.css` / `{name}.html`.
238
+ *
239
+ * In bundled mode the Vite plugin replaces this call with an inline array literal, so `.then()`
240
+ * fires synchronously — no async overhead.
241
+ *
242
+ * @hidden
243
+ * @param name - The component basename (e.g. `'shop-catalog'`).
244
+ * @param assets - Array of asset descriptors such as `'shadowCss'`, `'targetHtml'`, `'shadowHtml'`, `'css'`, `'html'`.
245
+ * @returns A promise resolving to an array of file contents in the same order as `assets`. If a file is not found, the corresponding entry is `'file not found: ' + filename`.
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * export const MyComponent = loadAssets('my-component', ['shadowCss', 'shadowHtml']).then(([shadowCss, shadowHtml]) => {
250
+ * class _MyComponent extends DC { ... }
251
+ * defineCE('my-component', _MyComponent);
252
+ * return _MyComponent;
253
+ * });
254
+ * ```
255
+ */
256
+ export declare function loadAssets(name: string, assets: string[]): Promise<string[]>;
257
+ /**
258
+ * Binds declarative event attributes in a template fragment to microcomponent triggers.
259
+ *
260
+ * Scans all elements under `root` for attributes whose names correspond to native DOM
261
+ * events (e.g. `click`, `submit`, `change`). The attribute value encodes the target and
262
+ * handler method in the form `target#methodName`:
263
+ *
264
+ * - `this#myHandler` — triggers locally on the component attached to this element.
265
+ * - `#other-comp#submit` — resolves elements via `querySelectorAll('#other-comp')` and
266
+ * triggers the event on each matched host's `_mc`.
267
+ *
268
+ * An optional sibling `payload` attribute supplies the data argument for `trigger()`.
269
+ * After binding, both the event attribute and the payload attribute are removed from the DOM.
270
+ *
271
+ * @param root - The container element whose descendants should be scanned for bindings.
272
+ *
273
+ * @example
274
+ * ```html
275
+ * <button click="this#onSubmit" payload='{"action":"go"}'>Submit</button>
276
+ * <input change="#cart-form#refresh">
277
+ * ```
278
+ */
279
+ export declare function bindTemplateEvents(root: HTMLElement | ShadowRoot, instance: McController): void;
195
280
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAiB1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqB7F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAiCzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,GAAE,MAAW,GAAG,OAAO,CAe1E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,KAAK,CAAC,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,EACpD,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,MAAM,EACjB,GAAG,IAAI,EAAE,OAAO,EAAE,GAChB,CAAC,CAgCH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,UAKxC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAC3C,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,EAC7B,YAAY,EAAE,MAAM,GAClB,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAStB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAU1D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAK5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EAAE,YAAY,EAAU,MAAM,qBAAqB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAiB1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqB7F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAiCzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,GAAE,MAAW,GAAG,OAAO,CAe1E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,KAAK,CAAC,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,EACpD,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,MAAM,EACjB,GAAG,IAAI,EAAE,OAAO,EAAE,GAChB,CAAC,CAgCH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,UAKxC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAC3C,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,EAC7B,YAAY,EAAE,MAAM,GAClB,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAStB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAU1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAOnF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA4B1F;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwB5E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CA0C/F"}
@@ -5,6 +5,7 @@ Small runtime-agnostic utilities shared across the library: environment
5
5
  detection, object flattening/comparison, debouncing, accessor copying, and
6
6
  kebab-to-PascalCase conversion.
7
7
  */
8
+ import { on, trigger } from './events.js';
8
9
  /**
9
10
  * Determines whether the current runtime environment is a web browser.
10
11
  *
@@ -275,6 +276,7 @@ export function debounce(func, milliseconds) {
275
276
  /**
276
277
  * Parses an HTML string and returns an array of DOM elements.
277
278
  * Handles self-closing tags (XHTML style) by converting them to full closing tags first.
279
+ * @hidden
278
280
  * @param html - The HTML string to parse.
279
281
  * @returns An array of HTMLElement objects representing the parsed HTML nodes.
280
282
  *
@@ -291,34 +293,10 @@ export function htmlToElements(html) {
291
293
  template.content.normalize();
292
294
  return Array.from(template.content.childNodes);
293
295
  }
294
- /**
295
- * Returns the correct event name to listen for on a DOM element.
296
- *
297
- * If `eventName` (case-insensitive) matches a native HTML event handler property
298
- * (`'on' + eventName.toLowerCase()`) on the target, returns the lowercase native
299
- * name so that standard DOM events like `'click'` are listened to correctly.
300
- * Otherwise, prefixes with `_mc` for microcomponent custom events.
301
- *
302
- * @param target - The DOM element whose native event support is checked.
303
- * @param eventName - The event name as provided by the caller (e.g., `'click'`, `'Update'`).
304
- * @returns The resolved event name: lowercase native or `_mc`-prefixed custom.
305
- *
306
- * @example
307
- * ```ts
308
- * const btn = document.createElement('button');
309
- * trueEventName(btn, 'Click'); // 'click' — native handler exists
310
- * trueEventName(btn, 'Update'); // '_mcUpdate' — custom event
311
- * ```
312
- */
313
- export function trueEventName(target, eventName) {
314
- const lower = eventName.toLowerCase();
315
- if ('on' + lower in target)
316
- return lower;
317
- return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
318
- }
319
296
  /**
320
297
  * Copies all getter and setter properties from a source object to a target object.
321
298
  * Does not copy methods or regular data properties—only accessor properties (get/set).
299
+ * @hidden
322
300
  * @param source - The source object with getters and/or setters to copy.
323
301
  * @param target - The target object to which the accessors will be copied.
324
302
  *
@@ -338,3 +316,199 @@ export function copyGettersSetters(source, target) {
338
316
  }
339
317
  }
340
318
  }
319
+ /**
320
+ * Injects a CSS string into the given scope (document or shadow root) as an adopted stylesheet.
321
+ * Enables css-in-js so that every custom element is self-contained and importable.
322
+ *
323
+ * @hidden
324
+ * @param target - The scope to inject styles into: `document` for global styles, or a `ShadowRoot` for encapsulated component internals.
325
+ * @param css - The CSS string to inject.
326
+ * @returns The created `CSSStyleSheet` instance that was adopted into the target scope.
327
+ *
328
+ * When targeting **document**: selectors like `:host`, `::slotted()`, and shadow-internal classes are **not allowed** — they have no effect outside a shadow root. Use only global selectors, element tags, or layout presets.
329
+ * When targeting **shadowRoot**: use `:host` for the custom element itself, standard selectors (`.inner`, `#id`) for internal structure, and `::slotted()` for projected light-DOM content.
330
+ *
331
+ * @example Inject global styles into the document:
332
+ * ```ts
333
+ * import { injectCSS } from '@twobirds/mc/utils/helpers';
334
+ * injectCSS(document, 'body { display: block; }');
335
+ * ```
336
+ *
337
+ * @example Inject global styles for a micro component into the document:
338
+ * ```ts
339
+ * import { injectCSS } from '@twobirds/mc/utils/helpers';
340
+ * injectCSS(document, 'my-microcomponent { display: block; }');
341
+ * ```
342
+ *
343
+ * @example Inject scoped styles into a shadow root:
344
+ * ```ts
345
+ * const shadow = element.attachShadow({ mode: 'open' });
346
+ * injectCSS(shadow, ':host { color: var(--text); } .inner { padding: 8px; }');
347
+ * ```
348
+ * Styles can also be added using a compound innerHTML statement: `shadow.innerHTML = \`<style>${style}</style>${template}\``
349
+ */
350
+ export function injectCSS(target, css) {
351
+ const sheet = new CSSStyleSheet();
352
+ sheet.replaceSync(css);
353
+ target.adoptedStyleSheets.push(sheet);
354
+ return sheet;
355
+ }
356
+ /**
357
+ * Processes a record of asset strings and applies them to the given target element.
358
+ * Supports injecting CSS into document or shadow root, and setting innerHTML for
359
+ * both target and shadow DOM. Automatically attaches a shadow root if any key starts
360
+ * with `'shadow'`.
361
+ *
362
+ * Recognized keys: `'css'`, `'targetCss'` (document-level styles), `'shadowCss'`
363
+ * (shadow-root styles), `'shadowHtml'` (shadow innerHTML), `'targetHtml'` (target innerHTML).
364
+ *
365
+ * @param target - The HTMLElement to apply assets onto.
366
+ * @param assets - A record mapping asset type keys to their string content.
367
+ *
368
+ * @example
369
+ * ```ts
370
+ * processAssets(this, {
371
+ * css: 'body { margin: 0; }',
372
+ * shadowCss: ':host { display: block; }',
373
+ * shadowHtml: '<slot></slot>'
374
+ * });
375
+ * ```
376
+ */
377
+ export function processAssets(instance, assets) {
378
+ const target = instance.target;
379
+ let shadow = target.shadowRoot;
380
+ if (!shadow && Object.keys(assets).some((key) => key.indexOf('shadow') === 0)) {
381
+ shadow = target.attachShadow({ mode: 'open' });
382
+ }
383
+ Object.keys(assets).forEach((key) => {
384
+ switch (key) {
385
+ case 'css':
386
+ injectCSS(document, assets[key]);
387
+ break;
388
+ case 'targetCss':
389
+ injectCSS(document, assets[key]);
390
+ break;
391
+ case 'shadowCss':
392
+ injectCSS(shadow, assets[key]);
393
+ break;
394
+ case 'shadowHtml':
395
+ shadow.innerHTML = assets[key];
396
+ bindTemplateEvents(shadow, instance);
397
+ break;
398
+ case 'targetHtml':
399
+ target.innerHTML = assets[key];
400
+ bindTemplateEvents(target, instance);
401
+ break;
402
+ }
403
+ });
404
+ }
405
+ /**
406
+ * Loads companion asset files (CSS, HTML) for a given component basename from the server.
407
+ *
408
+ * Each entry in `assets` encodes an optional variant and extension in camelCase, e.g. `'shadowCss'` → `{name}.shadow.css`,
409
+ * `'targetHtml'` → `{name}.target.html`. Plain entries like `'css'` or `'html'` resolve to
410
+ * `{name}.css` / `{name}.html`.
411
+ *
412
+ * In bundled mode the Vite plugin replaces this call with an inline array literal, so `.then()`
413
+ * fires synchronously — no async overhead.
414
+ *
415
+ * @hidden
416
+ * @param name - The component basename (e.g. `'shop-catalog'`).
417
+ * @param assets - Array of asset descriptors such as `'shadowCss'`, `'targetHtml'`, `'shadowHtml'`, `'css'`, `'html'`.
418
+ * @returns A promise resolving to an array of file contents in the same order as `assets`. If a file is not found, the corresponding entry is `'file not found: ' + filename`.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * export const MyComponent = loadAssets('my-component', ['shadowCss', 'shadowHtml']).then(([shadowCss, shadowHtml]) => {
423
+ * class _MyComponent extends DC { ... }
424
+ * defineCE('my-component', _MyComponent);
425
+ * return _MyComponent;
426
+ * });
427
+ * ```
428
+ */
429
+ export function loadAssets(name, assets) {
430
+ const parseAsset = (asset) => {
431
+ // Match camelCase variant+ext, e.g. 'shadowCss' → ['shadow','css']
432
+ const m = asset.match(/^([a-z]+)([A-Z][a-z]*)$/);
433
+ if (m) {
434
+ return `./${name.replace(/-/g, '/')}.${m[1]}.${m[2].toLowerCase()}`;
435
+ }
436
+ // Plain extension, e.g. 'css' → 'catalog.css'
437
+ return `./${name.replace(/-/g, '/')}.${asset.toLowerCase()}`;
438
+ };
439
+ return Promise.all(assets.map(async (asset) => {
440
+ const filename = parseAsset(asset);
441
+ //console.log(filename);
442
+ try {
443
+ const resp = await fetch(filename);
444
+ if (!resp.ok) {
445
+ return `file not found: ${filename}`;
446
+ }
447
+ return resp.text();
448
+ }
449
+ catch {
450
+ return `file not found: ${filename}`;
451
+ }
452
+ }));
453
+ }
454
+ /**
455
+ * Binds declarative event attributes in a template fragment to microcomponent triggers.
456
+ *
457
+ * Scans all elements under `root` for attributes whose names correspond to native DOM
458
+ * events (e.g. `click`, `submit`, `change`). The attribute value encodes the target and
459
+ * handler method in the form `target#methodName`:
460
+ *
461
+ * - `this#myHandler` — triggers locally on the component attached to this element.
462
+ * - `#other-comp#submit` — resolves elements via `querySelectorAll('#other-comp')` and
463
+ * triggers the event on each matched host's `_mc`.
464
+ *
465
+ * An optional sibling `payload` attribute supplies the data argument for `trigger()`.
466
+ * After binding, both the event attribute and the payload attribute are removed from the DOM.
467
+ *
468
+ * @param root - The container element whose descendants should be scanned for bindings.
469
+ *
470
+ * @example
471
+ * ```html
472
+ * <button click="this#onSubmit" payload='{"action":"go"}'>Submit</button>
473
+ * <input change="#cart-form#refresh">
474
+ * ```
475
+ */
476
+ export function bindTemplateEvents(root, instance) {
477
+ // Collect all elements that carry at least one attribute matching a native event name.
478
+ const allElements = root.querySelectorAll('*');
479
+ for (const el of allElements) {
480
+ if (!(el instanceof HTMLElement))
481
+ continue;
482
+ const attrs = Array.from(el.attributes);
483
+ for (const attr of attrs) {
484
+ const lowerName = attr.name.toLowerCase();
485
+ // Check if this attribute name maps to a native event handler property.
486
+ if (!('on' + lowerName in el))
487
+ continue;
488
+ const [selector, event, payload] = attr.value.trim().split('#');
489
+ if (!event)
490
+ continue; // need at least selector and eventname
491
+ // Resolve payload from sibling attribute if present.
492
+ let payloadData; // intentional
493
+ if (payload) {
494
+ try {
495
+ payloadData = JSON.parse(payload);
496
+ }
497
+ catch {
498
+ payloadData = payload;
499
+ }
500
+ }
501
+ // Wire the native event listener.
502
+ if (selector === 'this' || selector === '') {
503
+ // Local trigger — fire on the _mc attached to this element.
504
+ on(el, lowerName, () => instance.trigger(event, payloadData));
505
+ }
506
+ else {
507
+ // Selector-based trigger — query and fire on every match.
508
+ on(el, lowerName, () => trigger(selector, event, payloadData));
509
+ }
510
+ // Clean up binding attributes from the DOM.
511
+ el.removeAttribute(attr.name);
512
+ }
513
+ }
514
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twobirds/microcomponents",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Micro Components Organization Class",
5
5
  "keywords": [
6
6
  "API",
@@ -51,8 +51,8 @@
51
51
  "import": "./dist/core/mcGuards.js"
52
52
  },
53
53
  "./events": {
54
- "types": "./dist/core/events.d.ts",
55
- "import": "./dist/core/events.js"
54
+ "types": "./dist/utils/events.d.ts",
55
+ "import": "./dist/utils/events.js"
56
56
  },
57
57
  "./elements": {
58
58
  "types": "./dist/core/elements.d.ts",
@@ -78,17 +78,17 @@
78
78
  "test:run": "vitest run --reporter=verbose",
79
79
  "test:coverage": "vitest run --coverage",
80
80
  "demo:shop": "vite --config demo/shop/vite.config.ts",
81
- "demo:shop:bundled": "node vite.shop.bundle.ts",
82
- "demo:shop:build": "node vite.shop.ts && pnpm run demo:shop:bundled",
81
+ "demo:shop:bundled": "tsx vite.shop.bundle.ts",
82
+ "demo:shop:build": "tsx vite.shop.ts && pnpm run demo:shop:bundled",
83
83
  "demo:shop:serve": "vite preview --outDir public/shop --port 5174 --host",
84
84
  "typedoc": "typedoc",
85
- "bundle": "node vite.build.ts",
86
- "clean": "rm -rf dist public",
87
- "build": "pnpm run clean && tsc -p tsconfig.lib.json && pnpm run test:coverage && pnpm run bundle && pnpm run check:pack && pnpm run typedoc && pnpm run demo:build && pnpm run demo:shop:build",
88
- "demo:build": "node vite.demo.ts",
85
+ "bundle": "tsx vite.build.ts",
86
+ "clean": "rm -rf dist public build",
87
+ "build": "pnpm run clean && tsc -p tsconfig.lib.json && pnpm run test:coverage && pnpm run bundle && pnpm run check:pack && pnpm run typedoc && pnpm run demo:build && pnpm run demo:shop:build && find src -name '*.js' -delete",
88
+ "demo:build": "tsx vite.demo.ts",
89
89
  "dev": "vite preview --outDir public/test --port 10000 --host",
90
90
  "doc": "vite preview --outDir public/typedoc --port 11000 --host",
91
- "watch": "node watch.ts",
91
+ "watch": "tsx watch.ts",
92
92
  "prepublishOnly": "pnpm run build && pnpm run check"
93
93
  },
94
94
  "devDependencies": {
@@ -99,6 +99,7 @@
99
99
  "oxfmt": "^0.59.0",
100
100
  "oxlint": "^1.74.0",
101
101
  "publint": "^0.3.21",
102
+ "tsx": "^4.23.12",
102
103
  "typedoc": "^0.28.20",
103
104
  "typescript": "^6.0.3",
104
105
  "vite": "^8.1.5",
@@ -1 +0,0 @@
1
- {"version":3,"file":"autoload.d.ts","sourceRoot":"","sources":["../../src/core/autoload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;EASE;AAoJF;;;;;;;;;;;;EAYE;AACF,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;;;;;IAaG;IACH,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC5B;;;;;IAKG;IACH,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,MAAM;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;KACpB;CACD;AAID,eAAO,MAAM,QAAQ,EAAE,QAuBtB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDE;AAIF,OAAO,KAAK,EACX,gBAAgB,EAChB,kBAAkB,EAElB,cAAc,EACd,MAAM,qBAAqB,CAAC;AAG7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,mBAAmB,GAAG,SAAS,GAAG,IAAI,CAAC;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,GAAG,CAAC,CAAC,GAAG,OAAO,EAC9B,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACxB,IAAI,CAAC;AACR,wBAAgB,GAAG,CAClB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kCAAkC,GACzC,IAAI,CAAC;AA0BR;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,EAAE,CAAC,CAAC,GAAG,OAAO,EAC7B,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAC1B,IAAI,CAAC,EAAE,OAAO,GACZ,cAAc,CAAC,CAAC,CAAC,CAAC;AACrB,wBAAgB,EAAE,CACjB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kCAAkC,EAC3C,IAAI,CAAC,EAAE,OAAO,GACZ,kCAAkC,CAAC;AA8BtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,GAAG,CAAC,CAAC,GAAG,OAAO,EAC9B,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACxB,cAAc,CAAC,CAAC,CAAC,CAAC;AACrB,wBAAgB,GAAG,CAClB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kCAAkC,GACzC,kCAAkC,CAAC;AAsBtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,gBAAiB,YAAW,kBAAkB;IAC1D,OAAO,UAAS;;IAEhB;;;;;;;;;;;;;;;OAeG;IACH,IAAI;CAGJ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,OAAO,CAAC,CAAC,GAAG,OAAO,EAClC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,EAC/C,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,CAAW,EACjB,MAAM,GAAE,gBAAsB,EAAE,wBAAwB;AACxD,UAAU,CAAC,EAAE,gBAAgB,QA+C7B"}
File without changes