@sprlab/wccompiler 0.8.0 → 0.8.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.
@@ -1,9 +1,104 @@
1
+ /**
2
+ * Angular adapter for WCC defineModel — enables [(propName)] two-way binding.
3
+ *
4
+ * Import this ONCE in your Angular app's main.ts:
5
+ * import '@sprlab/wccompiler/adapters/angular'
6
+ *
7
+ * What it does:
8
+ * 1. Translates wcc:model events → propNameChange (enables [(prop)] syntax)
9
+ * 2. Uses queueMicrotask to defer event emission outside Angular's render cycle
10
+ * (prevents NG0600: "Writing to signals is not allowed while Angular renders")
11
+ *
12
+ * Usage:
13
+ * <!-- In Angular template (with CUSTOM_ELEMENTS_SCHEMA) -->
14
+ * <wcc-input [(value)]="text"></wcc-input>
15
+ * <wcc-counter [(count)]="myCount"></wcc-counter>
16
+ *
17
+ * For ngModel support, you need a ControlValueAccessor.
18
+ * See the exported WccValueAccessor class below.
19
+ *
20
+ * @module @sprlab/wccompiler/adapters/angular
21
+ */
22
+
23
+ // ── Document-level adapter: wcc:model → propNameChange ──────────────
24
+ // Angular's [(prop)] syntax listens for `propChange` events.
25
+ // Uses queueMicrotask to defer the re-dispatch outside Angular's synchronous
26
+ // render cycle, preventing NG0600 errors when Angular is mid-render.
27
+
1
28
  if (typeof document !== 'undefined') {
2
29
  document.addEventListener('wcc:model', (e) => {
3
30
  const { prop, value } = e.detail;
4
- e.target.dispatchEvent(new CustomEvent(`${prop}Change`, {
5
- detail: value,
6
- bubbles: true
7
- }));
31
+ const target = e.target;
32
+
33
+ // Defer to next microtask to avoid NG0600
34
+ // (Angular doesn't allow signal writes during render)
35
+ queueMicrotask(() => {
36
+ target.dispatchEvent(new CustomEvent(`${prop}Change`, {
37
+ detail: value,
38
+ bubbles: true
39
+ }));
40
+ });
8
41
  });
9
42
  }
