diff --git a/addon/components/-focusable.js b/addon/components/-focusable.js
new file mode 100644
index 000000000..6c94e0304
--- /dev/null
+++ b/addon/components/-focusable.js
@@ -0,0 +1,174 @@
+/**
+ * @module ember-paper
+ */
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+
+/**
+ * @class Focusable
+ * @extends @glimmer/component
+ *
+ * When extending from Focusable it is expected that md-focused be implemented
+ * on the top level tag along with setting tabindex and disabled. This component
+ * listens to a large number of events, therefore render listener register
+ * functions have been created to ease usage. Clearly, this is non-optimal and
+ * only the listeners that are required should be added using the `on` modifier.
+ *
+ * Use the following as a base:
+ * ```hbs
+ *
+ *
+ * ```
+ */
+export default class Focusable extends Component {
+ @tracked pressed = false;
+ @tracked active = false;
+ @tracked focused = false;
+ @tracked hover = false;
+
+ // classNameBindings: ['focused:md-focused'],
+ // attributeBindings: ['tabindex', 'disabledAttr:disabled'],
+
+ get disabled() {
+ return this.args.disabled || false;
+ }
+
+ toggle = false;
+
+ // Only render the "focused" state if the element gains focus due to
+ // keyboard navigation.
+ get focusOnlyOnKey() {
+ return this.args.focusOnlyOnKey || false;
+ }
+
+ @action registerListeners(element) {
+ element.addEventListener('focusin', this.handleFocusIn);
+ element.addEventListener('focusout', this.handleFocusOut);
+ element.addEventListener('mousedown', this.handleMouseDown);
+ element.addEventListener('mouseenter', this.handleMouseEnter);
+ element.addEventListener('mouseleave', this.handleMouseLeave);
+ element.addEventListener('mousemove', this.handleMouseMove);
+ element.addEventListener('mouseup', this.handleMouseUp);
+ element.addEventListener('pointermove', this.handlePointerMove);
+ // Set all touch events as passive listeners to remove scroll jank on
+ // mobile devices.
+ // refer: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
+ element.addEventListener('touchcancel', this.handleTouchCancel, {
+ passive: true,
+ });
+ element.addEventListener('touchend', this.handleTouchEnd, {
+ passive: true,
+ });
+ element.addEventListener('touchmove', this.handleTouchMove, {
+ passive: true,
+ });
+ element.addEventListener('touchstart', this.handleTouchStart, {
+ passive: true,
+ });
+ }
+
+ @action unregisterListeners(element) {
+ element.removeEventListener('focusin', this.handleFocusIn);
+ element.removeEventListener('focusout', this.handleFocusOut);
+ element.removeEventListener('mousedown', this.handleMouseDown);
+ element.removeEventListener('mouseenter', this.handleMouseEnter);
+ element.removeEventListener('mouseleave', this.handleMouseLeave);
+ element.removeEventListener('mousemove', this.handleMouseMove);
+ element.removeEventListener('mouseup', this.handleMouseUp);
+ element.removeEventListener('pointermove', this.handlePointerMove);
+ element.removeEventListener('touchcancel', this.handleTouchCancel);
+ element.removeEventListener('touchend', this.handleTouchEnd);
+ element.removeEventListener('touchmove', this.handleTouchMove);
+ element.removeEventListener('touchstart', this.handleTouchStart);
+ }
+
+ /*
+ * Listen to `focusIn` and `focusOut` events instead of `focus` and `blur`.
+ * This way we don't need to explicitly bubble the events.
+ * They bubble by default.
+ */
+ @action handleFocusIn(e) {
+ if ((!this.disabled && !this.focusOnlyOnKey) || !this.pressed) {
+ this.focused = true;
+ if (this.args.onFocusIn) {
+ this.args.onFocusIn(e);
+ }
+ }
+ }
+
+ @action handleFocusOut(e) {
+ this.focused = false;
+ if (this.args.onFocusOut) {
+ this.args.onFocusOut(e);
+ }
+ }
+
+ @action handleMouseDown(e) {
+ this.down(e);
+ if (this.args.onMouseDown) {
+ this.args.onMouseDown(e);
+ }
+ }
+
+ @action handleMouseEnter(e) {
+ this.hover = true;
+ if (this.args.onMouseEnter) {
+ this.args.onMouseEnter(e);
+ }
+ }
+
+ @action handleMouseLeave(e) {
+ this.hover = false;
+ this.up(e);
+ if (this.args.onMouseLeave) {
+ this.args.onMouseLeave(e);
+ }
+ }
+
+ @action handleMouseMove(e) {
+ return this.move(e);
+ }
+
+ @action handleMouseUp(e) {
+ return this.up(e);
+ }
+
+ @action handlePointerMove(e) {
+ return this.move(e);
+ }
+
+ @action handleTouchCancel(e) {
+ return this.up(e);
+ }
+
+ @action handleTouchEnd(e) {
+ return this.up(e);
+ }
+
+ @action handleTouchMove(e) {
+ return this.move(e);
+ }
+
+ @action handleTouchStart(e) {
+ return this.down(e);
+ }
+
+ @action up() {
+ this.pressed = false;
+ if (!this.toggle) {
+ this.active = false;
+ }
+ }
+
+ @action down() {
+ this.pressed = true;
+ if (this.toggle) {
+ this.active = !this.active;
+ } else {
+ this.active = true;
+ }
+ }
+
+ move() {}
+}
diff --git a/addon/components/paper-button.hbs b/addon/components/paper-button.hbs
index 79afe4288..50ce9514d 100644
--- a/addon/components/paper-button.hbs
+++ b/addon/components/paper-button.hbs
@@ -1,11 +1,39 @@
-{{! template-lint-disable no-curly-component-invocation }}
-{{#if (has-block)}}
- {{yield}}
-{{else}}
- {{@label}}
-{{/if}}
+{{#let (element this.tag) as |Tag|}}
+
+ {{#if (has-block)}}
+ {{yield}}
+ {{else}}
+ {{@label}}
+ {{/if}}
-
+
+
+{{/let}}
\ No newline at end of file
diff --git a/addon/components/paper-button.js b/addon/components/paper-button.js
index 3314b6963..abb15c15e 100644
--- a/addon/components/paper-button.js
+++ b/addon/components/paper-button.js
@@ -1,59 +1,44 @@
-/* eslint-disable ember/no-classic-components, ember/no-mixins, ember/require-tagless-components */
/**
* @module ember-paper
*/
-import { reads } from '@ember/object/computed';
-
-import Component from '@ember/component';
-import FocusableMixin from 'ember-paper/mixins/focusable-mixin';
-import ProxiableMixin from 'ember-paper/mixins/proxiable-mixin';
-import { invokeAction } from 'ember-paper/utils/invoke-action';
+import Focusable from './-focusable';
+import { action } from '@ember/object';
/**
* @class PaperButton
- * @extends Ember.Component
- * @uses FocusableMixin
- * @uses ProxiableMixin
+ * @extends Focusable
*/
-export default Component.extend(FocusableMixin, ProxiableMixin, {
- tagName: 'button',
- classNames: ['md-default-theme', 'md-button'],
- raised: false,
- iconButton: false,
-
- // circular button
- fab: reads('mini'),
-
- mini: false,
- type: 'button',
- href: null,
- target: null,
-
- attributeBindings: ['type', 'href', 'target', 'title', 'download', 'rel'],
-
- classNameBindings: [
- 'raised:md-raised',
- 'iconButton:md-icon-button',
- 'fab:md-fab',
- 'mini:md-mini',
- 'warn:md-warn',
- 'accent:md-accent',
- 'primary:md-primary',
- ],
-
- init() {
- this._super(...arguments);
- if (this.href) {
- this.setProperties({
- tagName: 'a',
- type: null,
- });
+export default class PaperButton extends Focusable {
+ get tag() {
+ if (this.args.href) {
+ return 'a';
+ }
+
+ return 'button';
+ }
+
+ get type() {
+ if (this.args.type) {
+ return this.args.type;
+ }
+
+ return 'button';
+ }
+
+ get fab() {
+ return this.args.fab || this.args.mini;
+ }
+
+ @action handleClick(e) {
+ if (this.args.onClick) {
+ this.args.onClick(e);
}
- },
- click(e) {
- invokeAction(this, 'onClick', e);
// Prevent bubbling, if specified. If undefined, the event will bubble.
- return this.bubbles;
- },
-});
+ if (this.args.bubbles === undefined) {
+ return true;
+ }
+
+ return this.args.bubbles;
+ }
+}