@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
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 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);
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(propNames)}
119
+ ${generatePropSyncLines(propBindings)}
73
120
 
74
- ${generateEventEffects(eventNames)}
121
+ ${generateEventEffects(eventBindings)}
75
122
 
76
123
  const slotChildren = [];
77
124
 
78
- ${generateNamedSlotRenderLines(slotNames)}
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 createReactEventMap(eventNames) {
118
- const map = {};
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
- ${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
129
- }, [${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(", ")}]);`;
130
183
  }
131
- function generateEventEffects(eventNames) {
132
- return eventNames.map((eventName) => {
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 || !${propName}) return;
189
+ if (!el || !${localName}) return;
138
190
 
139
191
  const handler = event => {
140
- ${propName}(event);
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
- }, [${propName}]);
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(namedSlots) {
157
- return namedSlots.map((name) => {
208
+ function generateNamedSlotRenderLines(bindings) {
209
+ return bindings.map(({ sourceName, localName }) => {
158
210
  return `
159
211
  {
160
- const node = createNamedSlot(${JSON.stringify(name)}, ${name});
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 : "auto",
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
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 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);
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(propNames)}
119
+ ${generatePropSyncLines(propBindings)}
73
120
 
74
- ${generateEventEffects(eventNames)}
121
+ ${generateEventEffects(eventBindings)}
75
122
 
76
123
  const slotChildren = [];
77
124
 
78
- ${generateNamedSlotRenderLines(slotNames)}
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 createReactEventMap(eventNames) {
118
- const map = {};
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
- ${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
129
- }, [${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(", ")}]);`;
130
183
  }
131
- function generateEventEffects(eventNames) {
132
- return eventNames.map((eventName) => {
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 || !${propName}) return;
189
+ if (!el || !${localName}) return;
138
190
 
139
191
  const handler = event => {
140
- ${propName}(event);
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
- }, [${propName}]);
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(namedSlots) {
157
- return namedSlots.map((name) => {
208
+ function generateNamedSlotRenderLines(bindings) {
209
+ return bindings.map(({ sourceName, localName }) => {
158
210
  return `
159
211
  {
160
- const node = createNamedSlot(${JSON.stringify(name)}, ${name});
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 : "auto",
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 '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.2
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 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);
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(propNames)}
115
+ ${generatePropSyncLines(propBindings)}
69
116
 
70
- ${generateEventEffects(eventNames)}
117
+ ${generateEventEffects(eventBindings)}
71
118
 
72
119
  const slotChildren = [];
73
120
 
74
- ${generateNamedSlotRenderLines(slotNames)}
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 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";
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
- ${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
125
- }, [${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(", ")}]);`;
126
179
  }
127
- function generateEventEffects(eventNames) {
128
- return eventNames.map((eventName) => {
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 || !${propName}) return;
185
+ if (!el || !${localName}) return;
134
186
 
135
187
  const handler = event => {
136
- ${propName}(event);
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
- }, [${propName}]);
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(namedSlots) {
153
- return namedSlots.map((name) => {
204
+ function generateNamedSlotRenderLines(bindings) {
205
+ return bindings.map(({ sourceName, localName }) => {
154
206
  return `
155
207
  {
156
- const node = createNamedSlot(${JSON.stringify(name)}, ${name});
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 : "auto",
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.2",
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/component-analyzer": "0.1.0-beta.2",
40
- "@zeus-js/bundler-plugin": "0.1.0-beta.2",
41
- "@zeus-js/component-dts": "0.1.0-beta.2"
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 || >=19"
44
+ "react": ">=18"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "react": {