diff --git a/.changeset/polite-pugs-pull.md b/.changeset/polite-pugs-pull.md
new file mode 100644
index 0000000000..a15e74df56
--- /dev/null
+++ b/.changeset/polite-pugs-pull.md
@@ -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 (
+
+ console.log(event)} />
+ Hello!
+
+ );
+}
+```
diff --git a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap
index 2b45f19ad0..6754f349ba 100644
--- a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap
@@ -5345,7 +5345,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -13218,7 +13220,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
diff --git a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap
index c9bec70a6b..6ba96e9d99 100644
--- a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap
@@ -5432,7 +5432,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -13435,7 +13437,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
diff --git a/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap
index 45acd5b34e..dab0bdc261 100644
--- a/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/angular.state.test.ts.snap
@@ -5545,7 +5545,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -13695,7 +13697,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
diff --git a/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap
index 04cd7f381e..c9fc1eeb7c 100644
--- a/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/angular.styles.test.ts.snap
@@ -4792,7 +4792,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -11845,7 +11847,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
diff --git a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap
index 7c26917739..60a69dc763 100644
--- a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap
@@ -10060,7 +10060,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -10098,7 +10100,9 @@ import { CommonModule } from \\"@angular/common\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -24878,7 +24882,9 @@ import { Component } from \\"@angular/core\\";
Hello! I can run in React, Vue, Solid, or Liquid!
@@ -24916,7 +24922,9 @@ import { CommonModule } from \\"@angular/common\\";
Hello! I can run in React, Vue, Solid, or Liquid!
diff --git a/packages/core/src/__tests__/data/angular/output-event-bindings.raw.tsx b/packages/core/src/__tests__/data/angular/output-event-bindings.raw.tsx
index d2fc81aa76..34095f04cc 100644
--- a/packages/core/src/__tests__/data/angular/output-event-bindings.raw.tsx
+++ b/packages/core/src/__tests__/data/angular/output-event-bindings.raw.tsx
@@ -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');
@@ -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!
diff --git a/packages/core/src/generators/alpine/generate.ts b/packages/core/src/generators/alpine/generate.ts
index f0a55519cb..93d11d9c11 100644
--- a/packages/core/src/generators/alpine/generate.ts
+++ b/packages/core/src/generators/alpine/generate.ts
@@ -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';
@@ -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';
@@ -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}"`;
diff --git a/packages/core/src/generators/angular/index.ts b/packages/core/src/generators/angular/index.ts
index b31710901d..20bd23ecf9 100644
--- a/packages/core/src/generators/angular/index.ts
+++ b/packages/core/src/generators/angular/index.ts
@@ -63,6 +63,7 @@ import {
ToAngularOptions,
} from './types';
+import { checkIsBindingNativeEvent, checkIsEvent } from '@/helpers/event-handlers';
import { parse } from './parse-selector';
const { types } = babel;
@@ -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') {
@@ -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,
@@ -311,6 +322,7 @@ export const blockToAngular = ({
options = {},
blockOptions = {
nativeAttributes: [],
+ nativeEvents: [],
},
}: {
root: MitosisComponent;
@@ -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 (
@@ -691,7 +703,7 @@ 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,
@@ -699,7 +711,7 @@ const handleBindings = (
`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('{') &&
@@ -941,6 +953,7 @@ export const componentToAngular: TranspilerGenerator =
blockOptions: {
childComponents,
nativeAttributes: useMetadata?.angular?.nativeAttributes ?? [],
+ nativeEvents: useMetadata?.angular?.nativeEvents ?? [],
},
});
if (options.state === 'inline-with-wrappers') {
@@ -964,6 +977,7 @@ export const componentToAngular: TranspilerGenerator =
{
childComponents,
nativeAttributes: useMetadata?.angular?.nativeAttributes ?? [],
+ nativeEvents: useMetadata?.angular?.nativeEvents ?? [],
},
);
diff --git a/packages/core/src/generators/angular/types.ts b/packages/core/src/generators/angular/types.ts
index ac4ef1cec2..9baa5e67e2 100644
--- a/packages/core/src/generators/angular/types.ts
+++ b/packages/core/src/generators/angular/types.ts
@@ -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;
};
diff --git a/packages/core/src/generators/helpers/rsc.ts b/packages/core/src/generators/helpers/rsc.ts
index 0b25b4f07c..a88dd8a7bb 100644
--- a/packages/core/src/generators/helpers/rsc.ts
+++ b/packages/core/src/generators/helpers/rsc.ts
@@ -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';
@@ -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();
}
diff --git a/packages/core/src/generators/html/generator.ts b/packages/core/src/generators/html/generator.ts
index a23ca9ed25..911d4f42cf 100644
--- a/packages/core/src/generators/html/generator.ts
+++ b/packages/core/src/generators/html/generator.ts
@@ -1,48 +1,50 @@
+import { SELF_CLOSING_HTML_TAGS } from '@/constants/html_tags';
import { ToHtmlOptions } from '@/generators/html/types';
-import { NodePath, types } from '@babel/core';
-import { pipe } from 'fp-ts/lib/function';
-import { camelCase, kebabCase } from 'lodash';
-import traverse from 'neotraverse/legacy';
-import { format } from 'prettier/standalone';
-import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
-import { babelTransformExpression } from '../../helpers/babel-transform';
-import { dashCase } from '../../helpers/dash-case';
-import { fastClone } from '../../helpers/fast-clone';
-import { getPropFunctions } from '../../helpers/get-prop-functions';
-import { getProps } from '../../helpers/get-props';
-import { getPropsRef } from '../../helpers/get-props-ref';
-import { getRefs } from '../../helpers/get-refs';
-import { getStateObjectStringFromComponent } from '../../helpers/get-state-object-string';
-import { hasBindingsText } from '../../helpers/has-bindings-text';
-import { hasComponent } from '../../helpers/has-component';
-import { hasProps } from '../../helpers/has-props';
-import { hasStatefulDom } from '../../helpers/has-stateful-dom';
-import isChildren from '../../helpers/is-children';
-import { isHtmlAttribute } from '../../helpers/is-html-attribute';
-import { isMitosisNode } from '../../helpers/is-mitosis-node';
-import { mapRefs } from '../../helpers/map-refs';
-import { initializeOptions } from '../../helpers/merge-options';
-import { getForArguments } from '../../helpers/nodes/for';
-import { removeSurroundingBlock } from '../../helpers/remove-surrounding-block';
-import { renderPreComponent } from '../../helpers/render-imports';
-import { stripMetaProperties } from '../../helpers/strip-meta-properties';
+import { babelTransformExpression } from '@/helpers/babel-transform';
+import { dashCase } from '@/helpers/dash-case';
+import { checkIsEvent } from '@/helpers/event-handlers';
+import { fastClone } from '@/helpers/fast-clone';
+import { getPropFunctions } from '@/helpers/get-prop-functions';
+import { getProps } from '@/helpers/get-props';
+import { getPropsRef } from '@/helpers/get-props-ref';
+import { getRefs } from '@/helpers/get-refs';
+import { getStateObjectStringFromComponent } from '@/helpers/get-state-object-string';
+import { hasBindingsText } from '@/helpers/has-bindings-text';
+import { hasComponent } from '@/helpers/has-component';
+import { hasProps } from '@/helpers/has-props';
+import { hasStatefulDom } from '@/helpers/has-stateful-dom';
+import { isHtmlAttribute } from '@/helpers/is-html-attribute';
+import { isMitosisNode } from '@/helpers/is-mitosis-node';
+import { mapRefs } from '@/helpers/map-refs';
+import { initializeOptions } from '@/helpers/merge-options';
+import { getForArguments } from '@/helpers/nodes/for';
+import { removeSurroundingBlock } from '@/helpers/remove-surrounding-block';
+import { renderPreComponent } from '@/helpers/render-imports';
+import { stripMetaProperties } from '@/helpers/strip-meta-properties';
import {
DO_NOT_USE_ARGS,
DO_NOT_USE_CONTEXT_VARS_TRANSFORMS,
DO_NOT_USE_VARS_TRANSFORMS,
StripStateAndPropsRefsOptions,
stripStateAndPropsRefs,
-} from '../../helpers/strip-state-and-props-refs';
-import { collectCss } from '../../helpers/styles/collect-css';
+} from '@/helpers/strip-state-and-props-refs';
+import { collectCss } from '@/helpers/styles/collect-css';
import {
runPostCodePlugins,
runPostJsonPlugins,
runPreCodePlugins,
runPreJsonPlugins,
-} from '../../modules/plugins';
-import { MitosisComponent } from '../../types/mitosis-component';
-import { MitosisNode, checkIsForNode } from '../../types/mitosis-node';
-import { TranspilerGenerator } from '../../types/transpiler';
+} from '@/modules/plugins';
+import { MitosisComponent } from '@/types/mitosis-component';
+import { MitosisNode, checkIsForNode } from '@/types/mitosis-node';
+import { TranspilerGenerator } from '@/types/transpiler';
+import { NodePath, types } from '@babel/core';
+import { pipe } from 'fp-ts/lib/function';
+import { camelCase, kebabCase } from 'lodash';
+import traverse from 'neotraverse/legacy';
+
+import isChildren from '@/helpers/is-children';
+import { format } from 'prettier/standalone';
import { stringifySingleScopeOnMount } from '../helpers/on-mount';
type ScopeVars = Array;
@@ -424,7 +426,7 @@ const blockToHtml = (
// TODO: proper babel transform to replace. Util for this
const useValue = value;
- if (key.startsWith('on')) {
+ if (checkIsEvent(key)) {
let event = key.replace('on', '').toLowerCase();
const fnName = camelCase(`on-${elId}-${event}`);
const codeContent: string = removeSurroundingBlock(
diff --git a/packages/core/src/generators/liquid/generator.ts b/packages/core/src/generators/liquid/generator.ts
index a2392e7ebf..bf9a43a0b2 100644
--- a/packages/core/src/generators/liquid/generator.ts
+++ b/packages/core/src/generators/liquid/generator.ts
@@ -1,4 +1,5 @@
import { ToLiquidOptions } from '@/generators/liquid/types';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { format } from 'prettier/standalone';
import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
import { fastClone } from '../../helpers/fast-clone';
@@ -113,7 +114,7 @@ const blockToLiquid = (json: MitosisNode, options: ToLiquidOptions = {}): string
// TODO: proper babel transform to replace. Util for this
const useValue = stripStateAndPropsRefs(value);
- if (key.startsWith('on')) {
+ if (checkIsEvent(key)) {
// Do nothing
} else if (isValidLiquidBinding(useValue)) {
str += ` ${key}="{{${useValue}}}" `;
diff --git a/packages/core/src/generators/lit/generate.ts b/packages/core/src/generators/lit/generate.ts
index a61b5be276..ace3cab4eb 100644
--- a/packages/core/src/generators/lit/generate.ts
+++ b/packages/core/src/generators/lit/generate.ts
@@ -1,6 +1,7 @@
import { ToLitOptions } from '@/generators/lit/types';
import { dashCase } from '@/helpers/dash-case';
import { dedent } from '@/helpers/dedent';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { fastClone } from '@/helpers/fast-clone';
import { filterEmptyTextNodes } from '@/helpers/filter-empty-text-nodes';
import { getProps } from '@/helpers/get-props';
@@ -92,7 +93,7 @@ const blockToLit = (json: MitosisNode, options: ToLitOptions = {}): string => {
// TODO: maybe use ref directive instead
// https://lit.dev/docs/templates/directives/#ref
str += ` ref="${code}" `;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
const asyncKeyword = json.bindings[key]?.async ? 'async ' : '';
const useKey = '@' + key.substring(2).toLowerCase();
diff --git a/packages/core/src/generators/marko/generate.ts b/packages/core/src/generators/marko/generate.ts
index 257ac43b73..edd79ab8d8 100644
--- a/packages/core/src/generators/marko/generate.ts
+++ b/packages/core/src/generators/marko/generate.ts
@@ -2,6 +2,7 @@ import { SELF_CLOSING_HTML_TAGS } from '@/constants/html_tags';
import { ToMarkoOptions } from '@/generators/marko/types';
import { dashCase } from '@/helpers/dash-case';
import { dedent } from '@/helpers/dedent';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { fastClone } from '@/helpers/fast-clone';
import { filterEmptyTextNodes } from '@/helpers/filter-empty-text-nodes';
import { getRefs } from '@/helpers/get-refs';
@@ -90,7 +91,7 @@ const blockToMarko = (json: MitosisNode, options: InternalToMarkoOptions): strin
str += ` ...(${code}) `;
} else if (key === 'ref') {
str += ` key="${camelCase(code)}" `;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
const asyncKeyword = async ? 'async ' : '';
const useKey = key === 'onChange' && json.name === 'input' ? 'onInput' : key;
str += ` ${dashCase(useKey)}=(${asyncKeyword}(${cusArgs.join(',')}) => ${processBinding(
diff --git a/packages/core/src/generators/mitosis/generator.ts b/packages/core/src/generators/mitosis/generator.ts
index 6dc1770eb5..98998f4f06 100644
--- a/packages/core/src/generators/mitosis/generator.ts
+++ b/packages/core/src/generators/mitosis/generator.ts
@@ -1,27 +1,29 @@
+import { HOOKS } from '@/constants/hooks';
+import { SELF_CLOSING_HTML_TAGS } from '@/constants/html_tags';
import { ToMitosisOptions } from '@/generators/mitosis/types';
+import { dedent } from '@/helpers/dedent';
+import { checkIsEvent } from '@/helpers/event-handlers';
+import { fastClone } from '@/helpers/fast-clone';
+import { getComponents } from '@/helpers/get-components';
+import { getRefs } from '@/helpers/get-refs';
+import { getStateObjectStringFromComponent } from '@/helpers/get-state-object-string';
import { isMitosisNode } from '@/helpers/is-mitosis-node';
+import { isRootTextNode } from '@/helpers/is-root-text-node';
+import { mapRefs } from '@/helpers/map-refs';
+import { renderPreComponent } from '@/helpers/render-imports';
+import { checkHasState } from '@/helpers/state';
import {
runPostCodePlugins,
runPostJsonPlugins,
runPreCodePlugins,
runPreJsonPlugins,
} from '@/modules/plugins';
+import { MitosisComponent } from '@/types/mitosis-component';
+import { MitosisNode, checkIsForNode, checkIsShowNode } from '@/types/mitosis-node';
+import { TranspilerGenerator } from '@/types/transpiler';
import json5 from 'json5';
import { format } from 'prettier/standalone';
-import { HOOKS } from '../../constants/hooks';
-import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
-import { dedent } from '../../helpers/dedent';
-import { fastClone } from '../../helpers/fast-clone';
-import { getComponents } from '../../helpers/get-components';
-import { getRefs } from '../../helpers/get-refs';
-import { getStateObjectStringFromComponent } from '../../helpers/get-state-object-string';
-import { isRootTextNode } from '../../helpers/is-root-text-node';
-import { mapRefs } from '../../helpers/map-refs';
-import { renderPreComponent } from '../../helpers/render-imports';
-import { checkHasState } from '../../helpers/state';
-import { MitosisComponent } from '../../types/mitosis-component';
-import { MitosisNode, checkIsForNode, checkIsShowNode } from '../../types/mitosis-node';
-import { TranspilerGenerator } from '../../types/transpiler';
+
import { blockToReact, componentToReact } from '../react';
export const DEFAULT_FORMAT: ToMitosisOptions['format'] = 'legacy';
@@ -144,7 +146,7 @@ export const blockToMitosis = (
if (json.bindings[key]?.type === 'spread') {
str += ` {...(${json.bindings[key]?.code})} `;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
const { arguments: cusArgs = ['event'], async } = json.bindings[key]!;
const asyncKeyword = async ? 'async ' : '';
str += ` ${key}={${asyncKeyword}(${cusArgs.join(',')}) => ${value.replace(/\s*;$/, '')}} `;
diff --git a/packages/core/src/generators/qwik/helpers/add-prevent-default.ts b/packages/core/src/generators/qwik/helpers/add-prevent-default.ts
index 72776ca000..1592b9c757 100644
--- a/packages/core/src/generators/qwik/helpers/add-prevent-default.ts
+++ b/packages/core/src/generators/qwik/helpers/add-prevent-default.ts
@@ -1,6 +1,7 @@
+import { checkIsEvent } from '@/helpers/event-handlers';
+import { isMitosisNode } from '@/helpers/is-mitosis-node';
+import { MitosisComponent } from '@/types/mitosis-component';
import traverse from 'neotraverse/legacy';
-import { isMitosisNode } from '../../../helpers/is-mitosis-node';
-import { MitosisComponent } from '../../../types/mitosis-component';
/**
* Find event handlers that explicitly call .preventDefault() and
@@ -12,7 +13,7 @@ export function addPreventDefault(json: MitosisComponent) {
if (isMitosisNode(node)) {
if (node.bindings) {
for (const key of Object.keys(node.bindings)) {
- if (key.startsWith('on')) {
+ if (checkIsEvent(key)) {
if (node.bindings[key]?.code.includes('.preventDefault()')) {
const event = key.slice(2).toLowerCase();
node.properties['preventdefault:' + event] = '';
diff --git a/packages/core/src/generators/qwik/helpers/handlers.ts b/packages/core/src/generators/qwik/helpers/handlers.ts
index 24abda603d..a4fdcfddd2 100644
--- a/packages/core/src/generators/qwik/helpers/handlers.ts
+++ b/packages/core/src/generators/qwik/helpers/handlers.ts
@@ -1,3 +1,4 @@
+import { checkIsEvent } from '@/helpers/event-handlers';
import { MitosisNode } from '../../../types/mitosis-node';
import { renderUseLexicalScope } from '../component';
import { arrowFnBlock, EmitFn, File, SrcBuilder } from '../src-generator';
@@ -61,5 +62,5 @@ function renderHandler(file: File, symbol: string, code: string) {
}
function isEventName(name: string) {
- return name.startsWith('on') && name.charAt(2).toUpperCase() == name.charAt(2);
+ return checkIsEvent(name) && name.charAt(2).toUpperCase() == name.charAt(2);
}
diff --git a/packages/core/src/generators/qwik/src-generator.ts b/packages/core/src/generators/qwik/src-generator.ts
index 5db00c84c0..abbf04ddd5 100644
--- a/packages/core/src/generators/qwik/src-generator.ts
+++ b/packages/core/src/generators/qwik/src-generator.ts
@@ -1,6 +1,7 @@
import parserTypeScript from 'prettier/parser-typescript';
import { format } from 'prettier/standalone';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
import { convertExportDefaultToReturn } from '../../parsers/builder';
import { stableJSONserialize } from './helpers/stable-serialize';
@@ -424,7 +425,7 @@ export class SrcBuilder {
}
function isEvent(name: string): boolean {
- return name.startsWith('on') && isUppercase(name.charAt(2)) && !name.endsWith('$');
+ return checkIsEvent(name) && isUppercase(name.charAt(2)) && !name.endsWith('$');
}
function isUppercase(ch: string): boolean {
diff --git a/packages/core/src/generators/react/blocks.ts b/packages/core/src/generators/react/blocks.ts
index 3d7fc3736e..40dc24b97d 100644
--- a/packages/core/src/generators/react/blocks.ts
+++ b/packages/core/src/generators/react/blocks.ts
@@ -1,3 +1,4 @@
+import { checkIsEvent } from '@/helpers/event-handlers';
import { filterEmptyTextNodes } from '@/helpers/filter-empty-text-nodes';
import isChildren from '@/helpers/is-children';
import { isRootTextNode } from '@/helpers/is-root-text-node';
@@ -296,7 +297,7 @@ export const blockToReact = (
}
if (json.bindings[key]?.type === 'spread') {
str += ` {...(${value})} `;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
const asyncKeyword = json.bindings[key]?.async ? 'async ' : '';
const { arguments: cusArgs = ['event'] } = json.bindings[key]!;
const eventName = options.type === 'native' ? NATIVE_EVENT_MAPPER[key] || key : key;
diff --git a/packages/core/src/generators/solid/blocks.ts b/packages/core/src/generators/solid/blocks.ts
index 3f6e2f0039..8f3b57741d 100644
--- a/packages/core/src/generators/solid/blocks.ts
+++ b/packages/core/src/generators/solid/blocks.ts
@@ -1,4 +1,5 @@
import { babelTransformExpression } from '@/helpers/babel-transform';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { filterEmptyTextNodes } from '@/helpers/filter-empty-text-nodes';
import { isMitosisNode } from '@/helpers/is-mitosis-node';
import { objectHasKey } from '@/helpers/typescript';
@@ -89,7 +90,7 @@ export const blockToSolid = (
if (type === 'spread') {
str += ` {...(${code})} `;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
const useKey = key === 'onChange' && json.name === 'input' ? 'onInput' : key;
const asyncKeyword = json.bindings[key]?.async ? 'async ' : '';
str += ` ${useKey}={${asyncKeyword}(${cusArg.join(',')}) => ${code}} `;
diff --git a/packages/core/src/generators/stencil/helpers/index.ts b/packages/core/src/generators/stencil/helpers/index.ts
index 61c5bb06bd..f4dd441239 100644
--- a/packages/core/src/generators/stencil/helpers/index.ts
+++ b/packages/core/src/generators/stencil/helpers/index.ts
@@ -1,11 +1,12 @@
import { ToStencilOptions } from '@/generators/stencil/types';
import { dashCase } from '@/helpers/dash-case';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { renderPreComponent } from '@/helpers/render-imports';
import { stripStateAndPropsRefs } from '@/helpers/strip-state-and-props-refs';
import { MitosisComponent, MitosisState } from '@/types/mitosis-component';
import { MitosisNode } from '@/types/mitosis-node';
-export const isEvent = (key: string): boolean => key.startsWith('on');
+export const isEvent = (key: string): boolean => checkIsEvent(key);
/**
* We need to "emit" events those can be on multiple places, so we do it as post step
diff --git a/packages/core/src/generators/svelte/blocks.ts b/packages/core/src/generators/svelte/blocks.ts
index ae492cfbe1..7fe2388256 100644
--- a/packages/core/src/generators/svelte/blocks.ts
+++ b/packages/core/src/generators/svelte/blocks.ts
@@ -1,4 +1,5 @@
import { createSingleBinding } from '@/helpers/bindings';
+import { checkIsEvent } from '@/helpers/event-handlers';
import isChildren from '@/helpers/is-children';
import { isUpperCase } from '@/helpers/is-upper-case';
import { getForArguments } from '@/helpers/nodes/for';
@@ -222,7 +223,7 @@ const stringifyBinding =
if (type === 'spread') {
const spreadValue = key === 'props' ? '$$props' : code;
return ` {...${spreadValue}} `;
- } else if (key.startsWith('on') && isValidHtmlTag) {
+ } else if (checkIsEvent(key) && isValidHtmlTag) {
const { async } = binding;
// handle html native on[event] props
const event = key.replace('on', '').toLowerCase();
@@ -236,7 +237,7 @@ const stringifyBinding =
const asyncKeyword = async ? 'async ' : '';
return ` on:${event}="{${asyncKeyword}(${cusArgs.join(',')}) => {${valueWithoutBlock}}}" `;
}
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
// handle on[custom event] props
const valueWithoutBlock = removeSurroundingBlock(code);
diff --git a/packages/core/src/generators/swift/generator.ts b/packages/core/src/generators/swift/generator.ts
index 5e9370c853..af7a87ee47 100644
--- a/packages/core/src/generators/swift/generator.ts
+++ b/packages/core/src/generators/swift/generator.ts
@@ -1,4 +1,5 @@
import { ToSwiftOptions } from '@/generators/swift/types';
+import { checkIsEvent } from '@/helpers/event-handlers';
import traverse from 'neotraverse/legacy';
import { dedent } from '../../helpers/dedent';
import { fastClone } from '../../helpers/fast-clone';
@@ -149,7 +150,7 @@ const blockToSwift = (json: MitosisNode, options: ToSwiftOptions): string => {
continue;
}
- if (key.startsWith('on')) {
+ if (checkIsEvent(key)) {
if (key === 'onClick') {
continue;
} else {
diff --git a/packages/core/src/generators/template/generator.ts b/packages/core/src/generators/template/generator.ts
index 9c8325d4e1..4877477662 100644
--- a/packages/core/src/generators/template/generator.ts
+++ b/packages/core/src/generators/template/generator.ts
@@ -1,4 +1,5 @@
import { ToTemplateOptions } from '@/generators/template/types';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { format } from 'prettier/standalone';
import { SELF_CLOSING_HTML_TAGS } from '../../constants/html_tags';
import { dedent } from '../../helpers/dedent';
@@ -76,7 +77,7 @@ const blockToTemplate = (json: MitosisNode, options: ToTemplateOptions = {}) =>
// TODO: proper babel transform to replace. Util for this
const useValue = value;
- if (key.startsWith('on')) {
+ if (checkIsEvent(key)) {
// Do nothing
} else {
str += ` ${key}="\${${useValue}}" `;
diff --git a/packages/core/src/generators/vue/blocks.ts b/packages/core/src/generators/vue/blocks.ts
index 639e7854f1..0f6f7e89fa 100644
--- a/packages/core/src/generators/vue/blocks.ts
+++ b/packages/core/src/generators/vue/blocks.ts
@@ -1,4 +1,5 @@
import { createSingleBinding } from '@/helpers/bindings';
+import { checkIsEvent } from '@/helpers/event-handlers';
import { filterEmptyTextNodes } from '@/helpers/filter-empty-text-nodes';
import isChildren from '@/helpers/is-children';
import { isMitosisNode } from '@/helpers/is-mitosis-node';
@@ -130,7 +131,7 @@ const stringifyBinding =
// TODO: proper babel transform to replace. Util for this
const useValue = value?.code || '';
- if (key.startsWith('on') && isValidHtmlTag) {
+ if (checkIsEvent(key) && isValidHtmlTag) {
// handle html native on[event] props
const { arguments: cusArgs = ['event'], async } = value;
let event = key.replace('on', '').toLowerCase();
@@ -154,7 +155,7 @@ const stringifyBinding =
const eventHandlerKey = `${SPECIAL_PROPERTIES.V_ON_AT}${event}`;
return `${eventHandlerKey}="${eventHandlerValue}"`;
- } else if (key.startsWith('on')) {
+ } else if (checkIsEvent(key)) {
// handle on[custom event] props
const { arguments: cusArgs = ['event'] } = node.bindings[key]!;
return `:${key}="(${cusArgs.join(',')}) => ${encodeQuotes(useValue)}"`;
diff --git a/packages/core/src/helpers/event-handlers.ts b/packages/core/src/helpers/event-handlers.ts
index d263630434..0f938db322 100644
--- a/packages/core/src/helpers/event-handlers.ts
+++ b/packages/core/src/helpers/event-handlers.ts
@@ -1 +1,110 @@
-export const checkIsBindingEventHandler = (code: string) => code.startsWith('on');
+export const checkIsEvent = (code: string) => code.startsWith('on');
+
+const nativeEvents = [
+ 'abort',
+ 'animationcancel',
+ 'animationend',
+ 'animationiteration',
+ 'animationstart',
+ 'auxclick',
+ 'beforeinput',
+ 'beforetoggle',
+ 'blur',
+ 'cancel',
+ 'canplay',
+ 'canplaythrough',
+ 'change',
+ 'click',
+ 'close',
+ 'compositionend',
+ 'compositionstart',
+ 'compositionupdate',
+ 'contextlost',
+ 'contextmenu',
+ 'contextrestored',
+ 'copy',
+ 'cuechange',
+ 'cut',
+ 'dblclick',
+ 'drag',
+ 'dragend',
+ 'dragenter',
+ 'dragleave',
+ 'dragover',
+ 'dragstart',
+ 'drop',
+ 'durationchange',
+ 'emptied',
+ 'ended',
+ 'error',
+ 'focus',
+ 'focusin',
+ 'focusout',
+ 'formdata',
+ 'gotpointercapture',
+ 'input',
+ 'invalid',
+ 'keydown',
+ 'keypress',
+ 'keyup',
+ 'load',
+ 'loadeddata',
+ 'loadedmetadata',
+ 'loadstart',
+ 'lostpointercapture',
+ 'mousedown',
+ 'mouseenter',
+ 'mouseleave',
+ 'mousemove',
+ 'mouseout',
+ 'mouseover',
+ 'mouseup',
+ 'paste',
+ 'pause',
+ 'play',
+ 'playing',
+ 'pointercancel',
+ 'pointerdown',
+ 'pointerenter',
+ 'pointerleave',
+ 'pointermove',
+ 'pointerout',
+ 'pointerover',
+ 'pointerup',
+ 'progress',
+ 'ratechange',
+ 'reset',
+ 'resize',
+ 'scroll',
+ 'scrollend',
+ 'securitypolicyviolation',
+ 'seeked',
+ 'seeking',
+ 'select',
+ 'selectionchange',
+ 'selectstart',
+ 'slotchange',
+ 'stalled',
+ 'submit',
+ 'suspend',
+ 'timeupdate',
+ 'toggle',
+ 'touchcancel',
+ 'touchend',
+ 'touchmove',
+ 'touchstart',
+ 'transitioncancel',
+ 'transitionend',
+ 'transitionrun',
+ 'transitionstart',
+ 'volumechange',
+ 'waiting',
+ 'webkitanimationend',
+ 'webkitanimationiteration',
+ 'webkitanimationstart',
+ 'webkittransitionend',
+ 'wheel',
+];
+
+export const checkIsBindingNativeEvent = (code: string) =>
+ !!nativeEvents.find((nativeEvent) => nativeEvent === code.toLowerCase());
diff --git a/packages/core/src/helpers/is-children.ts b/packages/core/src/helpers/is-children.ts
index acbceac1cd..7c7ef76a8b 100644
--- a/packages/core/src/helpers/is-children.ts
+++ b/packages/core/src/helpers/is-children.ts
@@ -1,4 +1,4 @@
-import { MitosisNode } from '../types/mitosis-node';
+import { MitosisNode } from '@/types/mitosis-node';
export const getTextValue = (node: MitosisNode) => {
const textValue = node.bindings._text?.code || node.properties.__text || '';