@builder.io/mitosis 0.12.1 → 0.13.0

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.
@@ -565,6 +565,59 @@ const blockToBuilder = (json, options = {}, _internalOptions = {}) => {
565
565
  const element = mapper(json, options);
566
566
  return processLocalizedValues(element, json);
567
567
  }
568
+ const symbolBinding = json.bindings.symbol;
569
+ if (symbolBinding === null || symbolBinding === void 0 ? void 0 : symbolBinding.code) {
570
+ const isExplicitUserSymbol = json.type === 'user-symbol';
571
+ let hasValidSymbolMetadata = false;
572
+ if (!isExplicitUserSymbol) {
573
+ const parsed = (0, lodash_1.attempt)(() => json5_1.default.parse(symbolBinding.code));
574
+ hasValidSymbolMetadata =
575
+ !(parsed instanceof Error) &&
576
+ parsed &&
577
+ typeof parsed === 'object' &&
578
+ (parsed.entry || parsed.model === 'symbol');
579
+ }
580
+ if (isExplicitUserSymbol || hasValidSymbolMetadata) {
581
+ const symbolOptions = (0, lodash_1.attempt)(() => json5_1.default.parse(symbolBinding.code));
582
+ if (!(symbolOptions instanceof Error)) {
583
+ if (!symbolOptions.name) {
584
+ const displayName = json.name
585
+ .replace(/^Symbol/, '')
586
+ .replace(/([A-Z])/g, ' $1')
587
+ .trim();
588
+ if (displayName) {
589
+ symbolOptions.name = displayName;
590
+ }
591
+ }
592
+ const inputData = {};
593
+ for (const key of Object.keys(json.bindings)) {
594
+ if (key !== 'symbol' && key !== 'css' && key !== 'style') {
595
+ const value = (0, lodash_1.attempt)(() => json5_1.default.parse(json.bindings[key].code));
596
+ if (!(value instanceof Error)) {
597
+ inputData[key] = value;
598
+ }
599
+ }
600
+ }
601
+ for (const key of Object.keys(json.properties)) {
602
+ if (!key.startsWith('$') && !key.startsWith('_') && !key.startsWith('data-')) {
603
+ inputData[key] = json.properties[key];
604
+ }
605
+ }
606
+ if (Object.keys(inputData).length > 0) {
607
+ symbolOptions.data = { ...symbolOptions.data, ...inputData };
608
+ }
609
+ const element = el({
610
+ component: {
611
+ name: 'Symbol',
612
+ options: {
613
+ symbol: symbolOptions,
614
+ },
615
+ },
616
+ }, options);
617
+ return processLocalizedValues(element, json);
618
+ }
619
+ }
620
+ }
568
621
  if (json.properties._text || ((_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code)) {
569
622
  const element = el({
570
623
  tagName: 'span',
@@ -233,6 +233,32 @@ const wrapBindingIfNeeded = (value, options) => {
233
233
  }
234
234
  return value;
235
235
  };
236
+ /**
237
+ * Sanitizes a symbol name to be a valid JSX component name.
238
+ * - Converts to PascalCase
239
+ * - Removes invalid characters
240
+ * - Adds "Symbol" prefix to avoid collisions
241
+ * - Returns "Symbol" if no valid name can be generated
242
+ */
243
+ const sanitizeSymbolName = (name) => {
244
+ if (!name || typeof name !== 'string') {
245
+ return 'Symbol';
246
+ }
247
+ // Remove special characters and split into words
248
+ const words = name
249
+ .replace(/[^a-zA-Z0-9\s]/g, ' ')
250
+ .split(/\s+/)
251
+ .filter(Boolean);
252
+ if (words.length === 0) {
253
+ return 'Symbol';
254
+ }
255
+ // Convert to PascalCase
256
+ const pascalCase = words
257
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
258
+ .join('');
259
+ // Add Symbol prefix to avoid collisions with other components
260
+ return `Symbol${pascalCase}`;
261
+ };
236
262
  const getBlockActions = (block, options) => {
237
263
  var _a;
238
264
  const obj = {
@@ -308,16 +334,46 @@ const getBlockBindings = (block, options) => {
308
334
  exports.symbolBlocksAsChildren = false;
309
335
  const componentMappers = {
310
336
  Symbol(block, options) {
311
- var _a;
337
+ var _a, _b, _c, _d, _e;
312
338
  let css = getCssFromBlock(block);
313
339
  const styleString = (0, exports.getStyleStringFromBlock)(block, options);
314
340
  const actionBindings = getActionBindingsFromBlock(block, options);
341
+ const symbolOptions = (_b = (_a = block.component) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.symbol;
342
+ const symbolName = ((_c = symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.content) === null || _c === void 0 ? void 0 : _c.name) || (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.name);
343
+ const componentName = ((_d = block.component) === null || _d === void 0 ? void 0 : _d.name) !== 'Symbol'
344
+ ? (_e = block.component) === null || _e === void 0 ? void 0 : _e.name // Use name already set by extractSymbols
345
+ : sanitizeSymbolName(symbolName);
346
+ // Extract inputs from symbol.data to make them visible as top-level JSX props
347
+ //
348
+ // In Builder.io, Symbol components can receive inputs through symbol.options.data,
349
+ // which contains key-value pairs for props passed to the symbol instance.
350
+ //
351
+ // We extract these from the nested data structure and create individual bindings
352
+ // for each input so they become first-class props in the generated code
353
+ // (e.g., <MySymbol title="Hello" count={5} />) instead of being buried in metadata
354
+ // (e.g., <MySymbol symbol={{ data: { title: "Hello", count: 5 } }} />).
355
+ //
356
+ // This transformation enables proper prop passing and makes the component usage
357
+ // more idiomatic in the target framework.
358
+ const symbolData = (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.data) || {};
359
+ const inputBindings = {};
360
+ const hasInputs = Object.keys(symbolData).length > 0;
361
+ // Only extract inputs if there are any to avoid data loss
362
+ if (hasInputs) {
363
+ // Create individual bindings for each input
364
+ for (const key in symbolData) {
365
+ inputBindings[key] = (0, bindings_1.createSingleBinding)({
366
+ code: json5_1.default.stringify(symbolData[key]),
367
+ });
368
+ }
369
+ }
370
+ // Keep symbol metadata - only omit data if we extracted inputs
371
+ const symbolMetadata = hasInputs ? (0, lodash_1.omit)(symbolOptions, 'data') : symbolOptions;
315
372
  const bindings = {
316
373
  symbol: (0, bindings_1.createSingleBinding)({
317
- code: JSON.stringify({
318
- ...(_a = block.component) === null || _a === void 0 ? void 0 : _a.options.symbol,
319
- }),
374
+ code: JSON.stringify(symbolMetadata),
320
375
  }),
376
+ ...inputBindings,
321
377
  ...actionBindings,
322
378
  ...(styleString && {
323
379
  style: (0, bindings_1.createSingleBinding)({ code: styleString }),
@@ -327,7 +383,8 @@ const componentMappers = {
327
383
  }),
328
384
  };
329
385
  return (0, create_mitosis_node_1.createMitosisNode)({
330
- name: 'Symbol',
386
+ name: componentName,
387
+ type: 'user-symbol',
331
388
  bindings: bindings,
332
389
  meta: (0, exports.getMetaFromBlock)(block, options),
333
390
  });
@@ -484,7 +541,7 @@ const processBoundLogic = (code) => {
484
541
  return code;
485
542
  };
486
543
  const builderElementToMitosisNode = (block, options, _internalOptions = {}) => {
487
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
544
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
488
545
  const { includeSpecialBindings = true } = options;
489
546
  const localizedValues = {};
490
547
  if (((_a = block.component) === null || _a === void 0 ? void 0 : _a.name) === 'Core:Fragment') {
@@ -568,7 +625,14 @@ const builderElementToMitosisNode = (block, options, _internalOptions = {}) => {
568
625
  });
569
626
  }
570
627
  }
571
- const mapper = !_internalOptions.skipMapper && block.component && componentMappers[block.component.name];
628
+ const mapper = !_internalOptions.skipMapper &&
629
+ block.component &&
630
+ (componentMappers[block.component.name] ||
631
+ // Handle symbols that were renamed by extractSymbols (e.g., "SymbolButtonComponent")
632
+ // Check for symbol options or exact 'Symbol' name instead of name pattern
633
+ (((_p = (_o = block.component) === null || _o === void 0 ? void 0 : _o.options) === null || _p === void 0 ? void 0 : _p.symbol) || ((_q = block.component) === null || _q === void 0 ? void 0 : _q.name) === 'Symbol'
634
+ ? componentMappers['Symbol']
635
+ : undefined));
572
636
  if (mapper) {
573
637
  return mapper(block, options);
574
638
  }
@@ -641,7 +705,7 @@ const builderElementToMitosisNode = (block, options, _internalOptions = {}) => {
641
705
  properties.href = linkUrl;
642
706
  }
643
707
  }
644
- if ((_o = block.component) === null || _o === void 0 ? void 0 : _o.options) {
708
+ if ((_r = block.component) === null || _r === void 0 ? void 0 : _r.options) {
645
709
  for (const key in block.component.options) {
646
710
  const value = block.component.options[key];
647
711
  const valueIsArrayOfBuilderElements = Array.isArray(value) && value.every(exports.isBuilderElement);
@@ -677,7 +741,7 @@ const builderElementToMitosisNode = (block, options, _internalOptions = {}) => {
677
741
  slots[key] = childrenElements;
678
742
  }
679
743
  else if (options.enableBlocksSlots &&
680
- !componentMappers[(_p = block.component) === null || _p === void 0 ? void 0 : _p.name] &&
744
+ !componentMappers[(_s = block.component) === null || _s === void 0 ? void 0 : _s.name] &&
681
745
  (Array.isArray(value) || (typeof value === 'object' && value !== null))) {
682
746
  /**
683
747
  * Builder Elements that have their own mappers should not use blocksSlots
@@ -728,13 +792,13 @@ const builderElementToMitosisNode = (block, options, _internalOptions = {}) => {
728
792
  if (block.groupLocked !== undefined) {
729
793
  dataAttributes['data-builder-groupLocked'] = String(block.groupLocked);
730
794
  }
731
- if (((_q = block.component) === null || _q === void 0 ? void 0 : _q.name) &&
732
- /:/.test((_r = block.component) === null || _r === void 0 ? void 0 : _r.name) &&
733
- !generator_1.builderBlockPrefixes.includes((_s = block.component) === null || _s === void 0 ? void 0 : _s.name.split(':')[0])) {
734
- dataAttributes['data-builder-originalName'] = (_t = block.component) === null || _t === void 0 ? void 0 : _t.name;
795
+ if (((_t = block.component) === null || _t === void 0 ? void 0 : _t.name) &&
796
+ /:/.test((_u = block.component) === null || _u === void 0 ? void 0 : _u.name) &&
797
+ !generator_1.builderBlockPrefixes.includes((_v = block.component) === null || _v === void 0 ? void 0 : _v.name.split(':')[0])) {
798
+ dataAttributes['data-builder-originalName'] = (_w = block.component) === null || _w === void 0 ? void 0 : _w.name;
735
799
  }
736
800
  const node = (0, create_mitosis_node_1.createMitosisNode)({
737
- name: ((_v = (_u = block.component) === null || _u === void 0 ? void 0 : _u.name) === null || _v === void 0 ? void 0 : _v.replace(/[^a-z0-9]/gi, '')) ||
801
+ name: ((_y = (_x = block.component) === null || _x === void 0 ? void 0 : _x.name) === null || _y === void 0 ? void 0 : _y.replace(/[^a-z0-9]/gi, '')) ||
738
802
  block.tagName ||
739
803
  (block.linkUrl ? 'a' : 'div'),
740
804
  properties: {
@@ -954,7 +1018,8 @@ function extractSymbols(json) {
954
1018
  }
955
1019
  continue;
956
1020
  }
957
- const componentName = 'Symbol' + ++symbolsFound;
1021
+ const symbolName = elContent.name || (symbolValue === null || symbolValue === void 0 ? void 0 : symbolValue.name);
1022
+ const componentName = sanitizeSymbolName(symbolName);
958
1023
  el.component.name = componentName;
959
1024
  if ((_d = el.component) === null || _d === void 0 ? void 0 : _d.options.symbol.content) {
960
1025
  delete el.component.options.symbol.content;
@@ -7,6 +7,7 @@ export declare function parseText(node: TemplateNode): {
7
7
  '@type': "@builder.io/mitosis/node";
8
8
  meta: import("../../../types/json").JSONObject;
9
9
  scope: {};
10
+ type?: "user-symbol" | undefined;
10
11
  bindings: {
11
12
  [key: string]: import("../../..").Binding | undefined;
12
13
  };
@@ -30,6 +31,7 @@ export declare function parseText(node: TemplateNode): {
30
31
  indexName: string | undefined;
31
32
  collectionName: string | undefined;
32
33
  };
34
+ type?: "user-symbol" | undefined;
33
35
  bindings: {
34
36
  [key: string]: import("../../..").Binding | undefined;
35
37
  };
@@ -49,6 +51,7 @@ export declare function parseText(node: TemplateNode): {
49
51
  '@type': "@builder.io/mitosis/node";
50
52
  meta: import("../../../types/json").JSONObject;
51
53
  scope: {};
54
+ type?: "user-symbol" | undefined;
52
55
  bindings: {
53
56
  [key: string]: import("../../..").Binding | undefined;
54
57
  };
@@ -38,6 +38,11 @@ export type BaseNode = {
38
38
  meta: JSONObject;
39
39
  name: string;
40
40
  scope: {};
41
+ /**
42
+ * Optional type identifier for special node types (e.g., 'user-symbol' for Builder.io user symbols).
43
+ * Used to explicitly identify node types without relying on name patterns.
44
+ */
45
+ type?: 'user-symbol';
41
46
  /**
42
47
  * Key-value store of string values for DOM attributes.
43
48
  * ```js
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.12.1",
25
+ "version": "0.13.0",
26
26
  "homepage": "https://github.com/BuilderIO/mitosis",
27
27
  "main": "./dist/src/index.js",
28
28
  "exports": {