Merge pull request #61 from samthor/domnoderemoved

use DOMNodeRemoved to watch for modal dialog removal from DOM
This commit is contained in:
Sam Thorogood 2015-07-29 10:25:47 +10:00
Родитель 608ad73b39 9f495b04f7
Коммит 9e896b4322
3 изменённых файлов: 48 добавлений и 5 удалений

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

@ -66,7 +66,6 @@ When using the polyfill, the backdrop will be an adjacent element:
- Modal dialogs have limitations-
- They should be a child of `<body>` or have parents without layout (aka, no position `absolute` or `relative` elements)
- DOM changes may not correctly clear the top layer, such as when the `<dialog>` is removed from the page
- The browser's chrome may not be accessible via the tab key
- Stacking can be ruined by playing with z-index
- Changes to the CSS top/bottom values while open aren't retained

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

@ -55,12 +55,13 @@
dialog.close = this.close.bind(this);
if ('MutationObserver' in window) {
var mo = new MutationObserver(this.maybeHideModal_.bind(this));
var mo = new MutationObserver(this.maybeHideModal.bind(this));
mo.observe(dialog, { attributes: true, attributeFilter: ['open'] });
} else {
// TODO: Support for IE9-10.
}
// TODO: Watch for removal from the DOM.
// Note that the DOM is observed inside DialogManager while any dialog
// is being displayed as a modal, to catch modal removal from the DOM.
Object.defineProperty(dialog, 'open', {
set: this.setOpen.bind(this),
@ -83,7 +84,7 @@
* a modal dialog may no longer be tenable, e.g., when the dialog is no
* longer open or is no longer part of the DOM.
*/
maybeHideModal_: function() {
maybeHideModal: function() {
if (!this.openAsModal_) { return; }
if (this.dialog_.hasAttribute('open') &&
document.body.contains(this.dialog_)) { return; }
@ -115,7 +116,7 @@
this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
} else {
this.dialog_.removeAttribute('open');
this.maybeHideModal_(); // nb. redundant with MutationObserver
this.maybeHideModal(); // nb. redundant with MutationObserver
}
},
@ -305,6 +306,7 @@
this.handleKey_ = this.handleKey_.bind(this);
this.handleFocus_ = this.handleFocus_.bind(this);
this.handleRemove_ = this.handleRemove_.bind(this);
this.zIndexLow_ = 100000;
this.zIndexHigh_ = 100000 + 150;
@ -329,6 +331,7 @@
document.body.appendChild(this.overlay);
document.body.addEventListener('focus', this.handleFocus_, true);
document.addEventListener('keydown', this.handleKey_);
document.addEventListener('DOMNodeRemoved', this.handleRemove_);
};
/**
@ -339,6 +342,7 @@
document.body.removeChild(this.overlay);
document.body.removeEventListener('focus', this.handleFocus_, true);
document.removeEventListener('keydown', this.handleKey_);
document.removeEventListener('DOMNodeRemoved', this.handleRemove_);
};
dialogPolyfill.DialogManager.prototype.updateStacking = function() {
@ -379,6 +383,23 @@
}
};
dialogPolyfill.DialogManager.prototype.handleRemove_ = function(event) {
if (!/dialog/i.test(event.target.nodeName)) { return; }
var dialog = /** @type {HTMLDialogElement} */ (event.target);
if (!dialog.open) { return; }
// Find a dialogPolyfillInfo which matches the removed <dialog>.
this.pendingDialogStack.some(function(dpi) {
if (dpi.dialog == dialog) {
// This call will clear the dialogPolyfillInfo on this DialogManager
// as a side effect.
dpi.maybeHideModal();
return true;
}
});
};
/**
* @param {!dialogPolyfillInfo} dpi
* @return {boolean} whether the dialog was allowed

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

@ -149,6 +149,29 @@ void function() {
assert.isTrue(dialog.open, 'can open non-modal outside document');
assert.isFalse(document.body.contains(dialog));
});
test('DOM removal', function(done) {
dialog.showModal();
assert.isTrue(dialog.open);
assert.isNotNull(document.querySelector('.backdrop'));
var parentNode = dialog.parentNode;
parentNode.removeChild(dialog);
// DOMNodeRemoved happens at the end of the frame: this test must be
// async to complete successfully.
window.setTimeout(function() {
assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
assert.isTrue(dialog.open, 'removed dialog should still be open');
parentNode.appendChild(dialog);
assert.isTrue(dialog.open, 'removed dialog should still be open');
assert.isNull(document.querySelector('.backdrop'), 're-add dialog should not be modal');
done();
}, 0);
});
});
suite('position', function() {