@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.
- package/dist/src/generators/swift/blocks.d.ts +8 -0
- package/dist/src/generators/swift/blocks.js +304 -0
- package/dist/src/generators/swift/generator.d.ts +1 -1
- package/dist/src/generators/swift/generator.js +256 -303
- package/dist/src/generators/swift/helpers.d.ts +42 -0
- package/dist/src/generators/swift/helpers.js +377 -0
- package/dist/src/generators/swift/types.d.ts +25 -0
- package/dist/src/parsers/builder/builder.js +2 -3
- package/dist/src/parsers/jsx/element-parser.js +9 -0
- package/jsx-runtime.d.ts +5 -0
- package/package.json +1 -1
|
@@ -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;
|