@sprlab/wccompiler 0.10.3 → 0.10.4

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,37 +1,39 @@
1
1
  /**
2
2
  * Vue Vite plugin for WCC custom elements.
3
- * Configures isCustomElement and enables v-model:propName on custom elements.
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 file is for vite.config.js (Node.js context).
8
- * For browser-side, use app.use(wccVue) from '@sprlab/wccompiler/adapters/vue'.
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
- * @example vite.config.js
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 main.js (optionalonly needed if NOT using wccVuePlugin)
28
+ * @example vite.config.js (without plugin still works for basic usage)
17
29
  * ```js
18
- * import { wccVue } from '@sprlab/wccompiler/adapters/vue'
19
- * app.use(wccVue)
20
- * ```
21
- *
22
- * With wccVuePlugin(), v-model:propName works natively on WCC custom elements:
23
- * ```vue
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,13 @@ export function wccVuePlugin(options = {}) {
63
65
 
64
66
  let result = code
65
67
 
68
+ // NOTE: As of WCC 0.10.3+, v-model:propName works WITHOUT this plugin
69
+ // because the compiled component emits 'update:propName' natively (Vue 3.4+ CE support).
70
+ // This transform is still useful for:
71
+ // 1. v-model modifiers (.trim, .number) — Vue doesn't apply modifiers to CE v-model natively
72
+ // 2. Older Vue versions (< 3.4) that don't support v-model on CE via update:propName
73
+ // 3. Scoped slot syntax transformation ({{prop}} → {%prop%})
74
+
66
75
  // Transform v-model:propName="expr" on custom elements (tags with hyphens)
67
76
  // Also handles modifiers: v-model:propName.trim.number="expr"
68
77
  // → :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(' this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));');
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,14 @@ 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 Vue v-model (Vue doesn't filter this name)
1787
- // 3. propNameChange — for Angular [(prop)] banana-box syntax
1802
+ // 2. propName-changed — kebab-case for direct addEventListener
1803
+ // 3. propNameChangedcamelCase for Angular WccEvents / direct binding
1804
+ // 4. propnamechanged — lowercase for React 19 (onPropnameChanged → 'propnamechanged')
1805
+ // 5. propNameChange — for Angular [(prop)] banana-box syntax
1806
+ // 6. update:propName — for Vue v-model:propName on custom elements (Vue 3.4+)
1788
1807
  for (const md of modelDefs) {
1789
1808
  const kebabName = camelToKebab(md.name);
1809
+ const camelChanged = `${md.name}Changed`;
1790
1810
  lines.push(` _modelSet_${md.name}(newVal) {`);
1791
1811
  lines.push(` const oldVal = this._m_${md.name}();`);
1792
1812
  lines.push(` this._m_${md.name}(newVal);`);
@@ -1795,10 +1815,16 @@ export function generateComponent(parseResult, options = {}) {
1795
1815
  lines.push(` bubbles: true,`);
1796
1816
  lines.push(` composed: true`);
1797
1817
  lines.push(` }));`);
1798
- // Vue: propName-changed (not filtered by Vue's isModelListener)
1818
+ // Kebab-case: prop-name-changed
1799
1819
  lines.push(` this.dispatchEvent(new CustomEvent('${kebabName}-changed', { detail: newVal, bubbles: true }));`);
1800
- // Angular: propNameChange (Angular's [(prop)] listens for propChange)
1820
+ // camelCase: propNameChanged
1821
+ lines.push(` this.dispatchEvent(new CustomEvent('${camelChanged}', { detail: newVal, bubbles: true }));`);
1822
+ // lowercase: propnamechanged (React 19)
1823
+ lines.push(` this.dispatchEvent(new CustomEvent('${camelChanged.toLowerCase()}', { detail: newVal, bubbles: true }));`);
1824
+ // Angular banana-box: propNameChange
1801
1825
  lines.push(` this.dispatchEvent(new CustomEvent('${md.name}Change', { detail: newVal, bubbles: true }));`);
1826
+ // Vue v-model: update:propName
1827
+ lines.push(` this.dispatchEvent(new CustomEvent('update:${md.name}', { detail: newVal, bubbles: true }));`);
1802
1828
  lines.push(' }');
1803
1829
  lines.push('');
1804
1830
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
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": {