@builder.io/mitosis 0.9.2 → 0.9.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.
@@ -0,0 +1,8 @@
1
+ import { MitosisComponent } from '../../types/mitosis-component';
2
+ import { MitosisNode } from '../../types/mitosis-node';
3
+ import { ToSwiftOptions } from './types';
4
+ export declare const blockToSwift: ({ json, options, parentComponent, }: {
5
+ json: MitosisNode;
6
+ options: ToSwiftOptions;
7
+ parentComponent: MitosisComponent;
8
+ }) => string;
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockToSwift = void 0;
4
+ const helpers_1 = require("./helpers");
5
+ // Helper function to sanitize text content for SwiftUI
6
+ const sanitizeTextForSwift = (text) => {
7
+ if (!text)
8
+ return '""';
9
+ // Check if text contains newlines
10
+ if (text.includes('\n')) {
11
+ // Use triple quotes for multiline strings
12
+ return `"""${text}"""`;
13
+ }
14
+ // Escape double quotes in the text
15
+ return `"${text.replace(/"/g, '\\"')}"`;
16
+ };
17
+ const blockToSwift = ({ json, options, parentComponent, }) => {
18
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
19
+ if (json.properties._text) {
20
+ return `Text(${sanitizeTextForSwift(json.properties._text)})`;
21
+ }
22
+ const tag = json.name;
23
+ // For fragments, render children without a wrapper
24
+ if (tag === 'Fragment') {
25
+ return json.children
26
+ .map((child) => (0, exports.blockToSwift)({ json: child, options, parentComponent }))
27
+ .join('\n');
28
+ }
29
+ // Process bindings and properties - use parentComponent here instead of json
30
+ const processCode = (0, helpers_1.stripStateAndProps)({ json: parentComponent, options });
31
+ // Handle ForEach blocks - use bindings.each pattern like other generators
32
+ if ((_a = json.bindings.each) === null || _a === void 0 ? void 0 : _a.code) {
33
+ const { collection, itemName, indexName } = (0, helpers_1.getForEachParams)(json, processCode);
34
+ const forEachContent = json.children
35
+ .map((child) => (0, exports.blockToSwift)({ json: child, options, parentComponent }))
36
+ .join('\n');
37
+ // Check if the collection is using Array.from({length: X}) pattern
38
+ const arrayFromMatch = collection.match(/Array\.from\(\s*\{\s*length:\s*(\d+)\s*\}\s*\)/);
39
+ if (arrayFromMatch) {
40
+ // Convert to SwiftUI's ForEach with a range
41
+ const length = arrayFromMatch[1];
42
+ if (indexName) {
43
+ // With index
44
+ return `ForEach(0..<${length}, id: \\.self) { ${indexName} in
45
+ let ${itemName} = ${indexName}
46
+ ${forEachContent}
47
+ }`;
48
+ }
49
+ else {
50
+ // Without index
51
+ return `ForEach(0..<${length}, id: \\.self) { ${itemName} in
52
+ ${forEachContent}
53
+ }`;
54
+ }
55
+ }
56
+ else {
57
+ // Standard collection-based ForEach
58
+ if (indexName) {
59
+ return `ForEach(Array(zip(${collection}.indices, ${collection})), id: \\.0) { ${indexName}, ${itemName} in
60
+ ${forEachContent}
61
+ }`;
62
+ }
63
+ else {
64
+ return `ForEach(${collection}, id: \\.self) { ${itemName} in
65
+ ${forEachContent}
66
+ }`;
67
+ }
68
+ }
69
+ }
70
+ // Determine the SwiftUI component name
71
+ const component = (0, helpers_1.jsxElementToSwiftUIView)(tag);
72
+ // Handle children
73
+ const hasChildren = json.children && json.children.length > 0;
74
+ // Handle event handlers
75
+ const eventHandlers = Object.entries(json.bindings)
76
+ .filter(([key]) => { var _a; return key.startsWith('on') && ((_a = json.bindings[key]) === null || _a === void 0 ? void 0 : _a.code); })
77
+ .map(([key, binding]) => {
78
+ const swiftEventName = (0, helpers_1.getEventHandlerName)(key);
79
+ return `.${swiftEventName}(${processCode((binding === null || binding === void 0 ? void 0 : binding.code) || '')})`;
80
+ });
81
+ // Handle data bindings (like bind:value)
82
+ const dataBindings = Object.entries(json.bindings)
83
+ .filter(([key]) => { var _a; return key.startsWith('bind:') && ((_a = json.bindings[key]) === null || _a === void 0 ? void 0 : _a.code); })
84
+ .map(([key, binding]) => {
85
+ const bindingType = (0, helpers_1.getBindingType)(key);
86
+ const bindingValue = processCode((binding === null || binding === void 0 ? void 0 : binding.code) || '');
87
+ return { type: bindingType, value: bindingValue };
88
+ });
89
+ // Handle style properties
90
+ const styleModifiers = [];
91
+ if (json.bindings.style) {
92
+ // Dynamic styles
93
+ styleModifiers.push(`// Dynamic styles not fully implemented`);
94
+ styleModifiers.push(`.modifier(/* Dynamic style handling needed here */)"`);
95
+ }
96
+ else if (json.properties.style) {
97
+ // Static styles
98
+ try {
99
+ const styleObj = JSON.parse(json.properties.style);
100
+ styleModifiers.push(...(0, helpers_1.cssToSwiftUIModifiers)(styleObj));
101
+ }
102
+ catch (e) {
103
+ styleModifiers.push(`// Could not parse style: ${json.properties.style}`);
104
+ }
105
+ }
106
+ // Check if we need a ScrollView
107
+ const needsScroll = (0, helpers_1.needsScrollView)(json);
108
+ // Conditional rendering
109
+ let result = '';
110
+ if ((_b = json.bindings.if) === null || _b === void 0 ? void 0 : _b.code) {
111
+ result += `if ${processCode(json.bindings.if.code)} {\n`;
112
+ }
113
+ else if ((_c = json.bindings.show) === null || _c === void 0 ? void 0 : _c.code) {
114
+ // In SwiftUI we can use opacity for show/hide
115
+ styleModifiers.push(`.opacity(${processCode(json.bindings.show.code)} ? 1 : 0)`);
116
+ }
117
+ // Start building the component
118
+ let componentCode = '';
119
+ switch (component) {
120
+ case 'Text':
121
+ // Text components in SwiftUI need their content as a parameter
122
+ let textContent = '';
123
+ if (json.properties._text) {
124
+ textContent = sanitizeTextForSwift(json.properties._text);
125
+ }
126
+ else if ((_d = json.bindings.innerHTML) === null || _d === void 0 ? void 0 : _d.code) {
127
+ // For dynamic content, we'll need to handle it as an expression
128
+ textContent = processCode(json.bindings.innerHTML.code);
129
+ }
130
+ else {
131
+ textContent = '""';
132
+ }
133
+ componentCode = `Text(${textContent})`;
134
+ break;
135
+ case 'Button':
136
+ // Find the action from eventHandlers or create an empty one
137
+ let buttonAction = '{}';
138
+ const onClickHandler = eventHandlers.find((h) => h.includes('onTapGesture'));
139
+ if (onClickHandler) {
140
+ buttonAction = onClickHandler.replace('.onTapGesture(', '').replace(')', '');
141
+ // Remove this handler from the list since we're using it directly
142
+ eventHandlers.splice(eventHandlers.indexOf(onClickHandler), 1);
143
+ }
144
+ const buttonLabel = hasChildren
145
+ ? json.children
146
+ .map((child) => (0, exports.blockToSwift)({ json: child, options, parentComponent }))
147
+ .join('\n')
148
+ : `Text("${json.properties._text || 'Button'}")`;
149
+ componentCode = `Button(action: { ${buttonAction} }) {\n${buttonLabel}\n}`;
150
+ break;
151
+ case 'TextField':
152
+ // TextField can have either bind:value or direct value binding
153
+ let bindingExpression = '';
154
+ // First check for explicit bind:value
155
+ const textBinding = dataBindings.find((b) => b.type === 'value');
156
+ // If not found, check for direct value binding
157
+ const directValueBinding = ((_e = json.bindings.value) === null || _e === void 0 ? void 0 : _e.code)
158
+ ? processCode(json.bindings.value.code)
159
+ : null;
160
+ if (textBinding) {
161
+ // Use the explicit binding from dataBindings
162
+ bindingExpression = textBinding.value;
163
+ }
164
+ else if (directValueBinding) {
165
+ // Use direct value binding
166
+ bindingExpression = directValueBinding;
167
+ }
168
+ if (bindingExpression) {
169
+ // Convert to SwiftUI binding syntax
170
+ bindingExpression = bindingExpression.replace(/self\.(\w+)/g, '$$$1');
171
+ // If it still doesn't start with $, add it
172
+ if (!bindingExpression.startsWith('$')) {
173
+ bindingExpression = `$${bindingExpression}`;
174
+ }
175
+ componentCode = `TextField("${json.properties.placeholder || ''}", text: ${bindingExpression})`;
176
+ }
177
+ else {
178
+ // No binding found, use constant value
179
+ componentCode = `TextField("${json.properties.placeholder || ''}", text: .constant("${json.properties.value || ''}"))`;
180
+ }
181
+ break;
182
+ case 'Image':
183
+ // Determine if using system image, URL, or asset
184
+ if ((_f = json.properties.src) === null || _f === void 0 ? void 0 : _f.startsWith('system-')) {
185
+ // System image
186
+ const systemName = json.properties.src.replace('system-', '');
187
+ componentCode = `Image(systemName: "${systemName}")`;
188
+ }
189
+ else if ((_g = json.properties.src) === null || _g === void 0 ? void 0 : _g.startsWith('http')) {
190
+ // URL image (requires AsyncImage)
191
+ componentCode = `AsyncImage(url: URL(string: "${json.properties.src}")!) { image in
192
+ image.resizable()
193
+ } placeholder: {
194
+ ProgressView()
195
+ }`;
196
+ }
197
+ else {
198
+ // Asset image
199
+ componentCode = `Image("${json.properties.src || 'placeholder'}")`;
200
+ }
201
+ if (json.properties.resizeMode) {
202
+ componentCode += `.resizable().aspectRatio(contentMode: .${json.properties.resizeMode})`;
203
+ }
204
+ else if (!((_h = json.properties.src) === null || _h === void 0 ? void 0 : _h.startsWith('http'))) {
205
+ // Add resizable for non-async images without specific mode
206
+ componentCode += `.resizable().aspectRatio(contentMode: .fit)`;
207
+ }
208
+ break;
209
+ case 'VStack':
210
+ case 'HStack':
211
+ case 'ZStack':
212
+ // Stacks with children
213
+ const alignment = json.properties.alignment || 'leading';
214
+ const spacing = json.properties.spacing || '8';
215
+ componentCode = `${component}(alignment: .${alignment}, spacing: ${spacing}) {\n`;
216
+ if (hasChildren) {
217
+ componentCode += json.children
218
+ .map((child) => {
219
+ return (0, exports.blockToSwift)({ json: child, options, parentComponent });
220
+ })
221
+ .join('\n');
222
+ }
223
+ componentCode += '\n}';
224
+ break;
225
+ case 'List':
226
+ // Lists in SwiftUI
227
+ componentCode = `List {\n`;
228
+ if (hasChildren) {
229
+ componentCode += json.children
230
+ .map((child) => {
231
+ return (0, exports.blockToSwift)({ json: child, options, parentComponent });
232
+ })
233
+ .join('\n');
234
+ }
235
+ componentCode += '\n}';
236
+ break;
237
+ case 'Picker':
238
+ // Handle select element
239
+ // Pickers in SwiftUI need a selection binding, a label, and content
240
+ const selectBinding = dataBindings.find((b) => b.type === 'value');
241
+ const selectionVar = selectBinding ? selectBinding.value : '.constant("")';
242
+ // Create label from the "label" property or a default
243
+ const pickerLabel = json.properties.label
244
+ ? `Text("${json.properties.label}")`
245
+ : 'Text("Select")';
246
+ // Start building the picker
247
+ componentCode = `Picker(selection: Binding(get: { ${selectionVar} }, set: { ${selectionVar} = $0 }), label: { ${pickerLabel} }) {`;
248
+ // Add options as children
249
+ if (hasChildren) {
250
+ json.children.forEach((child) => {
251
+ var _a;
252
+ // For option elements, extract the value and text
253
+ if (((_a = child.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'option') {
254
+ const optionValue = child.properties.value || '';
255
+ const optionText = child.properties._text || optionValue;
256
+ componentCode += `\nText("${optionText}").tag("${optionValue}")`;
257
+ }
258
+ else {
259
+ // Handle non-option children (unusual but possible)
260
+ componentCode += `\n${(0, exports.blockToSwift)({ json: child, options, parentComponent })}`;
261
+ }
262
+ });
263
+ }
264
+ componentCode += '\n}';
265
+ break;
266
+ default:
267
+ // Custom components or other SwiftUI views
268
+ if (hasChildren) {
269
+ componentCode = `${component} {\n`;
270
+ componentCode += json.children
271
+ .map((child) => {
272
+ return (0, exports.blockToSwift)({ json: child, options, parentComponent });
273
+ })
274
+ .join('\n');
275
+ componentCode += '\n}';
276
+ }
277
+ else {
278
+ componentCode = `${component}()`;
279
+ }
280
+ }
281
+ // Apply modifiers
282
+ styleModifiers.forEach((modifier) => {
283
+ componentCode += `\n${modifier}`;
284
+ });
285
+ // Add event handlers that weren't specifically handled above
286
+ eventHandlers
287
+ .filter((handler) => !componentCode.includes(handler))
288
+ .forEach((handler) => {
289
+ componentCode += `\n${handler}`;
290
+ });
291
+ // Wrap with ScrollView if needed
292
+ if (needsScroll) {
293
+ componentCode = `ScrollView {\n${componentCode}\n}`;
294
+ }
295
+ // Close conditional rendering block if needed
296
+ if ((_j = json.bindings.if) === null || _j === void 0 ? void 0 : _j.code) {
297
+ result += componentCode + '\n}';
298
+ }
299
+ else {
300
+ result += componentCode;
301
+ }
302
+ return result;
303
+ };
304
+ exports.blockToSwift = blockToSwift;
@@ -1,3 +1,3 @@
1
- import { ToSwiftOptions } from '../../generators/swift/types';
2
1
  import { TranspilerGenerator } from '../../types/transpiler';
2
+ import { ToSwiftOptions } from './types';
3
3
  export declare const componentToSwift: TranspilerGenerator<ToSwiftOptions>;