@lion/overlays
Advanced tools
Comparing version 0.2.5 to 0.2.6
@@ -6,2 +6,13 @@ # Change Log | ||
## [0.2.6](https://github.com/ing-bank/lion/compare/@lion/overlays@0.2.5...@lion/overlays@0.2.6) (2019-06-20) | ||
### Bug Fixes | ||
* **overlays:** trapsKeyboardFocus should work with contentNode ([4695cfd](https://github.com/ing-bank/lion/commit/4695cfd)) | ||
## [0.2.5](https://github.com/ing-bank/lion/compare/@lion/overlays@0.2.4...@lion/overlays@0.2.5) (2019-05-29) | ||
@@ -8,0 +19,0 @@ |
{ | ||
"name": "@lion/overlays", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"description": "Overlays System using lit-html for rendering", | ||
@@ -43,3 +43,3 @@ "author": "ing-bank", | ||
}, | ||
"gitHead": "98c74445690cb5d0509ba708b08e3c533ce5fa35" | ||
"gitHead": "4a1c289ff57784e2e3c1c506ae3b7bd4480b61b5" | ||
} |
@@ -18,6 +18,35 @@ import { render, html } from '@lion/core'; | ||
this.position = finalParams.position; | ||
/** | ||
* A wrapper to render into the invokerTemplate | ||
* | ||
* @property {HTMLElement} | ||
*/ | ||
this.invoker = document.createElement('div'); | ||
this.invoker.style.display = 'inline-block'; | ||
this.invokerTemplate = finalParams.invokerTemplate; | ||
/** | ||
* The actual invoker element we work with - it get's all the events and a11y | ||
* | ||
* @property {HTMLElement} | ||
*/ | ||
this.invokerNode = this.invoker; | ||
if (finalParams.invokerNode) { | ||
this.invokerNode = finalParams.invokerNode; | ||
this.invoker = this.invokerNode; | ||
} | ||
/** | ||
* A wrapper the contentTemplate renders into | ||
* | ||
* @property {HTMLElement} | ||
*/ | ||
this.content = document.createElement('div'); | ||
this.content.style.display = 'inline-block'; | ||
this.contentTemplate = finalParams.contentTemplate; | ||
this.contentNode = this.content; | ||
if (finalParams.contentNode) { | ||
this.contentNode = finalParams.contentNode; | ||
this.content = this.contentNode; | ||
} | ||
this.contentId = `overlay-content-${Math.random() | ||
@@ -27,6 +56,2 @@ .toString(36) | ||
this._contentData = {}; | ||
this.invokerTemplate = finalParams.invokerTemplate; | ||
this.invokerNode = finalParams.invokerNode; | ||
this.contentTemplate = finalParams.contentTemplate; | ||
this.contentNode = finalParams.contentNode; | ||
this.syncInvoker(); | ||
@@ -36,4 +61,3 @@ this._updateContent(); | ||
this._prevData = {}; | ||
if (this.hidesOnEsc) this._setupHidesOnEsc(); | ||
this.__boundEscKeyHandler = this.__escKeyHandler.bind(this); | ||
} | ||
@@ -115,2 +139,3 @@ | ||
if (this.hidesOnOutsideClick) this._setupHidesOnOutsideClick(); | ||
if (this.hidesOnEsc) this._setupHidesOnEsc(); | ||
} else { | ||
@@ -120,2 +145,3 @@ this._updateContent(); | ||
if (this.hidesOnOutsideClick) this._teardownHidesOnOutsideClick(); | ||
if (this.hidesOnEsc) this._teardownHidesOnEsc(); | ||
} | ||
@@ -135,13 +161,13 @@ this._prevShown = shown; | ||
} | ||
this._containFocusHandler = containFocus(this.content.firstElementChild); | ||
this._containFocusHandler = containFocus(this.contentNode); | ||
} | ||
_setupHidesOnEsc() { | ||
this.content.addEventListener('keyup', event => { | ||
if (event.keyCode === keyCodes.escape) { | ||
this.hide(); | ||
} | ||
}); | ||
this.contentNode.addEventListener('keyup', this.__boundEscKeyHandler); | ||
} | ||
_teardownHidesOnEsc() { | ||
this.contentNode.removeEventListener('keyup', this.__boundEscKeyHandler); | ||
} | ||
_setupHidesOnOutsideClick() { | ||
@@ -171,4 +197,4 @@ if (this.__preventCloseOutsideClick) { | ||
this.content.addEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.invoker.addEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.contentNode.addEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.invokerNode.addEventListener('click', this.__preventCloseOutsideClick, true); | ||
document.documentElement.addEventListener('click', this.__onCaptureHtmlClick, true); | ||
@@ -178,4 +204,4 @@ } | ||
_teardownHidesOnOutsideClick() { | ||
this.content.removeEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.invoker.removeEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.contentNode.removeEventListener('click', this.__preventCloseOutsideClick, true); | ||
this.invokerNode.removeEventListener('click', this.__preventCloseOutsideClick, true); | ||
document.documentElement.removeEventListener('click', this.__onCaptureHtmlClick, true); | ||
@@ -193,2 +219,8 @@ this.__preventCloseOutsideClick = null; | ||
} | ||
__escKeyHandler(e) { | ||
if (e.keyCode === keyCodes.escape) { | ||
this.hide(); | ||
} | ||
} | ||
} |
@@ -40,3 +40,3 @@ import { storiesOf, html } from '@open-wc/demoing-storybook'; | ||
html` | ||
<button @click=${() => popup.show()}>UK</button> | ||
<button @click=${() => popup.toggle()}>UK</button> | ||
`, | ||
@@ -66,3 +66,3 @@ }), | ||
html` | ||
<button @click=${() => popup.show()}>UK</button> | ||
<button @click=${() => popup.toggle()}>UK</button> | ||
`, | ||
@@ -93,3 +93,3 @@ }), | ||
html` | ||
<button @click=${() => popup.show()}>Click me</button> | ||
<button @click=${() => popup.toggle()}>Click me</button> | ||
`, | ||
@@ -159,3 +159,3 @@ }), | ||
}) | ||
.add('On toggle', () => { | ||
.add('trapsKeyboardFocus', () => { | ||
const popup = overlays.add( | ||
@@ -165,6 +165,16 @@ new LocalOverlayController({ | ||
hidesOnOutsideClick: true, | ||
contentTemplate: () => | ||
html` | ||
<div class="demo-popup">United Kingdom</div> | ||
`, | ||
trapsKeyboardFocus: true, | ||
contentTemplate: () => html` | ||
<div class="demo-popup"> | ||
<button id="el1">Button</button> | ||
<a id="el2" href="#">Anchor</a> | ||
<div id="el3" tabindex="0">Tabindex</div> | ||
<input id="el4" placeholder="Input" /> | ||
<div id="el5" contenteditable>Content editable</div> | ||
<textarea id="el6">Textarea</textarea> | ||
<select id="el7"> | ||
<option>1</option> | ||
</select> | ||
</div> | ||
`, | ||
invokerTemplate: () => | ||
@@ -181,7 +191,18 @@ html` | ||
<div class="demo-box"> | ||
<label for="input">Weather in ${popup.invoker}${popup.content} toggles.</label> | ||
${popup.invoker}${popup.content} | ||
</div> | ||
`; | ||
}) | ||
.add('trapsKeyboardFocus', () => { | ||
.add('trapsKeyboardFocus with nodes', () => { | ||
const invokerNode = document.createElement('button'); | ||
invokerNode.innerHTML = 'Invoker Button'; | ||
const contentNode = document.createElement('div'); | ||
contentNode.classList.add('demo-popup'); | ||
const contentButton = document.createElement('button'); | ||
contentButton.innerHTML = 'Content Button'; | ||
const contentInput = document.createElement('input'); | ||
contentNode.appendChild(contentButton); | ||
contentNode.appendChild(contentInput); | ||
const popup = overlays.add( | ||
@@ -192,21 +213,10 @@ new LocalOverlayController({ | ||
trapsKeyboardFocus: true, | ||
contentTemplate: () => html` | ||
<div class="demo-popup"> | ||
<button id="el1">Button</button> | ||
<a id="el2" href="#">Anchor</a> | ||
<div id="el3" tabindex="0">Tabindex</div> | ||
<input id="el4" placeholder="Input" /> | ||
<div id="el5" contenteditable>Content editable</div> | ||
<textarea id="el6">Textarea</textarea> | ||
<select id="el7"> | ||
<option>1</option> | ||
</select> | ||
</div> | ||
`, | ||
invokerTemplate: () => | ||
html` | ||
<button @click=${() => popup.show()}>UK</button> | ||
`, | ||
contentNode, | ||
invokerNode, | ||
}), | ||
); | ||
invokerNode.addEventListener('click', () => { | ||
popup.toggle(); | ||
}); | ||
return html` | ||
@@ -213,0 +223,0 @@ <style> |
@@ -211,3 +211,3 @@ import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing'; | ||
controller.show(); | ||
expect(controller.content.firstElementChild.style.top).to.equal('8px'); | ||
expect(controller.contentNode.style.top).to.equal('8px'); | ||
}); | ||
@@ -222,3 +222,5 @@ | ||
invokerTemplate: () => html` | ||
<button style="padding: 16px;" @click=${() => controller.show()}>Invoker</button> | ||
<button style="padding: 16px;" @click=${() => controller.show()}> | ||
Invoker | ||
</button> | ||
`, | ||
@@ -232,5 +234,5 @@ }); | ||
controller.show(); | ||
const invokerChild = controller.content.firstElementChild; | ||
expect(invokerChild.getAttribute('js-positioning-vertical')).to.equal('top'); | ||
expect(invokerChild.getAttribute('js-positioning-horizontal')).to.equal('centerHorizontal'); | ||
const { contentNode } = controller; | ||
expect(contentNode.getAttribute('js-positioning-vertical')).to.equal('top'); | ||
expect(contentNode.getAttribute('js-positioning-horizontal')).to.equal('centerHorizontal'); | ||
}); | ||
@@ -241,3 +243,5 @@ | ||
invokerTemplate: () => html` | ||
<button style="padding: 16px;" @click=${() => controller.show()}>Invoker</button> | ||
<button style="padding: 16px;" @click=${() => controller.show()}> | ||
Invoker | ||
</button> | ||
`, | ||
@@ -257,5 +261,5 @@ contentTemplate: () => | ||
controller.show(); | ||
const contentChild = controller.content.firstElementChild; | ||
expect(contentChild.getAttribute('js-positioning-vertical')).to.equal('top'); | ||
expect(contentChild.getAttribute('js-positioning-horizontal')).to.equal('right'); | ||
const { contentNode } = controller; | ||
expect(contentNode.getAttribute('js-positioning-vertical')).to.equal('top'); | ||
expect(contentNode.getAttribute('js-positioning-horizontal')).to.equal('right'); | ||
}); | ||
@@ -270,3 +274,5 @@ | ||
invokerTemplate: () => html` | ||
<button style="padding: 16px;" @click=${() => controller.show()}>Invoker</button> | ||
<button style="padding: 16px;" @click=${() => controller.show()}> | ||
Invoker | ||
</button> | ||
`, | ||
@@ -282,5 +288,5 @@ placement: 'top right', | ||
controller.show(); | ||
const invokerChild = controller.content.firstElementChild; | ||
expect(invokerChild.getAttribute('js-positioning-vertical')).to.equal('bottom'); | ||
expect(invokerChild.getAttribute('js-positioning-horizontal')).to.equal('right'); | ||
const { contentNode } = controller; | ||
expect(contentNode.getAttribute('js-positioning-vertical')).to.equal('bottom'); | ||
expect(contentNode.getAttribute('js-positioning-horizontal')).to.equal('right'); | ||
}); | ||
@@ -302,10 +308,10 @@ }); | ||
expect(controller.invoker.firstElementChild.getAttribute('aria-controls')).to.contain( | ||
expect(controller.invokerNode.getAttribute('aria-controls')).to.contain( | ||
controller.content.id, | ||
); | ||
expect(controller.invoker.firstElementChild.getAttribute('aria-expanded')).to.equal('false'); | ||
expect(controller.invokerNode.getAttribute('aria-expanded')).to.equal('false'); | ||
controller.show(); | ||
expect(controller.invoker.firstElementChild.getAttribute('aria-expanded')).to.equal('true'); | ||
expect(controller.invokerNode.getAttribute('aria-expanded')).to.equal('true'); | ||
controller.hide(); | ||
expect(controller.invoker.firstElementChild.getAttribute('aria-expanded')).to.equal('false'); | ||
expect(controller.invokerNode.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
@@ -328,14 +334,42 @@ | ||
// make sure we're connected to the dom | ||
await fixture( | ||
html` | ||
${controller.invoker}${controller.content} | ||
`, | ||
); | ||
await fixture(html` | ||
${controller.invoker}${controller.content} | ||
`); | ||
controller.show(); | ||
const elOutside = await fixture(`<button>click me</button>`); | ||
const [el1, el2] = [].slice.call( | ||
controller.content.firstElementChild.querySelectorAll('[id]'), | ||
); | ||
const [el1, el2] = [].slice.call(controller.contentNode.querySelectorAll('[id]')); | ||
el2.focus(); | ||
// this mimics a tab within the contain-focus system used | ||
const event = new CustomEvent('keydown', { detail: 0, bubbles: true }); | ||
event.keyCode = keyCodes.tab; | ||
window.dispatchEvent(event); | ||
expect(elOutside).to.not.equal(document.activeElement); | ||
expect(el1).to.equal(document.activeElement); | ||
}); | ||
it('traps the focus via option { trapsKeyboardFocus: true } when using contentNode', async () => { | ||
const invokerNode = await fixture('<button>Invoker</button>'); | ||
const contentNode = await fixture(` | ||
<div> | ||
<button id="el1">Button</button> | ||
<a id="el2" href="#">Anchor</a> | ||
</div> | ||
`); | ||
const controller = new LocalOverlayController({ | ||
contentNode, | ||
invokerNode, | ||
trapsKeyboardFocus: true, | ||
}); | ||
// make sure we're connected to the dom | ||
await fixture(html` | ||
${controller.invoker}${controller.content} | ||
`); | ||
controller.show(); | ||
const elOutside = await fixture(`<button>click me</button>`); | ||
const [el1, el2] = [].slice.call(controller.contentNode.querySelectorAll('[id]')); | ||
el2.focus(); | ||
@@ -372,3 +406,3 @@ // this mimics a tab within the contain-focus system used | ||
controller.show(); | ||
const el1 = controller.content.firstElementChild.querySelector('button'); | ||
const el1 = controller.content.querySelector('button'); | ||
@@ -401,3 +435,3 @@ el1.focus(); | ||
keyUpOn(ctrl.content, keyCodes.escape); | ||
keyUpOn(ctrl.contentNode, keyCodes.escape); | ||
ctrl.updateComplete; | ||
@@ -471,11 +505,9 @@ expect(ctrl.isShown).to.equal(false); | ||
}); | ||
const { content, invoker, invokerNode } = ctrl; | ||
await fixture( | ||
html` | ||
${invoker}${content} | ||
`, | ||
); | ||
const { content, invoker } = ctrl; | ||
await fixture(html` | ||
${invoker}${content} | ||
`); | ||
// Don't hide on first invoker click | ||
invokerNode.click(); | ||
ctrl.invokerNode.click(); | ||
await aTimeout(); | ||
@@ -485,3 +517,3 @@ expect(ctrl.isShown).to.equal(true); | ||
// Don't hide on inside (content) click | ||
content.click(); | ||
ctrl.contentNode.click(); | ||
await aTimeout(); | ||
@@ -491,3 +523,3 @@ expect(ctrl.isShown).to.equal(true); | ||
// Don't hide on invoker click when shown | ||
invokerNode.click(); | ||
ctrl.invokerNode.click(); | ||
await aTimeout(); | ||
@@ -494,0 +526,0 @@ expect(ctrl.isShown).to.equal(true); |
164026
4789