@builder.io/mitosis 0.12.0 → 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.
@@ -1 +1 @@
1
- export declare const tryFormat: (str: string, parser: string) => string;
1
+ export declare const tryFormat: (str: string, parser: string, htmlWhitespaceSensitivity?: 'css' | 'strict' | 'ignore') => string;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.tryFormat = void 0;
4
4
  const standalone_1 = require("prettier/standalone");
5
- const tryFormat = (str, parser) => {
5
+ const tryFormat = (str, parser, htmlWhitespaceSensitivity = 'css') => {
6
6
  try {
7
7
  return (0, standalone_1.format)(str, {
8
8
  parser,
@@ -13,7 +13,7 @@ const tryFormat = (str, parser) => {
13
13
  require('prettier/parser-html'),
14
14
  require('prettier/parser-babel'),
15
15
  ],
16
- htmlWhitespaceSensitivity: 'ignore',
16
+ htmlWhitespaceSensitivity,
17
17
  });
18
18
  }
19
19
  catch (err) {
@@ -6,13 +6,13 @@ const format_1 = require("../../../generators/angular/helpers/format");
6
6
  const get_outputs_1 = require("../../../generators/angular/helpers/get-outputs");
7
7
  const get_refs_1 = require("../../../generators/angular/helpers/get-refs");
8
8
  const get_styles_1 = require("../../../generators/angular/helpers/get-styles");
9
+ const hooks_1 = require("../../../generators/angular/helpers/hooks");
9
10
  const blocks_1 = require("../../../generators/angular/signals/blocks");
10
11
  const helpers_2 = require("../../../generators/angular/signals/helpers");
11
12
  const get_computed_1 = require("../../../generators/angular/signals/helpers/get-computed");
12
13
  const get_inputs_1 = require("../../../generators/angular/signals/helpers/get-inputs");
13
14
  const get_code_processor_plugins_1 = require("../../../generators/angular/signals/plugins/get-code-processor-plugins");
14
15
  const types_1 = require("../../../generators/angular/types");
15
- const on_mount_1 = require("../../../generators/helpers/on-mount");
16
16
  const dedent_1 = require("../../../helpers/dedent");
17
17
  const event_handlers_1 = require("../../../helpers/event-handlers");
18
18
  const fast_clone_1 = require("../../../helpers/fast-clone");
@@ -97,15 +97,6 @@ const componentToAngularSignals = (userOptions = {}) => {
97
97
  if (shouldUseSanitizer) {
98
98
  injectables.push('protected sanitizer: DomSanitizer');
99
99
  }
100
- if (json.hooks.onMount.length > 0) {
101
- json.compileContext.angular.hooks.ngAfterViewInit = {
102
- code: `
103
- if (typeof window !== 'undefined') {
104
- ${(0, on_mount_1.stringifySingleScopeOnMount)(json)}
105
- }
106
- `,
107
- };
108
- }
109
100
  // HTML
110
101
  let template = json.children
111
102
  .map((item) => {
@@ -125,7 +116,8 @@ const componentToAngularSignals = (userOptions = {}) => {
125
116
  })
126
117
  .join('\n');
127
118
  if (options.prettier !== false) {
128
- template = (0, format_1.tryFormat)(template, 'html');
119
+ // We need to use 'strict' mode for Angular otherwise it could add spaces around some content
120
+ template = (0, format_1.tryFormat)(template, 'html', 'strict');
129
121
  }
130
122
  const { components: dynamicComponents, dynamicTemplate } = (0, helpers_1.traverseToGetAllDynamicComponents)(json, options, {
131
123
  childComponents,
@@ -215,6 +207,10 @@ Please add a initial value for every state property even if it's \`undefined\`.`
215
207
  domRefs.size !== 0 ||
216
208
  ((_y = (_x = (_w = (_v = json.compileContext) === null || _v === void 0 ? void 0 : _v.angular) === null || _w === void 0 ? void 0 : _w.extra) === null || _x === void 0 ? void 0 : _x.spreadRefs) === null || _y === void 0 ? void 0 : _y.length) > 0;
217
209
  // Imports
210
+ const emptyOnMount = (0, is_hook_empty_1.isHookEmpty)(json.hooks.onMount);
211
+ const emptyOnUnMount = (0, is_hook_empty_1.isHookEmpty)(json.hooks.onUnMount);
212
+ const AfterViewInit = Boolean(!emptyOnMount || withAttributePassing);
213
+ const OnDestroy = !emptyOnUnMount;
218
214
  const coreImports = (0, helpers_2.getAngularCoreImportsAsString)({
219
215
  refs: domRefs.size !== 0,
220
216
  input: props.length !== 0,
@@ -224,11 +220,25 @@ Please add a initial value for every state property even if it's \`undefined\`.`
224
220
  signal: dataString.length !== 0 || hasDynamicComponents,
225
221
  computed: gettersString.length !== 0,
226
222
  onPush,
223
+ AfterViewInit,
224
+ OnDestroy,
227
225
  viewChild: importsViewChild,
228
226
  viewContainerRef: hasDynamicComponents,
229
227
  templateRef: hasDynamicComponents,
230
228
  renderer: usesRenderer2,
231
229
  });
230
+ // Hooks
231
+ if (!emptyOnMount) {
232
+ (0, hooks_1.addCodeNgAfterViewInit)(json, json.hooks.onMount.map((onMount) => onMount.code).join('\n'));
233
+ }
234
+ // Angular interfaces
235
+ const angularInterfaces = [];
236
+ if (AfterViewInit) {
237
+ angularInterfaces.push('AfterViewInit');
238
+ }
239
+ if (OnDestroy) {
240
+ angularInterfaces.push('OnDestroy');
241
+ }
232
242
  let str = (0, dedent_1.dedent) `
233
243
  import { ${coreImports} } from '@angular/core';
234
244
  import { CommonModule } from '@angular/common';
@@ -260,7 +270,7 @@ Please add a initial value for every state property even if it's \`undefined\`.`
260
270
  .map(([k, v]) => `${k}: ${v}`)
261
271
  .join(',')}
262
272
  })
263
- export ${options.defaultExportComponents ? 'default ' : ''}class ${json.name} {
273
+ export ${options.defaultExportComponents ? 'default ' : ''}class ${json.name} ${angularInterfaces.length ? ` implements ${angularInterfaces.join(',')}` : ''} {
264
274
  ${(0, lodash_1.uniq)(json.compileContext.angular.extra.importCalls)
265
275
  .map((importCall) => `protected readonly ${importCall} = ${importCall};`)
266
276
  .join('\n')}
@@ -336,17 +346,19 @@ Please add a initial value for every state property even if it's \`undefined\`.`
336
346
  .filter(([_, value]) => !(0, is_hook_empty_1.isHookEmpty)(value))
337
347
  .map(([key, value]) => {
338
348
  return `${key}() {
349
+ if (typeof window !== 'undefined') {
339
350
  ${value.code}
351
+ }
340
352
  }`;
341
353
  })
342
354
  .join('\n')
343
355
  : ''}
344
356
 
345
- ${json.hooks.onUnMount
346
- ? `ngOnDestroy() {
357
+ ${emptyOnUnMount
358
+ ? ''
359
+ : `ngOnDestroy() {
347
360
  ${((_10 = json.hooks.onUnMount) === null || _10 === void 0 ? void 0 : _10.code) || ''}
348
- }`
349
- : ''}
361
+ }`}
350
362
 
351
363
  }
352
364
  `;
@@ -1,9 +1,11 @@
1
- export declare const getAngularCoreImportsAsString: ({ refs, output, input, model, onPush, effect, signal, computed, viewChild, viewContainerRef, templateRef, renderer, }: {
1
+ export declare const getAngularCoreImportsAsString: ({ refs, output, input, model, onPush, effect, signal, AfterViewInit, OnDestroy, computed, viewChild, viewContainerRef, templateRef, renderer, }: {
2
2
  refs: boolean;
3
3
  output: boolean;
4
4
  input: boolean;
5
5
  model: boolean;
6
6
  onPush: boolean;
7
+ AfterViewInit: boolean;
8
+ OnDestroy: boolean;
7
9
  effect: boolean;
8
10
  signal: boolean;
9
11
  computed: boolean;
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAngularCoreImportsAsString = void 0;
4
- const getAngularCoreImportsAsString = ({ refs, output, input, model, onPush, effect, signal, computed, viewChild, viewContainerRef, templateRef, renderer, }) => {
4
+ const getAngularCoreImportsAsString = ({ refs, output, input, model, onPush, effect, signal, AfterViewInit, OnDestroy, computed, viewChild, viewContainerRef, templateRef, renderer, }) => {
5
5
  const angularCoreImports = {
6
6
  Component: true,
7
7
  viewChild: refs || viewChild,
8
8
  ElementRef: refs || viewChild,
9
9
  ViewContainerRef: viewContainerRef,
10
10
  TemplateRef: templateRef,
11
- Renderer2: !!renderer,
11
+ Renderer2: renderer,
12
12
  model,
13
13
  output,
14
14
  input,
@@ -16,6 +16,8 @@ const getAngularCoreImportsAsString = ({ refs, output, input, model, onPush, eff
16
16
  signal,
17
17
  computed,
18
18
  ChangeDetectionStrategy: onPush,
19
+ OnDestroy,
20
+ AfterViewInit,
19
21
  InputSignal: input,
20
22
  ModelSignal: model,
21
23
  };
@@ -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.0",
25
+ "version": "0.13.0",
26
26
  "homepage": "https://github.com/BuilderIO/mitosis",
27
27
  "main": "./dist/src/index.js",
28
28
  "exports": {