Skip to content

Commit

Permalink
refactor(engine): prevent focus events when engine manages focus
Browse files Browse the repository at this point in the history
  • Loading branch information
ekashida committed Mar 18, 2019
1 parent 5eb8bcc commit d0f659b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 25 deletions.
59 changes: 36 additions & 23 deletions packages/@lwc/engine/src/faux-shadow/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import assert from '../shared/assert';
import { windowAddEventListener, windowRemoveEventListener } from '../env/window';
import {
matches,
querySelectorAll,
Expand Down Expand Up @@ -206,24 +207,40 @@ function getNextTabbableElement(segments: QuerySegments): HTMLElement | null {
return getFirstTabbableMatch(next);
}

function muteEvent(event) {
event.preventDefault();
event.stopPropagation();
}
function muteFocusEventsDuringExecution(func: Function) {
windowAddEventListener.call(window, 'focusin', muteEvent, true);
windowAddEventListener.call(window, 'focusout', muteEvent, true);
func();
windowRemoveEventListener.call(window, 'focusin', muteEvent, true);
windowRemoveEventListener.call(window, 'focusout', muteEvent, true);
}

function focusOnNextOrBlur(focusEventTarget: EventTarget, segments: QuerySegments) {
const nextNode = getNextTabbableElement(segments);
if (isNull(nextNode)) {
// nothing to focus on, blur to invalidate the operation
(focusEventTarget as HTMLElement).blur();
return;
}
nextNode.focus();
muteFocusEventsDuringExecution(() => {
const nextNode = getNextTabbableElement(segments);
if (isNull(nextNode)) {
// nothing to focus on, blur to invalidate the operation
(focusEventTarget as HTMLElement).blur();
} else {
nextNode.focus();
}
});
}

function focusOnPrevOrBlur(focusEventTarget: EventTarget, segments: QuerySegments) {
const prevNode = getPreviousTabbableElement(segments);
if (isNull(prevNode)) {
// nothing to focus on, blur to invalidate the operation
(focusEventTarget as HTMLElement).blur();
return;
}
prevNode.focus();
muteFocusEventsDuringExecution(() => {
const prevNode = getPreviousTabbableElement(segments);
if (isNull(prevNode)) {
// nothing to focus on, blur to invalidate the operation
(focusEventTarget as HTMLElement).blur();
} else {
prevNode.focus();
}
});
}

function isFirstTabbableChild(target: EventTarget, segments: QuerySegments): boolean {
Expand Down Expand Up @@ -260,7 +277,9 @@ function keyboardFocusHandler(event: FocusEvent) {
// probably tabbing into element
const first = getFirstTabbableMatch(segments.inner);
if (!isNull(first)) {
first.focus();
muteFocusEventsDuringExecution(() => {
first.focus();
});
} else {
focusOnNextOrBlur(target, segments);
}
Expand Down Expand Up @@ -302,20 +321,14 @@ function keyboardFocusInHandler(event: FocusEvent) {
const post = relatedTargetPosition(host as HTMLElement, relatedTarget);
switch (post) {
case 1: // focus is probably coming from above
if (
isFirstFocusableChildReceivingFocus &&
relatedTarget === getPreviousTabbableElement(segments)
) {
if (isFirstFocusableChildReceivingFocus) {
// the focus was on the immediate focusable elements from above,
// it is almost certain that the focus is due to tab keypress
focusOnNextOrBlur(target, segments);
}
break;
case 2: // focus is probably coming from below
if (
isLastFocusableChildReceivingFocus &&
relatedTarget === getNextTabbableElement(segments)
) {
if (isLastFocusableChildReceivingFocus) {
// the focus was on the immediate focusable elements from above,
// it is almost certain that the focus is due to tab keypress
focusOnPrevOrBlur(target, segments);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<template>
<div>
<button class="initialize" onclick={handleClick}>initialize tabindex to 0</button>
</div>
<input class="first-outside" placeholder="first (outside)">
<div>
<integration-parent tabindex={computedTabIndex} onfocusout={handleFocusOut}></integration-parent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export default class Container extends LightningElement {
@track
computedTabIndex = 0;

handleClick() {
this.computedTabIndex = 0;
}

handleFocusOut() {
// Changes the tabindex in the middle of delegatesFocus simulation!
this.computedTabIndex = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
const assert = require('assert');
const URL = 'http://localhost:4567/issue-1031';

// Enable after fixing W-5936969
describe.skip('Tab navigation when tabindex -1', () => {
describe('issue #1031', () => {
before(() => {
browser.url(URL);
});

it('should skip child shadow when tabbing after dynamically updating parent tabindex from 0 to -1', function() {
browser.click('.initialize'); // init tabindex to 0
browser.click('.first-outside');
browser.keys(['Tab']); // host element
browser.keys(['Tab']); // second outside input
Expand All @@ -28,6 +28,7 @@ describe.skip('Tab navigation when tabindex -1', () => {
});

it('should skip child shadow when shift-tabbing after dynamically updating parent tabindex from 0 to -1', function() {
browser.click('.initialize'); // init tabindex to 0
browser.click('.second-outside');
browser.keys(['Shift', 'Tab', 'Shift']); // host element
browser.keys(['Shift', 'Tab', 'Shift']); // first outside input
Expand Down

0 comments on commit d0f659b

Please sign in to comment.