Skip to content

Commit

Permalink
fix(engine): handling empty shadow roots
Browse files Browse the repository at this point in the history
  • Loading branch information
David Turissini committed Nov 7, 2018
1 parent 599bfce commit e70185c
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 6 deletions.
18 changes: 12 additions & 6 deletions packages/lwc-engine/src/faux-shadow/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function getFocusableSegments(host: HTMLElement): QuerySegments {
const all = documentQuerySelectorAll.call(document, PossibleFocusableElementQuery);
const inner = querySelectorAll.call(host, PossibleFocusableElementQuery);
if (process.env.NODE_ENV !== 'production') {
assert.invariant(inner.length > 0, `When focusin event is received, there has to be a focusable target at least.`);
assert.invariant(inner.length > 0 || (tabIndexGetter.call(host) === 0 && isDelegatingFocus(host)), `When focusin event is received, there has to be a focusable target at least.`);
assert.invariant(tabIndexGetter.call(host) === -1 || isDelegatingFocus(host), `The focusin event is only relevant when the tabIndex property is -1 on the host.`);
}
const firstChild = inner[0];
Expand All @@ -72,8 +72,11 @@ function getFocusableSegments(host: HTMLElement): QuerySegments {
// Host element can show up in our "previous" section if its tabindex is 0
// We want to filter that out here
const firstChildIndex = (hostIndex > -1) ? hostIndex : ArrayIndexOf.call(all, firstChild);

// Account for an empty inner list
const lastChildIndex = inner.length === 0 ? firstChildIndex + 1 : ArrayIndexOf.call(all, lastChild) + 1;
const prev = ArraySlice.call(all, 0, firstChildIndex);
const next = ArraySlice.call(all, ArrayIndexOf.call(all, lastChild) + 1);
const next = ArraySlice.call(all, lastChildIndex);
return {
prev,
inner,
Expand Down Expand Up @@ -158,8 +161,12 @@ function isLastFocusableChild(target: EventTarget, segments: QuerySegments): boo
function handleDelegateFocusFocusIn(host: HTMLElement, target: HTMLElement, segments: QuerySegments, position: number) {
if (position === 1) {
// probably tabbing into element
const { inner } = segments;
inner[0].focus();
const first = getFirstFocusableMatch(segments.inner);
if (first) {
first.focus();
} else {
focusOnNextOrBlur(target, segments);
}
return;
} else if (host === target) { // Shift tabbed back to the host
focusOnPrevOrBlur(host, segments);
Expand Down Expand Up @@ -188,7 +195,6 @@ function keyboardFocusInHandler(event: FocusEvent) {
const isFirstFocusableChildReceivingFocus = isFirstFocusableChild(target, segments);
const isLastFocusableChildReceivingFocus = isLastFocusableChild(target, segments);


// Determine where the focus is coming from (Tab or Shift+Tab)
const post = relatedTargetPosition(host as HTMLElement, relatedTarget as HTMLElement);

Expand All @@ -202,7 +208,7 @@ function keyboardFocusInHandler(event: FocusEvent) {
// The host element itself is receiving focus
// Handle delegates focus with tabIndex=0
if (target === host) {
handleDelegateFocusFocusIn(host as HTMLElement, target as HTMLElement, segments, post)
handleDelegateFocusFocusIn(host as HTMLElement, target as HTMLElement, segments, post);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const assert = require('assert');
describe('Delegate focus with tabindex 0, no tabbable elements, and no tabbable elements after', () => {
const URL = 'http://localhost:4567/delegates-focus-tab-index-zero-no-focusable-elements-no-after-elements';
let element;

before(() => {
browser.url(URL);
});

it('should correctly have no activeelement', function () {
browser.keys(['Tab']);
browser.keys(['Tab']);
let active = browser.execute(function () {
return document.activeElement;
});

assert.deepEqual(active.getTagName().toLowerCase(), 'body');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
No focusable elements
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';

export default class Child extends LightningElement {
static delegatesFocus = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<template>
<button>first button</button>
<integration-child tabindex="0"></integration-child>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const assert = require('assert');
describe('Delegate focus with tabindex 0 and no tabbable elements', () => {
const URL = 'http://localhost:4567/delegates-focus-tab-index-zero-no-focusable-elements';
let element;

before(() => {
browser.url(URL);
});

it('should correctly skip the custom element', function () {
browser.keys(['Tab']);
browser.keys(['Tab']);
let active = browser.execute(function () {
return document.activeElement.shadowRoot.activeElement;
});

assert.deepEqual(active.getText(), 'second button')

browser.keys(['Shift', 'Tab']);

active = browser.execute(function () {
return document.activeElement.shadowRoot.activeElement;
});

assert.deepEqual(active.getText(), 'first button');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
No focusable elements
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';

export default class Child extends LightningElement {
static delegatesFocus = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<button>first button</button>
<integration-child tabindex="0"></integration-child>
<button>second button</button>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {

}

0 comments on commit e70185c

Please sign in to comment.