@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
243 lines (242 loc) • 10.3 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.blockToAngularSignals = void 0;
const html_tags_1 = require("../../../constants/html_tags");
const helpers_1 = require("../../../generators/angular/helpers");
const parse_selector_1 = require("../../../generators/angular/helpers/parse-selector");
const babel_transform_1 = require("../../../helpers/babel-transform");
const event_handlers_1 = require("../../../helpers/event-handlers");
const is_children_1 = __importDefault(require("../../../helpers/is-children"));
const is_mitosis_node_1 = require("../../../helpers/is-mitosis-node");
const slots_1 = require("../../../helpers/slots");
const types_1 = require("@babel/types");
const function_1 = require("fp-ts/function");
const lodash_1 = require("lodash");
const getChildren = (root, json, options, blockOptions) => {
var _a;
return (_a = json.children) === null || _a === void 0 ? void 0 : _a.map((item) => (0, exports.blockToAngularSignals)({ root, json: item, options, blockOptions })).join('\n');
};
const MAPPERS = {
Fragment: (root, json, options, blockOptions) => {
const children = getChildren(root, json, options, blockOptions);
// TODO: Handle `key`?
return `<ng-container>${children}</ng-container>`;
},
Slot: (root, json, options, blockOptions) => {
const children = getChildren(root, json, options, blockOptions);
const namedSlotTransform = Object.entries({ ...json.bindings, ...json.properties })
.map(([binding, value]) => {
if (value && binding === 'name') {
const selector = (0, function_1.pipe)((0, lodash_1.isString)(value) ? value : value.code, slots_1.stripSlotPrefix, lodash_1.kebabCase);
return `select="[${selector}]"`;
}
})
.join('\n');
return `<ng-content ${namedSlotTransform}>
${children}
</ng-content>`;
},
For: (root, json, options, blockOptions) => {
var _a, _b, _c;
const forNode = json;
const indexName = forNode.scope.indexName;
const forName = forNode.scope.forName;
let trackByFnName;
// Check if "key" is present for the first child of the for loop
if ((0, helpers_1.hasFirstChildKeyAttribute)(forNode)) {
const fnIndex = ((_a = root.meta) === null || _a === void 0 ? void 0 : _a._trackByForIndex) || 0;
trackByFnName = `trackBy${forName ? forName.charAt(0).toUpperCase() + forName.slice(1) : ''}${fnIndex}`;
root.meta._trackByForIndex = fnIndex + 1;
let code = (_b = forNode.children[0].bindings.key) === null || _b === void 0 ? void 0 : _b.code;
root.state[trackByFnName] = {
code: `${trackByFnName}(${indexName !== null && indexName !== void 0 ? indexName : '_'}: number, ${forName}: any) { return ${code}; }`,
type: 'method',
};
}
const children = getChildren(root, json, options, blockOptions);
const item = forName !== null && forName !== void 0 ? forName : '_';
const of = (_c = forNode.bindings.each) === null || _c === void 0 ? void 0 : _c.code;
const track = `track ${trackByFnName ? trackByFnName : indexName ? indexName : 'i'};`;
const index = indexName ? `let ${indexName} = $index` : 'let i = $index';
return `
@for (${item} of ${of};${track}${index}) {
${children}
}
`;
},
Show: (root, json, options, blockOptions) => {
var _a, _b;
let condition = (_a = json.bindings.when) === null || _a === void 0 ? void 0 : _a.code;
const children = getChildren(root, json, options, blockOptions);
let elseBlock = '';
// else condition
if ((0, is_mitosis_node_1.isMitosisNode)((_b = json.meta) === null || _b === void 0 ? void 0 : _b.else)) {
elseBlock = `@else{
${(0, exports.blockToAngularSignals)({ root, json: json.meta.else, options, blockOptions })}
}`;
}
if (condition === null || condition === void 0 ? void 0 : condition.includes('children()')) {
console.error(`
${json.name}: You can't use children() in a Show block for \`when\` targeting angular.
Try to invert it like this:
"<Show when={props.label} else={props.children}>{props.label}</Show>"
`);
}
return `@if(${condition}){
${children}
}${elseBlock}`;
},
};
// TODO: Maybe in the future allow defining `string | function` as values
const BINDINGS_MAPPER = {
innerHTML: 'innerHTML',
style: 'ngStyle',
};
const stringifyBinding = (node, blockOptions) => ([key, binding]) => {
var _a, _b;
if (key.startsWith('$') || key.startsWith('"') || key === 'key') {
return;
}
if ((binding === null || binding === void 0 ? void 0 : binding.type) === 'spread') {
return;
}
const keyToUse = BINDINGS_MAPPER[key] || key;
if (!binding)
return '';
const { code } = binding;
if ((0, event_handlers_1.checkIsEvent)(keyToUse)) {
const args = binding.arguments || [];
const event = (0, event_handlers_1.getEventNameWithoutOn)(keyToUse);
if (code.includes('event.target.')) {
console.error(`
Component ${node.name} has an event ${event} that uses 'event.target.xxx'.
This will cause an error in Angular.
Please create a new function with the EventTarget and use e.g:
'(event.target as HTMLInputElement).value'`);
}
const value = (0, babel_transform_1.babelTransformExpression)(code, {
Identifier(path) {
// Only change arguments inside a call expression or event
if (((0, types_1.isCallExpression)(path.parent) && args.includes(path.node.name)) ||
path.node.name === 'event') {
path.node.name = '$event';
}
},
});
// native events are all lowerCased
const lowerCaseEvent = event.toLowerCase();
const eventKey = (0, event_handlers_1.checkIsBindingNativeEvent)(event) ||
((_a = blockOptions.nativeEvents) === null || _a === void 0 ? void 0 : _a.find((nativeEvent) => nativeEvent === keyToUse || nativeEvent === event || nativeEvent === lowerCaseEvent))
? lowerCaseEvent
: event;
return ` (${eventKey})="${value}"`;
}
else if (keyToUse === 'class') {
return ` [class]="${code}" `;
}
else if (keyToUse === 'ref' || keyToUse === 'spreadRef') {
return ` #${code} `;
}
else if ((html_tags_1.VALID_HTML_TAGS.includes(node.name.trim()) || keyToUse.includes('-')) &&
!((_b = blockOptions.nativeAttributes) === null || _b === void 0 ? void 0 : _b.includes(keyToUse)) &&
!Object.values(BINDINGS_MAPPER).includes(keyToUse)) {
// standard html elements need the attr to satisfy the compiler in many cases: eg: svg elements and [fill]
return ` [attr.${keyToUse}]="${code}" `;
}
else if (keyToUse === 'innerHTML') {
return blockOptions.sanitizeInnerHTML
? ` [innerHTML]="${code}" `
: ` [innerHTML]="sanitizer.bypassSecurityTrustHtml(${code})" `;
}
else {
return `[${keyToUse}]="${code}"`;
}
};
const getElementTag = (json, blockOptions) => {
const childComponents = (blockOptions === null || blockOptions === void 0 ? void 0 : blockOptions.childComponents) || [];
let element, classNames = [], attributes;
const isComponent = childComponents.find((impName) => impName === json.name);
if (isComponent) {
const selector = json.meta.selector;
if (selector) {
try {
({ element, classNames, attributes } = (0, parse_selector_1.parseSelector)(`${selector}`));
}
catch (_a) {
element = (0, lodash_1.kebabCase)(json.name);
}
}
else {
element = (0, lodash_1.kebabCase)(json.name);
}
}
else {
element = json.name;
}
let additionalString = '';
// TODO: merge with existing classes/bindings
if (classNames.length) {
additionalString += `class="${classNames.join(' ')}" `;
}
// TODO: Merge with existing properties
if (attributes) {
Object.entries(attributes).forEach(([key, value]) => {
if (value) {
additionalString += `${key}=${JSON.stringify(value)} `;
}
else {
additionalString += `${key} `;
}
});
}
return { element, additionalString };
};
const blockToAngularSignals = ({ root, json, options = {}, blockOptions = {
nativeAttributes: [],
nativeEvents: [],
}, rootRef, }) => {
var _a;
if (MAPPERS[json.name]) {
return MAPPERS[json.name](root, json, options, blockOptions);
}
if ((0, is_children_1.default)({ node: json })) {
return `<ng-content></ng-content>`;
}
if (json.properties._text) {
return json.properties._text;
}
const textCode = (_a = json.bindings._text) === null || _a === void 0 ? void 0 : _a.code;
if (textCode) {
return `{{${textCode}}}`;
}
let str = '';
const { element, additionalString } = getElementTag(json, blockOptions);
str += `<${element} ${additionalString}`;
for (const key in json.properties) {
const value = json.properties[key];
str += ` ${key}="${value}" `;
}
const stringifiedBindings = Object.entries(json.bindings)
.map(stringifyBinding(json, blockOptions))
.join('');
str += stringifiedBindings;
if (rootRef && !str.includes(`#${rootRef}`)) {
// Add ref for passing attributes
str += `#${rootRef}`;
}
if (html_tags_1.SELF_CLOSING_HTML_TAGS.has(json.name)) {
return str + ' />';
}
str += '>';
if (json.children) {
str += json.children
.map((item) => (0, exports.blockToAngularSignals)({ root, json: item, options, blockOptions }))
.join('\n');
}
str += `</${element}>`;
return str;
};
exports.blockToAngularSignals = blockToAngularSignals;
;