Merge pull request #128 from GoogleChrome/tab-focus-stuck

focus work preventing stuck
This commit is contained in:
Sam Thorogood 2017-01-23 12:47:57 +11:00 коммит произвёл GitHub
Родитель 3116dc5386 c6b60de0c4
Коммит 08d5028704
4 изменённых файлов: 64 добавлений и 20 удалений

Просмотреть файл

@ -150,6 +150,19 @@
* @param {!Event} e to redirect
*/
backdropClick_: function(e) {
if (!this.dialog_.hasAttribute('tabindex')) {
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
// would not be needed - clicks would move the implicit cursor there.
var fake = document.createElement('div');
this.dialog_.insertBefore(fake, this.dialog_.firstChild);
fake.tabIndex = -1;
fake.focus();
this.dialog_.removeChild(fake);
} else {
this.dialog_.focus();
}
var redirectedEvent = document.createEvent('MouseEvents');
redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
@ -165,6 +178,9 @@
focus_: function() {
// Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
if (!target && this.dialog_.tabIndex >= 0) {
target = this.dialog_;
}
if (!target) {
// Note that this is 'any focusable area'. This list is probably not exhaustive, but the
// alternative involves stepping through and trying to focus everything.
@ -363,14 +379,8 @@
this.zIndexLow_ = 100000;
this.zIndexHigh_ = 100000 + 150;
};
/**
* @return {Element} the top HTML dialog element, if any
*/
dialogPolyfill.DialogManager.prototype.topDialogElement = function() {
var dpi = this.pendingDialogStack[0];
return dpi ? dpi.dialog : null;
this.forwardTab_ = undefined;
};
/**
@ -379,7 +389,7 @@
*/
dialogPolyfill.DialogManager.prototype.blockDocument = function() {
document.body.appendChild(this.overlay);
document.body.addEventListener('focus', this.handleFocus_, true);
document.documentElement.addEventListener('focus', this.handleFocus_, true);
document.addEventListener('keydown', this.handleKey_);
document.addEventListener('DOMNodeRemoved', this.handleRemove_);
};
@ -390,7 +400,7 @@
*/
dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
document.body.removeChild(this.overlay);
document.body.removeEventListener('focus', this.handleFocus_, true);
document.documentElement.removeEventListener('focus', this.handleFocus_, true);
document.removeEventListener('keydown', this.handleKey_);
document.removeEventListener('DOMNodeRemoved', this.handleRemove_);
};
@ -408,28 +418,42 @@
dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
var candidate = findNearestDialog(/** @type {Element} */ (event.target));
if (candidate != this.topDialogElement()) {
event.preventDefault();
event.stopPropagation();
safeBlur(/** @type {Element} */ (event.target));
// TODO: Focus on the browser chrome (aka document) or the dialog itself
// depending on the tab direction.
return false;
var dpi = this.pendingDialogStack[0];
if (!dpi || candidate === dpi.dialog) { return; }
event.preventDefault();
event.stopPropagation();
safeBlur(/** @type {Element} */ (event.target));
var position = dpi.dialog.compareDocumentPosition(event.target);
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
if (this.forwardTab_) {
dpi.focus_();
} else {
document.documentElement.focus();
}
} else {
// TODO: Focus after the dialog, is ignored.
}
return false;
};
dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
if (event.keyCode == 27) {
this.forwardTab_ = undefined;
if (event.keyCode === 27) {
event.preventDefault();
event.stopPropagation();
var cancelEvent = new supportCustomEvent('cancel', {
bubbles: false,
cancelable: true
});
var dialog = this.topDialogElement();
if (dialog.dispatchEvent(cancelEvent)) {
dialog.close();
var dpi = this.pendingDialogStack[0];
if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
dpi.dialog.close();
}
} else if (event.keyCode === 9) {
this.forwardTab_ = !event.shiftKey;
}
};

Просмотреть файл

@ -282,6 +282,21 @@ void function() {
backdrop.click();
assert.equal(clickFired, 1);
});
test('backdrop click focuses dialog', function() {
dialog.showModal();
dialog.tabIndex = 0;
var input = document.createElement('input');
input.type = 'text';
dialog.appendChild(input);
// TODO: It would be nice to check `input` instead here, but there's no more reliable ways
// to emulate a browser tab event (Firefox, Chrome etc have made it a security violation).
var backdrop = dialog.nextElementSibling;
backdrop.click();
assert.equal(document.activeElement, dialog);
});
});
suite('form focus', function() {

Просмотреть файл

@ -11,6 +11,8 @@
Enter a value and submit the form. The close event will be fired and the <code>returnValue</code> of the dialog will be alerted.
</p>
<input type="text" placeholder="Focusable pre" />
<dialog>
<form method="dialog">
<input type="text" placeholder="Enter value" />
@ -24,6 +26,8 @@ Enter a value and submit the form. The close event will be fired and the <code>r
<button id="show">Show Dialog</button>
<input type="text" placeholder="Focusable post" />
<script>
var dialog = document.querySelector('dialog');

Просмотреть файл

@ -3,6 +3,7 @@
<meta charset='utf-8'>
<head>
<script src="../dialog-polyfill.js"></script>
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="../dialog-polyfill.css">
<style>
dialog {