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