@sprlab/wccompiler 0.9.3 → 0.9.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.
- package/adapters/react.js +103 -0
- package/integrations/react.js +13 -109
- package/package.json +3 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React browser-side hooks for WCC custom elements.
|
|
3
|
+
* Bridges CustomEvent to React's ref-based event system.
|
|
4
|
+
*
|
|
5
|
+
* @module @sprlab/wccompiler/adapters/react
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Import hooks from THIS file (not from integrations/react).
|
|
8
|
+
* The integrations/react file is for vite.config.js only (contains Babel).
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { useWccEvent, useWccModel } from '@sprlab/wccompiler/adapters/react'
|
|
12
|
+
*
|
|
13
|
+
* // Listen to custom events
|
|
14
|
+
* const ref = useWccEvent('change', (e) => console.log(e.detail))
|
|
15
|
+
* <wcc-counter ref={ref}></wcc-counter>
|
|
16
|
+
*
|
|
17
|
+
* // Two-way binding with defineModel
|
|
18
|
+
* const [text, setText] = useState('')
|
|
19
|
+
* const inputRef = useWccModel('value', text, setText)
|
|
20
|
+
* <wcc-input ref={inputRef}></wcc-input>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { useRef, useEffect } from 'react'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook that attaches a CustomEvent listener to a DOM element via ref.
|
|
27
|
+
*
|
|
28
|
+
* Supports two calling conventions:
|
|
29
|
+
* - useWccEvent(ref, eventName, handler) — uses an existing ref
|
|
30
|
+
* - useWccEvent(eventName, handler) — creates and returns a new ref
|
|
31
|
+
*
|
|
32
|
+
* @param {import('react').RefObject<HTMLElement> | string} refOrEventName
|
|
33
|
+
* @param {string | ((event: CustomEvent) => void)} eventNameOrHandler
|
|
34
|
+
* @param {((event: CustomEvent) => void)} [handler]
|
|
35
|
+
* @returns {import('react').RefObject<HTMLElement> | void}
|
|
36
|
+
*/
|
|
37
|
+
export function useWccEvent(refOrEventName, eventNameOrHandler, handler) {
|
|
38
|
+
const isRefForm = typeof refOrEventName !== 'string'
|
|
39
|
+
const elementRef = isRefForm ? refOrEventName : useRef(null)
|
|
40
|
+
const eventName = isRefForm ? eventNameOrHandler : refOrEventName
|
|
41
|
+
const callback = isRefForm ? handler : eventNameOrHandler
|
|
42
|
+
|
|
43
|
+
const handlerRef = useRef(callback)
|
|
44
|
+
handlerRef.current = callback
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const el = elementRef.current
|
|
48
|
+
if (!el) return
|
|
49
|
+
const listener = (e) => handlerRef.current(e)
|
|
50
|
+
el.addEventListener(eventName, listener)
|
|
51
|
+
return () => el.removeEventListener(eventName, listener)
|
|
52
|
+
}, [eventName])
|
|
53
|
+
|
|
54
|
+
if (!isRefForm) return elementRef
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Hook for two-way binding with WCC defineModel props.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} propName - The model prop name (e.g., 'value', 'count')
|
|
61
|
+
* @param {*} value - Current React state value
|
|
62
|
+
* @param {(newValue: *) => void} setValue - React state setter
|
|
63
|
+
* @param {import('react').RefObject<HTMLElement>} [existingRef] - Optional existing ref
|
|
64
|
+
* @returns {import('react').RefObject<HTMLElement>} Ref to attach to the WCC element
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* const [text, setText] = useState('')
|
|
68
|
+
* const inputRef = useWccModel('value', text, setText)
|
|
69
|
+
* <wcc-input ref={inputRef}></wcc-input>
|
|
70
|
+
*/
|
|
71
|
+
export function useWccModel(propName, value, setValue, existingRef) {
|
|
72
|
+
const internalRef = useRef(null)
|
|
73
|
+
const elementRef = existingRef || internalRef
|
|
74
|
+
|
|
75
|
+
const setValueRef = useRef(setValue)
|
|
76
|
+
setValueRef.current = setValue
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const el = elementRef.current
|
|
80
|
+
if (!el) return
|
|
81
|
+
|
|
82
|
+
const listener = (e) => {
|
|
83
|
+
if (e.detail && e.detail.prop === propName) {
|
|
84
|
+
setValueRef.current(e.detail.value)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
el.addEventListener('wcc:model', listener)
|
|
89
|
+
return () => el.removeEventListener('wcc:model', listener)
|
|
90
|
+
}, [propName])
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const el = elementRef.current
|
|
94
|
+
if (!el) return
|
|
95
|
+
if (value != null) {
|
|
96
|
+
el.setAttribute(propName, String(value))
|
|
97
|
+
} else {
|
|
98
|
+
el.removeAttribute(propName)
|
|
99
|
+
}
|
|
100
|
+
}, [propName, value])
|
|
101
|
+
|
|
102
|
+
return elementRef
|
|
103
|
+
}
|
package/integrations/react.js
CHANGED
|
@@ -1,126 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React
|
|
3
|
-
*
|
|
2
|
+
* React Vite plugin for WCC custom elements.
|
|
3
|
+
* Transforms idiomatic React JSX slot patterns into WCC-compatible slot markup.
|
|
4
4
|
*
|
|
5
5
|
* @module @sprlab/wccompiler/integrations/react
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* const ref = useRef(null)
|
|
10
|
-
* useWccEvent(ref, 'change', (e) => console.log(e.detail))
|
|
11
|
-
* <wcc-counter ref={ref}></wcc-counter>
|
|
7
|
+
* IMPORTANT: This file is for vite.config.js (Node.js context) ONLY.
|
|
8
|
+
* For browser-side hooks, import from '@sprlab/wccompiler/adapters/react'.
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* @example vite.config.js
|
|
11
|
+
* ```js
|
|
12
|
+
* import { wccReactPlugin } from '@sprlab/wccompiler/integrations/react'
|
|
13
|
+
* export default { plugins: [wccReactPlugin()] }
|
|
14
|
+
* ```
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* @example Component (browser — import hooks from adapters)
|
|
17
|
+
* ```jsx
|
|
18
|
+
* import { useWccEvent, useWccModel } from '@sprlab/wccompiler/adapters/react'
|
|
19
|
+
* ```
|
|
21
20
|
*/
|
|
22
21
|
|
|
23
|
-
import { useRef, useEffect, useCallback } from 'react'
|
|
24
22
|
import { parse } from '@babel/parser'
|
|
25
23
|
import _traverse from '@babel/traverse'
|
|
26
24
|
import _generate from '@babel/generator'
|
|
27
25
|
const traverse = _traverse.default || _traverse
|
|
28
26
|
const generate = _generate.default || _generate
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* Hook that attaches a CustomEvent listener to a DOM element via ref.
|
|
32
|
-
*
|
|
33
|
-
* Supports two calling conventions:
|
|
34
|
-
* - useWccEvent(ref, eventName, handler) — uses an existing ref
|
|
35
|
-
* - useWccEvent(eventName, handler) — creates and returns a new ref
|
|
36
|
-
*
|
|
37
|
-
* @param {import('react').RefObject<HTMLElement> | string} refOrEventName
|
|
38
|
-
* @param {string | ((event: CustomEvent) => void)} eventNameOrHandler
|
|
39
|
-
* @param {((event: CustomEvent) => void)} [handler]
|
|
40
|
-
* @returns {import('react').RefObject<HTMLElement> | void}
|
|
41
|
-
*/
|
|
42
|
-
export function useWccEvent(refOrEventName, eventNameOrHandler, handler) {
|
|
43
|
-
// Detect calling convention
|
|
44
|
-
const isRefForm = typeof refOrEventName !== 'string'
|
|
45
|
-
const elementRef = isRefForm ? refOrEventName : useRef(null)
|
|
46
|
-
const eventName = isRefForm ? eventNameOrHandler : refOrEventName
|
|
47
|
-
const callback = isRefForm ? handler : eventNameOrHandler
|
|
48
|
-
|
|
49
|
-
const handlerRef = useRef(callback)
|
|
50
|
-
handlerRef.current = callback
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
const el = elementRef.current
|
|
54
|
-
if (!el) return
|
|
55
|
-
const listener = (e) => handlerRef.current(e)
|
|
56
|
-
el.addEventListener(eventName, listener)
|
|
57
|
-
return () => el.removeEventListener(eventName, listener)
|
|
58
|
-
}, [eventName])
|
|
59
|
-
|
|
60
|
-
// Only return ref if we created it (Form 2)
|
|
61
|
-
if (!isRefForm) return elementRef
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Hook for two-way binding with WCC defineModel props.
|
|
67
|
-
*
|
|
68
|
-
* Listens for `wcc:model` events on the element and calls the setter
|
|
69
|
-
* when the matching prop changes internally. Also syncs the React state
|
|
70
|
-
* to the element's attribute when the value changes externally.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} propName - The model prop name (e.g., 'value', 'count')
|
|
73
|
-
* @param {*} value - Current React state value
|
|
74
|
-
* @param {(newValue: *) => void} setValue - React state setter
|
|
75
|
-
* @param {import('react').RefObject<HTMLElement>} [existingRef] - Optional existing ref
|
|
76
|
-
* @returns {import('react').RefObject<HTMLElement>} Ref to attach to the WCC element
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```jsx
|
|
80
|
-
* function App() {
|
|
81
|
-
* const [text, setText] = useState('')
|
|
82
|
-
* const inputRef = useWccModel('value', text, setText)
|
|
83
|
-
* return <wcc-input ref={inputRef}></wcc-input>
|
|
84
|
-
* }
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export function useWccModel(propName, value, setValue, existingRef) {
|
|
88
|
-
const internalRef = useRef(null)
|
|
89
|
-
const elementRef = existingRef || internalRef
|
|
90
|
-
|
|
91
|
-
const setValueRef = useRef(setValue)
|
|
92
|
-
setValueRef.current = setValue
|
|
93
|
-
|
|
94
|
-
// Listen for wcc:model events from the component (child → parent)
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
const el = elementRef.current
|
|
97
|
-
if (!el) return
|
|
98
|
-
|
|
99
|
-
const listener = (e) => {
|
|
100
|
-
if (e.detail && e.detail.prop === propName) {
|
|
101
|
-
setValueRef.current(e.detail.value)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
el.addEventListener('wcc:model', listener)
|
|
106
|
-
return () => el.removeEventListener('wcc:model', listener)
|
|
107
|
-
}, [propName])
|
|
108
|
-
|
|
109
|
-
// Sync React state to the element's attribute (parent → child)
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
const el = elementRef.current
|
|
112
|
-
if (!el) return
|
|
113
|
-
if (value != null) {
|
|
114
|
-
el.setAttribute(propName, String(value))
|
|
115
|
-
} else {
|
|
116
|
-
el.removeAttribute(propName)
|
|
117
|
-
}
|
|
118
|
-
}, [propName, value])
|
|
119
|
-
|
|
120
|
-
return elementRef
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
28
|
/**
|
|
125
29
|
* JSX attribute name to HTML attribute name mapping.
|
|
126
30
|
* React uses camelCase for some attributes that HTML uses lowercase.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sprlab/wccompiler",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.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": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"./integrations/react": "./integrations/react.js",
|
|
10
10
|
"./integrations/angular": "./integrations/angular.js",
|
|
11
11
|
"./adapters/vue": "./adapters/vue.js",
|
|
12
|
-
"./adapters/angular": "./adapters/angular.js"
|
|
12
|
+
"./adapters/angular": "./adapters/angular.js",
|
|
13
|
+
"./adapters/react": "./adapters/react.js"
|
|
13
14
|
},
|
|
14
15
|
"bin": {
|
|
15
16
|
"wcc": "./bin/wcc.js"
|