@sprlab/wccompiler 0.8.5 → 0.8.7
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.
- package/adapters/angular.js +15 -41
- package/adapters/vue.js +36 -104
- package/integrations/vue.js +56 -97
- package/lib/codegen.js +10 -1
- package/package.json +1 -1
package/adapters/angular.js
CHANGED
|
@@ -1,51 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Angular adapter for WCC defineModel
|
|
2
|
+
* Angular adapter for WCC defineModel (OPTIONAL).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The WCC component already emits `propNameChange` directly from _modelSet,
|
|
5
|
+
* so Angular's [(prop)] banana-box syntax works WITHOUT this adapter.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
10
|
-
*
|
|
7
|
+
* This file is kept for:
|
|
8
|
+
* 1. Documentation of the Angular integration approach
|
|
9
|
+
* 2. The ControlValueAccessor guide for ngModel support
|
|
10
|
+
*
|
|
11
|
+
* Setup (Angular):
|
|
12
|
+
* // No adapter import needed! Just use CUSTOM_ELEMENTS_SCHEMA:
|
|
13
|
+
* import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
14
|
+
* @Component({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
|
|
11
15
|
*
|
|
12
16
|
* Usage:
|
|
13
|
-
* <!-- In Angular template (with CUSTOM_ELEMENTS_SCHEMA) -->
|
|
14
17
|
* <wcc-input [(value)]="text"></wcc-input>
|
|
15
18
|
* <wcc-counter [(count)]="myCount"></wcc-counter>
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
20
|
+
* How it works:
|
|
21
|
+
* Angular's [(prop)] expands to [prop]="value" (propChange)="value = $event.detail"
|
|
22
|
+
* WCC _modelSet emits propNameChange CustomEvent with detail=newValue
|
|
23
|
+
* Angular picks it up automatically — no adapter needed.
|
|
19
24
|
*
|
|
20
25
|
* @module @sprlab/wccompiler/adapters/angular
|
|
21
26
|
*/
|
|
22
27
|
|
|
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
|
-
|
|
28
|
-
if (typeof document !== 'undefined') {
|
|
29
|
-
document.addEventListener('wcc:model', (e) => {
|
|
30
|
-
const { prop, value } = e.detail;
|
|
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
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
// ── ControlValueAccessor for ngModel/ReactiveForms ──────────────────
|
|
45
29
|
// 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
30
|
// Copy this into your Angular project as a .ts file:
|
|
50
31
|
//
|
|
51
32
|
// ```ts
|
|
@@ -67,7 +48,6 @@ if (typeof document !== 'undefined') {
|
|
|
67
48
|
// constructor(private el: ElementRef<HTMLElement>) {}
|
|
68
49
|
//
|
|
69
50
|
// writeValue(value: any): void {
|
|
70
|
-
// // Parent → Child: set attribute
|
|
71
51
|
// if (value != null) {
|
|
72
52
|
// this.el.nativeElement.setAttribute('value', String(value));
|
|
73
53
|
// } else {
|
|
@@ -96,9 +76,3 @@ if (typeof document !== 'undefined') {
|
|
|
96
76
|
// }
|
|
97
77
|
// }
|
|
98
78
|
// ```
|
|
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,69 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vue adapter for WCC defineModel —
|
|
2
|
+
* Vue adapter for WCC defineModel (OPTIONAL — only needed without wccVuePlugin).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
* IMPORTANT: Also use wccVuePlugin() in vite.config.js to enable v-model:propName
|
|
13
|
-
* on custom elements (via AST nodeTransform):
|
|
14
|
-
* import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
|
|
15
|
-
* export default { plugins: [wccVuePlugin()] }
|
|
4
|
+
* If you use wccVuePlugin() in vite.config.js, you DON'T need this adapter.
|
|
5
|
+
* The plugin handles v-model:propName transformation at build time.
|
|
16
6
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* <wcc-input v-model:value="text"></wcc-input>
|
|
20
|
-
* <wcc-form v-model:count="countRef" v-model:title="titleRef"></wcc-form>
|
|
7
|
+
* This adapter is for non-Vite setups (webpack, etc.) where you can't use
|
|
8
|
+
* the Vite pre-transform plugin. It provides a Vue directive for two-way binding.
|
|
21
9
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
10
|
+
* Setup:
|
|
11
|
+
* import { createApp } from 'vue'
|
|
12
|
+
* import { wccVue } from '@sprlab/wccompiler/adapters/vue'
|
|
13
|
+
* app.use(wccVue)
|
|
24
14
|
*
|
|
25
|
-
*
|
|
15
|
+
* Usage:
|
|
26
16
|
* <wcc-input v-wcc-model:value="textRef"></wcc-input>
|
|
17
|
+
* <wcc-form v-wcc-model:count="countRef"></wcc-form>
|
|
27
18
|
*
|
|
28
19
|
* @module @sprlab/wccompiler/adapters/vue
|
|
29
20
|
*/
|
|
30
21
|
|
|
31
|
-
// ── Document-level adapter: wcc:model → update:propName ─────────────
|
|
32
|
-
// This enables Vue's native v-model on WCC custom elements.
|
|
33
|
-
// Vue v-model on custom elements listens for `update:modelValue` by default.
|
|
34
|
-
// The nodeTransform in wccVuePlugin makes v-model:propName listen for `update:propName`.
|
|
35
|
-
//
|
|
36
|
-
// IMPORTANT: We listen in CAPTURE phase so the update:propName event is dispatched
|
|
37
|
-
// BEFORE Vue's own bubble-phase listeners process the element. This ensures Vue
|
|
38
|
-
// picks up the translated event synchronously.
|
|
39
|
-
|
|
40
|
-
if (typeof document !== 'undefined') {
|
|
41
|
-
document.addEventListener('wcc:model', (e) => {
|
|
42
|
-
const { prop, value } = e.detail;
|
|
43
|
-
// Dispatch update:propName synchronously on the target element.
|
|
44
|
-
// bubbles:false because Vue listens directly on the element via addEventListener.
|
|
45
|
-
e.target.dispatchEvent(new CustomEvent(`update:${prop}`, {
|
|
46
|
-
detail: value,
|
|
47
|
-
bubbles: false
|
|
48
|
-
}));
|
|
49
|
-
}, true); // ← capture phase
|
|
50
|
-
}
|
|
51
|
-
|
|
52
22
|
// ── Vue directive: v-wcc-model ──────────────────────────────────────
|
|
53
|
-
// Fallback for non-Vite setups.
|
|
54
|
-
//
|
|
55
|
-
// Usage:
|
|
56
|
-
// <wcc-input v-wcc-model:value="textRef"></wcc-input>
|
|
57
|
-
//
|
|
58
|
-
// The bound value MUST be a Vue ref (or reactive property).
|
|
59
|
-
// The directive writes directly to ref.value for WCC→Vue updates.
|
|
23
|
+
// Fallback for non-Vite setups. Listens for propName-changed events directly.
|
|
60
24
|
|
|
61
25
|
/**
|
|
62
26
|
* Vue custom directive for two-way binding with WCC defineModel props.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* <wcc-counter v-wcc-model:count="myCountRef"></wcc-counter>
|
|
27
|
+
* Listens for `propName-changed` CustomEvent (emitted by WCC _modelSet).
|
|
67
28
|
*/
|
|
68
29
|
export const vWccModel = {
|
|
69
30
|
mounted(el, binding) {
|
|
@@ -74,62 +35,41 @@ export const vWccModel = {
|
|
|
74
35
|
}
|
|
75
36
|
|
|
76
37
|
// Set initial value (parent → child)
|
|
77
|
-
// Vue sets camelCase attributes, so set both camelCase and kebab-case
|
|
78
38
|
if (binding.value != null) {
|
|
79
39
|
el.setAttribute(propName, String(binding.value));
|
|
80
40
|
}
|
|
81
41
|
|
|
82
|
-
// Listen for
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// In Vue 3, we can use the dir's exp to find the variable name
|
|
96
|
-
// Fallback: emit a custom event that a parent @update handler can catch
|
|
97
|
-
const refName = binding.dir?.__wccRefName?.[el]?.[propName];
|
|
98
|
-
if (refName && setupState[refName] !== undefined) {
|
|
99
|
-
// Direct ref write
|
|
100
|
-
if (setupState[refName]?.value !== undefined) {
|
|
101
|
-
setupState[refName].value = newValue;
|
|
42
|
+
// Listen for propName-changed (WCC → Vue)
|
|
43
|
+
const kebabName = propName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
44
|
+
const handler = (e) => {
|
|
45
|
+
// Try to update the Vue ref via setupState
|
|
46
|
+
const instance = binding.instance;
|
|
47
|
+
if (instance) {
|
|
48
|
+
const setupState = instance.$.setupState;
|
|
49
|
+
// Find the ref that matches the current binding value
|
|
50
|
+
for (const key of Object.keys(setupState)) {
|
|
51
|
+
const val = setupState[key];
|
|
52
|
+
if (val === binding.value || val?.value === binding.value) {
|
|
53
|
+
if (val?.value !== undefined) {
|
|
54
|
+
val.value = e.detail;
|
|
102
55
|
} else {
|
|
103
|
-
setupState[
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
// Fallback: try to find by matching current value
|
|
107
|
-
for (const key of Object.keys(setupState)) {
|
|
108
|
-
const val = setupState[key];
|
|
109
|
-
if (val?.value === binding.value || val === binding.value) {
|
|
110
|
-
if (val?.value !== undefined) {
|
|
111
|
-
val.value = newValue;
|
|
112
|
-
} else {
|
|
113
|
-
setupState[key] = newValue;
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
56
|
+
setupState[key] = e.detail;
|
|
117
57
|
}
|
|
58
|
+
break;
|
|
118
59
|
}
|
|
119
60
|
}
|
|
120
61
|
}
|
|
121
62
|
};
|
|
122
63
|
|
|
123
|
-
el.addEventListener(
|
|
64
|
+
el.addEventListener(`${kebabName}-changed`, handler);
|
|
124
65
|
el.__wccModelHandlers = el.__wccModelHandlers || {};
|
|
125
|
-
el.__wccModelHandlers[propName] =
|
|
66
|
+
el.__wccModelHandlers[propName] = { handler, eventName: `${kebabName}-changed` };
|
|
126
67
|
},
|
|
127
68
|
|
|
128
69
|
updated(el, binding) {
|
|
129
70
|
const propName = binding.arg;
|
|
130
71
|
if (!propName) return;
|
|
131
72
|
|
|
132
|
-
// Sync parent → child on updates
|
|
133
73
|
if (binding.value != null) {
|
|
134
74
|
el.setAttribute(propName, String(binding.value));
|
|
135
75
|
} else {
|
|
@@ -141,27 +81,19 @@ export const vWccModel = {
|
|
|
141
81
|
const propName = binding.arg;
|
|
142
82
|
if (!propName) return;
|
|
143
83
|
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
el.removeEventListener(
|
|
84
|
+
const entry = el.__wccModelHandlers?.[propName];
|
|
85
|
+
if (entry) {
|
|
86
|
+
el.removeEventListener(entry.eventName, entry.handler);
|
|
147
87
|
delete el.__wccModelHandlers[propName];
|
|
148
88
|
}
|
|
149
89
|
}
|
|
150
90
|
};
|
|
151
91
|
|
|
152
|
-
// ── Vue Plugin
|
|
92
|
+
// ── Vue Plugin ──────────────────────────────────────────────────────
|
|
153
93
|
|
|
154
94
|
/**
|
|
155
|
-
* Vue plugin that registers
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* import { createApp } from 'vue'
|
|
160
|
-
* import { wccVue } from '@sprlab/wccompiler/adapters/vue'
|
|
161
|
-
*
|
|
162
|
-
* const app = createApp(App)
|
|
163
|
-
* app.use(wccVue)
|
|
164
|
-
* app.mount('#app')
|
|
95
|
+
* Vue plugin that registers v-wcc-model directive globally.
|
|
96
|
+
* Only needed if NOT using wccVuePlugin() in vite.config.js.
|
|
165
97
|
*/
|
|
166
98
|
export const wccVue = {
|
|
167
99
|
install(app) {
|
package/integrations/vue.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module @sprlab/wccompiler/integrations/vue
|
|
6
6
|
*
|
|
7
7
|
* IMPORTANT: This file is for vite.config.js (Node.js context).
|
|
8
|
-
* For browser-side
|
|
8
|
+
* For browser-side, use app.use(wccVue) from '@sprlab/wccompiler/adapters/vue'.
|
|
9
9
|
*
|
|
10
10
|
* @example vite.config.js
|
|
11
11
|
* ```js
|
|
@@ -13,17 +13,25 @@
|
|
|
13
13
|
* export default { plugins: [wccVuePlugin()] }
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
|
-
* @example main.js (
|
|
16
|
+
* @example main.js (optional — only needed if NOT using wccVuePlugin)
|
|
17
17
|
* ```js
|
|
18
18
|
* import { wccVue } from '@sprlab/wccompiler/adapters/vue'
|
|
19
19
|
* app.use(wccVue)
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
-
* With
|
|
22
|
+
* With wccVuePlugin(), v-model:propName works natively on WCC custom elements:
|
|
23
23
|
* ```vue
|
|
24
24
|
* <wcc-input v-model="text"></wcc-input>
|
|
25
25
|
* <wcc-form v-model:count="countRef" v-model:title="titleRef"></wcc-form>
|
|
26
26
|
* ```
|
|
27
|
+
*
|
|
28
|
+
* How it works:
|
|
29
|
+
* The plugin runs BEFORE @vitejs/plugin-vue and rewrites the template string:
|
|
30
|
+
* v-model:count="expr" → :count="expr" @count-changed="expr = $event.detail"
|
|
31
|
+
* v-model="expr" → :model-value="expr" @model-value-changed="expr = $event.detail"
|
|
32
|
+
*
|
|
33
|
+
* The WCC component emits `propName-changed` CustomEvent with detail=value on internal writes.
|
|
34
|
+
* Vue compiles @propName-changed as a normal event listener (not filtered like update:*).
|
|
27
35
|
*/
|
|
28
36
|
|
|
29
37
|
import vue from '@vitejs/plugin-vue'
|
|
@@ -34,115 +42,66 @@ import vue from '@vitejs/plugin-vue'
|
|
|
34
42
|
*/
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Vue's compiler normally doesn't support v-model with arguments on custom elements.
|
|
40
|
-
* This transform intercepts v-model:arg directives on custom elements and rewrites them
|
|
41
|
-
* to the equivalent :prop + @update:prop binding that Vue understands.
|
|
45
|
+
* Vite plugin that pre-transforms v-model:propName on custom elements
|
|
46
|
+
* before Vue's compiler processes the template.
|
|
42
47
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* <wcc-input :value="text" @update:value="text = $event" />
|
|
48
|
+
* This is necessary because Vue's compiler filters out `onUpdate:*` event listeners
|
|
49
|
+
* for custom elements (isModelListener check in patchProp). By rewriting to
|
|
50
|
+
* `@propName-changed`, we use an event name that Vue registers normally.
|
|
47
51
|
*
|
|
48
|
-
* @param {
|
|
49
|
-
* @
|
|
52
|
+
* @param {WccVuePluginOptions} [options]
|
|
53
|
+
* @returns {import('vite').Plugin[]}
|
|
50
54
|
*/
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
if (node.type !== 1) return;
|
|
54
|
-
|
|
55
|
-
// Only process custom elements (tag contains a hyphen)
|
|
56
|
-
if (!node.tag.includes('-')) return;
|
|
55
|
+
export function wccVuePlugin(options = {}) {
|
|
56
|
+
const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const preTransformPlugin = {
|
|
59
|
+
name: 'vite-plugin-wcc-vmodel',
|
|
60
|
+
enforce: 'pre',
|
|
61
|
+
transform(code, id) {
|
|
62
|
+
if (!id.endsWith('.vue')) return null
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
// Check if this is a v-model directive (with or without argument)
|
|
64
|
-
if (
|
|
65
|
-
prop.type === 7 && // DIRECTIVE
|
|
66
|
-
prop.name === 'model'
|
|
67
|
-
) {
|
|
68
|
-
// Determine prop name: explicit arg or default 'modelValue'
|
|
69
|
-
const propName = prop.arg ? prop.arg.content : 'modelValue';
|
|
70
|
-
const expr = prop.exp;
|
|
64
|
+
let result = code
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
// Transform v-model:propName="expr" on custom elements (tags with hyphens)
|
|
67
|
+
// → :propName="expr" @propName-changed="expr = $event.detail"
|
|
68
|
+
// Run in a loop to handle multiple v-model on the same element
|
|
69
|
+
let prev = ''
|
|
70
|
+
while (prev !== result) {
|
|
71
|
+
prev = result
|
|
72
|
+
result = result.replace(
|
|
73
|
+
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model:(\w+)="([^"]+)"/,
|
|
74
|
+
(match, prefix, prop, expr) => {
|
|
75
|
+
return `${prefix}:${prop}="${expr}" @${prop}-changed="${expr} = $event.detail"`
|
|
76
|
+
}
|
|
77
|
+
)
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
type: 7, // DIRECTIVE
|
|
90
|
-
name: 'bind',
|
|
91
|
-
arg: argNode,
|
|
92
|
-
exp: expr,
|
|
93
|
-
modifiers: [],
|
|
94
|
-
loc: prop.loc
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// @update:propName="$event => { expr = $event }" (on directive)
|
|
98
|
-
newProps.push({
|
|
99
|
-
type: 7, // DIRECTIVE
|
|
100
|
-
name: 'on',
|
|
101
|
-
arg: {
|
|
102
|
-
type: 4, // SIMPLE_EXPRESSION
|
|
103
|
-
content: `update:${propName}`,
|
|
104
|
-
isStatic: true,
|
|
105
|
-
constType: 3,
|
|
106
|
-
loc: prop.loc
|
|
107
|
-
},
|
|
108
|
-
exp: {
|
|
109
|
-
type: 4, // SIMPLE_EXPRESSION
|
|
110
|
-
content: `$event => { ${expr.content} = $event.detail ?? $event }`,
|
|
111
|
-
isStatic: false,
|
|
112
|
-
constType: 0,
|
|
113
|
-
loc: prop.loc
|
|
114
|
-
},
|
|
115
|
-
modifiers: [],
|
|
116
|
-
loc: prop.loc
|
|
117
|
-
});
|
|
80
|
+
// Transform v-model="expr" (without argument) on custom elements
|
|
81
|
+
// → :model-value="expr" @model-value-changed="expr = $event.detail"
|
|
82
|
+
prev = ''
|
|
83
|
+
while (prev !== result) {
|
|
84
|
+
prev = result
|
|
85
|
+
result = result.replace(
|
|
86
|
+
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model="([^"]+)"/,
|
|
87
|
+
(match, prefix, expr) => {
|
|
88
|
+
return `${prefix}:model-value="${expr}" @model-value-changed="${expr} = $event.detail"`
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
}
|
|
118
92
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
newProps.push(prop);
|
|
93
|
+
if (result !== code) return result
|
|
94
|
+
return null
|
|
122
95
|
}
|
|
123
96
|
}
|
|
124
97
|
|
|
125
|
-
|
|
126
|
-
node.props = newProps;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Creates a Vite plugin that configures Vue's template compiler
|
|
132
|
-
* to recognize custom elements with the given prefix and enables
|
|
133
|
-
* v-model:propName on those elements.
|
|
134
|
-
*
|
|
135
|
-
* @param {WccVuePluginOptions} [options]
|
|
136
|
-
* @returns {import('vite').Plugin}
|
|
137
|
-
*/
|
|
138
|
-
export function wccVuePlugin(options = {}) {
|
|
139
|
-
const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
|
|
140
|
-
return vue({
|
|
98
|
+
const vuePlugin = vue({
|
|
141
99
|
template: {
|
|
142
100
|
compilerOptions: {
|
|
143
|
-
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
144
|
-
nodeTransforms: [wccVModelTransform]
|
|
101
|
+
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
145
102
|
}
|
|
146
103
|
}
|
|
147
104
|
})
|
|
105
|
+
|
|
106
|
+
return [preTransformPlugin, vuePlugin]
|
|
148
107
|
}
|
package/lib/codegen.js
CHANGED
|
@@ -1664,8 +1664,13 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1664
1664
|
lines.push('');
|
|
1665
1665
|
}
|
|
1666
1666
|
|
|
1667
|
-
// _modelSet methods (one per defineModel prop — emits
|
|
1667
|
+
// _modelSet methods (one per defineModel prop — emits events on internal write)
|
|
1668
|
+
// Emits:
|
|
1669
|
+
// 1. wcc:model — generic event for vanilla JS and WCC-to-WCC binding
|
|
1670
|
+
// 2. propName-changed — for Vue v-model (Vue doesn't filter this name)
|
|
1671
|
+
// 3. propNameChange — for Angular [(prop)] banana-box syntax
|
|
1668
1672
|
for (const md of modelDefs) {
|
|
1673
|
+
const kebabName = camelToKebab(md.name);
|
|
1669
1674
|
lines.push(` _modelSet_${md.name}(newVal) {`);
|
|
1670
1675
|
lines.push(` const oldVal = this._m_${md.name}();`);
|
|
1671
1676
|
lines.push(` this._m_${md.name}(newVal);`);
|
|
@@ -1674,6 +1679,10 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1674
1679
|
lines.push(` bubbles: true,`);
|
|
1675
1680
|
lines.push(` composed: true`);
|
|
1676
1681
|
lines.push(` }));`);
|
|
1682
|
+
// Vue: propName-changed (not filtered by Vue's isModelListener)
|
|
1683
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${kebabName}-changed', { detail: newVal, bubbles: true }));`);
|
|
1684
|
+
// Angular: propNameChange (Angular's [(prop)] listens for propChange)
|
|
1685
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${md.name}Change', { detail: newVal, bubbles: true }));`);
|
|
1677
1686
|
lines.push(' }');
|
|
1678
1687
|
lines.push('');
|
|
1679
1688
|
}
|
package/package.json
CHANGED