Skip to content

Commit

Permalink
Merge pull request #5230 from Polymer/2.x-tap-fix-disabled
Browse files Browse the repository at this point in the history
Only disable tabs for "disablable" elements
  • Loading branch information
dfreedm authored May 15, 2018
2 parents 5f5d2c2 + 6255cdb commit 0df1e4c
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: node_js
sudo: false
dist: trusty
node_js: stable
node_js: 8
addons:
firefox: latest
chrome: stable
Expand Down
16 changes: 15 additions & 1 deletion lib/utils/gestures.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@
'select': true
};

// Defined at https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute
/** @type {!Object<boolean>} */
const canBeDisabled = {
'button': true,
'command': true,
'fieldset': true,
'input': true,
'keygen': true,
'optgroup': true,
'option': true,
'select': true,
'textarea': true
};

/**
* @param {HTMLElement} el Element to check labelling status
* @return {boolean} element can have labels
Expand Down Expand Up @@ -1058,7 +1072,7 @@
let dy = Math.abs(e.clientY - this.info.y);
// find original target from `preventer` for TouchEvents, or `e` for MouseEvents
let t = Gestures._findOriginalTarget(/** @type {Event} */(preventer || e));
if (!t || t.disabled) {
if (!t || (canBeDisabled[/** @type {!HTMLElement} */(t).localName] && t.hasAttribute('disabled'))) {
return;
}
// dx,dy can be NaN if `click` has been simulated and there was no `down` for `start`
Expand Down
78 changes: 74 additions & 4 deletions test/unit/gestures-elements.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,24 +244,94 @@
</script>
</dom-module>

<script>
class XDisabled extends Polymer.Element {
static get is() {
return 'x-disabled';
}
static get properties() {
return {
disabled: {
type: Boolean,
reflectToAttribute: true
}
};
}
constructor() {
super();
this.disabled = true;
}
}
customElements.define(XDisabled.is, XDisabled);
</script>

<dom-module id="x-disabled-tap">
<template>
<button id="disabled" on-tap="tap" disabled></button>
<div disabled>
<button id="nested" on-tap="tap"></button>
</div>
<x-disabled id="disabledEl" on-tap="tap"></x-disabled>
</template>
<script>
class XDisabledTap extends Polymer.GestureEventListeners(Polymer.Element) {
constructor() {
super();
this.taps = [];
}
static get is() {
return 'x-disabled-tap';
}
tap(e) {
const target = e.target;
this.taps.push(`${target.localName}${target.id ? '#' + target.id : ''}`);
}
}
customElements.define(XDisabledTap.is, XDisabledTap);
</script>
</dom-module>

<dom-module id="all-disabled">
<template>
<button></button>
<!-- MDN lists as obsolete -->
<!-- <command></command> -->
<fieldset></fieldset>
<input>
<!-- MDN lists as obsolete -->
<!-- <keygen> -->
<select>
<optgroup>
<option></option>
</optgroup>
</select>
<textarea></textarea>
</template>
<script>
class XDisabled extends Polymer.GestureEventListeners(Polymer.Element) {
class AllDisabled extends Polymer.GestureEventListeners(Polymer.Element) {
static get is() {
return 'all-disabled';
}
constructor() {
super();
this.taps = [];
}
static get is() {return 'x-disabled-tap';}
tap(e) {
this.taps.push(e.id);
this.taps.push(e.target.localName);
}
ready() {
super.ready();
this.shadowRoot.querySelectorAll('*').forEach((el) => {
el.setAttribute('disabled', '');
Polymer.Gestures.addListener(el, 'tap', (e) => this.tap(e));
});
}
tapAll() {
this.shadowRoot.querySelectorAll('*').forEach((el) => {
el.click();
});
}
}
customElements.define(XDisabled.is, XDisabled);
customElements.define(AllDisabled.is, AllDisabled);
</script>
</dom-module>
143 changes: 105 additions & 38 deletions test/unit/gestures.html
Original file line number Diff line number Diff line change
Expand Up @@ -615,44 +615,111 @@
});
});

