@zeus-js/output-react-wrapper 0.1.0-beta.2 → 0.1.0-beta.3
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,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -27,17 +27,67 @@ function toReactEventProp(eventName) {
|
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
29
|
function generateReactWrapper(input) {
|
|
30
|
+
const { mode = "minimal" } = input;
|
|
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 ");
|
|
52
|
+
}
|
|
53
|
+
function generateMinimalReactWrapper(input) {
|
|
30
54
|
const { component, namedSlots, wcModuleId } = input;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
56
|
+
const slotDestructure = slotBindings.length ? `\n ${generateDestructuredBindings(slotBindings)}` : "";
|
|
57
|
+
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
58
|
+
return `
|
|
59
|
+
import * as React from 'react';
|
|
60
|
+
|
|
61
|
+
import ${JSON.stringify(wcModuleId)};
|
|
62
|
+
|
|
63
|
+
export const ${component.name} = React.forwardRef(
|
|
64
|
+
function ${component.name}({
|
|
65
|
+
children,${slotDestructure}
|
|
66
|
+
...rest
|
|
67
|
+
} = {}, ref) {
|
|
68
|
+
${namedSlotLines}
|
|
69
|
+
return React.createElement(
|
|
70
|
+
${JSON.stringify(component.tag)},
|
|
71
|
+
{
|
|
72
|
+
...rest,
|
|
73
|
+
ref,
|
|
74
|
+
},
|
|
75
|
+
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
`.trimStart();
|
|
80
|
+
}
|
|
81
|
+
function generateEventBridgeReactWrapper(input) {
|
|
82
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
83
|
+
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
84
|
+
const eventBindings = createEventBindings(Object.keys(component.events));
|
|
85
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
86
|
+
const destructuredProps = generateDestructuredBindings([
|
|
87
|
+
...propBindings,
|
|
88
|
+
...eventBindings,
|
|
89
|
+
...slotBindings
|
|
90
|
+
]);
|
|
41
91
|
return `
|
|
42
92
|
import {
|
|
43
93
|
createElement,
|
|
@@ -52,10 +102,6 @@ import {
|
|
|
52
102
|
|
|
53
103
|
import ${JSON.stringify(wcModuleId)};
|
|
54
104
|
|
|
55
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
56
|
-
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
57
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
58
|
-
|
|
59
105
|
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
60
106
|
const {
|
|
61
107
|
children,
|
|
@@ -66,16 +112,17 @@ export const ${component.name} = forwardRef(function ${component.name}(props, re
|
|
|
66
112
|
} = props;
|
|
67
113
|
|
|
68
114
|
const innerRef = useRef(null);
|
|
115
|
+
const previousPropKeysRef = useRef(new Set());
|
|
69
116
|
|
|
70
117
|
useImperativeHandle(ref, () => innerRef.current);
|
|
71
118
|
|
|
72
|
-
${generatePropSyncLines(
|
|
119
|
+
${generatePropSyncLines(propBindings)}
|
|
73
120
|
|
|
74
|
-
${generateEventEffects(
|
|
121
|
+
${generateEventEffects(eventBindings)}
|
|
75
122
|
|
|
76
123
|
const slotChildren = [];
|
|
77
124
|
|
|
78
|
-
${generateNamedSlotRenderLines(
|
|
125
|
+
${generateNamedSlotRenderLines(slotBindings)}
|
|
79
126
|
|
|
80
127
|
if (children != null) {
|
|
81
128
|
slotChildren.push(children);
|
|
@@ -114,30 +161,35 @@ function createNamedSlot(name, value) {
|
|
|
114
161
|
}
|
|
115
162
|
`.trimStart();
|
|
116
163
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
120
|
-
return map;
|
|
121
|
-
}
|
|
122
|
-
function generatePropSyncLines(propNames) {
|
|
123
|
-
if (!propNames.length) return "// no props";
|
|
164
|
+
function generatePropSyncLines(bindings) {
|
|
165
|
+
if (!bindings.length) return "// no props";
|
|
124
166
|
return `useEffect(() => {
|
|
125
167
|
const el = innerRef.current;
|
|
126
168
|
if (!el) return;
|
|
127
169
|
|
|
128
|
-
|
|
129
|
-
|
|
170
|
+
const previousPropKeys = previousPropKeysRef.current;
|
|
171
|
+
|
|
172
|
+
${bindings.map(({ sourceName, localName }) => {
|
|
173
|
+
const key = JSON.stringify(sourceName);
|
|
174
|
+
return `if (Object.prototype.hasOwnProperty.call(props, ${key})) {
|
|
175
|
+
el[${key}] = ${localName};
|
|
176
|
+
previousPropKeys.add(${key});
|
|
177
|
+
} else if (previousPropKeys.has(${key})) {
|
|
178
|
+
el[${key}] = undefined;
|
|
179
|
+
previousPropKeys.delete(${key});
|
|
180
|
+
}`;
|
|
181
|
+
}).join("\n\n ")}
|
|
182
|
+
}, [props, ${bindings.map((binding) => binding.localName).join(", ")}]);`;
|
|
130
183
|
}
|
|
131
|
-
function generateEventEffects(
|
|
132
|
-
return
|
|
133
|
-
const propName = toReactEventProp(eventName);
|
|
184
|
+
function generateEventEffects(bindings) {
|
|
185
|
+
return bindings.map(({ eventName, localName }) => {
|
|
134
186
|
return `
|
|
135
187
|
useEffect(() => {
|
|
136
188
|
const el = innerRef.current;
|
|
137
|
-
if (!el || !${
|
|
189
|
+
if (!el || !${localName}) return;
|
|
138
190
|
|
|
139
191
|
const handler = event => {
|
|
140
|
-
${
|
|
192
|
+
${localName}(event);
|
|
141
193
|
};
|
|
142
194
|
|
|
143
195
|
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
@@ -145,7 +197,7 @@ function generateEventEffects(eventNames) {
|
|
|
145
197
|
return () => {
|
|
146
198
|
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
147
199
|
};
|
|
148
|
-
}, [${
|
|
200
|
+
}, [${localName}]);
|
|
149
201
|
`;
|
|
150
202
|
}).join("");
|
|
151
203
|
}
|
|
@@ -153,27 +205,39 @@ function getNamedSlots(component, namedSlots) {
|
|
|
153
205
|
if (namedSlots === "none") return [];
|
|
154
206
|
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
155
207
|
}
|
|
156
|
-
function generateNamedSlotRenderLines(
|
|
157
|
-
return
|
|
208
|
+
function generateNamedSlotRenderLines(bindings) {
|
|
209
|
+
return bindings.map(({ sourceName, localName }) => {
|
|
158
210
|
return `
|
|
159
211
|
{
|
|
160
|
-
const node = createNamedSlot(${JSON.stringify(
|
|
212
|
+
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
161
213
|
if (node != null) slotChildren.push(node);
|
|
162
214
|
}
|
|
163
215
|
`;
|
|
164
216
|
}).join("");
|
|
165
217
|
}
|
|
218
|
+
function generateMinimalNamedSlots(bindings) {
|
|
219
|
+
if (!bindings.length) return "";
|
|
220
|
+
return bindings.map(({ sourceName, localName }, index) => {
|
|
221
|
+
return ` const ${`slotNode${index}`} = ${localName} != null && ${localName} !== false
|
|
222
|
+
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
223
|
+
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
224
|
+
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
225
|
+
: null;
|
|
226
|
+
`;
|
|
227
|
+
}).join("") + "\n const slotNodes = [" + bindings.map((_, index) => `slotNode${index}`).join(", ") + "].filter(Boolean);\n";
|
|
228
|
+
}
|
|
166
229
|
//#endregion
|
|
167
230
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
168
231
|
function reactWrapper(options = {}) {
|
|
169
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
232
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots, _options$wrapper;
|
|
170
233
|
const normalized = {
|
|
171
234
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
172
235
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
173
236
|
fileName: options.fileName,
|
|
174
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
237
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
175
238
|
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
176
|
-
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
239
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props",
|
|
240
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
177
241
|
};
|
|
178
242
|
return {
|
|
179
243
|
name: "zeus-output-react-wrapper",
|
|
@@ -194,7 +258,8 @@ function reactWrapper(options = {}) {
|
|
|
194
258
|
code: generateReactWrapper({
|
|
195
259
|
component,
|
|
196
260
|
namedSlots: normalized.namedSlots,
|
|
197
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
261
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
262
|
+
mode: normalized.wrapper
|
|
198
263
|
})
|
|
199
264
|
});
|
|
200
265
|
if (normalized.index) modules.push({
|
|
@@ -209,7 +274,7 @@ function reactWrapper(options = {}) {
|
|
|
209
274
|
return [{
|
|
210
275
|
type: "asset",
|
|
211
276
|
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
212
|
-
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest)
|
|
277
|
+
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest, { namedSlots: normalized.namedSlots })
|
|
213
278
|
}];
|
|
214
279
|
}
|
|
215
280
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -27,17 +27,67 @@ function toReactEventProp(eventName) {
|
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
29
|
function generateReactWrapper(input) {
|
|
30
|
+
const { mode = "minimal" } = input;
|
|
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 ");
|
|
52
|
+
}
|
|
53
|
+
function generateMinimalReactWrapper(input) {
|
|
30
54
|
const { component, namedSlots, wcModuleId } = input;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
56
|
+
const slotDestructure = slotBindings.length ? `\n ${generateDestructuredBindings(slotBindings)}` : "";
|
|
57
|
+
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
58
|
+
return `
|
|
59
|
+
import * as React from 'react';
|
|
60
|
+
|
|
61
|
+
import ${JSON.stringify(wcModuleId)};
|
|
62
|
+
|
|
63
|
+
export const ${component.name} = React.forwardRef(
|
|
64
|
+
function ${component.name}({
|
|
65
|
+
children,${slotDestructure}
|
|
66
|
+
...rest
|
|
67
|
+
} = {}, ref) {
|
|
68
|
+
${namedSlotLines}
|
|
69
|
+
return React.createElement(
|
|
70
|
+
${JSON.stringify(component.tag)},
|
|
71
|
+
{
|
|
72
|
+
...rest,
|
|
73
|
+
ref,
|
|
74
|
+
},
|
|
75
|
+
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
`.trimStart();
|
|
80
|
+
}
|
|
81
|
+
function generateEventBridgeReactWrapper(input) {
|
|
82
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
83
|
+
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
84
|
+
const eventBindings = createEventBindings(Object.keys(component.events));
|
|
85
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
86
|
+
const destructuredProps = generateDestructuredBindings([
|
|
87
|
+
...propBindings,
|
|
88
|
+
...eventBindings,
|
|
89
|
+
...slotBindings
|
|
90
|
+
]);
|
|
41
91
|
return `
|
|
42
92
|
import {
|
|
43
93
|
createElement,
|
|
@@ -52,10 +102,6 @@ import {
|
|
|
52
102
|
|
|
53
103
|
import ${JSON.stringify(wcModuleId)};
|
|
54
104
|
|
|
55
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
56
|
-
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
57
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
58
|
-
|
|
59
105
|
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
60
106
|
const {
|
|
61
107
|
children,
|
|
@@ -66,16 +112,17 @@ export const ${component.name} = forwardRef(function ${component.name}(props, re
|
|
|
66
112
|
} = props;
|
|
67
113
|
|
|
68
114
|
const innerRef = useRef(null);
|
|
115
|
+
const previousPropKeysRef = useRef(new Set());
|
|
69
116
|
|
|
70
117
|
useImperativeHandle(ref, () => innerRef.current);
|
|
71
118
|
|
|
72
|
-
${generatePropSyncLines(
|
|
119
|
+
${generatePropSyncLines(propBindings)}
|
|
73
120
|
|
|
74
|
-
${generateEventEffects(
|
|
121
|
+
${generateEventEffects(eventBindings)}
|
|
75
122
|
|
|
76
123
|
const slotChildren = [];
|
|
77
124
|
|
|
78
|
-
${generateNamedSlotRenderLines(
|
|
125
|
+
${generateNamedSlotRenderLines(slotBindings)}
|
|
79
126
|
|
|
80
127
|
if (children != null) {
|
|
81
128
|
slotChildren.push(children);
|
|
@@ -114,30 +161,35 @@ function createNamedSlot(name, value) {
|
|
|
114
161
|
}
|
|
115
162
|
`.trimStart();
|
|
116
163
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
120
|
-
return map;
|
|
121
|
-
}
|
|
122
|
-
function generatePropSyncLines(propNames) {
|
|
123
|
-
if (!propNames.length) return "// no props";
|
|
164
|
+
function generatePropSyncLines(bindings) {
|
|
165
|
+
if (!bindings.length) return "// no props";
|
|
124
166
|
return `useEffect(() => {
|
|
125
167
|
const el = innerRef.current;
|
|
126
168
|
if (!el) return;
|
|
127
169
|
|
|
128
|
-
|
|
129
|
-
|
|
170
|
+
const previousPropKeys = previousPropKeysRef.current;
|
|
171
|
+
|
|
172
|
+
${bindings.map(({ sourceName, localName }) => {
|
|
173
|
+
const key = JSON.stringify(sourceName);
|
|
174
|
+
return `if (Object.prototype.hasOwnProperty.call(props, ${key})) {
|
|
175
|
+
el[${key}] = ${localName};
|
|
176
|
+
previousPropKeys.add(${key});
|
|
177
|
+
} else if (previousPropKeys.has(${key})) {
|
|
178
|
+
el[${key}] = undefined;
|
|
179
|
+
previousPropKeys.delete(${key});
|
|
180
|
+
}`;
|
|
181
|
+
}).join("\n\n ")}
|
|
182
|
+
}, [props, ${bindings.map((binding) => binding.localName).join(", ")}]);`;
|
|
130
183
|
}
|
|
131
|
-
function generateEventEffects(
|
|
132
|
-
return
|
|
133
|
-
const propName = toReactEventProp(eventName);
|
|
184
|
+
function generateEventEffects(bindings) {
|
|
185
|
+
return bindings.map(({ eventName, localName }) => {
|
|
134
186
|
return `
|
|
135
187
|
useEffect(() => {
|
|
136
188
|
const el = innerRef.current;
|
|
137
|
-
if (!el || !${
|
|
189
|
+
if (!el || !${localName}) return;
|
|
138
190
|
|
|
139
191
|
const handler = event => {
|
|
140
|
-
${
|
|
192
|
+
${localName}(event);
|
|
141
193
|
};
|
|
142
194
|
|
|
143
195
|
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
@@ -145,7 +197,7 @@ function generateEventEffects(eventNames) {
|
|
|
145
197
|
return () => {
|
|
146
198
|
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
147
199
|
};
|
|
148
|
-
}, [${
|
|
200
|
+
}, [${localName}]);
|
|
149
201
|
`;
|
|
150
202
|
}).join("");
|
|
151
203
|
}
|
|
@@ -153,27 +205,39 @@ function getNamedSlots(component, namedSlots) {
|
|
|
153
205
|
if (namedSlots === "none") return [];
|
|
154
206
|
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
155
207
|
}
|
|
156
|
-
function generateNamedSlotRenderLines(
|
|
157
|
-
return
|
|
208
|
+
function generateNamedSlotRenderLines(bindings) {
|
|
209
|
+
return bindings.map(({ sourceName, localName }) => {
|
|
158
210
|
return `
|
|
159
211
|
{
|
|
160
|
-
const node = createNamedSlot(${JSON.stringify(
|
|
212
|
+
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
161
213
|
if (node != null) slotChildren.push(node);
|
|
162
214
|
}
|
|
163
215
|
`;
|
|
164
216
|
}).join("");
|
|
165
217
|
}
|
|
218
|
+
function generateMinimalNamedSlots(bindings) {
|
|
219
|
+
if (!bindings.length) return "";
|
|
220
|
+
return bindings.map(({ sourceName, localName }, index) => {
|
|
221
|
+
return ` const ${`slotNode${index}`} = ${localName} != null && ${localName} !== false
|
|
222
|
+
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
223
|
+
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
224
|
+
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
225
|
+
: null;
|
|
226
|
+
`;
|
|
227
|
+
}).join("") + "\n const slotNodes = [" + bindings.map((_, index) => `slotNode${index}`).join(", ") + "].filter(Boolean);\n";
|
|
228
|
+
}
|
|
166
229
|
//#endregion
|
|
167
230
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
168
231
|
function reactWrapper(options = {}) {
|
|
169
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
232
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots, _options$wrapper;
|
|
170
233
|
const normalized = {
|
|
171
234
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
172
235
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
173
236
|
fileName: options.fileName,
|
|
174
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
237
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
175
238
|
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
176
|
-
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
239
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props",
|
|
240
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
177
241
|
};
|
|
178
242
|
return {
|
|
179
243
|
name: "zeus-output-react-wrapper",
|
|
@@ -194,7 +258,8 @@ function reactWrapper(options = {}) {
|
|
|
194
258
|
code: generateReactWrapper({
|
|
195
259
|
component,
|
|
196
260
|
namedSlots: normalized.namedSlots,
|
|
197
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
261
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
262
|
+
mode: normalized.wrapper
|
|
198
263
|
})
|
|
199
264
|
});
|
|
200
265
|
if (normalized.index) modules.push({
|
|
@@ -209,7 +274,7 @@ function reactWrapper(options = {}) {
|
|
|
209
274
|
return [{
|
|
210
275
|
type: "asset",
|
|
211
276
|
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
212
|
-
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest)
|
|
277
|
+
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest, { namedSlots: normalized.namedSlots })
|
|
213
278
|
}];
|
|
214
279
|
}
|
|
215
280
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DtsMode, ZeusComponentPlugin } from '@zeus-js/bundler-plugin';
|
|
2
2
|
|
|
3
|
+
type ReactWrapperMode = 'minimal' | 'event-bridge';
|
|
3
4
|
export interface OutputReactWrapperOptions {
|
|
4
5
|
/**
|
|
5
6
|
* React wrapper output directory.
|
|
@@ -20,7 +21,7 @@ export interface OutputReactWrapperOptions {
|
|
|
20
21
|
/**
|
|
21
22
|
* Generate react/index.d.ts.
|
|
22
23
|
*
|
|
23
|
-
* @default
|
|
24
|
+
* @default true
|
|
24
25
|
*/
|
|
25
26
|
dts?: DtsMode;
|
|
26
27
|
/**
|
|
@@ -30,7 +31,18 @@ export interface OutputReactWrapperOptions {
|
|
|
30
31
|
*/
|
|
31
32
|
index?: boolean;
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
+
* minimal:
|
|
35
|
+
* Default. Requires React 19+.
|
|
36
|
+
* React wrapper only renders the custom element tag.
|
|
37
|
+
* No useEffect prop sync, no event listeners.
|
|
38
|
+
*
|
|
39
|
+
* event-bridge:
|
|
40
|
+
* Compatibility mode for React 18 or applications that require explicit
|
|
41
|
+
* CustomEvent bridging and property assignment.
|
|
42
|
+
*/
|
|
43
|
+
wrapper?: ReactWrapperMode;
|
|
44
|
+
/**
|
|
45
|
+
* Named slot strategy (event-bridge mode only).
|
|
34
46
|
*
|
|
35
47
|
* props:
|
|
36
48
|
* <ZCard header={<div />} />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-react-wrapper v0.1.0-beta.
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -23,17 +23,67 @@ function toReactEventProp(eventName) {
|
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
25
25
|
function generateReactWrapper(input) {
|
|
26
|
+
const { mode = "minimal" } = input;
|
|
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 ");
|
|
48
|
+
}
|
|
49
|
+
function generateMinimalReactWrapper(input) {
|
|
26
50
|
const { component, namedSlots, wcModuleId } = input;
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
52
|
+
const slotDestructure = slotBindings.length ? `\n ${generateDestructuredBindings(slotBindings)}` : "";
|
|
53
|
+
const namedSlotLines = generateMinimalNamedSlots(slotBindings);
|
|
54
|
+
return `
|
|
55
|
+
import * as React from 'react';
|
|
56
|
+
|
|
57
|
+
import ${JSON.stringify(wcModuleId)};
|
|
58
|
+
|
|
59
|
+
export const ${component.name} = React.forwardRef(
|
|
60
|
+
function ${component.name}({
|
|
61
|
+
children,${slotDestructure}
|
|
62
|
+
...rest
|
|
63
|
+
} = {}, ref) {
|
|
64
|
+
${namedSlotLines}
|
|
65
|
+
return React.createElement(
|
|
66
|
+
${JSON.stringify(component.tag)},
|
|
67
|
+
{
|
|
68
|
+
...rest,
|
|
69
|
+
ref,
|
|
70
|
+
},
|
|
71
|
+
${namedSlotLines ? " ...slotNodes,\n" : ""} children,
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
`.trimStart();
|
|
76
|
+
}
|
|
77
|
+
function generateEventBridgeReactWrapper(input) {
|
|
78
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
79
|
+
const propBindings = createBindings(Object.keys(component.props), "propValue");
|
|
80
|
+
const eventBindings = createEventBindings(Object.keys(component.events));
|
|
81
|
+
const slotBindings = createBindings(getNamedSlots(component, namedSlots), "slotValue");
|
|
82
|
+
const destructuredProps = generateDestructuredBindings([
|
|
83
|
+
...propBindings,
|
|
84
|
+
...eventBindings,
|
|
85
|
+
...slotBindings
|
|
86
|
+
]);
|
|
37
87
|
return `
|
|
38
88
|
import {
|
|
39
89
|
createElement,
|
|
@@ -48,10 +98,6 @@ import {
|
|
|
48
98
|
|
|
49
99
|
import ${JSON.stringify(wcModuleId)};
|
|
50
100
|
|
|
51
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
52
|
-
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
53
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
54
|
-
|
|
55
101
|
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
56
102
|
const {
|
|
57
103
|
children,
|
|
@@ -62,16 +108,17 @@ export const ${component.name} = forwardRef(function ${component.name}(props, re
|
|
|
62
108
|
} = props;
|
|
63
109
|
|
|
64
110
|
const innerRef = useRef(null);
|
|
111
|
+
const previousPropKeysRef = useRef(new Set());
|
|
65
112
|
|
|
66
113
|
useImperativeHandle(ref, () => innerRef.current);
|
|
67
114
|
|
|
68
|
-
${generatePropSyncLines(
|
|
115
|
+
${generatePropSyncLines(propBindings)}
|
|
69
116
|
|
|
70
|
-
${generateEventEffects(
|
|
117
|
+
${generateEventEffects(eventBindings)}
|
|
71
118
|
|
|
72
119
|
const slotChildren = [];
|
|
73
120
|
|
|
74
|
-
${generateNamedSlotRenderLines(
|
|
121
|
+
${generateNamedSlotRenderLines(slotBindings)}
|
|
75
122
|
|
|
76
123
|
if (children != null) {
|
|
77
124
|
slotChildren.push(children);
|
|
@@ -110,30 +157,35 @@ function createNamedSlot(name, value) {
|
|
|
110
157
|
}
|
|
111
158
|
`.trimStart();
|
|
112
159
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
116
|
-
return map;
|
|
117
|
-
}
|
|
118
|
-
function generatePropSyncLines(propNames) {
|
|
119
|
-
if (!propNames.length) return "// no props";
|
|
160
|
+
function generatePropSyncLines(bindings) {
|
|
161
|
+
if (!bindings.length) return "// no props";
|
|
120
162
|
return `useEffect(() => {
|
|
121
163
|
const el = innerRef.current;
|
|
122
164
|
if (!el) return;
|
|
123
165
|
|
|
124
|
-
|
|
125
|
-
|
|
166
|
+
const previousPropKeys = previousPropKeysRef.current;
|
|
167
|
+
|
|
168
|
+
${bindings.map(({ sourceName, localName }) => {
|
|
169
|
+
const key = JSON.stringify(sourceName);
|
|
170
|
+
return `if (Object.prototype.hasOwnProperty.call(props, ${key})) {
|
|
171
|
+
el[${key}] = ${localName};
|
|
172
|
+
previousPropKeys.add(${key});
|
|
173
|
+
} else if (previousPropKeys.has(${key})) {
|
|
174
|
+
el[${key}] = undefined;
|
|
175
|
+
previousPropKeys.delete(${key});
|
|
176
|
+
}`;
|
|
177
|
+
}).join("\n\n ")}
|
|
178
|
+
}, [props, ${bindings.map((binding) => binding.localName).join(", ")}]);`;
|
|
126
179
|
}
|
|
127
|
-
function generateEventEffects(
|
|
128
|
-
return
|
|
129
|
-
const propName = toReactEventProp(eventName);
|
|
180
|
+
function generateEventEffects(bindings) {
|
|
181
|
+
return bindings.map(({ eventName, localName }) => {
|
|
130
182
|
return `
|
|
131
183
|
useEffect(() => {
|
|
132
184
|
const el = innerRef.current;
|
|
133
|
-
if (!el || !${
|
|
185
|
+
if (!el || !${localName}) return;
|
|
134
186
|
|
|
135
187
|
const handler = event => {
|
|
136
|
-
${
|
|
188
|
+
${localName}(event);
|
|
137
189
|
};
|
|
138
190
|
|
|
139
191
|
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
@@ -141,7 +193,7 @@ function generateEventEffects(eventNames) {
|
|
|
141
193
|
return () => {
|
|
142
194
|
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
143
195
|
};
|
|
144
|
-
}, [${
|
|
196
|
+
}, [${localName}]);
|
|
145
197
|
`;
|
|
146
198
|
}).join("");
|
|
147
199
|
}
|
|
@@ -149,27 +201,39 @@ function getNamedSlots(component, namedSlots) {
|
|
|
149
201
|
if (namedSlots === "none") return [];
|
|
150
202
|
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
151
203
|
}
|
|
152
|
-
function generateNamedSlotRenderLines(
|
|
153
|
-
return
|
|
204
|
+
function generateNamedSlotRenderLines(bindings) {
|
|
205
|
+
return bindings.map(({ sourceName, localName }) => {
|
|
154
206
|
return `
|
|
155
207
|
{
|
|
156
|
-
const node = createNamedSlot(${JSON.stringify(
|
|
208
|
+
const node = createNamedSlot(${JSON.stringify(sourceName)}, ${localName});
|
|
157
209
|
if (node != null) slotChildren.push(node);
|
|
158
210
|
}
|
|
159
211
|
`;
|
|
160
212
|
}).join("");
|
|
161
213
|
}
|
|
214
|
+
function generateMinimalNamedSlots(bindings) {
|
|
215
|
+
if (!bindings.length) return "";
|
|
216
|
+
return bindings.map(({ sourceName, localName }, index) => {
|
|
217
|
+
return ` const ${`slotNode${index}`} = ${localName} != null && ${localName} !== false
|
|
218
|
+
? (React.isValidElement(${localName}) && ${localName}.type !== React.Fragment
|
|
219
|
+
? React.cloneElement(${localName}, { slot: ${JSON.stringify(sourceName)} })
|
|
220
|
+
: React.createElement('span', { slot: ${JSON.stringify(sourceName)}, style: { display: 'contents' } }, ${localName}))
|
|
221
|
+
: null;
|
|
222
|
+
`;
|
|
223
|
+
}).join("") + "\n const slotNodes = [" + bindings.map((_, index) => `slotNode${index}`).join(", ") + "].filter(Boolean);\n";
|
|
224
|
+
}
|
|
162
225
|
//#endregion
|
|
163
226
|
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
164
227
|
function reactWrapper(options = {}) {
|
|
165
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
228
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots, _options$wrapper;
|
|
166
229
|
const normalized = {
|
|
167
230
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
168
231
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
169
232
|
fileName: options.fileName,
|
|
170
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
233
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
171
234
|
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
172
|
-
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
235
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props",
|
|
236
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
173
237
|
};
|
|
174
238
|
return {
|
|
175
239
|
name: "zeus-output-react-wrapper",
|
|
@@ -190,7 +254,8 @@ function reactWrapper(options = {}) {
|
|
|
190
254
|
code: generateReactWrapper({
|
|
191
255
|
component,
|
|
192
256
|
namedSlots: normalized.namedSlots,
|
|
193
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
257
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
258
|
+
mode: normalized.wrapper
|
|
194
259
|
})
|
|
195
260
|
});
|
|
196
261
|
if (normalized.index) modules.push({
|
|
@@ -205,7 +270,7 @@ function reactWrapper(options = {}) {
|
|
|
205
270
|
return [{
|
|
206
271
|
type: "asset",
|
|
207
272
|
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
208
|
-
source: generateReactDts(ctx.manifest)
|
|
273
|
+
source: generateReactDts(ctx.manifest, { namedSlots: normalized.namedSlots })
|
|
209
274
|
}];
|
|
210
275
|
}
|
|
211
276
|
};
|
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.3",
|
|
4
4
|
"description": "Zeus React wrapper output plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"types": "./dist/output-react-wrapper.d.ts",
|
|
16
|
+
"module": "./dist/output-react-wrapper.esm-bundler.js",
|
|
17
|
+
"import": "./dist/output-react-wrapper.esm-bundler.js",
|
|
18
|
+
"require": "./index.js",
|
|
16
19
|
"node": {
|
|
17
20
|
"production": "./dist/output-react-wrapper.cjs.prod.js",
|
|
18
21
|
"development": "./dist/output-react-wrapper.cjs.js",
|
|
19
22
|
"default": "./index.js"
|
|
20
|
-
}
|
|
21
|
-
"module": "./dist/output-react-wrapper.esm-bundler.js",
|
|
22
|
-
"import": "./dist/output-react-wrapper.esm-bundler.js",
|
|
23
|
-
"require": "./index.js"
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"sideEffects": false,
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@zeus-js/
|
|
40
|
-
"@zeus-js/
|
|
41
|
-
"@zeus-js/component-
|
|
39
|
+
"@zeus-js/bundler-plugin": "0.1.0-beta.3",
|
|
40
|
+
"@zeus-js/component-dts": "0.1.0-beta.3",
|
|
41
|
+
"@zeus-js/component-analyzer": "0.1.0-beta.3"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"react": ">=18
|
|
44
|
+
"react": ">=18"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"react": {
|