Skip to content

Commit

Permalink
fix(shared): prevent focusable returning focus to host
Browse files Browse the repository at this point in the history
  • Loading branch information
Westbrook Johnson authored and Westbrook committed Jul 22, 2020
1 parent 12bdecb commit 745f7b0
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
55 changes: 55 additions & 0 deletions packages/checkbox/test/checkbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
triggerBlurFor,
html,
expect,
nextFrame,
} from '@open-wc/testing';
import { waitForPredicate } from '../../../test/testing-helpers';
import '@spectrum-web-components/shared/src/focus-visible.js';
Expand Down Expand Up @@ -132,6 +133,60 @@ describe('Checkbox', () => {
expect(document.activeElement).to.not.equal(autoElement);
});

it('`click()`ing host clicks `focusElement`', async () => {
const el = await fixture<Checkbox>(
html`
<sp-checkbox checked autofocus>Checked</sp-checkbox>
`
);

await elementUpdated(el);

expect(el.checked, 'checked initially').to.be.true;

el.click();
await elementUpdated(el);

expect(el.checked, 'unchecked').to.be.false;

el.click();
await elementUpdated(el);

expect(el.checked, 'checked again').to.be.true;
});

it('focus is not relenquished to host on second click', async () => {
const el = await fixture<Checkbox>(
html`
<sp-checkbox checked>Checked</sp-checkbox>
`
);

await elementUpdated(el);

const root = el.shadowRoot ? el.shadowRoot : document;
expect(
document.activeElement,
'based on autofocus external'
).to.not.equal(el);
expect(root.activeElement, 'based on autofocus internal').to.not.equal(
el.focusElement
);

// emulate the events that occur during a "second click" on the `:host()`
el.dispatchEvent(new CustomEvent('focusin'));
HTMLElement.prototype.focus.apply(el);
el.focusElement.dispatchEvent(new CustomEvent('focusout'));
await nextFrame();
expect(
document.activeElement,
'based on repeated click external'
).to.equal(el);
expect(root.activeElement, 'based on repeated click internal').to.equal(
el.focusElement
);
});

it('respects checked attribute', () => {
let el = testFixture.querySelector('#checkbox0') as Checkbox;
expect(el.checked).to.be.false;
Expand Down
18 changes: 18 additions & 0 deletions packages/shared/src/focusable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export class Focusable extends FocusVisiblePolyfillMixin(LitElement) {
this.focusElement.blur();
}

public click(): void {
this.focusElement.click();
}

protected manageAutoFocus(): void {
if (this.autofocus) {
/* Trick :focus-visible polyfill into thinking keyboard based focus */
Expand All @@ -89,11 +93,25 @@ export class Focusable extends FocusVisiblePolyfillMixin(LitElement) {
this.manageShiftTab();
}

private refocusTimeout = 0;

protected manageFocusIn(): void {
this.addEventListener('focusin', (event) => {
if (event.composedPath()[0] === this) {
this.handleFocus();
}
const innerHandler = (): void => {
this.refocusTimeout = setTimeout(() => {
this.focus();
});
};
const outerHandler = (): void => {
clearTimeout(this.refocusTimeout);
this.focusElement.removeEventListener('focusout', innerHandler);
this.removeEventListener('focusout', outerHandler);
};
this.focusElement.addEventListener('focusout', innerHandler);
this.addEventListener('focusout', outerHandler);
});
}

Expand Down

0 comments on commit 745f7b0

Please sign in to comment.