@zeus-js/output-react-wrapper 0.1.0-beta.3 → 0.1.0-beta.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/dist/output-react-wrapper.cjs.js +209 -140
- package/dist/output-react-wrapper.cjs.prod.js +209 -140
- package/dist/output-react-wrapper.esm-bundler.js +209 -140
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -20,212 +20,281 @@ function generateReactIndex(components, options) {
|
|
|
20
20
|
return lines.join("\n");
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
24
|
-
function toReactEventProp(eventName) {
|
|
25
|
-
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
23
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
24
|
function generateReactWrapper(input) {
|
|
30
|
-
|
|
31
|
-
if (mode === "minimal") return generateMinimalReactWrapper(input);
|
|
32
|
-
return generateEventBridgeReactWrapper(input);
|
|
33
|
-
}
|
|
34
|
-
function createBindings(names, prefix) {
|
|
35
|
-
return names.map((sourceName, index) => ({
|
|
36
|
-
sourceName,
|
|
37
|
-
localName: `${prefix}${index}`
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
function createEventBindings(eventNames) {
|
|
41
|
-
return eventNames.map((eventName, index) => ({
|
|
42
|
-
eventName,
|
|
43
|
-
sourceName: toReactEventProp(eventName),
|
|
44
|
-
localName: `eventHandler${index}`
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
function generateDestructuredBindings(bindings) {
|
|
48
|
-
if (!bindings.length) return "";
|
|
49
|
-
return bindings.map(({ sourceName, localName }) => {
|
|
50
|
-
return `${JSON.stringify(sourceName)}: ${localName},`;
|
|
51
|
-
}).join("\n ");
|
|
25
|
+
return input.mode === "event-bridge" ? generateEventBridgeReactWrapper(input) : generateMinimalReactWrapper(input);
|
|
52
26
|
}
|
|
53
27
|
function generateMinimalReactWrapper(input) {
|
|
54
28
|
const { component, namedSlots, wcModuleId } = input;
|
|
55
29
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
56
|
-
const
|
|
30
|
+
const omittedKeys = ["children", ...slotBindings.map(({ sourceName }) => sourceName)];
|
|
31
|
+
const slotAssignments = generatePropAssignments(slotBindings);
|
|
57
32
|
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
33
|
+
const childSetup = slotBindings.length ? `${namedSlotLines}
|
|
34
|
+
const childArgs = [];
|
|
35
|
+
pushAll(childArgs, slotNodes);
|
|
36
|
+
if (children != null) childArgs.push(children);` : "";
|
|
37
|
+
const render = slotBindings.length ? `React.createElement.apply(
|
|
38
|
+
React,
|
|
39
|
+
[${JSON.stringify(component.tag)}, rest].concat(childArgs),
|
|
40
|
+
)` : `React.createElement(${JSON.stringify(component.tag)}, rest, children)`;
|
|
58
41
|
return `
|
|
59
42
|
import * as React from 'react';
|
|
60
43
|
|
|
61
44
|
import ${JSON.stringify(wcModuleId)};
|
|
62
45
|
|
|
46
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
47
|
+
|
|
63
48
|
export const ${component.name} = React.forwardRef(
|
|
64
|
-
function ${component.name}({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ref,
|
|
74
|
-
},
|
|
75
|
-
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
76
|
-
);
|
|
49
|
+
function ${component.name}(inputProps, ref) {
|
|
50
|
+
const props = inputProps || {};
|
|
51
|
+
const children = props.children;
|
|
52
|
+
${slotAssignments}
|
|
53
|
+
const rest = omitProps(props);
|
|
54
|
+
rest.ref = ref;
|
|
55
|
+
${childSetup}
|
|
56
|
+
|
|
57
|
+
return ${render};
|
|
77
58
|
},
|
|
78
59
|
);
|
|
60
|
+
|
|
61
|
+
function omitProps(source) {
|
|
62
|
+
const output = {};
|
|
63
|
+
for (const key in source) {
|
|
64
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
65
|
+
output[key] = source[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasOwn(source, key) {
|
|
72
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
73
|
+
}
|
|
74
|
+
${slotBindings.length ? PUSH_ALL_HELPER : ""}
|
|
79
75
|
`.trimStart();
|
|
80
76
|
}
|
|
81
77
|
function generateEventBridgeReactWrapper(input) {
|
|
82
78
|
const { component, namedSlots, wcModuleId } = input;
|
|
83
79
|
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
84
|
-
const eventBindings = createEventBindings(
|
|
80
|
+
const eventBindings = createEventBindings(component.events);
|
|
81
|
+
if (!propBindings.length && !eventBindings.length) return generateMinimalReactWrapper(input);
|
|
85
82
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
86
|
-
const
|
|
83
|
+
const destructuredBindings = [
|
|
87
84
|
...propBindings,
|
|
88
85
|
...eventBindings,
|
|
89
86
|
...slotBindings
|
|
90
|
-
]
|
|
87
|
+
];
|
|
88
|
+
const omittedKeys = [
|
|
89
|
+
"children",
|
|
90
|
+
"className",
|
|
91
|
+
"style",
|
|
92
|
+
...destructuredBindings.map(({ sourceName }) => sourceName)
|
|
93
|
+
];
|
|
91
94
|
return `
|
|
92
95
|
import {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
${[
|
|
97
|
+
"createElement",
|
|
98
|
+
...slotBindings.length ? [
|
|
99
|
+
"cloneElement",
|
|
100
|
+
"Fragment",
|
|
101
|
+
"isValidElement"
|
|
102
|
+
] : [],
|
|
103
|
+
"forwardRef",
|
|
104
|
+
...propBindings.length || eventBindings.length ? ["useEffect"] : [],
|
|
105
|
+
"useImperativeHandle",
|
|
106
|
+
"useRef"
|
|
107
|
+
].join(",\n ")},
|
|
101
108
|
} from 'react';
|
|
102
109
|
|
|
103
110
|
import ${JSON.stringify(wcModuleId)};
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
children,
|
|
108
|
-
className,
|
|
109
|
-
style,
|
|
110
|
-
${destructuredProps}
|
|
111
|
-
...rest
|
|
112
|
-
} = props;
|
|
112
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
113
|
+
${eventBindings.length ? `const EVENT_NAMES = ${JSON.stringify(eventBindings.map((binding) => binding.eventName))};` : ""}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const slotChildren = [];
|
|
115
|
+
export const ${component.name} = forwardRef(function ${component.name}(inputProps, ref) {
|
|
116
|
+
const props = inputProps || {};
|
|
117
|
+
const children = props.children;
|
|
118
|
+
const className = props.className;
|
|
119
|
+
const style = props.style;
|
|
120
|
+
${generatePropAssignments(destructuredBindings)}
|
|
121
|
+
${generatePropPresenceAssignments(propBindings)}
|
|
122
|
+
const rest = omitProps(props);
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
const innerRef = useRef(null);
|
|
125
|
+
${generatePropRefs(propBindings)}
|
|
126
|
+
${generateEventRefs(eventBindings)}
|
|
127
|
+
useImperativeHandle(ref, () => innerRef.current, []);
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
${generatePropSyncEffect(propBindings)}
|
|
130
|
+
${generateEventEffect(eventBindings)}
|
|
131
|
+
${generateChildrenSetup(slotBindings)}
|
|
132
|
+
rest.ref = innerRef;
|
|
133
|
+
rest.className = className;
|
|
134
|
+
rest.style = style;
|
|
130
135
|
|
|
131
|
-
return
|
|
132
|
-
${JSON.stringify(component.tag)},
|
|
133
|
-
{
|
|
134
|
-
...rest,
|
|
135
|
-
ref: innerRef,
|
|
136
|
-
className,
|
|
137
|
-
style,
|
|
138
|
-
},
|
|
139
|
-
...slotChildren,
|
|
140
|
-
);
|
|
136
|
+
return ${generateReactRender(component.tag, slotBindings)};
|
|
141
137
|
});
|
|
142
|
-
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
) {
|
|
150
|
-
return cloneElement(value, { slot: name });
|
|
138
|
+
${slotBindings.length ? NAMED_SLOT_HELPER : ""}
|
|
139
|
+
function omitProps(source) {
|
|
140
|
+
const output = {};
|
|
141
|
+
for (const key in source) {
|
|
142
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
143
|
+
output[key] = source[key];
|
|
144
|
+
}
|
|
151
145
|
}
|
|
146
|
+
return output;
|
|
147
|
+
}
|
|
152
148
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
slot: name,
|
|
157
|
-
style: { display: 'contents' },
|
|
158
|
-
},
|
|
159
|
-
value,
|
|
160
|
-
);
|
|
149
|
+
function hasOwn(source, key) {
|
|
150
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
161
151
|
}
|
|
162
152
|
`.trimStart();
|
|
163
153
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
function createBindings(names, prefix) {
|
|
155
|
+
return names.map((sourceName, index) => ({
|
|
156
|
+
sourceName,
|
|
157
|
+
localName: `${prefix}${index}`
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
function createEventBindings(events) {
|
|
161
|
+
return Object.entries(events).map(([key, event], index) => {
|
|
162
|
+
var _event$key, _event$name, _event$reactName;
|
|
163
|
+
const sourceEventName = (_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key;
|
|
164
|
+
return {
|
|
165
|
+
eventName: (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase(sourceEventName),
|
|
166
|
+
sourceName: (_event$reactName = event.reactName) !== null && _event$reactName !== void 0 ? _event$reactName : toReactEventProp(sourceEventName),
|
|
167
|
+
localName: `eventHandler${index}`
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function generatePropAssignments(bindings) {
|
|
172
|
+
return bindings.map(({ sourceName, localName }) => ` const ${localName} = props[${JSON.stringify(sourceName)}];`).join("\n");
|
|
173
|
+
}
|
|
174
|
+
function generatePropPresenceAssignments(bindings) {
|
|
175
|
+
return bindings.map(({ sourceName }, index) => ` const propPresent${index} = hasOwn(props, ${JSON.stringify(sourceName)});`).join("\n");
|
|
176
|
+
}
|
|
177
|
+
function generatePropRefs(bindings) {
|
|
178
|
+
if (!bindings.length) return "";
|
|
179
|
+
return ` const previousPropPresenceRef = useRef([]);
|
|
180
|
+
const previousPropValuesRef = useRef([]);`;
|
|
181
|
+
}
|
|
182
|
+
function generateEventRefs(bindings) {
|
|
183
|
+
if (!bindings.length) return "";
|
|
184
|
+
return ` const eventHandlersRef = useRef([]);
|
|
185
|
+
${bindings.map(({ localName }, index) => ` eventHandlersRef.current[${index}] = ${localName};`).join("\n")}`;
|
|
186
|
+
}
|
|
187
|
+
function generatePropSyncEffect(bindings) {
|
|
188
|
+
if (!bindings.length) return "";
|
|
189
|
+
return ` useEffect(() => {
|
|
167
190
|
const el = innerRef.current;
|
|
168
191
|
if (!el) return;
|
|
169
192
|
|
|
170
|
-
const
|
|
193
|
+
const previousPropPresence = previousPropPresenceRef.current;
|
|
194
|
+
const previousPropValues = previousPropValuesRef.current;
|
|
171
195
|
|
|
172
|
-
|
|
196
|
+
${bindings.map(({ sourceName, localName }, index) => {
|
|
173
197
|
const key = JSON.stringify(sourceName);
|
|
174
|
-
return `if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
return ` if (propPresent${index}) {
|
|
199
|
+
if (
|
|
200
|
+
!previousPropPresence[${index}] ||
|
|
201
|
+
!Object.is(previousPropValues[${index}], ${localName})
|
|
202
|
+
) {
|
|
203
|
+
el[${key}] = ${localName};
|
|
204
|
+
previousPropValues[${index}] = ${localName};
|
|
205
|
+
}
|
|
206
|
+
previousPropPresence[${index}] = true;
|
|
207
|
+
} else if (previousPropPresence[${index}]) {
|
|
178
208
|
el[${key}] = undefined;
|
|
179
|
-
|
|
209
|
+
previousPropPresence[${index}] = false;
|
|
210
|
+
previousPropValues[${index}] = undefined;
|
|
180
211
|
}`;
|
|
181
|
-
}).join("\n\n
|
|
182
|
-
}, [
|
|
212
|
+
}).join("\n\n")}
|
|
213
|
+
}, [${bindings.flatMap((binding, index) => [`propPresent${index}`, binding.localName]).join(", ")}]);
|
|
214
|
+
`;
|
|
183
215
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
216
|
+
function generateEventEffect(bindings) {
|
|
217
|
+
if (!bindings.length) return "";
|
|
218
|
+
return ` useEffect(() => {
|
|
188
219
|
const el = innerRef.current;
|
|
189
|
-
if (!el
|
|
220
|
+
if (!el) return;
|
|
190
221
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
222
|
+
const listeners = EVENT_NAMES.map(
|
|
223
|
+
(_eventName, index) => event => {
|
|
224
|
+
const handler = eventHandlersRef.current[index];
|
|
225
|
+
if (handler) handler(event);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
194
228
|
|
|
195
|
-
|
|
229
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
230
|
+
el.addEventListener(EVENT_NAMES[index], listeners[index]);
|
|
231
|
+
}
|
|
196
232
|
|
|
197
233
|
return () => {
|
|
198
|
-
|
|
234
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
235
|
+
el.removeEventListener(EVENT_NAMES[index], listeners[index]);
|
|
236
|
+
}
|
|
199
237
|
};
|
|
200
|
-
}, [
|
|
238
|
+
}, []);
|
|
201
239
|
`;
|
|
202
|
-
}).join("");
|
|
203
|
-
}
|
|
204
|
-
function getNamedSlots(component, namedSlots) {
|
|
205
|
-
if (namedSlots === "none") return [];
|
|
206
|
-
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
207
240
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
241
|
+
function generateChildrenSetup(bindings) {
|
|
242
|
+
if (!bindings.length) return "";
|
|
243
|
+
return ` const slotChildren = [];
|
|
244
|
+
${bindings.map(({ sourceName, localName }) => ` {
|
|
212
245
|
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
213
246
|
if (node != null) slotChildren.push(node);
|
|
214
|
-
}
|
|
247
|
+
}`).join("\n")}
|
|
248
|
+
if (children != null) slotChildren.push(children);
|
|
215
249
|
`;
|
|
216
|
-
|
|
250
|
+
}
|
|
251
|
+
function generateReactRender(tag, slotBindings) {
|
|
252
|
+
if (!slotBindings.length) return `createElement(${JSON.stringify(tag)}, rest, children)`;
|
|
253
|
+
return `createElement.apply(
|
|
254
|
+
null,
|
|
255
|
+
[${JSON.stringify(tag)}, rest].concat(slotChildren),
|
|
256
|
+
)`;
|
|
257
|
+
}
|
|
258
|
+
function getNamedSlots(component, namedSlots) {
|
|
259
|
+
return namedSlots === "none" ? [] : Object.keys(component.slots).filter((name) => name !== "default");
|
|
217
260
|
}
|
|
218
261
|
function generateMinimalNamedSlots(bindings) {
|
|
219
262
|
if (!bindings.length) return "";
|
|
220
|
-
return bindings.map(({ sourceName, localName }, index) => {
|
|
221
|
-
return ` const
|
|
263
|
+
return `${bindings.map(({ sourceName, localName }, index) => {
|
|
264
|
+
return ` const slotNode${index} = ${localName} != null && ${localName} !== false
|
|
222
265
|
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
223
266
|
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
224
267
|
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
225
|
-
: null
|
|
268
|
+
: null;`;
|
|
269
|
+
}).join("\n")}
|
|
270
|
+
const slotNodes = [${bindings.map((_, index) => `slotNode${index}`).join(", ")}].filter(Boolean);`;
|
|
271
|
+
}
|
|
272
|
+
function toKebabCase(value) {
|
|
273
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
274
|
+
}
|
|
275
|
+
function toReactEventProp(value) {
|
|
276
|
+
return `on${value.split("-").filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}`;
|
|
277
|
+
}
|
|
278
|
+
const PUSH_ALL_HELPER = `
|
|
279
|
+
function pushAll(target, values) {
|
|
280
|
+
for (const value of values) target.push(value);
|
|
281
|
+
}
|
|
226
282
|
`;
|
|
227
|
-
|
|
283
|
+
const NAMED_SLOT_HELPER = `
|
|
284
|
+
function createNamedSlot(name, value) {
|
|
285
|
+
if (value == null || value === false) return null;
|
|
286
|
+
|
|
287
|
+
if (isValidElement(value) && value.type !== Fragment) {
|
|
288
|
+
return cloneElement(value, { slot: name });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return createElement(
|
|
292
|
+
'span',
|
|
293
|
+
{ slot: name, style: { display: 'contents' } },
|
|
294
|
+
value,
|
|
295
|
+
);
|
|
228
296
|
}
|
|
297
|
+
`;
|
|
229
298
|
//#endregion
|
|
230
299
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
231
300
|
function reactWrapper(options = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -20,212 +20,281 @@ function generateReactIndex(components, options) {
|
|
|
20
20
|
return lines.join("\n");
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
24
|
-
function toReactEventProp(eventName) {
|
|
25
|
-
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
23
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
24
|
function generateReactWrapper(input) {
|
|
30
|
-
|
|
31
|
-
if (mode === "minimal") return generateMinimalReactWrapper(input);
|
|
32
|
-
return generateEventBridgeReactWrapper(input);
|
|
33
|
-
}
|
|
34
|
-
function createBindings(names, prefix) {
|
|
35
|
-
return names.map((sourceName, index) => ({
|
|
36
|
-
sourceName,
|
|
37
|
-
localName: `${prefix}${index}`
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
function createEventBindings(eventNames) {
|
|
41
|
-
return eventNames.map((eventName, index) => ({
|
|
42
|
-
eventName,
|
|
43
|
-
sourceName: toReactEventProp(eventName),
|
|
44
|
-
localName: `eventHandler${index}`
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
function generateDestructuredBindings(bindings) {
|
|
48
|
-
if (!bindings.length) return "";
|
|
49
|
-
return bindings.map(({ sourceName, localName }) => {
|
|
50
|
-
return `${JSON.stringify(sourceName)}: ${localName},`;
|
|
51
|
-
}).join("\n ");
|
|
25
|
+
return input.mode === "event-bridge" ? generateEventBridgeReactWrapper(input) : generateMinimalReactWrapper(input);
|
|
52
26
|
}
|
|
53
27
|
function generateMinimalReactWrapper(input) {
|
|
54
28
|
const { component, namedSlots, wcModuleId } = input;
|
|
55
29
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
56
|
-
const
|
|
30
|
+
const omittedKeys = ["children", ...slotBindings.map(({ sourceName }) => sourceName)];
|
|
31
|
+
const slotAssignments = generatePropAssignments(slotBindings);
|
|
57
32
|
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
33
|
+
const childSetup = slotBindings.length ? `${namedSlotLines}
|
|
34
|
+
const childArgs = [];
|
|
35
|
+
pushAll(childArgs, slotNodes);
|
|
36
|
+
if (children != null) childArgs.push(children);` : "";
|
|
37
|
+
const render = slotBindings.length ? `React.createElement.apply(
|
|
38
|
+
React,
|
|
39
|
+
[${JSON.stringify(component.tag)}, rest].concat(childArgs),
|
|
40
|
+
)` : `React.createElement(${JSON.stringify(component.tag)}, rest, children)`;
|
|
58
41
|
return `
|
|
59
42
|
import * as React from 'react';
|
|
60
43
|
|
|
61
44
|
import ${JSON.stringify(wcModuleId)};
|
|
62
45
|
|
|
46
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
47
|
+
|
|
63
48
|
export const ${component.name} = React.forwardRef(
|
|
64
|
-
function ${component.name}({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ref,
|
|
74
|
-
},
|
|
75
|
-
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
76
|
-
);
|
|
49
|
+
function ${component.name}(inputProps, ref) {
|
|
50
|
+
const props = inputProps || {};
|
|
51
|
+
const children = props.children;
|
|
52
|
+
${slotAssignments}
|
|
53
|
+
const rest = omitProps(props);
|
|
54
|
+
rest.ref = ref;
|
|
55
|
+
${childSetup}
|
|
56
|
+
|
|
57
|
+
return ${render};
|
|
77
58
|
},
|
|
78
59
|
);
|
|
60
|
+
|
|
61
|
+
function omitProps(source) {
|
|
62
|
+
const output = {};
|
|
63
|
+
for (const key in source) {
|
|
64
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
65
|
+
output[key] = source[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasOwn(source, key) {
|
|
72
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
73
|
+
}
|
|
74
|
+
${slotBindings.length ? PUSH_ALL_HELPER : ""}
|
|
79
75
|
`.trimStart();
|
|
80
76
|
}
|
|
81
77
|
function generateEventBridgeReactWrapper(input) {
|
|
82
78
|
const { component, namedSlots, wcModuleId } = input;
|
|
83
79
|
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
84
|
-
const eventBindings = createEventBindings(
|
|
80
|
+
const eventBindings = createEventBindings(component.events);
|
|
81
|
+
if (!propBindings.length && !eventBindings.length) return generateMinimalReactWrapper(input);
|
|
85
82
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
86
|
-
const
|
|
83
|
+
const destructuredBindings = [
|
|
87
84
|
...propBindings,
|
|
88
85
|
...eventBindings,
|
|
89
86
|
...slotBindings
|
|
90
|
-
]
|
|
87
|
+
];
|
|
88
|
+
const omittedKeys = [
|
|
89
|
+
"children",
|
|
90
|
+
"className",
|
|
91
|
+
"style",
|
|
92
|
+
...destructuredBindings.map(({ sourceName }) => sourceName)
|
|
93
|
+
];
|
|
91
94
|
return `
|
|
92
95
|
import {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
${[
|
|
97
|
+
"createElement",
|
|
98
|
+
...slotBindings.length ? [
|
|
99
|
+
"cloneElement",
|
|
100
|
+
"Fragment",
|
|
101
|
+
"isValidElement"
|
|
102
|
+
] : [],
|
|
103
|
+
"forwardRef",
|
|
104
|
+
...propBindings.length || eventBindings.length ? ["useEffect"] : [],
|
|
105
|
+
"useImperativeHandle",
|
|
106
|
+
"useRef"
|
|
107
|
+
].join(",\n ")},
|
|
101
108
|
} from 'react';
|
|
102
109
|
|
|
103
110
|
import ${JSON.stringify(wcModuleId)};
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
children,
|
|
108
|
-
className,
|
|
109
|
-
style,
|
|
110
|
-
${destructuredProps}
|
|
111
|
-
...rest
|
|
112
|
-
} = props;
|
|
112
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
113
|
+
${eventBindings.length ? `const EVENT_NAMES = ${JSON.stringify(eventBindings.map((binding) => binding.eventName))};` : ""}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const slotChildren = [];
|
|
115
|
+
export const ${component.name} = forwardRef(function ${component.name}(inputProps, ref) {
|
|
116
|
+
const props = inputProps || {};
|
|
117
|
+
const children = props.children;
|
|
118
|
+
const className = props.className;
|
|
119
|
+
const style = props.style;
|
|
120
|
+
${generatePropAssignments(destructuredBindings)}
|
|
121
|
+
${generatePropPresenceAssignments(propBindings)}
|
|
122
|
+
const rest = omitProps(props);
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
const innerRef = useRef(null);
|
|
125
|
+
${generatePropRefs(propBindings)}
|
|
126
|
+
${generateEventRefs(eventBindings)}
|
|
127
|
+
useImperativeHandle(ref, () => innerRef.current, []);
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
${generatePropSyncEffect(propBindings)}
|
|
130
|
+
${generateEventEffect(eventBindings)}
|
|
131
|
+
${generateChildrenSetup(slotBindings)}
|
|
132
|
+
rest.ref = innerRef;
|
|
133
|
+
rest.className = className;
|
|
134
|
+
rest.style = style;
|
|
130
135
|
|
|
131
|
-
return
|
|
132
|
-
${JSON.stringify(component.tag)},
|
|
133
|
-
{
|
|
134
|
-
...rest,
|
|
135
|
-
ref: innerRef,
|
|
136
|
-
className,
|
|
137
|
-
style,
|
|
138
|
-
},
|
|
139
|
-
...slotChildren,
|
|
140
|
-
);
|
|
136
|
+
return ${generateReactRender(component.tag, slotBindings)};
|
|
141
137
|
});
|
|
142
|
-
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
) {
|
|
150
|
-
return cloneElement(value, { slot: name });
|
|
138
|
+
${slotBindings.length ? NAMED_SLOT_HELPER : ""}
|
|
139
|
+
function omitProps(source) {
|
|
140
|
+
const output = {};
|
|
141
|
+
for (const key in source) {
|
|
142
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
143
|
+
output[key] = source[key];
|
|
144
|
+
}
|
|
151
145
|
}
|
|
146
|
+
return output;
|
|
147
|
+
}
|
|
152
148
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
slot: name,
|
|
157
|
-
style: { display: 'contents' },
|
|
158
|
-
},
|
|
159
|
-
value,
|
|
160
|
-
);
|
|
149
|
+
function hasOwn(source, key) {
|
|
150
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
161
151
|
}
|
|
162
152
|
`.trimStart();
|
|
163
153
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
function createBindings(names, prefix) {
|
|
155
|
+
return names.map((sourceName, index) => ({
|
|
156
|
+
sourceName,
|
|
157
|
+
localName: `${prefix}${index}`
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
function createEventBindings(events) {
|
|
161
|
+
return Object.entries(events).map(([key, event], index) => {
|
|
162
|
+
var _event$key, _event$name, _event$reactName;
|
|
163
|
+
const sourceEventName = (_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key;
|
|
164
|
+
return {
|
|
165
|
+
eventName: (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase(sourceEventName),
|
|
166
|
+
sourceName: (_event$reactName = event.reactName) !== null && _event$reactName !== void 0 ? _event$reactName : toReactEventProp(sourceEventName),
|
|
167
|
+
localName: `eventHandler${index}`
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function generatePropAssignments(bindings) {
|
|
172
|
+
return bindings.map(({ sourceName, localName }) => ` const ${localName} = props[${JSON.stringify(sourceName)}];`).join("\n");
|
|
173
|
+
}
|
|
174
|
+
function generatePropPresenceAssignments(bindings) {
|
|
175
|
+
return bindings.map(({ sourceName }, index) => ` const propPresent${index} = hasOwn(props, ${JSON.stringify(sourceName)});`).join("\n");
|
|
176
|
+
}
|
|
177
|
+
function generatePropRefs(bindings) {
|
|
178
|
+
if (!bindings.length) return "";
|
|
179
|
+
return ` const previousPropPresenceRef = useRef([]);
|
|
180
|
+
const previousPropValuesRef = useRef([]);`;
|
|
181
|
+
}
|
|
182
|
+
function generateEventRefs(bindings) {
|
|
183
|
+
if (!bindings.length) return "";
|
|
184
|
+
return ` const eventHandlersRef = useRef([]);
|
|
185
|
+
${bindings.map(({ localName }, index) => ` eventHandlersRef.current[${index}] = ${localName};`).join("\n")}`;
|
|
186
|
+
}
|
|
187
|
+
function generatePropSyncEffect(bindings) {
|
|
188
|
+
if (!bindings.length) return "";
|
|
189
|
+
return ` useEffect(() => {
|
|
167
190
|
const el = innerRef.current;
|
|
168
191
|
if (!el) return;
|
|
169
192
|
|
|
170
|
-
const
|
|
193
|
+
const previousPropPresence = previousPropPresenceRef.current;
|
|
194
|
+
const previousPropValues = previousPropValuesRef.current;
|
|
171
195
|
|
|
172
|
-
|
|
196
|
+
${bindings.map(({ sourceName, localName }, index) => {
|
|
173
197
|
const key = JSON.stringify(sourceName);
|
|
174
|
-
return `if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
return ` if (propPresent${index}) {
|
|
199
|
+
if (
|
|
200
|
+
!previousPropPresence[${index}] ||
|
|
201
|
+
!Object.is(previousPropValues[${index}], ${localName})
|
|
202
|
+
) {
|
|
203
|
+
el[${key}] = ${localName};
|
|
204
|
+
previousPropValues[${index}] = ${localName};
|
|
205
|
+
}
|
|
206
|
+
previousPropPresence[${index}] = true;
|
|
207
|
+
} else if (previousPropPresence[${index}]) {
|
|
178
208
|
el[${key}] = undefined;
|
|
179
|
-
|
|
209
|
+
previousPropPresence[${index}] = false;
|
|
210
|
+
previousPropValues[${index}] = undefined;
|
|
180
211
|
}`;
|
|
181
|
-
}).join("\n\n
|
|
182
|
-
}, [
|
|
212
|
+
}).join("\n\n")}
|
|
213
|
+
}, [${bindings.flatMap((binding, index) => [`propPresent${index}`, binding.localName]).join(", ")}]);
|
|
214
|
+
`;
|
|
183
215
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
216
|
+
function generateEventEffect(bindings) {
|
|
217
|
+
if (!bindings.length) return "";
|
|
218
|
+
return ` useEffect(() => {
|
|
188
219
|
const el = innerRef.current;
|
|
189
|
-
if (!el
|
|
220
|
+
if (!el) return;
|
|
190
221
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
222
|
+
const listeners = EVENT_NAMES.map(
|
|
223
|
+
(_eventName, index) => event => {
|
|
224
|
+
const handler = eventHandlersRef.current[index];
|
|
225
|
+
if (handler) handler(event);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
194
228
|
|
|
195
|
-
|
|
229
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
230
|
+
el.addEventListener(EVENT_NAMES[index], listeners[index]);
|
|
231
|
+
}
|
|
196
232
|
|
|
197
233
|
return () => {
|
|
198
|
-
|
|
234
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
235
|
+
el.removeEventListener(EVENT_NAMES[index], listeners[index]);
|
|
236
|
+
}
|
|
199
237
|
};
|
|
200
|
-
}, [
|
|
238
|
+
}, []);
|
|
201
239
|
`;
|
|
202
|
-
}).join("");
|
|
203
|
-
}
|
|
204
|
-
function getNamedSlots(component, namedSlots) {
|
|
205
|
-
if (namedSlots === "none") return [];
|
|
206
|
-
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
207
240
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
241
|
+
function generateChildrenSetup(bindings) {
|
|
242
|
+
if (!bindings.length) return "";
|
|
243
|
+
return ` const slotChildren = [];
|
|
244
|
+
${bindings.map(({ sourceName, localName }) => ` {
|
|
212
245
|
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
213
246
|
if (node != null) slotChildren.push(node);
|
|
214
|
-
}
|
|
247
|
+
}`).join("\n")}
|
|
248
|
+
if (children != null) slotChildren.push(children);
|
|
215
249
|
`;
|
|
216
|
-
|
|
250
|
+
}
|
|
251
|
+
function generateReactRender(tag, slotBindings) {
|
|
252
|
+
if (!slotBindings.length) return `createElement(${JSON.stringify(tag)}, rest, children)`;
|
|
253
|
+
return `createElement.apply(
|
|
254
|
+
null,
|
|
255
|
+
[${JSON.stringify(tag)}, rest].concat(slotChildren),
|
|
256
|
+
)`;
|
|
257
|
+
}
|
|
258
|
+
function getNamedSlots(component, namedSlots) {
|
|
259
|
+
return namedSlots === "none" ? [] : Object.keys(component.slots).filter((name) => name !== "default");
|
|
217
260
|
}
|
|
218
261
|
function generateMinimalNamedSlots(bindings) {
|
|
219
262
|
if (!bindings.length) return "";
|
|
220
|
-
return bindings.map(({ sourceName, localName }, index) => {
|
|
221
|
-
return ` const
|
|
263
|
+
return `${bindings.map(({ sourceName, localName }, index) => {
|
|
264
|
+
return ` const slotNode${index} = ${localName} != null && ${localName} !== false
|
|
222
265
|
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
223
266
|
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
224
267
|
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
225
|
-
: null
|
|
268
|
+
: null;`;
|
|
269
|
+
}).join("\n")}
|
|
270
|
+
const slotNodes = [${bindings.map((_, index) => `slotNode${index}`).join(", ")}].filter(Boolean);`;
|
|
271
|
+
}
|
|
272
|
+
function toKebabCase(value) {
|
|
273
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
274
|
+
}
|
|
275
|
+
function toReactEventProp(value) {
|
|
276
|
+
return `on${value.split("-").filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}`;
|
|
277
|
+
}
|
|
278
|
+
const PUSH_ALL_HELPER = `
|
|
279
|
+
function pushAll(target, values) {
|
|
280
|
+
for (const value of values) target.push(value);
|
|
281
|
+
}
|
|
226
282
|
`;
|
|
227
|
-
|
|
283
|
+
const NAMED_SLOT_HELPER = `
|
|
284
|
+
function createNamedSlot(name, value) {
|
|
285
|
+
if (value == null || value === false) return null;
|
|
286
|
+
|
|
287
|
+
if (isValidElement(value) && value.type !== Fragment) {
|
|
288
|
+
return cloneElement(value, { slot: name });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return createElement(
|
|
292
|
+
'span',
|
|
293
|
+
{ slot: name, style: { display: 'contents' } },
|
|
294
|
+
value,
|
|
295
|
+
);
|
|
228
296
|
}
|
|
297
|
+
`;
|
|
229
298
|
//#endregion
|
|
230
299
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
231
300
|
function reactWrapper(options = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -16,212 +16,281 @@ function generateReactIndex(components, options) {
|
|
|
16
16
|
return lines.join("\n");
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
|
-
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
20
|
-
function toReactEventProp(eventName) {
|
|
21
|
-
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
22
|
-
}
|
|
23
|
-
//#endregion
|
|
24
19
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
25
20
|
function generateReactWrapper(input) {
|
|
26
|
-
|
|
27
|
-
if (mode === "minimal") return generateMinimalReactWrapper(input);
|
|
28
|
-
return generateEventBridgeReactWrapper(input);
|
|
29
|
-
}
|
|
30
|
-
function createBindings(names, prefix) {
|
|
31
|
-
return names.map((sourceName, index) => ({
|
|
32
|
-
sourceName,
|
|
33
|
-
localName: `${prefix}${index}`
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
function createEventBindings(eventNames) {
|
|
37
|
-
return eventNames.map((eventName, index) => ({
|
|
38
|
-
eventName,
|
|
39
|
-
sourceName: toReactEventProp(eventName),
|
|
40
|
-
localName: `eventHandler${index}`
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
function generateDestructuredBindings(bindings) {
|
|
44
|
-
if (!bindings.length) return "";
|
|
45
|
-
return bindings.map(({ sourceName, localName }) => {
|
|
46
|
-
return `${JSON.stringify(sourceName)}: ${localName},`;
|
|
47
|
-
}).join("\n ");
|
|
21
|
+
return input.mode === "event-bridge" ? generateEventBridgeReactWrapper(input) : generateMinimalReactWrapper(input);
|
|
48
22
|
}
|
|
49
23
|
function generateMinimalReactWrapper(input) {
|
|
50
24
|
const { component, namedSlots, wcModuleId } = input;
|
|
51
25
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
52
|
-
const
|
|
26
|
+
const omittedKeys = ["children", ...slotBindings.map(({ sourceName }) => sourceName)];
|
|
27
|
+
const slotAssignments = generatePropAssignments(slotBindings);
|
|
53
28
|
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
29
|
+
const childSetup = slotBindings.length ? `${namedSlotLines}
|
|
30
|
+
const childArgs = [];
|
|
31
|
+
pushAll(childArgs, slotNodes);
|
|
32
|
+
if (children != null) childArgs.push(children);` : "";
|
|
33
|
+
const render = slotBindings.length ? `React.createElement.apply(
|
|
34
|
+
React,
|
|
35
|
+
[${JSON.stringify(component.tag)}, rest].concat(childArgs),
|
|
36
|
+
)` : `React.createElement(${JSON.stringify(component.tag)}, rest, children)`;
|
|
54
37
|
return `
|
|
55
38
|
import * as React from 'react';
|
|
56
39
|
|
|
57
40
|
import ${JSON.stringify(wcModuleId)};
|
|
58
41
|
|
|
42
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
43
|
+
|
|
59
44
|
export const ${component.name} = React.forwardRef(
|
|
60
|
-
function ${component.name}({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ref,
|
|
70
|
-
},
|
|
71
|
-
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
72
|
-
);
|
|
45
|
+
function ${component.name}(inputProps, ref) {
|
|
46
|
+
const props = inputProps || {};
|
|
47
|
+
const children = props.children;
|
|
48
|
+
${slotAssignments}
|
|
49
|
+
const rest = omitProps(props);
|
|
50
|
+
rest.ref = ref;
|
|
51
|
+
${childSetup}
|
|
52
|
+
|
|
53
|
+
return ${render};
|
|
73
54
|
},
|
|
74
55
|
);
|
|
56
|
+
|
|
57
|
+
function omitProps(source) {
|
|
58
|
+
const output = {};
|
|
59
|
+
for (const key in source) {
|
|
60
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
61
|
+
output[key] = source[key];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasOwn(source, key) {
|
|
68
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
69
|
+
}
|
|
70
|
+
${slotBindings.length ? PUSH_ALL_HELPER : ""}
|
|
75
71
|
`.trimStart();
|
|
76
72
|
}
|
|
77
73
|
function generateEventBridgeReactWrapper(input) {
|
|
78
74
|
const { component, namedSlots, wcModuleId } = input;
|
|
79
75
|
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
80
|
-
const eventBindings = createEventBindings(
|
|
76
|
+
const eventBindings = createEventBindings(component.events);
|
|
77
|
+
if (!propBindings.length && !eventBindings.length) return generateMinimalReactWrapper(input);
|
|
81
78
|
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
82
|
-
const
|
|
79
|
+
const destructuredBindings = [
|
|
83
80
|
...propBindings,
|
|
84
81
|
...eventBindings,
|
|
85
82
|
...slotBindings
|
|
86
|
-
]
|
|
83
|
+
];
|
|
84
|
+
const omittedKeys = [
|
|
85
|
+
"children",
|
|
86
|
+
"className",
|
|
87
|
+
"style",
|
|
88
|
+
...destructuredBindings.map(({ sourceName }) => sourceName)
|
|
89
|
+
];
|
|
87
90
|
return `
|
|
88
91
|
import {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
${[
|
|
93
|
+
"createElement",
|
|
94
|
+
...slotBindings.length ? [
|
|
95
|
+
"cloneElement",
|
|
96
|
+
"Fragment",
|
|
97
|
+
"isValidElement"
|
|
98
|
+
] : [],
|
|
99
|
+
"forwardRef",
|
|
100
|
+
...propBindings.length || eventBindings.length ? ["useEffect"] : [],
|
|
101
|
+
"useImperativeHandle",
|
|
102
|
+
"useRef"
|
|
103
|
+
].join(",\n ")},
|
|
97
104
|
} from 'react';
|
|
98
105
|
|
|
99
106
|
import ${JSON.stringify(wcModuleId)};
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
children,
|
|
104
|
-
className,
|
|
105
|
-
style,
|
|
106
|
-
${destructuredProps}
|
|
107
|
-
...rest
|
|
108
|
-
} = props;
|
|
108
|
+
const OMITTED_PROPS = new Set(${JSON.stringify(omittedKeys)});
|
|
109
|
+
${eventBindings.length ? `const EVENT_NAMES = ${JSON.stringify(eventBindings.map((binding) => binding.eventName))};` : ""}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const slotChildren = [];
|
|
111
|
+
export const ${component.name} = forwardRef(function ${component.name}(inputProps, ref) {
|
|
112
|
+
const props = inputProps || {};
|
|
113
|
+
const children = props.children;
|
|
114
|
+
const className = props.className;
|
|
115
|
+
const style = props.style;
|
|
116
|
+
${generatePropAssignments(destructuredBindings)}
|
|
117
|
+
${generatePropPresenceAssignments(propBindings)}
|
|
118
|
+
const rest = omitProps(props);
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
const innerRef = useRef(null);
|
|
121
|
+
${generatePropRefs(propBindings)}
|
|
122
|
+
${generateEventRefs(eventBindings)}
|
|
123
|
+
useImperativeHandle(ref, () => innerRef.current, []);
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
${generatePropSyncEffect(propBindings)}
|
|
126
|
+
${generateEventEffect(eventBindings)}
|
|
127
|
+
${generateChildrenSetup(slotBindings)}
|
|
128
|
+
rest.ref = innerRef;
|
|
129
|
+
rest.className = className;
|
|
130
|
+
rest.style = style;
|
|
126
131
|
|
|
127
|
-
return
|
|
128
|
-
${JSON.stringify(component.tag)},
|
|
129
|
-
{
|
|
130
|
-
...rest,
|
|
131
|
-
ref: innerRef,
|
|
132
|
-
className,
|
|
133
|
-
style,
|
|
134
|
-
},
|
|
135
|
-
...slotChildren,
|
|
136
|
-
);
|
|
132
|
+
return ${generateReactRender(component.tag, slotBindings)};
|
|
137
133
|
});
|
|
138
|
-
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
) {
|
|
146
|
-
return cloneElement(value, { slot: name });
|
|
134
|
+
${slotBindings.length ? NAMED_SLOT_HELPER : ""}
|
|
135
|
+
function omitProps(source) {
|
|
136
|
+
const output = {};
|
|
137
|
+
for (const key in source) {
|
|
138
|
+
if (hasOwn(source, key) && !OMITTED_PROPS.has(key)) {
|
|
139
|
+
output[key] = source[key];
|
|
140
|
+
}
|
|
147
141
|
}
|
|
142
|
+
return output;
|
|
143
|
+
}
|
|
148
144
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
slot: name,
|
|
153
|
-
style: { display: 'contents' },
|
|
154
|
-
},
|
|
155
|
-
value,
|
|
156
|
-
);
|
|
145
|
+
function hasOwn(source, key) {
|
|
146
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
157
147
|
}
|
|
158
148
|
`.trimStart();
|
|
159
149
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
150
|
+
function createBindings(names, prefix) {
|
|
151
|
+
return names.map((sourceName, index) => ({
|
|
152
|
+
sourceName,
|
|
153
|
+
localName: `${prefix}${index}`
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
function createEventBindings(events) {
|
|
157
|
+
return Object.entries(events).map(([key, event], index) => {
|
|
158
|
+
var _event$key, _event$name, _event$reactName;
|
|
159
|
+
const sourceEventName = (_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key;
|
|
160
|
+
return {
|
|
161
|
+
eventName: (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase(sourceEventName),
|
|
162
|
+
sourceName: (_event$reactName = event.reactName) !== null && _event$reactName !== void 0 ? _event$reactName : toReactEventProp(sourceEventName),
|
|
163
|
+
localName: `eventHandler${index}`
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function generatePropAssignments(bindings) {
|
|
168
|
+
return bindings.map(({ sourceName, localName }) => ` const ${localName} = props[${JSON.stringify(sourceName)}];`).join("\n");
|
|
169
|
+
}
|
|
170
|
+
function generatePropPresenceAssignments(bindings) {
|
|
171
|
+
return bindings.map(({ sourceName }, index) => ` const propPresent${index} = hasOwn(props, ${JSON.stringify(sourceName)});`).join("\n");
|
|
172
|
+
}
|
|
173
|
+
function generatePropRefs(bindings) {
|
|
174
|
+
if (!bindings.length) return "";
|
|
175
|
+
return ` const previousPropPresenceRef = useRef([]);
|
|
176
|
+
const previousPropValuesRef = useRef([]);`;
|
|
177
|
+
}
|
|
178
|
+
function generateEventRefs(bindings) {
|
|
179
|
+
if (!bindings.length) return "";
|
|
180
|
+
return ` const eventHandlersRef = useRef([]);
|
|
181
|
+
${bindings.map(({ localName }, index) => ` eventHandlersRef.current[${index}] = ${localName};`).join("\n")}`;
|
|
182
|
+
}
|
|
183
|
+
function generatePropSyncEffect(bindings) {
|
|
184
|
+
if (!bindings.length) return "";
|
|
185
|
+
return ` useEffect(() => {
|
|
163
186
|
const el = innerRef.current;
|
|
164
187
|
if (!el) return;
|
|
165
188
|
|
|
166
|
-
const
|
|
189
|
+
const previousPropPresence = previousPropPresenceRef.current;
|
|
190
|
+
const previousPropValues = previousPropValuesRef.current;
|
|
167
191
|
|
|
168
|
-
|
|
192
|
+
${bindings.map(({ sourceName, localName }, index) => {
|
|
169
193
|
const key = JSON.stringify(sourceName);
|
|
170
|
-
return `if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
194
|
+
return ` if (propPresent${index}) {
|
|
195
|
+
if (
|
|
196
|
+
!previousPropPresence[${index}] ||
|
|
197
|
+
!Object.is(previousPropValues[${index}], ${localName})
|
|
198
|
+
) {
|
|
199
|
+
el[${key}] = ${localName};
|
|
200
|
+
previousPropValues[${index}] = ${localName};
|
|
201
|
+
}
|
|
202
|
+
previousPropPresence[${index}] = true;
|
|
203
|
+
} else if (previousPropPresence[${index}]) {
|
|
174
204
|
el[${key}] = undefined;
|
|
175
|
-
|
|
205
|
+
previousPropPresence[${index}] = false;
|
|
206
|
+
previousPropValues[${index}] = undefined;
|
|
176
207
|
}`;
|
|
177
|
-
}).join("\n\n
|
|
178
|
-
}, [
|
|
208
|
+
}).join("\n\n")}
|
|
209
|
+
}, [${bindings.flatMap((binding, index) => [`propPresent${index}`, binding.localName]).join(", ")}]);
|
|
210
|
+
`;
|
|
179
211
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
useEffect(() => {
|
|
212
|
+
function generateEventEffect(bindings) {
|
|
213
|
+
if (!bindings.length) return "";
|
|
214
|
+
return ` useEffect(() => {
|
|
184
215
|
const el = innerRef.current;
|
|
185
|
-
if (!el
|
|
216
|
+
if (!el) return;
|
|
186
217
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
218
|
+
const listeners = EVENT_NAMES.map(
|
|
219
|
+
(_eventName, index) => event => {
|
|
220
|
+
const handler = eventHandlersRef.current[index];
|
|
221
|
+
if (handler) handler(event);
|
|
222
|
+
},
|
|
223
|
+
);
|
|
190
224
|
|
|
191
|
-
|
|
225
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
226
|
+
el.addEventListener(EVENT_NAMES[index], listeners[index]);
|
|
227
|
+
}
|
|
192
228
|
|
|
193
229
|
return () => {
|
|
194
|
-
|
|
230
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
231
|
+
el.removeEventListener(EVENT_NAMES[index], listeners[index]);
|
|
232
|
+
}
|
|
195
233
|
};
|
|
196
|
-
}, [
|
|
234
|
+
}, []);
|
|
197
235
|
`;
|
|
198
|
-
}).join("");
|
|
199
|
-
}
|
|
200
|
-
function getNamedSlots(component, namedSlots) {
|
|
201
|
-
if (namedSlots === "none") return [];
|
|
202
|
-
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
203
236
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
{
|
|
237
|
+
function generateChildrenSetup(bindings) {
|
|
238
|
+
if (!bindings.length) return "";
|
|
239
|
+
return ` const slotChildren = [];
|
|
240
|
+
${bindings.map(({ sourceName, localName }) => ` {
|
|
208
241
|
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
209
242
|
if (node != null) slotChildren.push(node);
|
|
210
|
-
}
|
|
243
|
+
}`).join("\n")}
|
|
244
|
+
if (children != null) slotChildren.push(children);
|
|
211
245
|
`;
|
|
212
|
-
|
|
246
|
+
}
|
|
247
|
+
function generateReactRender(tag, slotBindings) {
|
|
248
|
+
if (!slotBindings.length) return `createElement(${JSON.stringify(tag)}, rest, children)`;
|
|
249
|
+
return `createElement.apply(
|
|
250
|
+
null,
|
|
251
|
+
[${JSON.stringify(tag)}, rest].concat(slotChildren),
|
|
252
|
+
)`;
|
|
253
|
+
}
|
|
254
|
+
function getNamedSlots(component, namedSlots) {
|
|
255
|
+
return namedSlots === "none" ? [] : Object.keys(component.slots).filter((name) => name !== "default");
|
|
213
256
|
}
|
|
214
257
|
function generateMinimalNamedSlots(bindings) {
|
|
215
258
|
if (!bindings.length) return "";
|
|
216
|
-
return bindings.map(({ sourceName, localName }, index) => {
|
|
217
|
-
return ` const
|
|
259
|
+
return `${bindings.map(({ sourceName, localName }, index) => {
|
|
260
|
+
return ` const slotNode${index} = ${localName} != null && ${localName} !== false
|
|
218
261
|
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
219
262
|
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
220
263
|
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
221
|
-
: null
|
|
264
|
+
: null;`;
|
|
265
|
+
}).join("\n")}
|
|
266
|
+
const slotNodes = [${bindings.map((_, index) => `slotNode${index}`).join(", ")}].filter(Boolean);`;
|
|
267
|
+
}
|
|
268
|
+
function toKebabCase(value) {
|
|
269
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
270
|
+
}
|
|
271
|
+
function toReactEventProp(value) {
|
|
272
|
+
return `on${value.split("-").filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("")}`;
|
|
273
|
+
}
|
|
274
|
+
const PUSH_ALL_HELPER = `
|
|
275
|
+
function pushAll(target, values) {
|
|
276
|
+
for (const value of values) target.push(value);
|
|
277
|
+
}
|
|
222
278
|
`;
|
|
223
|
-
|
|
279
|
+
const NAMED_SLOT_HELPER = `
|
|
280
|
+
function createNamedSlot(name, value) {
|
|
281
|
+
if (value == null || value === false) return null;
|
|
282
|
+
|
|
283
|
+
if (isValidElement(value) && value.type !== Fragment) {
|
|
284
|
+
return cloneElement(value, { slot: name });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return createElement(
|
|
288
|
+
'span',
|
|
289
|
+
{ slot: name, style: { display: 'contents' } },
|
|
290
|
+
value,
|
|
291
|
+
);
|
|
224
292
|
}
|
|
293
|
+
`;
|
|
225
294
|
//#endregion
|
|
226
295
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
227
296
|
function reactWrapper(options = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeus-js/output-react-wrapper",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "Zeus React wrapper output plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@zeus-js/bundler-plugin": "0.1.0-beta.
|
|
40
|
-
"@zeus-js/component-
|
|
41
|
-
"@zeus-js/component-
|
|
39
|
+
"@zeus-js/bundler-plugin": "0.1.0-beta.4",
|
|
40
|
+
"@zeus-js/component-analyzer": "0.1.0-beta.4",
|
|
41
|
+
"@zeus-js/component-dts": "0.1.0-beta.4"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=18"
|