43
+
44
+ // ── ControlValueAccessor for ngModel/ReactiveForms ──────────────────
45
+ // Angular's ngModel requires a ControlValueAccessor to bridge form controls.
46
+ // Since this is a JS file (not TypeScript with decorators), we export the
47
+ // implementation as a guide. Users need to create a TypeScript directive.
48
+ //
49
+ // Copy this into your Angular project as a .ts file:
50
+ //
51
+ // ```ts
52
+ // import { Directive, ElementRef, forwardRef, HostListener } from '@angular/core';
53
+ // import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
54
+ //
55
+ // @Directive({
56
+ // selector: '[wccModel]',
57
+ // providers: [{
58
+ // provide: NG_VALUE_ACCESSOR,
59
+ // useExisting: forwardRef(() => WccValueAccessor),
60
+ // multi: true
61
+ // }]
62
+ // })
63
+ // export class WccValueAccessor implements ControlValueAccessor {
64
+ // private onChange: (value: any) => void = () => {};
65
+ // private onTouched: () => void = () => {};
66
+ //
67
+ // constructor(private el: ElementRef<HTMLElement>) {}
68
+ //
69
+ // writeValue(value: any): void {
70
+ // // Parent → Child: set attribute
71
+ // if (value != null) {
72
+ // this.el.nativeElement.setAttribute('value', String(value));
73
+ // } else {
74
+ // this.el.nativeElement.removeAttribute('value');
75
+ // }
76
+ // }
77
+ //
78
+ // registerOnChange(fn: (value: any) => void): void {
79
+ // this.onChange = fn;
80
+ // }
81
+ //
82
+ // registerOnTouched(fn: () => void): void {
83
+ // this.onTouched = fn;
84
+ // }
85
+ //
86
+ // @HostListener('wcc:model', ['$event'])
87
+ // onModelChange(event: CustomEvent): void {
88
+ // if (event.detail && event.detail.prop === 'value') {
89
+ // this.onChange(event.detail.value);
90
+ // }
91
+ // }
92
+ //
93
+ // @HostListener('blur')
94
+ // onBlur(): void {
95
+ // this.onTouched();
96
+ // }
97
+ // }
98
+ // ```
99
+ //
100
+ // Usage with ngModel:
101
+ // <wcc-input wccModel [(ngModel)]="text"></wcc-input>
102
+ //
103
+ // Usage with Reactive Forms:
104
+ // <wcc-input wccModel [formControl]="myControl"></wcc-input>
package/adapters/vue.js CHANGED
@@ -1,3 +1,32 @@
1
+ /**
2
+ * Vue adapter for WCC defineModel — enables v-model and multi-model binding.
3
+ *
4
+ * Usage (ONE line in main.js):
5
+ * import { createApp } from 'vue'
6
+ * import { wccVue } from '@sprlab/wccompiler/adapters/vue'
7
+ *
8
+ * const app = createApp(App)
9
+ * app.use(wccVue) // registers adapter + v-wcc-model directive globally
10
+ * app.mount('#app')
11
+ *
12
+ * What it does:
13
+ * 1. Registers document-level wcc:model → update:propName translation (enables v-model)
14
+ * 2. Registers v-wcc-model directive globally (enables multi-prop two-way binding)
15
+ *
16
+ * Template usage:
17
+ * <!-- Single model prop (Vue's native v-model) -->
18
+ * <!-- Component must declare: defineModel({ name: 'modelValue', default: '' }) -->
19
+ * <wcc-input v-model="text"></wcc-input>
20
+ *
21
+ * <!-- Multiple model props (v-wcc-model:propName) -->
22
+ * <wcc-form v-model="mainValue" v-wcc-model:count="countRef" v-wcc-model:title="titleRef"></wcc-form>
23
+ *
24
+ * @module @sprlab/wccompiler/adapters/vue
25
+ */
26
+
27
+ // ── Document-level adapter: wcc:model → update:propName ─────────────
28
+ // This enables Vue's native v-model on WCC custom elements.
29
+
1
30
  if (typeof document !== 'undefined') {
2
31
  document.addEventListener('wcc:model', (e) => {
3
32
  const { prop, value } = e.detail;
@@ -7,3 +36,97 @@ if (typeof document !== 'undefined') {
7
36
  }));
8
37
  });
9
38
  }
