Port disabled fixes from 2.x

Related to #5190
Port of #5230 to 3.x
This commit is contained in:
Daniel Freedman
2018-05-14 17:37:43 -07:00
parent dfc8ea3700
commit 358a1c6708
3 changed files with 198 additions and 51 deletions

View File

@@ -112,6 +112,20 @@ const labellable = {
'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
@@ -1051,7 +1065,7 @@ register({
let dy = Math.abs(e.clientY - this.info.y);
// find original target from `preventer` for TouchEvents, or `e` for MouseEvents
let t = _findOriginalTarget((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`

View File

@@ -12,6 +12,7 @@ import { Polymer } from '../../lib/legacy/polymer-fn.js';
import { html } from '../../lib/utils/html-tag.js';
import { PolymerElement } from '../../polymer-element.js';
import { GestureEventListeners } from '../../lib/mixins/gesture-event-listeners.js';
import { addListener } from '../../lib/utils/gestures.js';
Polymer({
_template: html`
<style>
@@ -196,23 +197,88 @@ class XNativeLabelNested extends PolymerElement {
}
}
customElements.define(XNativeLabelNested.is, XNativeLabelNested);
class XDisabled extends GestureEventListeners(PolymerElement) {
static get template() {
return html`
<button id="disabled" on-tap="tap" disabled=""></button>
<div disabled="">
<button id="nested" on-tap="tap"></button>
</div>
`;
}
class XDisabled extends PolymerElement {
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);
class XDisabledTap extends GestureEventListeners(PolymerElement) {
constructor() {
super();
this.taps = [];
}
static get is() {return 'x-disabled-tap';}
static get template() {
return html`
<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>`
}
static get is() {
return 'x-disabled-tap';
}
tap(e) {
this.taps.push(e.id);
const target = e.target;
this.taps.push(`${target.localName}${target.id ? '#' + target.id : ''}`);
}
}
customElements.define(XDisabled.is, XDisabled);
customElements.define(XDisabledTap.is, XDisabledTap);
class AllDisabled extends GestureEventListeners(PolymerElement) {
static get is() {
return 'all-disabled';
}
static get template() {
return html`
<button></button>
<!-- MDN lists as obsolete -->
<!-- <command></command> -->
<fieldset></fieldset>
<input>
<!-- MDN lists as obsolete -->
<!-- <keygen> -->
<select>
<optgroup>
<option></option>
</optgroup>
</select>
<textarea></textarea>`;
}
constructor() {
super();
this.taps = [];
}
tap(e) {
this.taps.push(e.target.localName);
}
ready() {
super.ready();
this.shadowRoot.querySelectorAll('*').forEach((el) => {
el.setAttribute('disabled', '');
addListener(el, 'tap', (e) => this.tap(e));
});
}
tapAll() {
this.shadowRoot.querySelectorAll('*').forEach((el) => {
el.click();
});
}
}
customElements.define(AllDisabled.is, AllDisabled);

View File

@@ -619,44 +619,111 @@ suite('Regression Testing', function() {
});
});
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() {
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']);
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>