Merge pull request #815 from ing-bank/fix/overlayBugs

Fix/overlay bugs
This commit is contained in:
Thijs Louisse 2020-07-13 13:48:42 +02:00 committed by GitHub
commit e28b981afe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 11 deletions

View file

@ -67,8 +67,7 @@ const supportsCSSTypedObject = window.CSS && CSS.number;
* Note that a contentWrapperNode should be provided for [l2], [l3] and [l4] * Note that a contentWrapperNode should be provided for [l2], [l3] and [l4]
* In case of a global overlay ([g1]), it's enough to provide just the contentNode. * In case of a global overlay ([g1]), it's enough to provide just the contentNode.
* In case of a local overlay or a responsive overlay switching from placementMode, one should * In case of a local overlay or a responsive overlay switching from placementMode, one should
* always configure as if it was a local overlay. * always configure as if it were a local overlay.
*
*/ */
export class OverlayController { export class OverlayController {
@ -358,11 +357,14 @@ export class OverlayController {
__setupTeardownAccessibility({ phase }) { __setupTeardownAccessibility({ phase }) {
if (phase === 'init') { if (phase === 'init') {
this.__storeOriginalAttrs(this.contentNode, ['role', 'id']); this.__storeOriginalAttrs(this.contentNode, ['role', 'id']);
if (this.invokerNode) {
this.__storeOriginalAttrs(this.invokerNode, [ this.__storeOriginalAttrs(this.invokerNode, [
'aria-expanded', 'aria-expanded',
'aria-labelledby', 'aria-labelledby',
'aria-describedby', 'aria-describedby',
]); ]);
}
if (!this.contentNode.id) { if (!this.contentNode.id) {
this.contentNode.setAttribute('id', this._contentId); this.contentNode.setAttribute('id', this._contentId);
@ -379,7 +381,7 @@ export class OverlayController {
if (this.invokerNode) { if (this.invokerNode) {
this.invokerNode.setAttribute('aria-expanded', this.isShown); this.invokerNode.setAttribute('aria-expanded', this.isShown);
} }
if (!this.contentNode.role) { if (!this.contentNode.getAttribute('role')) {
this.contentNode.setAttribute('role', 'dialog'); this.contentNode.setAttribute('role', 'dialog');
} }
} }
@ -806,17 +808,28 @@ export class OverlayController {
if (phase === 'show') { if (phase === 'show') {
let wasClickInside = false; let wasClickInside = false;
// handle on capture phase and remember till the next task that there was an inside click let wasIndirectSynchronousClick = false;
// Handle on capture phase and remember till the next task that there was an inside click
this.__preventCloseOutsideClick = () => { this.__preventCloseOutsideClick = () => {
if (wasClickInside) {
// This occurs when a synchronous new click is triggered from a previous click.
// For instance, when we have a label pointing to an input, the platform triggers
// a new click on the input. Not taking this click into account, will hide the overlay
// in `__onCaptureHtmlClick`
wasIndirectSynchronousClick = true;
}
wasClickInside = true; wasClickInside = true;
setTimeout(() => { setTimeout(() => {
wasClickInside = false; wasClickInside = false;
setTimeout(() => {
wasIndirectSynchronousClick = false;
});
}); });
}; };
// handle on capture phase and schedule the hide if needed // handle on capture phase and schedule the hide if needed
this.__onCaptureHtmlClick = () => { this.__onCaptureHtmlClick = () => {
setTimeout(() => { setTimeout(() => {
if (wasClickInside === false) { if (wasClickInside === false && !wasIndirectSynchronousClick) {
this.hide(); this.hide();
} }
}); });

View file

@ -68,10 +68,11 @@ describe('OverlayController', () => {
} }
if (mode === 'inline') { if (mode === 'inline') {
contentNode = await fixture(html` contentNode = await fixture(html`
<div style="z-index: ${zIndexVal} ;"> <div>
I should be on top I should be on top
</div> </div>
`); `);
contentNode.style.zIndex = zIndexVal;
} }
return contentNode; return contentNode;
} }
@ -430,6 +431,7 @@ describe('OverlayController', () => {
// Don't hide on inside (content) click // Don't hide on inside (content) click
ctrl.contentNode.click(); ctrl.contentNode.click();
await aTimeout(); await aTimeout();
expect(ctrl.isShown).to.be.true; expect(ctrl.isShown).to.be.true;
// Important to check if it can be still shown after, because we do some hacks inside // Important to check if it can be still shown after, because we do some hacks inside
@ -566,6 +568,28 @@ describe('OverlayController', () => {
await ctrl.show(); await ctrl.show();
expect(ctrl.isShown).to.equal(true); expect(ctrl.isShown).to.equal(true);
}); });
it('doesn\'t hide on "inside label" click', async () => {
const contentNode = await fixture(`
<div>
<label for="test">test</label>
<input id="test">
Content
</div>`);
const labelNode = contentNode.querySelector('label[for=test]');
const ctrl = new OverlayController({
...withGlobalTestConfig(),
hidesOnOutsideClick: true,
contentNode,
});
await ctrl.show();
// Don't hide on label click
labelNode.click();
await aTimeout();
expect(ctrl.isShown).to.be.true;
});
}); });
describe('elementToFocusAfterHide', () => { describe('elementToFocusAfterHide', () => {
@ -1113,6 +1137,33 @@ describe('OverlayController', () => {
expect(ctrl.contentNode.getAttribute('role')).to.equal('dialog'); expect(ctrl.contentNode.getAttribute('role')).to.equal('dialog');
}); });
it('preserves [role] on content when present', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');
const contentNode = await fixture('<div role="menu">invoker</div>');
const ctrl = new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
invokerNode,
contentNode,
});
expect(ctrl.contentNode.getAttribute('role')).to.equal('menu');
});
it('allows to not provide an invokerNode', async () => {
let properlyInstantiated = false;
try {
new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
invokerNode: null,
});
properlyInstantiated = true;
} catch (e) {
throw new Error(e);
}
expect(properlyInstantiated).to.be.true;
});
it('adds attributes inert and aria-hidden="true" on all siblings of rootNode if an overlay is shown', async () => { it('adds attributes inert and aria-hidden="true" on all siblings of rootNode if an overlay is shown', async () => {
const ctrl = new OverlayController({ const ctrl = new OverlayController({
...withGlobalTestConfig(), ...withGlobalTestConfig(),