39
+
40
+ // ── Vue directive: v-wcc-model ──────────────────────────────────────
41
+
42
+ /**
43
+ * Vue custom directive for two-way binding with WCC defineModel props.
44
+ *
45
+ * Binds a Vue ref to a WCC component's model prop bidirectionally:
46
+ * - Parent → Child: sets the attribute when the Vue ref changes
47
+ * - Child → Parent: updates the Vue ref when wcc:model fires for the matching prop
48
+ *
49
+ * @example
50
+ * <wcc-counter v-wcc-model:count="myCount"></wcc-counter>
51
+ */
52
+ export const vWccModel = {
53
+ mounted(el, binding) {
54
+ const propName = binding.arg;
55
+ if (!propName) {
56
+ console.warn('[v-wcc-model] Missing argument. Usage: v-wcc-model:propName="ref"');
57
+ return;
58
+ }
59
+
60
+ // Set initial value (parent → child)
61
+ if (binding.value != null) {
62
+ el.setAttribute(propName, String(binding.value));
63
+ }
64
+
65
+ // Listen for child → parent changes via the update:propName event
66
+ // (which is already dispatched by the document-level adapter above)
67
+ const handler = (e) => {
68
+ // Use the update:propName event dispatched by the adapter
69
+ // Vue will handle the ref update through the directive binding
70
+ };
71
+
72
+ // Listen directly for wcc:model to update the binding
73
+ const wccHandler = (e) => {
74
+ if (e.detail && e.detail.prop === propName) {
75
+ // Trigger Vue reactivity by emitting on the component instance
76
+ // This works because Vue tracks directive bindings
77
+ el.dispatchEvent(new CustomEvent(`update:${propName}`, {
78
+ detail: e.detail.value,
79
+ bubbles: false
80
+ }));
81
+ }
82
+ };
83
+
84
+ el.addEventListener('wcc:model', wccHandler);
85
+ // Store handler for cleanup
86
+ el.__wccModelHandlers = el.__wccModelHandlers || {};
87
+ el.__wccModelHandlers[propName] = wccHandler;
88
+ },
89
+
90
+ updated(el, binding) {
91
+ const propName = binding.arg;
92
+ if (!propName) return;
93
+
94
+ // Sync parent → child on updates
95
+ if (binding.value != null) {
96
+ el.setAttribute(propName, String(binding.value));
97
+ } else {
98
+ el.removeAttribute(propName);
99
+ }
100
+ },
101
+
102
+ beforeUnmount(el, binding) {
103
+ const propName = binding.arg;
104
+ if (!propName) return;
105
+
106
+ // Cleanup listener
107
+ const handler = el.__wccModelHandlers?.[propName];
108
+ if (handler) {
109
+ el.removeEventListener('wcc:model', handler);
110
+ delete el.__wccModelHandlers[propName];
111
+ }
112
+ }
113
+ };
114
+
115
+ // ── Vue Plugin: app.use(wccVue) ─────────────────────────────────────
116
+
117
+ /**
118
+ * Vue plugin that registers the wcc:model adapter and v-wcc-model directive globally.
119
+ *
120
+ * @example
121
+ * import { createApp } from 'vue'
122
+ * import { wccVue } from '@sprlab/wccompiler/adapters/vue'
123
+ *
124
+ * const app = createApp(App)
125
+ * app.use(wccVue)
126
+ * app.mount('#app')
127
+ */
128
+ export const wccVue = {
129
+ install(app) {
130
+ app.directive('wcc-model', vWccModel);
131
+ }
132
+ };
@@ -3,21 +3,34 @@
3
3
  *
4
4
  * @module @sprlab/wccompiler/integrations/angular
5
5
  *
6
- * Angular's AOT compiler requires schemas to be statically analyzable,
7
- * so we cannot provide a re-exported schema constant that works at compile time.
8
- * Instead, use Angular's built-in CUSTOM_ELEMENTS_SCHEMA directly:
6
+ * Setup requires two steps:
7
+ *
8
+ * 1. Import the adapter in main.ts (enables [(prop)] two-way binding):
9
+ * ```ts
10
+ * import '@sprlab/wccompiler/adapters/angular'
11
+ * ```
12
+ *
13
+ * 2. Add CUSTOM_ELEMENTS_SCHEMA to your component/module:
9
14
  *
10
15
  * @example Standalone component (Angular 17+)
11
16
  * ```ts
12
- * import { Component } from '@angular/core';
13
- * import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
17
+ * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
14
18
  *
15
19
  * @Component({
16
20
  * selector: 'app-root',
17
21
  * schemas: [CUSTOM_ELEMENTS_SCHEMA],
18
- * template: `<wcc-counter></wcc-counter>`
22
+ * template: `
23
+ * <!-- Simple one-way binding -->
24
+ * <wcc-counter [count]="myCount"></wcc-counter>
25
+ *
26
+ * <!-- Two-way binding with [(prop)] -->
27
+ * <wcc-input [(value)]="text"></wcc-input>
28
+ * `
19
29
  * })
20
- * export class AppComponent {}
30
+ * export class AppComponent {
31
+ * text = '';
32
+ * myCount = 0;
33
+ * }
21
34
  * ```
