@sprlab/wccompiler 0.10.0 → 0.10.2

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.
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @module @sprlab/wccompiler/adapters/angular
27
27
  */
28
- import { TemplateRef, QueryList, AfterContentInit, OnDestroy } from '@angular/core';
28
+ import { TemplateRef, QueryList, AfterContentInit, OnDestroy, OnInit, EventEmitter } from '@angular/core';
29
29
  import * as i0 from "@angular/core";
30
30
  /** Context object passed to createEmbeddedView for scoped slots */
31
31
  export interface SlotContext {
@@ -96,3 +96,78 @@ export declare class WccSlotsDirective implements AfterContentInit, OnDestroy {
96
96
  static ɵfac: i0.ɵɵFactoryDeclaration<WccSlotsDirective, never>;
97
97
  static ɵdir: i0.ɵɵDirectiveDeclaration<WccSlotsDirective, "[wccSlots]", never, {}, {}, ["slotDefs"], never, true, never>;
98
98
  }
99
+ /**
100
+ * Directive that bridges WCC custom element events to Angular output bindings.
101
+ *
102
+ * Problem: Angular's `(event-name)="handler($event)"` works on custom elements,
103
+ * but `$event` is the raw CustomEvent. The developer must write `$event.detail`
104
+ * to get the payload. This is verbose and error-prone.
105
+ *
106
+ * Solution: This directive listens for CustomEvents on the host element and
107
+ * re-emits them as Angular outputs with `$event = event.detail`.
108
+ *
109
+ * Usage:
110
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
111
+ *
112
+ * Or for multiple events, use WccEvents (plural) with a comma-separated list:
113
+ * <wcc-counter wccEvents="count-changed, value-changed"
114
+ * (countChanged)="onCount($event)"
115
+ * (valueChanged)="onValue($event)">
116
+ * </wcc-counter>
117
+ *
118
+ * The event name is converted from kebab-case to camelCase for the output:
119
+ * 'count-changed' → (countChanged)
120
+ * 'value-changed' → (valueChanged)
121
+ * 'change' → (change)
122
+ */
123
+ /**
124
+ * Single-event directive: listens for one CustomEvent and emits its detail.
125
+ *
126
+ * Usage:
127
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="handler($event)"></wcc-counter>
128
+ */
129
+ export declare class WccEvent implements OnInit, OnDestroy {
130
+ wccEvent: string;
131
+ wccEmit: EventEmitter<any>;
132
+ private el;
133
+ private listener;
134
+ ngOnInit(): void;
135
+ ngOnDestroy(): void;
136
+ static ɵfac: i0.ɵɵFactoryDeclaration<WccEvent, never>;
137
+ static ɵdir: i0.ɵɵDirectiveDeclaration<WccEvent, "[wccEvent]", never, { "wccEvent": { "alias": "wccEvent"; "required": false; }; }, { "wccEmit": "wccEmit"; }, never, never, true, never>;
138
+ }
139
+ /**
140
+ * Event bridging directive: allows using camelCase event bindings on WCC elements.
141
+ *
142
+ * Without this directive, Angular devs must use kebab-case event names:
143
+ * <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
144
+ *
145
+ * With this directive, they can use camelCase (more Angular-idiomatic):
146
+ * <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
147
+ *
148
+ * The directive listens for kebab-case CustomEvents from the WCC component
149
+ * and re-dispatches them with camelCase names so Angular's event binding picks them up.
150
+ *
151
+ * Event name conversion:
152
+ * 'count-changed' → dispatches 'countChanged'
153
+ * 'value-changed' → dispatches 'valueChanged'
154
+ * 'change' → dispatches 'change' (no conversion needed)
155
+ *
156
+ * Event discovery:
157
+ * - Auto: reads `static __events` from the WCC component class (set by codegen)
158
+ * - Manual: pass an explicit array via [wccEvents]="['count-changed', 'value-changed']"
159
+ *
160
+ * Note: $event is still the CustomEvent — use $event.detail to get the payload.
161
+ * This is consistent with how Angular handles all DOM events.
162
+ */
163
+ export declare class WccEvents implements OnInit, OnDestroy {
164
+ /** Optional explicit list of kebab-case event names to bridge */
165
+ wccEvents: string[] | '';
166
+ private el;
167
+ private listeners;
168
+ ngOnInit(): void;
169
+ private setupEvents;
170
+ ngOnDestroy(): void;
171
+ static ɵfac: i0.ɵɵFactoryDeclaration<WccEvents, never>;
172
+ static ɵdir: i0.ɵɵDirectiveDeclaration<WccEvents, "[wccEvents]", never, { "wccEvents": { "alias": "wccEvents"; "required": false; }; }, {}, never, never, true, never>;
173
+ }
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @module @sprlab/wccompiler/adapters/angular
27
27
  */
28
- import { Directive, TemplateRef, ElementRef, ViewContainerRef, ChangeDetectorRef, ContentChildren, inject, Attribute, } from '@angular/core';
28
+ import { Directive, TemplateRef, ElementRef, ViewContainerRef, ChangeDetectorRef, ContentChildren, Output, EventEmitter, inject, Attribute, Input, } from '@angular/core';
29
29
  import * as i0 from "@angular/core";
30
30
  // ─── WccSlotDef — Auxiliary Directive ───────────────────────────────────────
31
31
  /**
@@ -297,3 +297,151 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
297
297
  type: ContentChildren,
298
298
  args: [WccSlotDef]
299
299
  }] } });
300
+ // ─── WccEvent — Event Binding Directive ─────────────────────────────────────
301
+ /**
302
+ * Directive that bridges WCC custom element events to Angular output bindings.
303
+ *
304
+ * Problem: Angular's `(event-name)="handler($event)"` works on custom elements,
305
+ * but `$event` is the raw CustomEvent. The developer must write `$event.detail`
306
+ * to get the payload. This is verbose and error-prone.
307
+ *
308
+ * Solution: This directive listens for CustomEvents on the host element and
309
+ * re-emits them as Angular outputs with `$event = event.detail`.
310
+ *
311
+ * Usage:
312
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
313
+ *
314
+ * Or for multiple events, use WccEvents (plural) with a comma-separated list:
315
+ * <wcc-counter wccEvents="count-changed, value-changed"
316
+ * (countChanged)="onCount($event)"
317
+ * (valueChanged)="onValue($event)">
318
+ * </wcc-counter>
319
+ *
320
+ * The event name is converted from kebab-case to camelCase for the output:
321
+ * 'count-changed' → (countChanged)
322
+ * 'value-changed' → (valueChanged)
323
+ * 'change' → (change)
324
+ */
325
+ /**
326
+ * Single-event directive: listens for one CustomEvent and emits its detail.
327
+ *
328
+ * Usage:
329
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="handler($event)"></wcc-counter>
330
+ */
331
+ export class WccEvent {
332
+ wccEvent = '';
333
+ wccEmit = new EventEmitter();
334
+ el = inject(ElementRef);
335
+ listener = null;
336
+ ngOnInit() {
337
+ if (!this.wccEvent)
338
+ return;
339
+ this.listener = (e) => {
340
+ this.wccEmit.emit(e.detail);
341
+ };
342
+ this.el.nativeElement.addEventListener(this.wccEvent, this.listener);
343
+ }
344
+ ngOnDestroy() {
345
+ if (this.listener && this.wccEvent) {
346
+ this.el.nativeElement.removeEventListener(this.wccEvent, this.listener);
347
+ }
348
+ }
349
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: WccEvent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
350
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.21", type: WccEvent, isStandalone: true, selector: "[wccEvent]", inputs: { wccEvent: "wccEvent" }, outputs: { wccEmit: "wccEmit" }, ngImport: i0 });
351
+ }
352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: WccEvent, decorators: [{
353
+ type: Directive,
354
+ args: [{
355
+ selector: '[wccEvent]',
356
+ standalone: true,
357
+ }]
358
+ }], propDecorators: { wccEvent: [{
359
+ type: Input
360
+ }], wccEmit: [{
361
+ type: Output
362
+ }] } });
363
+ /**
364
+ * Event bridging directive: allows using camelCase event bindings on WCC elements.
365
+ *
366
+ * Without this directive, Angular devs must use kebab-case event names:
367
+ * <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
368
+ *
369
+ * With this directive, they can use camelCase (more Angular-idiomatic):
370
+ * <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
371
+ *
372
+ * The directive listens for kebab-case CustomEvents from the WCC component
373
+ * and re-dispatches them with camelCase names so Angular's event binding picks them up.
374
+ *
375
+ * Event name conversion:
376
+ * 'count-changed' → dispatches 'countChanged'
377
+ * 'value-changed' → dispatches 'valueChanged'
378
+ * 'change' → dispatches 'change' (no conversion needed)
379
+ *
380
+ * Event discovery:
381
+ * - Auto: reads `static __events` from the WCC component class (set by codegen)
382
+ * - Manual: pass an explicit array via [wccEvents]="['count-changed', 'value-changed']"
383
+ *
384
+ * Note: $event is still the CustomEvent — use $event.detail to get the payload.
385
+ * This is consistent with how Angular handles all DOM events.
386
+ */
387
+ export class WccEvents {
388
+ /** Optional explicit list of kebab-case event names to bridge */
389
+ wccEvents = '';
390
+ el = inject(ElementRef);
391
+ listeners = [];
392
+ ngOnInit() {
393
+ const hostEl = this.el.nativeElement;
394
+ const tagName = hostEl.tagName.toLowerCase();
395
+ if (!tagName.includes('-'))
396
+ return;
397
+ this.setupEvents(hostEl, tagName);
398
+ }
399
+ async setupEvents(hostEl, tagName) {
400
+ let eventNames;
401
+ if (Array.isArray(this.wccEvents) && this.wccEvents.length > 0) {
402
+ eventNames = this.wccEvents;
403
+ }
404
+ else {
405
+ // Auto-discover from component metadata
406
+ await customElements.whenDefined(tagName);
407
+ const ctor = customElements.get(tagName);
408
+ eventNames = ctor?.__events || [];
409
+ }
410
+ if (eventNames.length === 0)
411
+ return;
412
+ for (const eventName of eventNames) {
413
+ // Only bridge events that have hyphens (already camelCase events don't need bridging)
414
+ if (!eventName.includes('-'))
415
+ continue;
416
+ const camelName = eventName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
417
+ const listener = (e) => {
418
+ // Re-dispatch with camelCase name — Angular's (camelName) binding will catch it
419
+ hostEl.dispatchEvent(new CustomEvent(camelName, {
420
+ detail: e.detail,
421
+ bubbles: false,
422
+ cancelable: false,
423
+ }));
424
+ };
425
+ hostEl.addEventListener(eventName, listener);
426
+ this.listeners.push([eventName, listener]);
427
+ }
428
+ }
429
+ ngOnDestroy() {
430
+ const hostEl = this.el.nativeElement;
431
+ for (const [name, listener] of this.listeners) {
432
+ hostEl.removeEventListener(name, listener);
433
+ }
434
+ this.listeners = [];
435
+ }
436
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: WccEvents, deps: [], target: i0.ɵɵFactoryTarget.Directive });
437
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.21", type: WccEvents, isStandalone: true, selector: "[wccEvents]", inputs: { wccEvents: "wccEvents" }, ngImport: i0 });
438
+ }
439
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: WccEvents, decorators: [{
440
+ type: Directive,
441
+ args: [{
442
+ selector: '[wccEvents]',
443
+ standalone: true,
444
+ }]
445
+ }], propDecorators: { wccEvents: [{
446
+ type: Input
447
+ }] } });
@@ -1,26 +1,37 @@
1
1
  /**
2
- * Angular adapter for WCC Scoped Slots.
2
+ * Angular adapter for WCC Scoped Slots and Event Binding.
3
3
  *
4
4
  * Exports:
5
5
  * - WccSlotDef: Auxiliary directive for ng-template[slot]
6
6
  * - WccSlotsDirective: Main directive activated via [wccSlots] attribute
7
+ * - WccEvent: Single-event directive (wccEvent="name" + wccEmit output)
8
+ * - WccEvents: Multi-event bridging directive (kebab-case → camelCase)
7
9
  * - SlotContext: Interface for template context typing
8
10
  *
9
11
  * Usage:
10
- * import { WccSlotsDirective, WccSlotDef } from '@sprlab/wccompiler/adapters/angular';
12
+ * import { WccSlotsDirective, WccSlotDef, WccEvent, WccEvents } from '@sprlab/wccompiler/adapters/angular';
11
13
  *
12
14
  * @Component({
13
- * imports: [WccSlotsDirective, WccSlotDef],
15
+ * imports: [WccSlotsDirective, WccSlotDef, WccEvent, WccEvents],
14
16
  * schemas: [CUSTOM_ELEMENTS_SCHEMA],
15
17
  * template: `
16
18
  * <wcc-card wccSlots>
17
19
  * <ng-template slot="header"><strong>Header</strong></ng-template>
18
20
  * <ng-template slot="stats" let-likes>{{ likes }} likes</ng-template>
19
21
  * </wcc-card>
22
+ *
23
+ * <!-- Event binding option 1: single event with unwrapped detail -->
24
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
25
+ *
26
+ * <!-- Event binding option 2: camelCase event names -->
27
+ * <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
28
+ *
29
+ * <!-- Event binding option 3: standard Angular (always works) -->
30
+ * <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
20
31
  * `
21
32
  * })
22
33
  *
23
- * Note: Add the `wccSlots` attribute to any WCC custom element that uses slots.
34
+ * Note: Add the `wccSlots` attribute to any WCC element that uses slots.
24
35
  * This is required because Angular AOT cannot evaluate dynamic selectors.
25
36
  *
26
37
  * @module @sprlab/wccompiler/adapters/angular
@@ -37,8 +48,12 @@ import {
37
48
  EmbeddedViewRef,
38
49
  AfterContentInit,
39
50
  OnDestroy,
51
+ OnInit,
52
+ Output,
53
+ EventEmitter,
40
54
  inject,
41
55
  Attribute,
56
+ Input,
42
57
  } from '@angular/core';
43
58
 
44
59
  // ─── Interfaces ─────────────────────────────────────────────────────────────
@@ -351,3 +366,149 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
351
366
  this.eventCleanups = [];
352
367
  }
353
368
  }
369
+
370
+
371
+ // ─── WccEvent — Event Binding Directive ─────────────────────────────────────
372
+
373
+ /**
374
+ * Directive that bridges WCC custom element events to Angular output bindings.
375
+ *
376
+ * Problem: Angular's `(event-name)="handler($event)"` works on custom elements,
377
+ * but `$event` is the raw CustomEvent. The developer must write `$event.detail`
378
+ * to get the payload. This is verbose and error-prone.
379
+ *
380
+ * Solution: This directive listens for CustomEvents on the host element and
381
+ * re-emits them as Angular outputs with `$event = event.detail`.
382
+ *
383
+ * Usage:
384
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
385
+ *
386
+ * Or for multiple events, use WccEvents (plural) with a comma-separated list:
387
+ * <wcc-counter wccEvents="count-changed, value-changed"
388
+ * (countChanged)="onCount($event)"
389
+ * (valueChanged)="onValue($event)">
390
+ * </wcc-counter>
391
+ *
392
+ * The event name is converted from kebab-case to camelCase for the output:
393
+ * 'count-changed' → (countChanged)
394
+ * 'value-changed' → (valueChanged)
395
+ * 'change' → (change)
396
+ */
397
+
398
+ /**
399
+ * Single-event directive: listens for one CustomEvent and emits its detail.
400
+ *
401
+ * Usage:
402
+ * <wcc-counter wccEvent="count-changed" (wccEmit)="handler($event)"></wcc-counter>
403
+ */
404
+ @Directive({
405
+ selector: '[wccEvent]',
406
+ standalone: true,
407
+ })
408
+ export class WccEvent implements OnInit, OnDestroy {
409
+ @Input() wccEvent = '';
410
+ @Output() wccEmit = new EventEmitter<any>();
411
+
412
+ private el = inject<ElementRef<HTMLElement>>(ElementRef);
413
+ private listener: ((e: Event) => void) | null = null;
414
+
415
+ ngOnInit(): void {
416
+ if (!this.wccEvent) return;
417
+ this.listener = (e: Event) => {
418
+ this.wccEmit.emit((e as CustomEvent).detail);
419
+ };
420
+ this.el.nativeElement.addEventListener(this.wccEvent, this.listener);
421
+ }
422
+
423
+ ngOnDestroy(): void {
424
+ if (this.listener && this.wccEvent) {
425
+ this.el.nativeElement.removeEventListener(this.wccEvent, this.listener);
426
+ }
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Event bridging directive: allows using camelCase event bindings on WCC elements.
432
+ *
433
+ * Without this directive, Angular devs must use kebab-case event names:
434
+ * <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
435
+ *
436
+ * With this directive, they can use camelCase (more Angular-idiomatic):
437
+ * <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
438
+ *
439
+ * The directive listens for kebab-case CustomEvents from the WCC component
440
+ * and re-dispatches them with camelCase names so Angular's event binding picks them up.
441
+ *
442
+ * Event name conversion:
443
+ * 'count-changed' → dispatches 'countChanged'
444
+ * 'value-changed' → dispatches 'valueChanged'
445
+ * 'change' → dispatches 'change' (no conversion needed)
446
+ *
447
+ * Event discovery:
448
+ * - Auto: reads `static __events` from the WCC component class (set by codegen)
449
+ * - Manual: pass an explicit array via [wccEvents]="['count-changed', 'value-changed']"
450
+ *
451
+ * Note: $event is still the CustomEvent — use $event.detail to get the payload.
452
+ * This is consistent with how Angular handles all DOM events.
453
+ */
454
+ @Directive({
455
+ selector: '[wccEvents]',
456
+ standalone: true,
457
+ })
458
+ export class WccEvents implements OnInit, OnDestroy {
459
+ /** Optional explicit list of kebab-case event names to bridge */
460
+ @Input() wccEvents: string[] | '' = '';
461
+
462
+ private el = inject<ElementRef<HTMLElement>>(ElementRef);
463
+ private listeners: Array<[string, (e: Event) => void]> = [];
464
+
465
+ ngOnInit(): void {
466
+ const hostEl = this.el.nativeElement;
467
+ const tagName = hostEl.tagName.toLowerCase();
468
+ if (!tagName.includes('-')) return;
469
+
470
+ this.setupEvents(hostEl, tagName);
471
+ }
472
+
473
+ private async setupEvents(hostEl: HTMLElement, tagName: string): Promise<void> {
474
+ let eventNames: string[];
475
+
476
+ if (Array.isArray(this.wccEvents) && this.wccEvents.length > 0) {
477
+ eventNames = this.wccEvents;
478
+ } else {
479
+ // Auto-discover from component metadata
480
+ await customElements.whenDefined(tagName);
481
+ const ctor = customElements.get(tagName) as any;
482
+ eventNames = ctor?.__events || [];
483
+ }
484
+
485
+ if (eventNames.length === 0) return;
486
+
487
+ for (const eventName of eventNames) {
488
+ // Only bridge events that have hyphens (already camelCase events don't need bridging)
489
+ if (!eventName.includes('-')) continue;
490
+
491
+ const camelName = eventName.replace(/-([a-z])/g, (_: string, c: string) => c.toUpperCase());
492
+
493
+ const listener = (e: Event) => {
494
+ // Re-dispatch with camelCase name — Angular's (camelName) binding will catch it
495
+ hostEl.dispatchEvent(new CustomEvent(camelName, {
496
+ detail: (e as CustomEvent).detail,
497
+ bubbles: false,
498
+ cancelable: false,
499
+ }));
500
+ };
501
+
502
+ hostEl.addEventListener(eventName, listener);
503
+ this.listeners.push([eventName, listener]);
504
+ }
505
+ }
506
+
507
+ ngOnDestroy(): void {
508
+ const hostEl = this.el.nativeElement;
509
+ for (const [name, listener] of this.listeners) {
510
+ hostEl.removeEventListener(name, listener);
511
+ }
512
+ this.listeners = [];
513
+ }
514
+ }
package/adapters/react.js CHANGED
@@ -249,3 +249,87 @@ export function createWccWrapper(tagName, config = {}) {
249
249
  return WccWrapper
250
250
  }
251
251
 
252
+
253
+
254
+ /**
255
+ * Creates a React wrapper from a WCC component class that has `static __meta`.
256
+ *
257
+ * Unlike `createWccWrapper` which requires manual event/model configuration,
258
+ * this function reads the metadata directly from the compiled component class.
259
+ *
260
+ * @param {Function} WccClass - The WCC custom element class (must have static __meta)
261
+ * @returns {import('react').ForwardRefExoticComponent} A React component
262
+ *
263
+ * @example
264
+ * import { wrapWccComponent } from '@sprlab/wccompiler/adapters/react'
265
+ * import '../wcc-components/wcc-counter.js' // registers the custom element
266
+ *
267
+ * // Read metadata directly from the registered class
268
+ * const WccCounter = wrapWccComponent(customElements.get('wcc-counter'))
269
+ *
270
+ * // Use idiomatically — no manual config needed
271
+ * <WccCounter count={count} onCountChanged={setCount} onChange={handler} />
272
+ */
273
+ export function wrapWccComponent(WccClass) {
274
+ const meta = WccClass?.__meta
275
+ if (!meta) {
276
+ throw new Error(`wrapWccComponent: class does not have static __meta. Is it a compiled WCC component?`)
277
+ }
278
+
279
+ return createWccWrapper(meta.tag, {
280
+ events: meta.events || [],
281
+ models: meta.models || [],
282
+ })
283
+ }
284
+
285
+ /**
286
+ * Creates React wrappers for all registered WCC custom elements matching a prefix.
287
+ *
288
+ * Scans the custom elements registry for components with `static __meta` and
289
+ * generates typed wrapper components for each one.
290
+ *
291
+ * @param {Object} [options]
292
+ * @param {string} [options.prefix='wcc-'] - Tag prefix to filter components
293
+ * @returns {Record<string, import('react').ForwardRefExoticComponent>} Map of PascalCase name → React component
294
+ *
295
+ * @example
296
+ * // In your app entry point, after importing all WCC components:
297
+ * import '../wcc-components/wcc-counter.js'
298
+ * import '../wcc-components/wcc-card.js'
299
+ * import { createWccWrappers } from '@sprlab/wccompiler/adapters/react'
300
+ *
301
+ * export const { WccCounter, WccCard } = createWccWrappers()
302
+ *
303
+ * // Then use anywhere:
304
+ * <WccCounter count={count} onCountChanged={setCount} />
305
+ * <WccCard><div slot="header">Title</div></WccCard>
306
+ */
307
+ export function createWccWrappers(options = {}) {
308
+ const { prefix = 'wcc-' } = options
309
+ const wrappers = {}
310
+
311
+ // Note: customElements registry doesn't have a list API,
312
+ // so we need the component files to be imported first (which registers them).
313
+ // This function is meant to be called after all component imports.
314
+
315
+ // We'll use a Proxy that lazily creates wrappers on first access
316
+ return new Proxy(wrappers, {
317
+ get(target, prop) {
318
+ if (typeof prop !== 'string') return undefined
319
+ if (prop in target) return target[prop]
320
+
321
+ // Convert PascalCase to kebab-case: WccCounter → wcc-counter
322
+ const kebab = prop.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
323
+
324
+ // Check if it starts with the prefix
325
+ if (!kebab.startsWith(prefix)) return undefined
326
+
327
+ const ctor = customElements.get(kebab)
328
+ if (!ctor || !(ctor).__meta) return undefined
329
+
330
+ const wrapper = wrapWccComponent(ctor)
331
+ target[prop] = wrapper
332
+ return wrapper
333
+ }
334
+ })
335
+ }
package/lib/codegen.js CHANGED
@@ -970,6 +970,16 @@ export function generateComponent(parseResult, options = {}) {
970
970
  lines.push('');
971
971
  }
972
972
 
973
+ // Static __meta — component metadata for framework adapters (React wrappers, Angular events, etc.)
974
+ {
975
+ const metaProps = propDefs.map(p => `{ name: '${p.name}', default: ${p.default} }`).join(', ');
976
+ const metaEvents = emits.map(e => `'${e}'`).join(', ');
977
+ const metaModels = modelDefs.map(m => `'${m.name}'`).join(', ');
978
+ const metaSlots = slots.filter(s => s.name).map(s => `'${s.name}'`).join(', ');
979
+ lines.push(` static __meta = { tag: '${tagName}', props: [${metaProps}], events: [${metaEvents}], models: [${metaModels}], slots: [${metaSlots}] };`);
980
+ lines.push('');
981
+ }
982
+
973
983
  // Constructor — reactive state only (no DOM manipulation per Custom Elements spec)
974
984
  lines.push(' constructor() {');
975
985
  lines.push(' super();');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
6
  "exports": {