@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,377 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertJsFunctionToSwift = exports.extractFunctionSignature = exports.getBindingType = exports.camelToSnakeCase = exports.getForEachParams = exports.isForEachBlock = exports.needsScrollView = exports.getEventHandlerName = exports.getStatePropertyTypeAnnotation = exports.cssToSwiftUIModifiers = exports.jsxElementToSwiftUIView = exports.getSwiftType = exports.stripStateAndProps = exports.ensureSwiftStringFormat = exports.convertConsoleLogToPrint = void 0;
4
+ const capitalize_1 = require("../../helpers/capitalize");
5
+ const strip_state_and_props_refs_1 = require("../../helpers/strip-state-and-props-refs");
6
+ // TODO(kyle): use babel here to do ast
7
+ const convertConsoleLogToPrint = (code) => {
8
+ if (!code)
9
+ return code;
10
+ if (code.includes('console.log')) {
11
+ console.log('Converting console.log to print');
12
+ }
13
+ // Match console.log statements with various argument patterns
14
+ return code.replace(/console\.log\s*\(\s*(.*?)\s*\)/g, (match, args) => {
15
+ // Handle empty console.log()
16
+ if (!args.trim()) {
17
+ return 'print()';
18
+ }
19
+ // Simple handling for basic console.log calls
20
+ // For complex cases, we'd need a more sophisticated parser
21
+ return `print(${(0, exports.ensureSwiftStringFormat)(args)})`;
22
+ });
23
+ };
24
+ exports.convertConsoleLogToPrint = convertConsoleLogToPrint;
25
+ // Helper function to ensure Swift strings use double quotes
26
+ const ensureSwiftStringFormat = (code) => {
27
+ if (!code)
28
+ return code;
29
+ // Replace string literals enclosed in single quotes with double quotes
30
+ // This regex looks for single-quoted strings not inside double quotes
31
+ return code.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'(?=(?:[^"]*"[^"]*")*[^"]*$)/g, '"$1"');
32
+ };
33
+ exports.ensureSwiftStringFormat = ensureSwiftStringFormat;
34
+ const stripStateAndProps = ({ json, options, }) => {
35
+ return (code) => {
36
+ // Convert console.log statements to Swift print
37
+ code = (0, exports.convertConsoleLogToPrint)(code);
38
+ // Ensure Swift strings use double quotes
39
+ code = (0, exports.ensureSwiftStringFormat)(code);
40
+ // In Swift, we use self.propertyName for accessing properties
41
+ return (0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(code, {
42
+ includeState: true,
43
+ includeProps: true,
44
+ replaceWith: (name) => {
45
+ // In Swift, we access properties with self.propertyName
46
+ return `self.${name}`;
47
+ },
48
+ });
49
+ };
50
+ };
51
+ exports.stripStateAndProps = stripStateAndProps;
52
+ const getSwiftType = (type) => {
53
+ if (!type)
54
+ return 'Any';
55
+ // Handle array types with proper Swift syntax
56
+ if (type.includes('Array<') || type.includes('[]') || type.toLowerCase().startsWith('array')) {
57
+ // Extract the element type from Array<ElementType>
58
+ let elementType = 'Any';
59
+ // Match different array type patterns
60
+ const arrayMatch = type.match(/Array<([^>]+)>/i) ||
61
+ type.match(/([^[\]]+)\[\]/i) ||
62
+ type.match(/array\s*<([^>]+)>/i);
63
+ if (arrayMatch && arrayMatch[1]) {
64
+ elementType = (0, exports.getSwiftType)(arrayMatch[1].trim());
65
+ }
66
+ // Return Swift array type: [ElementType]
67
+ return `[${elementType}]`;
68
+ }
69
+ // Handle primitive types
70
+ switch (type.toLowerCase()) {
71
+ case 'string':
72
+ return 'String';
73
+ case 'number':
74
+ return 'Double';
75
+ case 'boolean':
76
+ case 'bool':
77
+ return 'Bool';
78
+ case 'any':
79
+ return 'Any';
80
+ case 'void':
81
+ return 'Void';
82
+ case 'object':
83
+ return '[String: Any]';
84
+ case 'null':
85
+ case 'undefined':
86
+ return 'Optional<Any>';
87
+ default:
88
+ // For complex types, return as is with first letter capitalized
89
+ return type.charAt(0).toUpperCase() + type.slice(1);
90
+ }
91
+ };
92
+ exports.getSwiftType = getSwiftType;
93
+ const jsxElementToSwiftUIView = (tagName) => {
94
+ // Map JSX/HTML elements to SwiftUI components
95
+ switch (tagName.toLowerCase()) {
96
+ case 'div':
97
+ return 'VStack';
98
+ case 'span':
99
+ case 'p':
100
+ case 'h1':
101
+ case 'h2':
102
+ case 'h3':
103
+ case 'h4':
104
+ case 'h5':
105
+ case 'h6':
106
+ return 'Text';
107
+ case 'img':
108
+ return 'Image';
109
+ case 'input':
110
+ return 'TextField';
111
+ case 'button':
112
+ return 'Button';
113
+ case 'a':
114
+ return 'Link';
115
+ case 'ul':
116
+ return 'List';
117
+ case 'li':
118
+ return 'Text'; // Will be wrapped in List
119
+ case 'form':
120
+ return 'Form';
121
+ case 'select':
122
+ return 'Picker';
123
+ case 'option':
124
+ return 'Text'; // Options in SwiftUI are part of the Picker content
125
+ default:
126
+ // For custom components or unrecognized tags
127
+ return (0, capitalize_1.capitalize)(tagName);
128
+ }
129
+ };
130
+ exports.jsxElementToSwiftUIView = jsxElementToSwiftUIView;
131
+ const cssToSwiftUIModifiers = (style) => {
132
+ const modifiers = [];
133
+ // Map CSS properties to SwiftUI modifiers
134
+ Object.entries(style).forEach(([key, value]) => {
135
+ switch (key) {
136
+ case 'backgroundColor':
137
+ modifiers.push(`.background(Color("${value}"))`);
138
+ break;
139
+ case 'color':
140
+ modifiers.push(`.foregroundColor(Color("${value}"))`);
141
+ break;
142
+ case 'fontSize':
143
+ const fontSize = parseInt(value);
144
+ if (!isNaN(fontSize)) {
145
+ modifiers.push(`.font(.system(size: ${fontSize}))`);
146
+ }
147
+ break;
148
+ case 'fontWeight':
149
+ modifiers.push(`.fontWeight(.${value})`);
150
+ break;
151
+ case 'padding':
152
+ modifiers.push(`.padding(${value})`);
153
+ break;
154
+ case 'margin':
155
+ // Swift doesn't have direct margin equivalent, we'll use padding
156
+ modifiers.push(`// Note: 'margin' converted to padding: ${value}`);
157
+ modifiers.push(`.padding(${value})`);
158
+ break;
159
+ case 'width':
160
+ modifiers.push(`.frame(width: ${value})`);
161
+ break;
162
+ case 'height':
163
+ modifiers.push(`.frame(height: ${value})`);
164
+ break;
165
+ // Add more CSS to SwiftUI modifier mappings as needed
166
+ default:
167
+ modifiers.push(`// Unmapped style: ${key}: ${value}`);
168
+ }
169
+ });
170
+ return modifiers;
171
+ };
172
+ exports.cssToSwiftUIModifiers = cssToSwiftUIModifiers;
173
+ const getStatePropertyTypeAnnotation = (propertyType, type) => {
174
+ // Use appropriate SwiftUI property wrappers
175
+ switch (propertyType) {
176
+ case 'reactive':
177
+ // For reactive state, use @State for simple values
178
+ // @Observable would be used for classes but requires Swift 5.9+/iOS 17+
179
+ return `@State private var`;
180
+ case 'normal':
181
+ // For normal state, use @State for simple values
182
+ return `@State private var`;
183
+ default:
184
+ // For non-reactive values, use a regular property
185
+ return `var`;
186
+ }
187
+ };
188
+ exports.getStatePropertyTypeAnnotation = getStatePropertyTypeAnnotation;
189
+ const getEventHandlerName = (eventName) => {
190
+ switch (eventName) {
191
+ case 'onClick':
192
+ return 'onTapGesture';
193
+ case 'onChange':
194
+ return 'onChange';
195
+ case 'onInput':
196
+ return 'onEditingChanged';
197
+ case 'onBlur':
198
+ return 'onSubmit';
199
+ case 'onFocus':
200
+ return 'onEditingChanged';
201
+ default:
202
+ return eventName;
203
+ }
204
+ };
205
+ exports.getEventHandlerName = getEventHandlerName;
206
+ const needsScrollView = (json) => {
207
+ // Check if overflow property indicates scrolling
208
+ if (json.properties.style) {
209
+ try {
210
+ const styleObj = JSON.parse(json.properties.style);
211
+ return (styleObj.overflow === 'auto' ||
212
+ styleObj.overflow === 'scroll' ||
213
+ styleObj.overflowY === 'auto' ||
214
+ styleObj.overflowY === 'scroll' ||
215
+ styleObj.overflowX === 'auto' ||
216
+ styleObj.overflowX === 'scroll');
217
+ }
218
+ catch (e) {
219
+ // If style can't be parsed, check for overflow directly in the style string
220
+ const styleStr = json.properties.style;
221
+ return (styleStr.includes('overflow:auto') ||
222
+ styleStr.includes('overflow:scroll') ||
223
+ styleStr.includes('overflow-y:auto') ||
224
+ styleStr.includes('overflow-y:scroll') ||
225
+ styleStr.includes('overflow-x:auto') ||
226
+ styleStr.includes('overflow-x:scroll'));
227
+ }
228
+ }
229
+ return false;
230
+ };
231
+ exports.needsScrollView = needsScrollView;
232
+ const isForEachBlock = (json) => {
233
+ var _a;
234
+ // Check if this is a ForEach binding using the bindings.each pattern
235
+ return !!((_a = json.bindings.each) === null || _a === void 0 ? void 0 : _a.code);
236
+ };
237
+ exports.isForEachBlock = isForEachBlock;
238
+ const getForEachParams = (json, processCode) => {
239
+ var _a, _b;
240
+ if (!((_a = json.bindings.each) === null || _a === void 0 ? void 0 : _a.code)) {
241
+ return { collection: '', itemName: 'item', indexName: null };
242
+ }
243
+ const eachCode = json.bindings.each.code;
244
+ let itemName = 'item';
245
+ let indexName = null;
246
+ // Extract collection, item name, and index name from each binding
247
+ try {
248
+ // Parse expressions like: items.map(item => ...)
249
+ // or items.map((item, index) => ...)
250
+ const match = eachCode.match(/(\w+)\.map\(\s*(?:\()?([^,)]+)(?:,\s*([^)]+))?\)?/);
251
+ if (match) {
252
+ const collection = processCode(match[1]);
253
+ itemName = match[2].trim();
254
+ indexName = ((_b = match[3]) === null || _b === void 0 ? void 0 : _b.trim()) || null;
255
+ return { collection, itemName, indexName };
256
+ }
257
+ // Fallback to the whole code as collection if pattern doesn't match
258
+ return {
259
+ collection: processCode(eachCode),
260
+ itemName,
261
+ indexName,
262
+ };
263
+ }
264
+ catch (e) {
265
+ console.warn('Failed to parse each binding:', eachCode);
266
+ return {
267
+ collection: processCode(eachCode),
268
+ itemName,
269
+ indexName,
270
+ };
271
+ }
272
+ };
273
+ exports.getForEachParams = getForEachParams;
274
+ const camelToSnakeCase = (str) => {
275
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
276
+ };
277
+ exports.camelToSnakeCase = camelToSnakeCase;
278
+ const getBindingType = (key) => {
279
+ if (key.startsWith('bind:')) {
280
+ return key.substring(5);
281
+ }
282
+ return key;
283
+ };
284
+ exports.getBindingType = getBindingType;
285
+ /**
286
+ * Extract function signature information from JavaScript function code
287
+ */
288
+ const extractFunctionSignature = (code) => {
289
+ // Default values
290
+ let name = '';
291
+ let params = [];
292
+ let returnType = 'Void';
293
+ let body = '';
294
+ // Extract function name, parameters, and body
295
+ const funcMatch = code.match(/(?:function\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)?\s*\(([^)]*)\)\s*(?:=>)?\s*(?:{([\s\S]*)}|(.*))/);
296
+ if (funcMatch) {
297
+ name = funcMatch[1] || '';
298
+ // Extract parameters
299
+ const paramsStr = funcMatch[2].trim();
300
+ if (paramsStr) {
301
+ params = paramsStr.split(',').map((param) => {
302
+ // Handle TypeScript-style parameter types if present
303
+ const paramParts = param.trim().split(':');
304
+ const paramName = paramParts[0].trim();
305
+ const paramType = paramParts.length > 1 ? (0, exports.getSwiftType)(paramParts[1].trim()) : 'Any';
306
+ return { name: paramName, type: paramType };
307
+ });
308
+ }
309
+ // Extract function body
310
+ body = funcMatch[3] || funcMatch[4] || '';
311
+ // Try to determine return type from TypeScript annotations or infer from return statements
312
+ const returnTypeMatch = code.match(/\)\s*:\s*([^{]+)/);
313
+ if (returnTypeMatch) {
314
+ returnType = (0, exports.getSwiftType)(returnTypeMatch[1].trim());
315
+ }
316
+ else if (body.includes('return')) {
317
+ // Try to infer from return statements
318
+ const returnValueMatch = body.match(/return\s+(["'].*["']|true|false|\d+|\d+\.\d+|\[.*\])/);
319
+ if (returnValueMatch) {
320
+ const returnValue = returnValueMatch[1];
321
+ if (returnValue.startsWith('"') || returnValue.startsWith("'")) {
322
+ returnType = 'String';
323
+ }
324
+ else if (returnValue === 'true' || returnValue === 'false') {
325
+ returnType = 'Bool';
326
+ }
327
+ else if (returnValue.match(/^\d+$/)) {
328
+ returnType = 'Int';
329
+ }
330
+ else if (returnValue.match(/^\d+\.\d+$/)) {
331
+ returnType = 'Double';
332
+ }
333
+ else if (returnValue.startsWith('[')) {
334
+ returnType = '[Any]';
335
+ }
336
+ }
337
+ }
338
+ }
339
+ return { name, params, returnType, body };
340
+ };
341
+ exports.extractFunctionSignature = extractFunctionSignature;
342
+ /**
343
+ * Convert JavaScript function code to Swift function syntax
344
+ */
345
+ const convertJsFunctionToSwift = (code, functionName) => {
346
+ // Extract the function signature
347
+ const { name, params, returnType, body } = (0, exports.extractFunctionSignature)(code);
348
+ // Use provided name or extracted name
349
+ const finalName = functionName || name || 'function';
350
+ // Convert function body to Swift
351
+ let swiftBody = body
352
+ // Convert variable declarations
353
+ .replace(/\bvar\s+(\w+)/g, 'var $1')
354
+ .replace(/\blet\s+(\w+)/g, 'let $1')
355
+ .replace(/\bconst\s+(\w+)/g, 'let $1')
356
+ // Convert common array methods
357
+ .replace(/\.push\(/g, '.append(')
358
+ .replace(/\.map\(/g, '.map(')
359
+ .replace(/\.filter\(/g, '.filter(')
360
+ .replace(/\.includes\(/g, '.contains(')
361
+ .replace(/\.indexOf\(/g, '.firstIndex(of: ')
362
+ // Convert null/undefined checks
363
+ .replace(/=== null/g, '== nil')
364
+ .replace(/!== null/g, '!= nil')
365
+ .replace(/=== undefined/g, '== nil')
366
+ .replace(/!== undefined/g, '!= nil')
367
+ // Convert console.log
368
+ .replace(/console\.log\((.+?)\)/g, 'print($1)');
369
+ // Create parameter list with Swift types
370
+ const paramList = params.map((p) => `${p.name}: ${p.type}`).join(', ');
371
+ // Build the Swift function signature
372
+ const signature = `func ${finalName}(${paramList}) -> ${returnType}`;
373
+ // Build the complete Swift function
374
+ const swiftCode = `${signature} {\n ${swiftBody}\n}`;
375
+ return { swiftCode, signature };
376
+ };
377
+ exports.convertJsFunctionToSwift = convertJsFunctionToSwift;
@@ -1,4 +1,29 @@
1
1
  import { BaseTranspilerOptions } from '../../types/transpiler';
2
2
  export interface ToSwiftOptions extends BaseTranspilerOptions {
3
+ /**
4
+ * Format generated Swift code
5
+ * @default true
6
+ */
7
+ formatCode?: boolean;
8
+ /**
9
+ * Include type annotations in Swift code
10
+ * @default true
11
+ */
12
+ includeTypes?: boolean;
13
+ /**
14
+ * Type of state management to use
15
+ * @default 'state'
16
+ */
17
+ stateType?: 'state' | 'stateObject' | 'observable';
18
+ /**
19
+ * Prefix for class names
20
+ * @default ''
21
+ */
22
+ classPrefix?: string;
23
+ /**
24
+ * Whether to include SwiftUI preview code
25
+ * @default true
26
+ */
27
+ includePreview?: boolean;
3
28
  }
4
29
  export type SwiftMetadata = {};
@@ -299,15 +299,14 @@ const getBlockBindings = (block, options) => {
299
299
  exports.symbolBlocksAsChildren = false;
300
300
  const componentMappers = {
301
301
  Symbol(block, options) {
302
- var _a, _b;
302
+ var _a;
303
303
  let css = getCssFromBlock(block);
304
304
  const styleString = getStyleStringFromBlock(block, options);
305
305
  const actionBindings = getActionBindingsFromBlock(block, options);
306
306
  const bindings = {
307
307
  symbol: (0, bindings_1.createSingleBinding)({
308
308
  code: JSON.stringify({
309
- data: (_a = block.component) === null || _a === void 0 ? void 0 : _a.options.symbol.data,
310
- content: (_b = block.component) === null || _b === void 0 ? void 0 : _b.options.symbol.content,
309
+ ...(_a = block.component) === null || _a === void 0 ? void 0 : _a.options.symbol,
311
310
  }),
312
311
  }),
313
312
  ...actionBindings,
@@ -284,6 +284,15 @@ const jsxElementToJson = (node) => {
284
284
  bindingType: 'function',
285
285
  });
286
286
  }
287
+ else if (/^on[A-Z]/.test(key) && types.isExpression(expression)) {
288
+ // regex ignores props that happen to start with "on" but are not handlers
289
+ // <Foo onClick={state.handler} />
290
+ const call = types.callExpression(expression, []);
291
+ memo.bindings[key] = (0, bindings_1.createSingleBinding)({
292
+ code: (0, generator_1.default)(call, { compact: true }).code,
293
+ bindingType: 'function',
294
+ });
295
+ }
287
296
  else if (types.isJSXElement(expression) || types.isJSXFragment(expression)) {
288
297
  // <Foo myProp={<MoreMitosisNode><div /></MoreMitosisNode>} />
289
298
  // <Foo myProp={<><Node /><Node /></>} />
package/jsx-runtime.d.ts CHANGED
@@ -393,6 +393,9 @@ export declare namespace JSX {
393
393
  }
394
394
 
395
395
  interface DetailsHtmlAttributes<T> extends HTMLAttributes<T> {
396
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLDetailsElement/name) */
397
+ name?: string;
398
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLDetailsElement/open) */
396
399
  open?: boolean;
397
400
  }
398
401
 
@@ -500,6 +503,8 @@ export declare namespace JSX {
500
503
  // camelcase
501
504
  crossOrigin?: HTMLCrossorigin;
502
505
  formAction?: string;
506
+ autoComplete?: string;
507
+ autoFocus?: boolean;
503
508
  formEnctype?: HTMLFormEncType;
504
509
  formMethod?: HTMLFormMethod;
505
510
  formNoValidate?: boolean;
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "name": "Builder.io",
23
23
  "url": "https://www.builder.io"
24
24
  },
25
- "version": "0.9.2",
25
+ "version": "0.9.4",
26
26
  "homepage": "https://github.com/BuilderIO/mitosis",
27
27
  "main": "./dist/src/index.js",
28
28
  "exports": {