@sprlab/wccompiler 0.10.3 → 0.10.5
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/integrations/vue.js +34 -21
- package/lib/codegen.js +32 -5
- package/package.json +1 -1
package/integrations/vue.js
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vue Vite plugin for WCC custom elements.
|
|
3
|
-
* Configures isCustomElement and
|
|
3
|
+
* Configures isCustomElement and provides enhanced DX for v-model modifiers and scoped slots.
|
|
4
4
|
*
|
|
5
5
|
* @module @sprlab/wccompiler/integrations/vue
|
|
6
6
|
*
|
|
7
|
-
* IMPORTANT: This
|
|
8
|
-
*
|
|
7
|
+
* IMPORTANT: This plugin is OPTIONAL for basic usage.
|
|
8
|
+
* WCC components work in Vue with zero WCC-specific config:
|
|
9
|
+
* - Props: <wcc-counter :count="val"></wcc-counter>
|
|
10
|
+
* - Events: <wcc-counter @count-changed="handler($event.detail)"></wcc-counter>
|
|
11
|
+
* - v-model: <wcc-counter v-model:count="val"></wcc-counter> (Vue 3.4+ CE support)
|
|
12
|
+
* - Named slots: <div slot="header">...</div>
|
|
9
13
|
*
|
|
10
|
-
*
|
|
14
|
+
* The only Vue-specific config needed (same as Lit, Shoelace, FAST):
|
|
15
|
+
* vue({ template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } } })
|
|
16
|
+
*
|
|
17
|
+
* This plugin adds:
|
|
18
|
+
* 1. isCustomElement config (so you don't need to write it manually)
|
|
19
|
+
* 2. v-model modifier support (.trim, .number, .lazy)
|
|
20
|
+
* 3. Scoped slot syntax: <template #item="{ name }">{{name}}</template>
|
|
21
|
+
*
|
|
22
|
+
* @example vite.config.js (with plugin — full DX)
|
|
11
23
|
* ```js
|
|
12
24
|
* import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
|
|
13
25
|
* export default { plugins: [wccVuePlugin()] }
|
|
14
26
|
* ```
|
|
15
27
|
*
|
|
16
|
-
* @example
|
|
28
|
+
* @example vite.config.js (without plugin — still works for basic usage)
|
|
17
29
|
* ```js
|
|
18
|
-
* import
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* <wcc-input v-model="text"></wcc-input>
|
|
25
|
-
* <wcc-form v-model:count="countRef" v-model:title="titleRef"></wcc-form>
|
|
30
|
+
* import vue from '@vitejs/plugin-vue'
|
|
31
|
+
* export default {
|
|
32
|
+
* plugins: [vue({
|
|
33
|
+
* template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } }
|
|
34
|
+
* })]
|
|
35
|
+
* }
|
|
26
36
|
* ```
|
|
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:*).
|
|
35
37
|
*/
|
|
36
38
|
|
|
37
39
|
import vue from '@vitejs/plugin-vue'
|
|
@@ -63,6 +65,17 @@ export function wccVuePlugin(options = {}) {
|
|
|
63
65
|
|
|
64
66
|
let result = code
|
|
65
67
|
|
|
68
|
+
// NOTE: As of WCC 0.10.3+, basic events work WITHOUT this plugin because
|
|
69
|
+
// the compiled component emits events in multiple formats (kebab, camelCase, lowercase).
|
|
70
|
+
// However, v-model:propName STILL REQUIRES this plugin because Vue assigns the raw
|
|
71
|
+
// Event object to the ref — it doesn't extract .detail automatically.
|
|
72
|
+
// This transform rewrites v-model:prop to @prop-changed="ref = $event.detail".
|
|
73
|
+
//
|
|
74
|
+
// This plugin is needed for:
|
|
75
|
+
// 1. v-model:propName (Vue can't unwrap CustomEvent.detail natively)
|
|
76
|
+
// 2. v-model modifiers (.trim, .number)
|
|
77
|
+
// 3. Scoped slot syntax transformation ({{prop}} → {%prop%})
|
|
78
|
+
|
|
66
79
|
// Transform v-model:propName="expr" on custom elements (tags with hyphens)
|
|
67
80
|
// Also handles modifiers: v-model:propName.trim.number="expr"
|
|
68
81
|
// → :propName="expr" @propName-changed="expr = $event.detail"
|
package/lib/codegen.js
CHANGED
|
@@ -1773,9 +1773,25 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1773
1773
|
}
|
|
1774
1774
|
|
|
1775
1775
|
// _emit method (if emits declared)
|
|
1776
|
+
// Emits the event in multiple formats for cross-framework compatibility:
|
|
1777
|
+
// 1. Original name (kebab-case) — for direct addEventListener and Angular (event-name)
|
|
1778
|
+
// 2. camelCase — for Angular WccEvents directive
|
|
1779
|
+
// 3. lowercase — for React 19 (onEventName → addEventListener('eventname'))
|
|
1776
1780
|
if (emits.length > 0) {
|
|
1777
1781
|
lines.push(' _emit(name, detail) {');
|
|
1778
|
-
lines.push('
|
|
1782
|
+
lines.push(' const evt = { detail, bubbles: true, composed: true };');
|
|
1783
|
+
lines.push(' this.dispatchEvent(new CustomEvent(name, evt));');
|
|
1784
|
+
// camelCase version (only if name contains hyphens)
|
|
1785
|
+
lines.push(" if (name.includes('-')) {");
|
|
1786
|
+
lines.push(" const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());");
|
|
1787
|
+
lines.push(' this.dispatchEvent(new CustomEvent(camel, evt));');
|
|
1788
|
+
// lowercase version for React 19
|
|
1789
|
+
lines.push(' this.dispatchEvent(new CustomEvent(camel.toLowerCase(), evt));');
|
|
1790
|
+
lines.push(' } else {');
|
|
1791
|
+
// If no hyphens, just emit lowercase (for React 19)
|
|
1792
|
+
lines.push(' const lower = name.toLowerCase();');
|
|
1793
|
+
lines.push(' if (lower !== name) this.dispatchEvent(new CustomEvent(lower, evt));');
|
|
1794
|
+
lines.push(' }');
|
|
1779
1795
|
lines.push(' }');
|
|
1780
1796
|
lines.push('');
|
|
1781
1797
|
}
|
|
@@ -1783,10 +1799,17 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1783
1799
|
// _modelSet methods (one per defineModel prop — emits events on internal write)
|
|
1784
1800
|
// Emits:
|
|
1785
1801
|
// 1. wcc:model — generic event for vanilla JS and WCC-to-WCC binding
|
|
1786
|
-
// 2. propName-changed — for
|
|
1787
|
-
// 3.
|
|
1802
|
+
// 2. propName-changed — kebab-case for direct addEventListener and Vue plugin
|
|
1803
|
+
// 3. propNameChanged — camelCase for Angular WccEvents / direct binding
|
|
1804
|
+
// 4. propnamechanged — lowercase for React 19 (onPropnameChanged → 'propnamechanged')
|
|
1805
|
+
// 5. propNameChange — for Angular [(prop)] banana-box syntax
|
|
1806
|
+
//
|
|
1807
|
+
// NOTE: Vue v-model:prop requires the wccVuePlugin because Vue assigns the raw
|
|
1808
|
+
// Event object to the ref (not event.detail). The plugin transforms v-model:prop
|
|
1809
|
+
// to @prop-changed="ref = $event.detail" which correctly extracts the value.
|
|
1788
1810
|
for (const md of modelDefs) {
|
|
1789
1811
|
const kebabName = camelToKebab(md.name);
|
|
1812
|
+
const camelChanged = `${md.name}Changed`;
|
|
1790
1813
|
lines.push(` _modelSet_${md.name}(newVal) {`);
|
|
1791
1814
|
lines.push(` const oldVal = this._m_${md.name}();`);
|
|
1792
1815
|
lines.push(` this._m_${md.name}(newVal);`);
|
|
@@ -1795,9 +1818,13 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1795
1818
|
lines.push(` bubbles: true,`);
|
|
1796
1819
|
lines.push(` composed: true`);
|
|
1797
1820
|
lines.push(` }));`);
|
|
1798
|
-
//
|
|
1821
|
+
// Kebab-case: prop-name-changed (Vue plugin, addEventListener)
|
|
1799
1822
|
lines.push(` this.dispatchEvent(new CustomEvent('${kebabName}-changed', { detail: newVal, bubbles: true }));`);
|
|
1800
|
-
//
|
|
1823
|
+
// camelCase: propNameChanged (Angular, addEventListener)
|
|
1824
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${camelChanged}', { detail: newVal, bubbles: true }));`);
|
|
1825
|
+
// lowercase: propnamechanged (React 19)
|
|
1826
|
+
lines.push(` this.dispatchEvent(new CustomEvent('${camelChanged.toLowerCase()}', { detail: newVal, bubbles: true }));`);
|
|
1827
|
+
// Angular banana-box: propNameChange
|
|
1801
1828
|
lines.push(` this.dispatchEvent(new CustomEvent('${md.name}Change', { detail: newVal, bubbles: true }));`);
|
|
1802
1829
|
lines.push(' }');
|
|
1803
1830
|
lines.push('');
|
package/package.json
CHANGED