Skip to content

Commit

Permalink
Editable Combobox With List Autocomplete: Remove "onBlur" events for …
Browse files Browse the repository at this point in the history
…better iOS compatibility (pull #1699)

Resolves #1619.
With VoiceOver running in iOS, moving screen reader focus moves browser focus, which of course triggers blur.
The combo text input was closing the popup when it was blurred.
Thus, VO users on iOS could not get to the popup.
This commit removes use of blur and instead uses only mouse and keyboard events to determine when to close the popup.
 
Co-authored-by: Jon Gunderson <[email protected]>
  • Loading branch information
spectranaut authored Feb 21, 2021
1 parent 11cbdd6 commit 2a80944
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 8 deletions.
26 changes: 18 additions & 8 deletions examples/combobox/js/combobox-autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ class ComboboxAutocomplete {
'focus',
this.onComboboxFocus.bind(this)
);
this.comboboxNode.addEventListener('blur', this.onComboboxBlur.bind(this));

document.body.addEventListener(
'mouseup',
this.onBackgroundMouseUp.bind(this),
true
);

// initialize pop up menu

Expand Down Expand Up @@ -507,11 +512,17 @@ class ComboboxAutocomplete {
this.setCurrentOptionStyle(null);
}

onComboboxBlur() {
this.comboboxHasVisualFocus = false;
this.setCurrentOptionStyle(null);
this.removeVisualFocusAll();
setTimeout(this.close.bind(this, false), 300);
onBackgroundMouseUp(event) {
if (
!this.comboboxNode.contains(event.target) &&
!this.listboxNode.contains(event.target) &&
!this.buttonNode.contains(event.target)
) {
this.comboboxHasVisualFocus = false;
this.setCurrentOptionStyle(null);
this.removeVisualFocusAll();
setTimeout(this.close.bind(this, true), 300);
}
}

onButtonClick() {
Expand Down Expand Up @@ -563,7 +574,6 @@ window.addEventListener('load', function () {
var comboboxNode = combobox.querySelector('input');
var buttonNode = combobox.querySelector('button');
var listboxNode = combobox.querySelector('[role="listbox"]');
var cba = new ComboboxAutocomplete(comboboxNode, buttonNode, listboxNode);
cba.init();
new ComboboxAutocomplete(comboboxNode, buttonNode, listboxNode);
}
});
87 changes: 87 additions & 0 deletions test/tests/combobox_autocomplete-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ex = {
optionsSelector: '#ex1 [role="option"]',
buttonSelector: '#ex1 button',
numAOptions: 5,
exampleHeadingSelector: '.example-header',
};

const waitForFocusChange = async (t, textboxSelector, originalFocus) => {
Expand Down Expand Up @@ -850,6 +851,92 @@ ariaTest(
}
);

ariaTest(
'Clicking outside of textbox and listbox when focus is on listbox closes listbox',
exampleFile,
'test-additional-behavior',
async (t) => {
// Send key "a" to open listbox then key "ARROW_DOWN" to put the focus on the listbox
await t.context.session
.findElement(By.css(ex.textboxSelector))
.sendKeys('a', Key.ARROW_DOWN);

// click outside the listbox
await t.context.session
.findElement(By.css(ex.exampleHeadingSelector))
.click();

await t.context.session.wait(
async function () {
let listbox = await t.context.session.findElement(
By.css(ex.listboxSelector)
);
return !(await listbox.isDisplayed());
},
t.context.waitTime,
'Error waiting for listbox to close after outside click'
);

// Confirm the listbox is closed and the textbox is cleared
await assertAttributeValues(
t,
ex.textboxSelector,
'aria-expanded',
'false'
);
t.is(
await t.context.session
.findElement(By.css(ex.textboxSelector))
.getAttribute('value'),
'a',
'Click outside of a textbox will close the textbox without selecting the highlighted value'
);
}
);

ariaTest(
'Clicking outside of textbox and listbox when focus is on textbox closes listbox',
exampleFile,
'test-additional-behavior',
async (t) => {
// Send key "a" to open listbox to put focus on textbox
await t.context.session
.findElement(By.css(ex.textboxSelector))
.sendKeys('a');

// click outside the listbox
await t.context.session
.findElement(By.css(ex.exampleHeadingSelector))
.click();

await t.context.session.wait(
async function () {
let listbox = await t.context.session.findElement(
By.css(ex.listboxSelector)
);
return !(await listbox.isDisplayed());
},
t.context.waitTime,
'Error waiting for listbox to close after outside click'
);

// Confirm the listbox is closed and the textbox is cleared
await assertAttributeValues(
t,
ex.textboxSelector,
'aria-expanded',
'false'
);
t.is(
await t.context.session
.findElement(By.css(ex.textboxSelector))
.getAttribute('value'),
'a',
'Click outside of a textbox will close the textbox without selecting the highlighted value'
);
}
);

ariaTest(
'left arrow from focus on list puts focus on listbox and moves cursor right',
exampleFile,
Expand Down

0 comments on commit 2a80944

Please sign in to comment.