@zeus-js/output-react-wrapper 0.1.0-beta.3 → 0.1.0-beta.4

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