diff --git a/.changeset/little-impalas-turn.md b/.changeset/little-impalas-turn.md new file mode 100644 index 0000000000..669ef601b6 --- /dev/null +++ b/.changeset/little-impalas-turn.md @@ -0,0 +1,5 @@ +--- +'@builder.io/mitosis': patch +--- + +Angular selector support in code generation diff --git a/packages/core/src/__tests__/angular.selector.test.ts b/packages/core/src/__tests__/angular.selector.test.ts new file mode 100644 index 0000000000..00ef480c20 --- /dev/null +++ b/packages/core/src/__tests__/angular.selector.test.ts @@ -0,0 +1,32 @@ +import { parse } from '../generators/angular/parse-selector'; + +describe('Angular selectors', () => { + test('should parse gnarly selectors', () => { + expect(parse('ccc.c1#wat[co].c2[counter="cool"]#wat[x=\'y\'].c3')).toEqual({ + element: 'ccc', + classNames: ['c1', 'c2', 'c3'], + attributes: { + co: '', + counter: 'cool', + id: 'wat', + x: 'y', + }, + }); + }); + + test('parsing multiple returns only the first', () => { + expect(parse('dropzone, [dropzone]')).toEqual({ + element: 'dropzone', + classNames: [], + attributes: {}, + }); + }); + + test(':not parses but is unused', () => { + expect(parse('list-item:not(.foo)')).toEqual({ + element: 'list-item', + classNames: [], + attributes: {}, + }); + }); +}); diff --git a/packages/core/src/generators/angular/index.ts b/packages/core/src/generators/angular/index.ts index ac228ba6ac..c7c35ae389 100644 --- a/packages/core/src/generators/angular/index.ts +++ b/packages/core/src/generators/angular/index.ts @@ -62,6 +62,8 @@ import { ToAngularOptions, } from './types'; +import { parse } from './parse-selector'; + const { types } = babel; const mappers: { @@ -427,10 +429,44 @@ export const blockToAngular = ({ str += ``; } else { - const elSelector = childComponents.find((impName) => impName === json.name) - ? kebabCase(json.name) - : json.name; - str += `<${elSelector} `; + let element, + classNames: string[] = [], + attributes; + + const isComponent = childComponents.find((impName) => impName === json.name); + + if (isComponent) { + const selector = json.meta.selector || blockOptions?.selector; + if (selector) { + try { + ({ element, classNames, attributes } = parse(`${selector}`)); + } catch { + element = kebabCase(json.name); + } + } else { + element = kebabCase(json.name); + } + } else { + element = json.name; + } + + str += `<${element} `; + + // TODO: merge with existing classes/bindings + if (classNames.length) { + str += `class="${classNames.join(' ')}" `; + } + + // TODO: Merge with existing properties + if (attributes) { + Object.entries(attributes).forEach(([key, value]) => { + if (value) { + str += `${key}=${JSON.stringify(value)} `; + } else { + str += `${key} `; + } + }); + } for (const key in json.properties) { if (key.startsWith('$')) { @@ -516,7 +552,7 @@ export const blockToAngular = ({ .join('\n'); } - str += ``; + str += ``; } return str; }; diff --git a/packages/core/src/generators/angular/parse-selector.ts b/packages/core/src/generators/angular/parse-selector.ts new file mode 100644 index 0000000000..5cf99761e2 --- /dev/null +++ b/packages/core/src/generators/angular/parse-selector.ts @@ -0,0 +1,17 @@ +import { CssSelector } from '@angular/compiler'; + +export function parse(selector: string) { + const { element, classNames, attrs } = CssSelector.parse(selector)[0]; + const attributes = attrs.reduce((acc, attr, i) => { + if (i % 2 === 0) { + acc[attr] = attrs[i + 1]; + } + return acc; + }, {} as Record); + + return { + element, + classNames, + attributes, + }; +}