@sprlab/wccompiler 0.8.5 → 0.8.8
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 +83 -94
- package/lib/codegen.js +15 -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,96 @@ 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;
|
|
55
|
+
export function wccVuePlugin(options = {}) {
|
|
56
|
+
const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const preTransformPlugin = {
|
|
59
|
+
name: 'vite-plugin-wcc-vmodel',
|
|
60
|
+
enforce: 'pre',
|
|
61
|
+
transform(code, id) {
|
|
62
|
+
if (!id.endsWith('.vue')) return null
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
const newProps = [];
|
|
60
|
-
let modified = false;
|
|
64
|
+
let result = code
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
)
|
|
78
|
+
}
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
)
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
constType: 3,
|
|
83
|
-
loc: prop.loc
|
|
84
|
-
};
|
|
93
|
+
// ── Slot transforms ──
|
|
94
|
+
// Transform <template #name>content</template> inside custom elements
|
|
95
|
+
// → <div slot="name">content</div>
|
|
96
|
+
// This prevents Vue from intercepting the slot syntax and erroring.
|
|
97
|
+
// The WCC component's runtime slot parser detects slot="name" on regular elements.
|
|
85
98
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
// Handle <template #name>...</template> (shorthand)
|
|
100
|
+
prev = ''
|
|
101
|
+
while (prev !== result) {
|
|
102
|
+
prev = result
|
|
103
|
+
result = result.replace(
|
|
104
|
+
/<template\s+#(\w+)>([\s\S]*?)<\/template>/,
|
|
105
|
+
(match, slotName, content) => {
|
|
106
|
+
return `<div slot="${slotName}">${content}</div>`
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
}
|
|
96
110
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
content
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
});
|
|
111
|
+
// Handle <template v-slot:name>...</template> (verbose)
|
|
112
|
+
prev = ''
|
|
113
|
+
while (prev !== result) {
|
|
114
|
+
prev = result
|
|
115
|
+
result = result.replace(
|
|
116
|
+
/<template\s+v-slot:(\w+)>([\s\S]*?)<\/template>/,
|
|
117
|
+
(match, slotName, content) => {
|
|
118
|
+
return `<div slot="${slotName}">${content}</div>`
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
}
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
newProps.push(prop);
|
|
123
|
+
if (result !== code) return result
|
|
124
|
+
return null
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
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({
|
|
128
|
+
const vuePlugin = vue({
|
|
141
129
|
template: {
|
|
142
130
|
compilerOptions: {
|
|
143
|
-
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
144
|
-
nodeTransforms: [wccVModelTransform]
|
|
131
|
+
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
145
132
|
}
|
|
146
133
|
}
|
|
147
134
|
})
|
|
135
|
+
|
|
136
|
+
return [preTransformPlugin, vuePlugin]
|
|
148
137
|
}
|
package/lib/codegen.js
CHANGED
|
@@ -1039,6 +1039,11 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1039
1039
|
lines.push(' __slotMap[slotName] = { content: child.innerHTML, propsExpr: attr.value };');
|
|
1040
1040
|
lines.push(' }');
|
|
1041
1041
|
lines.push(' }');
|
|
1042
|
+
lines.push(" } else if (child.nodeType === 1 && child.getAttribute('slot')) {");
|
|
1043
|
+
// NEW: regular element with slot="name" (cross-framework support)
|
|
1044
|
+
lines.push(" const slotName = child.getAttribute('slot');");
|
|
1045
|
+
lines.push(" child.removeAttribute('slot');");
|
|
1046
|
+
lines.push(' __slotMap[slotName] = { content: child.outerHTML, propsExpr: \'\' };');
|
|
1042
1047
|
lines.push(" } else if (child.nodeType === 1 || (child.nodeType === 3 && child.textContent.trim())) {");
|
|
1043
1048
|
lines.push(' __defaultSlotNodes.push(child);');
|
|
1044
1049
|
lines.push(' }');
|
|
@@ -1664,8 +1669,13 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1664
1669
|
lines.push('');
|
|
1665
1670
|
}
|
|
1666
1671
|
|
|
1667
|
-
// _modelSet methods (one per defineModel prop — emits
|
|
1672
|
+
// _modelSet methods (one per defineModel prop — emits events on internal write)
|
|
1673
|
+
// Emits:
|
|
1674
|
+
// 1. wcc:model — generic event for vanilla JS and WCC-to-WCC binding
|
|
1675
|
+
// 2. propName-changed — for Vue v-model (Vue doesn't filter this name)
|
|
1676
|
+
// 3. propNameChange — for Angular [(prop)] banana-box syntax
|
|
1668
1677
|
for (const md of modelDefs) {
|
|
1678
|
+
const kebabName = camelToKebab(md.name);
|
|
1669
1679
|
lines.push(` _modelSet_${md.name}(newVal) {`);
|
|
1670
1680
|
lines.push(` const oldVal = this._m_${md.name}();`);
|
|
1671
1681
|
lines.push(` this._m_${md.name}(newVal);`);
|
|
@@ -1674,6 +1684,10 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1674
1684
|
lines.push(` bubbles: true,`);
|
|
1675
1685
|
lines.push(` composed: true`);
|
|
1676
1686
|
lines.push(` }));`);
|
|
1687
|
+
// Vue: propName-changed (not filtered by Vue's isModelListener)
|
|
1688
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${kebabName}-changed', { detail: newVal, bubbles: true }));`);
|
|
1689
|
+
// Angular: propNameChange (Angular's [(prop)] listens for propChange)
|
|
1690
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${md.name}Change', { detail: newVal, bubbles: true }));`);
|
|
1677
1691
|
lines.push(' }');
|
|
1678
1692
|
lines.push('');
|
|
1679
1693
|
}
|
package/package.json
CHANGED