diff --git a/lib/__tests__/__snapshots__/transform.js.snap b/lib/__tests__/__snapshots__/transform.js.snap
index 6932bdd..c227d2c 100644
--- a/lib/__tests__/__snapshots__/transform.js.snap
+++ b/lib/__tests__/__snapshots__/transform.js.snap
@@ -559,6 +559,51 @@ foo
=========="
`;
+exports[`native components handles multiple event handlers correctly 1`] = `
+"==========
+
+ import Component from '@ember/component';
+
+ export default class FooComponent extends Component {
+ mouseDown() {
+ console.log('Hello!');
+ }
+
+ mouseUp() {
+ console.log('World!');
+ }
+ }
+
+~~~~~~~~~~
+foo
+~~~~~~~~~~
+ => tagName: div
+~~~~~~~~~~
+
+ import { tagName } from \\"@ember-decorators/component\\";
+ import { action } from \\"@ember/object\\";
+ import Component from '@ember/component';
+
+ @tagName(\\"\\")
+ export default class FooComponent extends Component {
+ @action
+ handleMouseDown() {
+ console.log('Hello!');
+ }
+
+ @action
+ handleMouseUp() {
+ console.log('World!');
+ }
+ }
+
+~~~~~~~~~~
+
+ foo
+
+=========="
+`;
+
exports[`native components handles single \`@classNames\` item correctly 1`] = `
"==========
@@ -589,6 +634,42 @@ foo
=========="
`;
+exports[`native components handles single event handler correctly 1`] = `
+"==========
+
+ import Component from '@ember/component';
+
+ export default class FooComponent extends Component {
+ click() {
+ console.log('Hello World!');
+ }
+ }
+
+~~~~~~~~~~
+foo
+~~~~~~~~~~
+ => tagName: div
+~~~~~~~~~~
+
+ import { tagName } from \\"@ember-decorators/component\\";
+ import { action } from \\"@ember/object\\";
+ import Component from '@ember/component';
+
+ @tagName(\\"\\")
+ export default class FooComponent extends Component {
+ @action
+ handleClick() {
+ console.log('Hello World!');
+ }
+ }
+
+~~~~~~~~~~
+
+ foo
+
+=========="
+`;
+
exports[`native components keeps unrelated decorators in place 1`] = `
"==========
diff --git a/lib/__tests__/transform.js b/lib/__tests__/transform.js
index a7e89ba..3cb8658 100644
--- a/lib/__tests__/transform.js
+++ b/lib/__tests__/transform.js
@@ -457,6 +457,42 @@ describe('native components', function() {
expect(generateSnapshot(source, template)).toMatchSnapshot();
});
+ test('handles single event handler correctly', () => {
+ let source = `
+ import Component from '@ember/component';
+
+ export default class FooComponent extends Component {
+ click() {
+ console.log('Hello World!');
+ }
+ }
+ `;
+
+ let template = `foo`;
+
+ expect(generateSnapshot(source, template)).toMatchSnapshot();
+ });
+
+ test('handles multiple event handlers correctly', () => {
+ let source = `
+ import Component from '@ember/component';
+
+ export default class FooComponent extends Component {
+ mouseDown() {
+ console.log('Hello!');
+ }
+
+ mouseUp() {
+ console.log('World!');
+ }
+ }
+ `;
+
+ let template = `foo`;
+
+ expect(generateSnapshot(source, template)).toMatchSnapshot();
+ });
+
test('throws for non-boolean @classNameBindings', () => {
let source = `
import Component from '@ember/component';
@@ -538,38 +574,6 @@ describe('native components', function() {
);
});
- test('throws if component is using `keyDown()`', () => {
- let source = `
- import Component from '@ember/component';
-
- export default class FooComponent extends Component {
- keyDown() {
- console.log('Hello World!');
- }
- }
- `;
-
- expect(() => transform(source, '')).toThrowErrorMatchingInlineSnapshot(
- `"Using \`keyDown()\` is not supported in tagless components"`
- );
- });
-
- test('throws if component is using `click()`', () => {
- let source = `
- import Component from '@ember/component';
-
- export default class FooComponent extends Component {
- click() {
- console.log('Hello World!');
- }
- }
- `;
-
- expect(() => transform(source, '')).toThrowErrorMatchingInlineSnapshot(
- `"Using \`click()\` is not supported in tagless components"`
- );
- });
-
test('multi-line template', () => {
let source = `
import Component from '@ember/component';
diff --git a/lib/transform/native.js b/lib/transform/native.js
index 566dbf1..c6c71de 100644
--- a/lib/transform/native.js
+++ b/lib/transform/native.js
@@ -13,6 +13,7 @@ const {
removeDecorator,
ensureImport,
isProperty,
+ renameEventHandler,
} = require('../utils/native');
const EVENT_HANDLER_METHODS = [
@@ -98,14 +99,6 @@ module.exports = function transformNativeComponent(root, options) {
throw new SilentError(`Using \`this.elementId\` is not supported in tagless components`);
}
- // skip components that use `click()` etc.
- for (let methodName of EVENT_HANDLER_METHODS) {
- let handlerMethod = classBody.filter(path => isMethod(path, methodName))[0];
- if (handlerMethod) {
- throw new SilentError(`Using \`${methodName}()\` is not supported in tagless components`);
- }
- }
-
// analyze `elementId`, `attributeBindings`, `classNames` and `classNameBindings`
let elementId = findElementId(classBody);
debug('elementId: %o', elementId);
@@ -119,6 +112,19 @@ module.exports = function transformNativeComponent(root, options) {
let classNameBindings = findClassNameBindings(classDeclaration);
debug('classNameBindings: %o', classNameBindings);
+ let eventHandlers = new Map();
+ // rename event handlers and add @action
+ for (let eventName of EVENT_HANDLER_METHODS) {
+ let handlerMethod = classBody.filter(path => isMethod(path, eventName))[0];
+
+ if (handlerMethod) {
+ let methodName = renameEventHandler(handlerMethod);
+ addClassDecorator(handlerMethod, 'action');
+ ensureImport(root, 'action', '@ember/object');
+ eventHandlers.set(eventName.toLowerCase(), methodName);
+ }
+ }
+
// set `@tagName('')`
addClassDecorator(classDeclaration, 'tagName', [j.stringLiteral('')]);
ensureImport(root, 'tagName', '@ember-decorators/component');
@@ -142,5 +148,13 @@ module.exports = function transformNativeComponent(root, options) {
let newSource = root.toSource();
- return { newSource, tagName, elementId, classNames, classNameBindings, attributeBindings };
+ return {
+ newSource,
+ tagName,
+ elementId,
+ classNames,
+ classNameBindings,
+ attributeBindings,
+ eventHandlers,
+ };
};
diff --git a/lib/transform/template.js b/lib/transform/template.js
index fc0171e..6561bec 100644
--- a/lib/transform/template.js
+++ b/lib/transform/template.js
@@ -8,7 +8,7 @@ const PLACEHOLDER = '@@@PLACEHOLDER@@@';
module.exports = function transformTemplate(
template,
- { tagName, elementId, classNames, classNameBindings, attributeBindings, ariaRole },
+ { tagName, elementId, classNames, classNameBindings, attributeBindings, ariaRole, eventHandlers },
options
) {
// wrap existing template with root element
@@ -50,11 +50,19 @@ module.exports = function transformTemplate(
}
attrs.push(b.attr('...attributes', b.text('')));
+ let modifiers = [];
+ if (eventHandlers) {
+ eventHandlers.forEach((methodName, eventName) => {
+ modifiers.push(b.elementModifier('on', [b.string(eventName), b.path(`this.${methodName}`)]));
+ });
+ }
+
let templateAST = templateRecast.parse(template);
templateAST.body = [
b.element(tagName, {
attrs,
+ modifiers,
children: [b.text(`\n${PLACEHOLDER}\n`)],
}),
];
diff --git a/lib/utils/native.js b/lib/utils/native.js
index ff3e486..b5329a1 100644
--- a/lib/utils/native.js
+++ b/lib/utils/native.js
@@ -8,11 +8,13 @@ function addClassDecorator(classDeclaration, name, args) {
if (existing) {
existing.value.expression.arguments = args;
} else {
- if (classDeclaration.value.decorators === undefined) {
+ if (!classDeclaration.value.decorators) {
classDeclaration.value.decorators = [];
}
classDeclaration.value.decorators.unshift(
- j.decorator(j.callExpression(j.identifier(name), args))
+ args === undefined
+ ? j.decorator(j.identifier(name))
+ : j.decorator(j.callExpression(j.identifier(name), args))
);
}
}
@@ -56,7 +58,7 @@ function findStringProperty(properties, name, defaultValue = null) {
function findDecorator(path, name, withArgs) {
let decorators = path.get('decorators');
- if (decorators.value === undefined) {
+ if (!decorators.value) {
return;
}
@@ -257,6 +259,15 @@ function createImportStatement(source, imported, local) {
return declaration;
}
+function renameEventHandler(path) {
+ let oldName = path.value.key.name;
+ let newName = `handle${oldName.charAt(0).toUpperCase()}${oldName.slice(1)}`;
+
+ path.value.key.name = newName;
+
+ return newName;
+}
+
module.exports = {
addClassDecorator,
isProperty,
@@ -270,4 +281,5 @@ module.exports = {
removeDecorator,
ensureImport,
removeImport,
+ renameEventHandler,
};