test('disabled elements don\'t fire taps', function() {
let el = document.createElement('x-disabled-tap');
document.body.appendChild(el);
// tap an element with disabled attribute
let target = el.$.disabled;
// simulate the event sequence of a touch on the screen
let touches = [{
clientX: 0,
clientY: 0,
identifier: 1,
// target is set to the element with `addEventListener`, which is `target`
target
}];
let touchstart = new CustomEvent('touchstart', {bubbles: true, composed: true});
touchstart.changedTouches = touchstart.touches = touches;
target.dispatchEvent(touchstart);
let touchend = new CustomEvent('touchend', {bubbles: true, composed: true});
touchend.touches = touchend.changedTouches = touches;
target.dispatchEvent(touchend);
assert.equal(el.taps.length, 0);
// tap an element with a disabled ancestor
target = el.$.nested;
// simulate the event sequence of a touch on the screen
touches = [{
clientX: 0,
clientY: 0,
identifier: 1,
// target is set to the element with `addEventListener`, which is `target`
target
}];
touchstart = new CustomEvent('touchstart', {bubbles: true, composed: true});
touchstart.changedTouches = touchstart.touches = touches;
target.dispatchEvent(touchstart);
touchend = new CustomEvent('touchend', {bubbles: true, composed: true});
touchend.touches = touchend.changedTouches = touches;
target.dispatchEvent(touchend);
assert.equal(el.taps.length, 1);
document.body.removeChild(el);
suite('disabled', function() {
let shouldSkip = true;

suiteSetup(function() {
/*
* IE 11 does not dispatch events to elements with `disabled` attribute
* This is different from all other browsers, so skip these tests in IE 11
*/
const div = document.createElement('div');
div.setAttribute('disabled', '');
document.body.appendChild(div);
div.addEventListener('click', () => {
shouldSkip = false;
});
div.click();
document.body.removeChild(div);
});
setup(function() {
Polymer.Gestures.resetMouseCanceller();
});

test('click() function works as expected on disabled elements', function() {
if (shouldSkip) {
this.skip();
}
let el = document.createElement('x-disabled-tap');
document.body.appendChild(el);
el.$.disabled.click();
el.$.nested.click();
el.$.disabledEl.click();
assert.deepEqual(el.taps, ['button#nested', 'x-disabled#disabledEl']);
document.body.removeChild(el);
});

test('disabled elements don\'t fire taps', function() {
if (shouldSkip) {
this.skip();
}
let el = document.createElement('x-disabled-tap');
document.body.appendChild(el);
// tap an element with disabled attribute
let target = el.$.disabled;
// simulate the event sequence of a touch on the screen
let touches = [{
clientX: 0,
clientY: 0,
identifier: 1,
// target is set to the element with `addEventListener`, which is `target`
target
}];
let touchstart = new CustomEvent('touchstart', {bubbles: true, composed: true});
touchstart.changedTouches = touchstart.touches = touches;
target.dispatchEvent(touchstart);
let touchend = new CustomEvent('touchend', {bubbles: true, composed: true});
touchend.touches = touchend.changedTouches = touches;
target.dispatchEvent(touchend);
assert.deepEqual(el.taps, []);

// tap an element with a disabled ancestor
target = el.$.nested;
// simulate the event sequence of a touch on the screen
touches = [{
clientX: 0,
clientY: 0,
identifier: 1,
// target is set to the element with `addEventListener`, which is `target`
target
}];
touchstart = new CustomEvent('touchstart', {bubbles: true, composed: true});
touchstart.changedTouches = touchstart.touches = touches;
target.dispatchEvent(touchstart);
touchend = new CustomEvent('touchend', {bubbles: true, composed: true});
touchend.touches = touchend.changedTouches = touches;
target.dispatchEvent(touchend);
assert.deepEqual(el.taps, ['button#nested']);

Polymer.Gestures.resetMouseCanceller();

// tap a custom element with a `disabled` property
target = el.$.disabledEl;
// simulate the event sequence of a touch on the screen
touches = [{
clientX: 0,
clientY: 0,
identifier: 1,
// target is set to the element with `addEventListener`, which is `target`
target
}];
touchstart = new CustomEvent('touchstart', {bubbles: true, composed: true});
touchstart.changedTouches = touchstart.touches = touches;
target.dispatchEvent(touchstart);
touchend = new CustomEvent('touchend', {bubbles: true, composed: true});
touchend.touches = touchend.changedTouches = touches;
target.dispatchEvent(touchend);
assert.deepEqual(el.taps, ['button#nested', 'x-disabled#disabledEl']);
document.body.removeChild(el);
});

test('test all "disableable" elements', function() {
const el = document.createElement('all-disabled');
document.body.appendChild(el);
el.tapAll();
assert.deepEqual(el.taps, []);
document.body.removeChild(el);
});
});
});
</script>
Expand Down

0 comments on commit 0df1e4c

Please sign in to comment.