fix(button): remove active when mouse/key up on other element (fix #210)

This commit is contained in:
Mikhail Bashkirov 2019-07-26 16:19:01 +02:00
parent 471d662cb2
commit f3303ae014
2 changed files with 84 additions and 31 deletions

View file

@ -138,12 +138,12 @@ export class LionButton extends DisabledWithTabIndexMixin(
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.__setupDelegation(); this.__setupEvents();
} }
disconnectedCallback() { disconnectedCallback() {
super.disconnectedCallback(); super.disconnectedCallback();
this.__teardownDelegation(); this.__teardownEvents();
} }
_redispatchClickEvent(oldEvent) { _redispatchClickEvent(oldEvent) {
@ -180,42 +180,50 @@ export class LionButton extends DisabledWithTabIndexMixin(
this.addEventListener('click', this.__clickDelegationHandler, true); this.addEventListener('click', this.__clickDelegationHandler, true);
} }
__setupDelegation() { __setupEvents() {
this.addEventListener('mousedown', this.__mousedownDelegationHandler); this.addEventListener('mousedown', this.__mousedownHandler);
this.addEventListener('mouseup', this.__mouseupDelegationHandler); this.addEventListener('keydown', this.__keydownHandler);
this.addEventListener('keydown', this.__keydownDelegationHandler); this.addEventListener('keyup', this.__keyupHandler);
this.addEventListener('keyup', this.__keyupDelegationHandler);
} }
__teardownDelegation() { __teardownEvents() {
this.removeEventListener('mousedown', this.__mousedownDelegationHandler); this.removeEventListener('mousedown', this.__mousedownHandler);
this.removeEventListener('mouseup', this.__mouseupDelegationHandler); this.removeEventListener('keydown', this.__keydownHandler);
this.removeEventListener('keydown', this.__keydownDelegationHandler); this.removeEventListener('keyup', this.__keyupHandler);
this.removeEventListener('keyup', this.__keyupDelegationHandler);
} }
__mousedownDelegationHandler() { __mousedownHandler() {
this.active = true; this.active = true;
} const mouseupHandler = () => {
__mouseupDelegationHandler() {
this.active = false; this.active = false;
document.removeEventListener('mouseup', mouseupHandler);
};
document.addEventListener('mouseup', mouseupHandler);
} }
__keydownDelegationHandler(e) { __keydownHandler(e) {
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) { if (!this.__isKeyboardClickEvent(e)) {
e.preventDefault(); return;
}
this.active = true; this.active = true;
const keyupHandler = keyupEvent => {
if (this.__isKeyboardClickEvent(keyupEvent)) {
this.active = false;
document.removeEventListener('keyup', keyupHandler, true);
} }
};
document.addEventListener('keyup', keyupHandler, true);
} }
__keyupDelegationHandler(e) { __keyupHandler(e) {
// Makes the real button the trigger in forms (will submit form, as opposed to paper-button) if (this.__isKeyboardClickEvent(e)) {
// and make click handlers on button work on space and enter // redispatch click
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) {
e.preventDefault();
this.active = false;
this.shadowRoot.querySelector('.click-area').click(); this.shadowRoot.querySelector('.click-area').click();
} }
} }
// eslint-disable-next-line class-methods-use-this
__isKeyboardClickEvent(e) {
return e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */;
}
} }

View file

@ -77,6 +77,21 @@ describe('lion-button', () => {
expect(el.hasAttribute('active')).to.be.false; expect(el.hasAttribute('active')).to.be.false;
}); });
it('updates "active" attribute on host when mousedown on button and mouseup anywhere else', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`);
const topEl = getTopElement(el);
down(topEl);
expect(el.active).to.be.true;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.true;
up(document.body);
expect(el.active).to.be.false;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.false;
});
it('updates "active" attribute on host when space keydown/keyup on button', async () => { it('updates "active" attribute on host when space keydown/keyup on button', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`); const el = await fixture(`<lion-button>foo</lion-button>`);
const topEl = getTopElement(el); const topEl = getTopElement(el);
@ -92,6 +107,21 @@ describe('lion-button', () => {
expect(el.hasAttribute('active')).to.be.false; expect(el.hasAttribute('active')).to.be.false;
}); });
it('updates "active" attribute on host when space keydown on button and space keyup anywhere else', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`);
const topEl = getTopElement(el);
keyDownOn(topEl, 32);
expect(el.active).to.be.true;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.true;
keyUpOn(document.body, 32);
expect(el.active).to.be.false;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.false;
});
it('updates "active" attribute on host when enter keydown/keyup on button', async () => { it('updates "active" attribute on host when enter keydown/keyup on button', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`); const el = await fixture(`<lion-button>foo</lion-button>`);
const topEl = getTopElement(el); const topEl = getTopElement(el);
@ -106,6 +136,21 @@ describe('lion-button', () => {
await el.updateComplete; await el.updateComplete;
expect(el.hasAttribute('active')).to.be.false; expect(el.hasAttribute('active')).to.be.false;
}); });
it('updates "active" attribute on host when enter keydown on button and space keyup anywhere else', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`);
const topEl = getTopElement(el);
keyDownOn(topEl, 13);
expect(el.active).to.be.true;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.true;
keyUpOn(document.body, 13);
expect(el.active).to.be.false;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.false;
});
}); });
describe('a11y', () => { describe('a11y', () => {