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; + } +}