diff --git a/text/0486-deprecate-mouseenter.md b/text/0486-deprecate-mouseenter.md new file mode 100644 index 0000000000..25a92051a7 --- /dev/null +++ b/text/0486-deprecate-mouseenter.md @@ -0,0 +1,165 @@ +- Start Date: 2019-04-28 +- Relevant Team(s): Ember.js +- RFC PR: https://github.com/emberjs/rfcs/pull/486 +- Tracking: + +# Deprecate support for mouseEnter/Leave/Move Ember events + +## Summary + +Deprecate support for `mouseenter`, `mouseleave` and `mousemove` events in Ember's EventDispatcher. This affects +the corresponding event handler methods (`mouseEnter() {}`) in Ember components and +`{{action "some" on="mouseenter"}}`. + +## Motivation + +Ember's EventDispatcher handles "Ember events" by attaching listeners to the app's root element +and relying on the events bubbling up to that element (aka event delegation). There they +are processed and invoke any matching `Ember.Component` event handler method with +the same (camel-cased) method as the event type. Same for element-space `{{action}}` +modifiers. + +> Note: for a "Deep Dive on Ember Events" and how they differ from "native events" I refer to +Marie Chatfield's excellent +[blog post](https://medium.com/square-corner-blog/deep-dive-on-ember-events-cf684fd3b808) +or [EmberConf talk](https://youtu.be/G9hXjjHFJVs) + +This works fine in general, but `mouseenter`/`mouseleave` events are special as they do +not bubble. In the past it still worked nevertheless as jQuery transparently handled this +for us, as it had special support for event delegation for these events, essentially by using +(bubbling) `mouseover` events to replicate the semantics of `mouseenter`/`mouseleave` events. + +When support for jQuery-less apps was introduced, this [left a hole](https://github.com/emberjs/ember.js/issues/16591) +in the jQuery-less EventDispatcher implementation. But as support for those events was and +still is part of Ember's pubic API, we had no chance other than to [implement support +for jQuery-less apps](https://github.com/emberjs/ember.js/pull/16603) using the same +`mouseover` based approach. + +This however comes with a cost: besides an [unresolved issue](https://github.com/emberjs/ember.js/issues/17228) +the implementation has some performance drawbacks, as it has to process every `mouseover` event on +*any* element, create fake `mouseenter`/`mouseleave` events and try to dispatch them, even when +not a single component/action needs them. + +Deprecating support for `mousemove` is also proposed, which is a (bubbling) event that does not have the higher +implementation cost as `mouseenter`/`mouseleave`, but nevertheless requires the EventDispatcher to optimistically handle +these extremely high-volume events. + +While efforts to make this more "pay as you go" are [possible](https://github.com/emberjs/ember.js/pull/17911), +the trade-off of keeping support around still seems unfavorable, as these events fire so +frequently, while they are (most certainly) very rarely used. + +This is even more so given that Glimmer Components with their outerHTML semantics do not +work with event handler methods, and `{{action}}` will eventually fade away in favor of +`{{on}}` using native `addListener()`. + +## Transition Path + +#### Ember.Component + +When a component with a `mouseEnter`, `mouseLeave` or `mouseMove` method is created, a deprecation warning will be issued. + +The linked migration guide will cover these examples: + +Before: + +```js +import Component from '@ember/component'; + +export default class MyComponent extends Component { + mouseEnter(e) { + // do something + } +} +``` + +After: + +```js +import Component from '@ember/component'; +import { action } from '@ember/object'; + +export default class MyComponent extends Component { + @action + handleMouseEnter(e) { + // do something + } + + didInsertElement() { + super.didInsertElement(...arguments); + this.element.addEventListener('mouseenter', this.handleMouseEnter); + } + + willDestroyElement() { + super.willDestroyElement(...arguments); + this.element.removeEventListener('mouseenter', this.handleMouseEnter); + } +} +``` + +An alternative to attaching the event listener in the component class is to opt into outer HTML semantics by making the +component tag-less and using the `{{on}}` modifier in the template: + +```js +import Component from '@ember/component'; +import { action } from '@ember/object'; + +export default class MyComponent extends Component { + tagName = ''; + + @action + handleMouseEnter(e) { + // do something + } +} +``` + +```hbs +