Skip to content

Commit

Permalink
Add support for stylus (Apple Pencil) (#1406)
Browse files Browse the repository at this point in the history
* Add support for stylus (Apple Pencil)

* Comment about distance

* Resolve merge issues

* extra arg
  • Loading branch information
krasnoukhov authored Aug 16, 2022
1 parent ed0b379 commit 0a1c8bd
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 7 deletions.
30 changes: 24 additions & 6 deletions addon/components/power-select/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface Args {
select: Select
highlightOnHover?: boolean
options: any[]
extra: any
}

const isTouchDevice = (!!window && 'ontouchstart' in window);
Expand All @@ -32,8 +33,8 @@ if(typeof FastBoot === 'undefined'){
}

export default class Options extends Component<Args> {
private isTouchDevice = isTouchDevice
private hasMoved = false
private isTouchDevice = this.args.extra?._isTouchDevice || isTouchDevice
private touchMoveEvent?: TouchEvent
private mouseOverHandler = (_: MouseEvent): void => {}
private mouseUpHandler = (_: MouseEvent): void => {}
private touchEndHandler = (_: TouchEvent): void => {}
Expand Down Expand Up @@ -66,8 +67,8 @@ export default class Options extends Component<Args> {
element.addEventListener('mouseover', this.mouseOverHandler);
}
if (this.isTouchDevice) {
this.touchMoveHandler = (_: TouchEvent): void => {
this.hasMoved = true;
this.touchMoveHandler = (e: TouchEvent): void => {
this.touchMoveEvent = e;
if (element) {
element.removeEventListener('touchmove', this.touchMoveHandler);
}
Expand All @@ -81,8 +82,8 @@ export default class Options extends Component<Args> {
let optionItem = (e.target as Element).closest('[data-option-index]');
if (optionItem === null) return;
e.preventDefault();
if (this.hasMoved) {
this.hasMoved = false;
if (this._hasMoved(e)) {
this.touchMoveEvent = undefined;
return;
}

Expand Down Expand Up @@ -119,4 +120,21 @@ export default class Options extends Component<Args> {
}
return option;
}

_hasMoved(endEvent: TouchEvent): boolean {
let moveEvent = this.touchMoveEvent;
if (!moveEvent) {
return false;
}

let changedTouch = moveEvent.changedTouches[0];
if (!endEvent.changedTouches?.[0] || (changedTouch as any).touchType !== 'stylus') {
return true;
}

// Distinguish stylus scroll and tap: if touch "distance" < 5px, we consider it a tap
let horizontalDistance = Math.abs(changedTouch.pageX - endEvent.changedTouches[0].pageX);
let verticalDistance = Math.abs(changedTouch.pageY - endEvent.changedTouches[0].pageY);
return horizontalDistance >= 5 || verticalDistance >= 5;
}
}
58 changes: 57 additions & 1 deletion tests/integration/components/power-select/touch-control-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, tap } from '@ember/test-helpers';
import { render, tap, triggerEvent } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { numbers } from '../constants';

Expand Down Expand Up @@ -67,5 +67,61 @@ module(
await tap('.ember-power-select-clear-btn');
assert.dom('.ember-power-select-selected-item').doesNotExist();
});

test('Scrolling (touchstart + touchmove + touchend) should not select the option', async function (assert) {
assert.expect(1);

this.numbers = numbers;
await render(hbs`
<PowerSelect @options={{this.numbers}} @selected={{this.foo}} @extra={{hash _isTouchDevice=true}} @onChange={{action (mut this.foo)}} as |option|>
{{option}}
</PowerSelect>
`);

await tap('.ember-power-select-trigger');
let option = document.querySelectorAll('.ember-power-select-option')[3];
await triggerEvent(option, 'touchstart');
await triggerEvent(option, 'touchmove', {
changedTouches: [{ touchType: 'direct', pageX: 0, pageY: 0 }],
});
await triggerEvent(option, 'touchend', {
changedTouches: [{ touchType: 'direct', pageX: 0, pageY: 10 }],
});
assert.dom('.ember-power-select-selected-item').doesNotExist();
});

test('Using stylus on touch device should select the option', async function (assert) {
assert.expect(2);

this.numbers = numbers;
await render(hbs`
<PowerSelect @options={{this.numbers}} @selected={{this.foo}} @extra={{hash _isTouchDevice=true}} @onChange={{action (mut this.foo)}} as |option|>
{{option}}
</PowerSelect>
`);

await tap('.ember-power-select-trigger');
let option = document.querySelectorAll('.ember-power-select-option')[3];

// scroll
await triggerEvent(option, 'touchstart');
await triggerEvent(option, 'touchmove', {
changedTouches: [{ touchType: 'stylus', pageX: 0, pageY: 0 }],
});
await triggerEvent(option, 'touchend', {
changedTouches: [{ touchType: 'stylus', pageX: 0, pageY: 10 }],
});
assert.dom('.ember-power-select-selected-item').doesNotExist();

// tap
await triggerEvent(option, 'touchstart');
await triggerEvent(option, 'touchmove', {
changedTouches: [{ touchType: 'stylus', pageX: 0, pageY: 0 }],
});
await triggerEvent(option, 'touchend', {
changedTouches: [{ touchType: 'stylus', pageX: 4, pageY: 0 }],
});
assert.dom('.ember-power-select-selected-item').hasText('four');
});
}
);

0 comments on commit 0a1c8bd

Please sign in to comment.