Skip to content

Commit

Permalink
Fire blur on click/dbl-click/tap on non-focusable element
Browse files Browse the repository at this point in the history
The root cause of the issue is that the blur event is not triggered on
the current active element in the __focus__ helper in case if the
clicked (double-clicked, tapped) element is not focusable.

If the clicked (double-clicked, tapped) element is focusable the
previous active element gets
blur event trough the __focus__ helper.

A call to __blur__ was added to the __focus__ helper for the case
when the clicked (double-clicked, tapped) element element
is not focusable.
  • Loading branch information
Olga Torkhanova committed May 28, 2021
1 parent 77022fb commit 1e0c6a4
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 14 deletions.
6 changes: 3 additions & 3 deletions addon-test-support/@ember/test-helpers/dom/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { getWindowOrElement } from './-get-window-or-element';
import fireEvent from './fire-event';
import { __focus__ } from './focus';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { Promise } from '../-utils';
import isFormControl from './-is-form-control';
import Target from './-target';
import Target, { isWindow } from './-target';
import { log } from '@ember/test-helpers/dom/-logging';
import { runHooks, registerHook } from '../-internal/helper-hooks';
import { __blur__ } from './blur';

This comment has been minimized.

Copy link
@izelnakri

izelnakri May 30, 2021

Contributor

@rwjblue this is unused.


const PRIMARY_BUTTON = 1;
const MAIN_BUTTON_PRESSED = 0;
Expand All @@ -34,7 +34,7 @@ export const DEFAULT_CLICK_OPTIONS = {
export function __click__(element: Element | Document | Window, options: MouseEventInit): void {
fireEvent(element, 'mousedown', options);

if (isFocusable(element)) {
if (!isWindow(element)) {
__focus__(element);
}

Expand Down
5 changes: 2 additions & 3 deletions addon-test-support/@ember/test-helpers/dom/double-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { getWindowOrElement } from './-get-window-or-element';
import fireEvent from './fire-event';
import { __focus__ } from './focus';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { Promise } from '../-utils';
import { DEFAULT_CLICK_OPTIONS } from './click';
import Target from './-target';
import Target, { isWindow } from './-target';
import { log } from '@ember/test-helpers/dom/-logging';
import isFormControl from './-is-form-control';
import { runHooks, registerHook } from '../-internal/helper-hooks';
Expand All @@ -26,7 +25,7 @@ export function __doubleClick__(
): void {
fireEvent(element, 'mousedown', options);

if (isFocusable(element)) {
if (!isWindow(element)) {
__focus__(element);
}

Expand Down
28 changes: 20 additions & 8 deletions addon-test-support/@ember/test-helpers/dom/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,29 @@ registerHook('focus', 'start', (target: Target) => {
@param {Element} element the element to trigger events on
*/
export function __focus__(element: HTMLElement | Element | Document | SVGElement): void {
const previousFocusedElement =
document.activeElement &&
document.activeElement !== element &&
isFocusable(document.activeElement)
? document.activeElement
: null;

// fire __blur__ manually with the null relatedTarget when the target is not focusable
// and there was a previously focused element
if (!isFocusable(element)) {
throw new Error(`${element} is not focusable`);
if (previousFocusedElement) {
__blur__(previousFocusedElement, null);
}

return;
}

let browserIsNotFocused = document.hasFocus && !document.hasFocus();

// fire __blur__ manually with the correct relatedTarget when the browser is not
// already in focus and there was a previously focused element
if (
document.activeElement &&
document.activeElement !== element &&
isFocusable(document.activeElement) &&
browserIsNotFocused
) {
__blur__(document.activeElement, element);
if (previousFocusedElement && browserIsNotFocused) {
__blur__(previousFocusedElement, element);
}

// makes `document.activeElement` be `element`. If the browser is focused, it also fires a focus event
Expand Down Expand Up @@ -88,6 +96,10 @@ export default function focus(target: Target): Promise<void> {
throw new Error(`Element not found when calling \`focus('${target}')\`.`);
}

if (!isFocusable(element)) {
throw new Error(`${element} is not focusable`);
}

__focus__(element);

return settled();
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/dom/click-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,47 @@ module('DOM Helper: click', function (hooks) {
assert.verifySteps(['mousedown', 'mouseup', 'click']);
});
});

module('focusable and non-focusable elements interaction', function () {
test('clicking on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await click(focusableElement);
await click(element);

assert.verifySteps(['mousedown', 'focus', 'focusin', 'mouseup', 'click', 'blur', 'focusout']);
});

test('clicking on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await click(focusableElement);
await click(element);

assert.verifySteps(['mousedown', 'focus', 'focusin', 'mouseup', 'click', 'blur', 'focusout']);
});

test('clicking on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await click(nonFocusableElement);
await click(element);

assert.verifySteps(['mousedown', 'mouseup', 'click']);
});
});
});

module('DOM Helper: click with window', function () {
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/dom/double-click-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,79 @@ module('DOM Helper: doubleClick', function (hooks) {
]);
});
});

module('focusable and non-focusable elements interaction', function () {
test('cdouble-licking on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await doubleClick(focusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
'blur',
'focusout',
]);
});

test('double-clicking on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await doubleClick(focusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
'blur',
'focusout',
]);
});

test('double-clicking on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await doubleClick(nonFocusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});
});
});

module('DOM Helper: doubleClick with window', function () {
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/dom/tap-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,65 @@ module('DOM Helper: tap', function (hooks) {
assert.rejects(tap(element), new Error('Can not `tap` disabled [object HTMLInputElement]'));
});
});

module('focusable and non-focusable elements interaction', function () {
test('tapping on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await tap(focusableElement);
await tap(element);

assert.verifySteps([
'touchstart',
'touchend',
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'blur',
'focusout',
]);
});

test('tapping on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await tap(focusableElement);
await tap(element);

assert.verifySteps([
'touchstart',
'touchend',
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'blur',
'focusout',
]);
});

test('tapping on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await tap(nonFocusableElement);
await tap(element);

assert.verifySteps(['touchstart', 'touchend', 'mousedown', 'mouseup', 'click']);
});
});
});

0 comments on commit 1e0c6a4

Please sign in to comment.