Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(addon/components/paper-switch): converts to a glimmer component. #1310

Open
wants to merge 2 commits into
base: feature/glimmer-paper-radio
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions addon/components/paper-switch.hbs
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
{{! template-lint-disable no-curly-component-invocation }}
<div class="md-switch-bar"></div>
<div class="md-container">
<div class="md-bar"></div>
<div class="md-thumb-container" style={{this.thumbContainerStyle}}>
<div class="md-thumb">
<PaperRipple @center={{true}} @fitRipple={{true}}/>
<md-switch
class='paper-switch md-default-theme
{{~if @accent " md-accent"}}
{{~if @primary " md-primary"}}
{{~if @warn " md-warn"}}
{{~if this.dragging " md-dragging"}}
{{~if this.focused " md-focused"}}
{{~if this.value " md-checked"}} {{@class}}'
disabled={{this.disabled}}
tabindex={{if this.disabled '-1' '0'}}
{{did-insert this.didInsertNode}}
{{did-update this.didUpdateNode this.disabled this._switchContainerHammer}}
{{will-destroy this.willDestroyNode}}
{{on 'keypress' this.handleKeyPress}}
...attributes
>
<div class='md-switch-bar'></div>
<div class='md-container'>
<div class='md-bar'></div>
<div class='md-thumb-container' style={{this.thumbContainerStyle}}>
<div class='md-thumb'>
<PaperRipple @center={{true}} @fitRipple={{true}} />
</div>
</div>
</div>
</div>
{{#if (has-block)}}
<div class="md-label">
{{yield}}
</div>
{{else}}
<div class="md-label">
{{@label}}
</div>
{{/if}}
{{#if (has-block)}}
<div class='md-label'>
{{yield}}
</div>
{{else}}
<div class='md-label'>
{{@label}}
</div>
{{/if}}
</md-switch>
247 changes: 157 additions & 90 deletions addon/components/paper-switch.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,156 @@
/* eslint-disable ember/no-classic-components, ember/no-component-lifecycle-hooks, ember/no-get, ember/no-mixins, ember/require-tagless-components, no-unused-vars */
/* global Hammer */
/**
* @module ember-paper
*/
import { inject as service } from '@ember/service';

import Component from '@ember/component';
import Focusable from './-focusable';
import { tracked } from '@glimmer/tracking';
import { assert } from '@ember/debug';
import { get, computed } from '@ember/object';
import { bind } from '@ember/runloop';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { htmlSafe } from '@ember/template';
import FocusableMixin from 'ember-paper/mixins/focusable-mixin';
import ProxiableMixin from 'ember-paper/mixins/proxiable-mixin';
import { invokeAction } from 'ember-paper/utils/invoke-action';

/* global Hammer */

/**
* @class PaperSwitch
* @extends Ember.Component
* @uses FocusableMixin
* @uses ProxiableMixin
* @extends Component
*/
export default Component.extend(FocusableMixin, ProxiableMixin, {
tagName: 'md-switch',
classNames: ['paper-switch', 'md-default-theme'],
classNameBindings: [
'value:md-checked',
'dragging:md-dragging',
'warn:md-warn',
'accent:md-accent',
'primary:md-primary',
],
toggle: true,
constants: service(),
value: false,
disabled: false,
dragging: false,

thumbContainerStyle: computed('dragging', 'dragAmount', function () {
if (!this.dragging) {
return htmlSafe('');
export default class PaperSwitch extends Focusable {
@service constants;

/**
* Reference to the component's DOM element
* @type {HTMLElement}
*/
element;
/**
* The parent this component is bound to.
* @type {PaperRadioGroup|PaperForm|PaperItem|PaperTabs}
*/
parent;
/**
* Marks whether the component should register itself to the supplied parent
* @type {Boolean}
*/
shouldRegister;
/**
* Marks whether the component should skip being proxied.
* @type {Boolean}
*/
skipProxy;

/* Focusable Overrides */
toggle = true;

/**
* specifies the positive amount the switch has been dragged.
* @type {number|null}
*/
@tracked dragAmount = null;
/**
* specifies whether the switch is currently being dragged.
* @type {boolean}
*/
@tracked dragging = false;
/**
* specifies the width of the switch to calculate drag deltas.
* @type {number}
*/
@tracked switchWidth = 0;

// Lifecycle hooks
constructor(owner, args) {
super(owner, args);

this.shouldRegister = this.args.shouldRegister || false;
this.skipProxy = this.args.skipProxy || false;
this.toggle = this.args.toggle || false;

if (this.shouldRegister) {
assert(
'A parent component should be supplied to <PaperSwitch> when shouldRegister=true',
this.args.parentComponent
);
this.parent = this.args.parentComponent;
}

let translate = Math.max(0, Math.min(100, this.dragAmount * 100));
let transformProp = `translate3d(${translate}%, 0, 0)`;
return htmlSafe(
`transform: ${transformProp};-webkit-transform: ${transformProp}`
assert(
'<PaperSwitch> requires an `onChange` action or null for no action.',
this.args.onChange !== undefined
);
}),

didInsertElement() {
this._super(...arguments);
}

/**
* Performs any required DOM setup.
* @param element
*/
@action didInsertNode(element) {
this.element = element;
this.registerListeners(element);

if (this.shouldRegister) {
this.parent.registerChild(this);
}

// Only setup if the switch is not disabled
if (!this.disabled) {
this._setupSwitch();
}
},

init() {
this._super(...arguments);
assert(
'{{paper-switch}} requires an `onChange` action or null for no action.',
this.onChange !== undefined
);
},

willDestroyElement() {
this._super(...arguments);
this._teardownSwitch();
},

didUpdateAttrs() {
this._super(...arguments);
}

@action didUpdateNode() {
if (!this.disabled && !this._switchContainerHammer) {
this._setupSwitch();
} else if (!this.disabled && this._switchContainerHammer) {
this._switchContainerHammer.set({ enable: true });
} else if (this.disabled && this._switchContainerHammer) {
this._switchContainerHammer.set({ enable: false });
}
},
}

/**
* Performs any required DOM teardown.
* @param element
*/
@action willDestroyNode(element) {
this.unregisterListeners(element);
this._teardownSwitch();
}

_setupSwitch() {
this.set(
'switchWidth',
this.element.querySelector('.md-thumb-container').offsetWidth
willDestroy() {
super.willDestroy(...arguments);

if (this.shouldRegister) {
this.parent.unregisterChild(this);
}
}

/**
* specifies the current switch value.
* @type {boolean}
*/
get value() {
return this.args.value;
}

/**
* Calculates and returns a css animation transform for the switch's thumb.
* @returns {string}
*/
get thumbContainerStyle() {
if (!this.dragging) {
return htmlSafe('');
}

let translate = Math.max(0, Math.min(100, this.dragAmount * 100));
let transformProp = `translate3d(${translate}%, 0, 0)`;
return htmlSafe(
`transform: ${transformProp};-webkit-transform: ${transformProp}`
);
}

_setupSwitch() {
this.switchWidth = this.element.querySelector(
'.md-thumb-container'
).offsetWidth;

let switchContainer = this.element.querySelector('.md-container');
let switchHammer = new Hammer(switchContainer);
Expand All @@ -96,20 +159,20 @@ export default Component.extend(FocusableMixin, ProxiableMixin, {
// Enable dragging the switch container
switchHammer.get('pan').set({ threshold: 1 });
switchHammer
.on('panstart', bind(this, this._dragStart))
.on('panmove', bind(this, this._drag))
.on('panend', bind(this, this._dragEnd));
.on('panstart', this._dragStart.bind(this))
.on('panmove', this._drag.bind(this))
.on('panend', this._dragEnd.bind(this));

// Enable tapping gesture on the switch
this._switchHammer = new Hammer(this.element);
this._switchHammer.on('tap', bind(this, this._dragEnd));
this._switchHammer.on('tap', this._dragEnd.bind(this));

this._onClickHandleNativeClick = bind(this, this._handleNativeClick);
this._onClickHandleNativeClick = this._handleNativeClick.bind(this);

this.element
.querySelector('.md-container')
.addEventListener('click', this._onClickHandleNativeClick);
},
}

_handleNativeClick(ev) {
let bubbles = this.bubbles;
Expand All @@ -119,7 +182,7 @@ export default Component.extend(FocusableMixin, ProxiableMixin, {
}

return bubbles;
},
}

_teardownSwitch() {
if (this._switchContainerHammer) {
Expand All @@ -130,19 +193,19 @@ export default Component.extend(FocusableMixin, ProxiableMixin, {
.querySelector('.md-container')
.removeEventListener('click', this._onClickHandleNativeClick);
this._onClickHandleNativeClick = null;
},
}

_dragStart() {
this.set('dragAmount', +this.value);
this.set('dragging', true);
},
this.dragAmount = +this.value;
this.dragging = true;
}

_drag(event) {
if (!this.disabled) {
// Set the amount the switch has been dragged
this.set('dragAmount', +this.value + event.deltaX / this.switchWidth);
this.dragAmount = +this.value + event.deltaX / this.switchWidth;
}
},
}

_dragEnd() {
if (!this.disabled) {
Expand All @@ -154,31 +217,35 @@ export default Component.extend(FocusableMixin, ProxiableMixin, {
(value && dragAmount < 0.5) ||
(!value && dragAmount > 0.5)
) {
invokeAction(this, 'onChange', !value);
if (this.args.onChange) {
this.args.onChange(!value);
}
}
this.set('dragging', false);
this.set('dragAmount', null);
this.dragging = false;
this.dragAmount = null;
}
},
}

focusIn() {
@action handleFocusIn() {
// Focusing in w/o being pressed should use the default behavior
if (!this.pressed) {
this._super(...arguments);
super.handleFocusIn(...arguments);
}
},
}

keyPress(ev) {
@action handleKeyPress(ev) {
if (
ev.which === this.get('constants.KEYCODE.SPACE') ||
ev.which === this.get('constants.KEYCODE.ENTER')
ev.which === this.constants.KEYCODE.SPACE ||
ev.which === this.constants.KEYCODE.ENTER
) {
ev.preventDefault();
this._dragEnd();
}
},
}

processProxy() {
invokeAction(this, 'onChange', !this.value);
},
});
if (this.args.onChange) {
this.args.onChange(!this.value);
}
}
}
Loading