Skip to content

Commit

Permalink
Merge branch 'main' into feat-complex-metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
nmerget authored Jan 14, 2025
2 parents 7e9d8fb + 20ad8dc commit a0c9735
Show file tree
Hide file tree
Showing 28 changed files with 314 additions and 100 deletions.
31 changes: 31 additions & 0 deletions .changeset/polite-pugs-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@builder.io/mitosis': patch
---

[angular]: Fix issue with events forced to become `toLowerCase()`.

Based on [choosing-event-names](https://angular.dev/guide/components/outputs#choosing-event-names) custom events are camelCase.
[DOM events](https://www.w3schools.com/jsref/dom_obj_event.asp) are always lower-cased for Angular components.

Checkout [event-handlers.ts](https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/helpers/event-handlers.ts) for a list of all events that are automatically lower-cased. Everything else will be treated as a custom event and therefore camelCased.

If you need some other event to be lower-cased you can use `useMetadata.angular.nativeEvents`:

```tsx
import { useMetadata } from '@builder.io/mitosis';

useMetadata({
angular: {
nativeEvents: ['onNativeEvent'],
},
});

export default function MyComponent(props) {
return (
<div>
<input onNativeEvent={(event) => console.log(event)} />
Hello!
</div>
);
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -5345,7 +5345,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -13218,7 +13220,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5432,7 +5432,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -13435,7 +13437,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5545,7 +5545,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -13695,7 +13697,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4792,7 +4792,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -11845,7 +11847,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down
16 changes: 12 additions & 4 deletions packages/core/src/__tests__/__snapshots__/angular.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10060,7 +10060,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -10098,7 +10100,9 @@ import { CommonModule } from \\"@angular/common\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -24878,7 +24882,9 @@ import { Component } from \\"@angular/core\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down Expand Up @@ -24916,7 +24922,9 @@ import { CommonModule } from \\"@angular/common\\";
<input
[attr.value]=\\"name\\"
(change)=\\"name = $event.target.value\\"
(changeorsomething)=\\"name = $event.target.value\\"
(changeOrSomething)=\\"name = $event.target.value\\"
(fakenative)=\\"name = $event.target.value\\"
(animationend)=\\"name = $event.target.value\\"
/>

Hello! I can run in React, Vue, Solid, or Liquid!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { useState } from '@builder.io/mitosis';
import { useMetadata, useState } from '@builder.io/mitosis';

useMetadata({
angular: {
nativeEvents: ['onFakeNative'],
},
});

export default function MyComponent(props) {
const [name, setName] = useState('Steve');
Expand All @@ -9,6 +15,8 @@ export default function MyComponent(props) {
value={name}
onChange={(event) => setName(event.target.value)}
onChangeOrSomething={(event) => setName(event.target.value)}
onFakeNative={(event) => setName(event.target.value)}
onAnimationEnd={(event) => setName(event.target.value)}
/>
Hello! I can run in React, Vue, Solid, or Liquid!
</div>
Expand Down
17 changes: 9 additions & 8 deletions packages/core/src/generators/alpine/generate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { SELF_CLOSING_HTML_TAGS } from '@/constants/html_tags';
import { ToAlpineOptions } from '@/generators/alpine/types';
import { babelTransformCode } from '@/helpers/babel-transform';
import { dashCase } from '@/helpers/dash-case';
import { checkIsEvent } from '@/helpers/event-handlers';
import { fastClone } from '@/helpers/fast-clone';
import { getRefs } from '@/helpers/get-refs';
import { getStateObjectStringFromComponent } from '@/helpers/get-state-object-string';
Expand All @@ -10,18 +12,17 @@ import { replaceIdentifiers } from '@/helpers/replace-identifiers';
import { stripMetaProperties } from '@/helpers/strip-meta-properties';
import { stripStateAndPropsRefs } from '@/helpers/strip-state-and-props-refs';
import { collectCss } from '@/helpers/styles/collect-css';
import { MitosisComponent } from '@/types/mitosis-component';
import { checkIsForNode, ForNode, MitosisNode } from '@/types/mitosis-node';
import { TranspilerGenerator } from '@/types/transpiler';
import { camelCase, flowRight as compose, curry, flow } from 'lodash';
import { format } from 'prettier/standalone';
import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
import {
runPostCodePlugins,
runPostJsonPlugins,
runPreCodePlugins,
runPreJsonPlugins,
} from '../../modules/plugins';
} from '@/modules/plugins';
import { MitosisComponent } from '@/types/mitosis-component';
import { ForNode, MitosisNode, checkIsForNode } from '@/types/mitosis-node';
import { TranspilerGenerator } from '@/types/transpiler';
import { camelCase, flowRight as compose, curry, flow } from 'lodash';
import { format } from 'prettier/standalone';
import { renderMountHook } from './render-mount-hook';
import { hasRootUpdateHook, renderUpdateHooks } from './render-update-hooks';

Expand Down Expand Up @@ -167,7 +168,7 @@ const blockToAlpine = (json: MitosisNode | ForNode, options: ToAlpineOptions = {
// TODO: proper babel transform to replace. Util for this
const useValue = stripStateAndPropsRefs(value);

if (key.startsWith('on')) {
if (checkIsEvent(key)) {
str += bindEventHandler(options)(key, value);
} else if (key === 'ref') {
str += ` x-ref="${useValue}"`;
Expand Down
28 changes: 21 additions & 7 deletions packages/core/src/generators/angular/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
ToAngularOptions,
} from './types';

import { checkIsBindingNativeEvent, checkIsEvent } from '@/helpers/event-handlers';
import { parse } from './parse-selector';

const { types } = babel;
Expand Down Expand Up @@ -226,10 +227,20 @@ const stringifyBinding =
const { code, arguments: cusArgs = ['event'] } = binding!;
// TODO: proper babel transform to replace. Util for this

if (keyToUse.startsWith('on')) {
if (checkIsEvent(keyToUse)) {
const { event, value } = processEventBinding(keyToUse, code, node.name, cusArgs[0]);
// Angular events are all lowerCased
return ` (${event.toLowerCase()})="${value}"`;

// native events are all lowerCased
const lowerCaseEvent = event.toLowerCase();
const eventKey =
checkIsBindingNativeEvent(event) ||
blockOptions.nativeEvents?.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') {
Expand Down Expand Up @@ -281,7 +292,7 @@ const handleNgOutletBindings = (node: MitosisNode, options: ToAngularOptions) =>
let keyToUse = key.includes('-') ? `'${key}'` : key;
keyToUse = keyToUse.replace('state.', '').replace('props.', '');

if (key.startsWith('on')) {
if (checkIsEvent(key)) {
const { event, value } = processEventBinding(key, code, node.name, cusArgs[0]);
allProps += `on${event.charAt(0).toUpperCase() + event.slice(1)}: ${value.replace(
/\(.*?\)/g,
Expand Down Expand Up @@ -311,6 +322,7 @@ export const blockToAngular = ({
options = {},
blockOptions = {
nativeAttributes: [],
nativeEvents: [],
},
}: {
root: MitosisComponent;
Expand Down Expand Up @@ -659,7 +671,7 @@ const handleBindings = (
if (item.name === 'For') continue;
if (key === 'key') continue;

if (key.startsWith('on')) {
if (checkIsEvent(key)) {
const { arguments: cusArgs = ['event'] } = item.bindings[key]!;
const eventBindingName = `${generateNewBindingName(index, item.name)}_event`;
if (
Expand Down Expand Up @@ -691,15 +703,15 @@ const handleBindings = (
})`;
}
} else if (item.bindings[key]?.code) {
if (item.bindings[key]?.type !== 'spread' && !key.startsWith('on')) {
if (item.bindings[key]?.type !== 'spread' && !checkIsEvent(key)) {
json.state[newBindingName] = { code: 'null', type: 'property' };
makeReactiveState(
json,
newBindingName,
`this.${newBindingName} = ${item.bindings[key]!.code}`,
);
item.bindings[key]!.code = `state.${newBindingName}`;
} else if (key.startsWith('on')) {
} else if (checkIsEvent(key)) {
const { arguments: cusArgs = ['event'] } = item.bindings[key]!;
if (
item.bindings[key]?.code.trim().startsWith('{') &&
Expand Down Expand Up @@ -941,6 +953,7 @@ export const componentToAngular: TranspilerGenerator<ToAngularOptions> =
blockOptions: {
childComponents,
nativeAttributes: useMetadata?.angular?.nativeAttributes ?? [],
nativeEvents: useMetadata?.angular?.nativeEvents ?? [],
},
});
if (options.state === 'inline-with-wrappers') {
Expand All @@ -964,6 +977,7 @@ export const componentToAngular: TranspilerGenerator<ToAngularOptions> =
{
childComponents,
nativeAttributes: useMetadata?.angular?.nativeAttributes ?? [],
nativeEvents: useMetadata?.angular?.nativeEvents ?? [],
},
);

Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/generators/angular/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,19 @@ export const DEFAULT_ANGULAR_OPTIONS: ToAngularOptions = {
};

export type AngularMetadata = {
/* Mitosis uses `attr.XXX` as default see https://angular.io/guide/attribute-binding.
If you want to skip some you can use the 'nativeAttributes'. */
/**
* Mitosis uses `attr.XXX` as default see https://angular.io/guide/attribute-binding.
* If you want to skip some you can use the 'nativeAttributes'.
*/
nativeAttributes?: string[];
/* Overwrite default selector for component. Default will be kebab case (MyComponent -> my-component) */
/**
* If you encounter some native events which aren't generated in lower-case.
* Create a new PR inside [event-handlers.ts](https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/helpers/event-handlers.ts) to fix it for all.
*/
nativeEvents?: string[];
/**
* Overwrite default selector for component. Default will be kebab case (MyComponent -> my-component)
*/
selector?: string;
};

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/generators/helpers/rsc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { checkIsEvent } from '@/helpers/event-handlers';
import { isMitosisNode } from '@/helpers/is-mitosis-node';
import type { MitosisComponent } from '@/types/mitosis-component';
import type { MitosisNode } from '@/types/mitosis-node';
Expand All @@ -18,7 +19,7 @@ export const checkIfIsClientComponent = (json: MitosisComponent) => {
let foundEventListener = false;
traverse(json).forEach(function (node) {
if (isMitosisNode(node) && !checkIsNodeAMitosisComponent(node)) {
if (Object.keys(node.bindings).filter((item) => item.startsWith('on')).length) {
if (Object.keys(node.bindings).filter((item) => checkIsEvent(item)).length) {
foundEventListener = true;
this.stop();
}
Expand Down
Loading

0 comments on commit a0c9735

Please sign in to comment.