22
35
  *
23
36
  * @example NgModule approach
@@ -30,33 +43,31 @@
30
43
  * export class AppModule {}
31
44
  * ```
32
45
  *
33
- * @example Two-way binding with defineModel
34
- * ```ts
35
- * // The adapter translates wcc:model events to Angular's propNameChange convention.
36
- * // Import the integration once in your main.ts or app module:
37
- * import '@sprlab/wccompiler/integrations/angular'
46
+ * @example Two-way binding with [(prop)]
47
+ * The adapter translates wcc:model events to Angular's propChange convention.
48
+ * Angular's banana-in-a-box [(prop)] expands to:
49
+ * [prop]="value" (propChange)="value = $event.detail"
38
50
  *
39
- * // Then use Angular's banana-in-a-box syntax:
40
- * // <wcc-input [(value)]="myValue"></wcc-input>
41
- * ```
51
+ * So when the WCC component emits wcc:model with { prop: 'value', value: 'new' },
52
+ * the adapter re-dispatches as 'valueChange' CustomEvent, which Angular picks up.
53
+ *
54
+ * @example ngModel support (requires ControlValueAccessor)
55
+ * For ngModel/ReactiveForms, see the WccValueAccessor guide in:
56
+ * @sprlab/wccompiler/adapters/angular
42
57
  *
43
- * That's it one line of config. WCC components work as native custom elements
44
- * in Angular without any additional wrapper or helper.
58
+ * That file contains a copy-paste TypeScript directive implementation.
45
59
  */
46
60
 
47
- // Side-effect: registers document-level wcc:model → propNameChange translation
48
- // This enables [(propName)] two-way binding on WCC components in Angular templates.
49
- import '../adapters/angular.js'
50
-
51
61
  /**
52
62
  * Configuration instructions for Angular projects using WCC components.
53
63
  * This is a documentation-only export — Angular's AOT compiler requires
54
64
  * CUSTOM_ELEMENTS_SCHEMA to be imported directly from @angular/core.
55
65
  *
56
- * @type {{ schema: string, standalone: string, ngModule: string }}
66
+ * @type {{ schema: string, standalone: string, ngModule: string, adapter: string }}
57
67
  */
58
68
  export const WCC_ANGULAR_CONFIG = {
59
69
  schema: "import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'",
60
70
  standalone: "@Component({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
61
71
  ngModule: "@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
72
+ adapter: "import '@sprlab/wccompiler/adapters/angular' // in main.ts",
62
73
  }
@@ -1,17 +1,26 @@
1
1
  /**
2
2
  * Vue Vite plugin for WCC custom elements.
3
3
  * Configures isCustomElement to recognize WCC component tags.
4
- * Also re-exports the defineModel adapter for v-model support.
5
4
  *
6
5
  * @module @sprlab/wccompiler/integrations/vue
6
+ *
7
+ * IMPORTANT: This file is for vite.config.js (Node.js context).
8
+ * For browser-side model adapter, import '@sprlab/wccompiler/adapters/vue' in your main.js.
9
+ *
10
+ * @example vite.config.js
11
+ * ```js
12
+ * import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
13
+ * export default { plugins: [wccVuePlugin()] }
14
+ * ```
15
+ *
16
+ * @example main.js (browser — enables v-model on WCC components)
17
+ * ```js
18
+ * import '@sprlab/wccompiler/adapters/vue'
19
+ * ```
7
20
  */
8
21
 
9
22
  import vue from '@vitejs/plugin-vue'
10
23
 
11
- // Side-effect: registers document-level wcc:model → update:propName translation
12
- // This enables v-model:propName on WCC components in Vue templates.
13
- import '../adapters/vue.js'
14
-
15
24
  /**
16
25
  * @typedef {Object} WccVuePluginOptions
17
26
  * @property {string} [prefix='wcc-'] - Tag prefix for custom element detection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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": {