UNPKG

@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
"use strict"; 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;