Skip to content

Commit 4695cfd

Browse files
fix(overlays): trapsKeyboardFocus should work with contentNode
1 parent 0463ec6 commit 4695cfd

File tree

3 files changed

+153
-79
lines changed

3 files changed

+153
-79
lines changed

packages/overlays/src/LocalOverlayController.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,48 @@ export class LocalOverlayController {
1515
this.trapsKeyboardFocus = finalParams.trapsKeyboardFocus;
1616
this.placement = finalParams.placement;
1717
this.position = finalParams.position;
18+
/**
19+
* A wrapper to render into the invokerTemplate
20+
*
21+
* @property {HTMLElement}
22+
*/
1823
this.invoker = document.createElement('div');
1924
this.invoker.style.display = 'inline-block';
25+
this.invokerTemplate = finalParams.invokerTemplate;
26+
/**
27+
* The actual invoker element we work with - it get's all the events and a11y
28+
*
29+
* @property {HTMLElement}
30+
*/
31+
this.invokerNode = this.invoker;
32+
if (finalParams.invokerNode) {
33+
this.invokerNode = finalParams.invokerNode;
34+
this.invoker = this.invokerNode;
35+
}
36+
37+
/**
38+
* A wrapper the contentTemplate renders into
39+
*
40+
* @property {HTMLElement}
41+
*/
2042
this.content = document.createElement('div');
2143
this.content.style.display = 'inline-block';
44+
this.contentTemplate = finalParams.contentTemplate;
45+
this.contentNode = this.content;
46+
if (finalParams.contentNode) {
47+
this.contentNode = finalParams.contentNode;
48+
this.content = this.contentNode;
49+
}
50+
2251
this.contentId = `overlay-content-${Math.random()
2352
.toString(36)
2453
.substr(2, 10)}`;
2554
this._contentData = {};
26-
this.invokerTemplate = finalParams.invokerTemplate;
27-
this.invokerNode = finalParams.invokerNode;
28-
this.contentTemplate = finalParams.contentTemplate;
29-
this.contentNode = finalParams.contentNode;
3055
this.syncInvoker();
3156
this._updateContent();
3257
this._prevShown = false;
3358
this._prevData = {};
34-
35-
if (this.hidesOnEsc) this._setupHidesOnEsc();
59+
this.__boundEscKeyHandler = this.__escKeyHandler.bind(this);
3660
}
3761

3862
get isShown() {
@@ -109,10 +133,12 @@ export class LocalOverlayController {
109133

110134
if (this.trapsKeyboardFocus) this._setupTrapsKeyboardFocus();
111135
if (this.hidesOnOutsideClick) this._setupHidesOnOutsideClick();
136+
if (this.hidesOnEsc) this._setupHidesOnEsc();
112137
} else {
113138
this._updateContent();
114139
this.invokerNode.setAttribute('aria-expanded', false);
115140
if (this.hidesOnOutsideClick) this._teardownHidesOnOutsideClick();
141+
if (this.hidesOnEsc) this._teardownHidesOnEsc();
116142
}
117143
this._prevShown = shown;
118144
this._prevData = data;
@@ -127,15 +153,15 @@ export class LocalOverlayController {
127153
this._containFocusHandler.disconnect();
128154
this._containFocusHandler = undefined; // eslint-disable-line no-param-reassign
129155
}
130-
this._containFocusHandler = containFocus(this.content.firstElementChild);
156+
this._containFocusHandler = containFocus(this.contentNode);
131157
}
132158

133159
_setupHidesOnEsc() {
134-
this.content.addEventListener('keyup', event => {
135-
if (event.keyCode === keyCodes.escape) {
136-
this.hide();
137-
}
138-
});
160+
this.contentNode.addEventListener('keyup', this.__boundEscKeyHandler);
161+
}
162+
163+
_teardownHidesOnEsc() {
164+
this.contentNode.removeEventListener('keyup', this.__boundEscKeyHandler);
139165
}
140166

141167
_setupHidesOnOutsideClick() {
@@ -162,14 +188,14 @@ export class LocalOverlayController {
162188
});
163189
};
164190

165-
this.content.addEventListener('click', this.__preventCloseOutsideClick, true);
166-
this.invoker.addEventListener('click', this.__preventCloseOutsideClick, true);
191+
this.contentNode.addEventListener('click', this.__preventCloseOutsideClick, true);
192+
this.invokerNode.addEventListener('click', this.__preventCloseOutsideClick, true);
167193
document.documentElement.addEventListener('click', this.__onCaptureHtmlClick, true);
168194
}
169195

170196
_teardownHidesOnOutsideClick() {
171-
this.content.removeEventListener('click', this.__preventCloseOutsideClick, true);
172-
this.invoker.removeEventListener('click', this.__preventCloseOutsideClick, true);
197+
this.contentNode.removeEventListener('click', this.__preventCloseOutsideClick, true);
198+
this.invokerNode.removeEventListener('click', this.__preventCloseOutsideClick, true);
173199
document.documentElement.removeEventListener('click', this.__onCaptureHtmlClick, true);
174200
this.__preventCloseOutsideClick = null;
175201
this.__onCaptureHtmlClick = null;
@@ -182,4 +208,10 @@ export class LocalOverlayController {
182208
this.contentNode.style.display = 'none';
183209
}
184210
}
211+
212+
__escKeyHandler(e) {
213+
if (e.keyCode === keyCodes.escape) {
214+
this.hide();
215+
}
216+
}
185217
}

packages/overlays/stories/local-overlay.stories.js

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ storiesOf('Local Overlay System|Local Overlay', module)
3737
`,
3838
invokerTemplate: () =>
3939
html`
40-
<button @click=${() => popup.show()}>UK</button>
40+
<button @click=${() => popup.toggle()}>UK</button>
4141
`,
4242
}),
4343
);
@@ -62,7 +62,7 @@ storiesOf('Local Overlay System|Local Overlay', module)
6262
`,
6363
invokerTemplate: () =>
6464
html`
65-
<button @click=${() => popup.show()}>UK</button>
65+
<button @click=${() => popup.toggle()}>UK</button>
6666
`,
6767
}),
6868
);
@@ -88,7 +88,7 @@ storiesOf('Local Overlay System|Local Overlay', module)
8888
`,
8989
invokerTemplate: () =>
9090
html`
91-
<button @click=${() => popup.show()}>Click me</button>
91+
<button @click=${() => popup.toggle()}>Click me</button>
9292
`,
9393
}),
9494
);
@@ -153,15 +153,25 @@ storiesOf('Local Overlay System|Local Overlay', module)
153153
</div>
154154
`;
155155
})
156-
.add('On toggle', () => {
156+
.add('trapsKeyboardFocus', () => {
157157
const popup = overlays.add(
158158
new LocalOverlayController({
159159
hidesOnEsc: true,
160160
hidesOnOutsideClick: true,
161-
contentTemplate: () =>
162-
html`
163-
<div class="demo-popup">United Kingdom</div>
164-
`,
161+
trapsKeyboardFocus: true,
162+
contentTemplate: () => html`
163+
<div class="demo-popup">
164+
<button id="el1">Button</button>
165+
<a id="el2" href="#">Anchor</a>
166+
<div id="el3" tabindex="0">Tabindex</div>
167+
<input id="el4" placeholder="Input" />
168+
<div id="el5" contenteditable>Content editable</div>
169+
<textarea id="el6">Textarea</textarea>
170+
<select id="el7">
171+
<option>1</option>
172+
</select>
173+
</div>
174+
`,
165175
invokerTemplate: () =>
166176
html`
167177
<button @click=${() => popup.toggle()}>UK</button>
@@ -173,35 +183,35 @@ storiesOf('Local Overlay System|Local Overlay', module)
173183
${popupDemoStyle}
174184
</style>
175185
<div class="demo-box">
176-
<label for="input">Weather in ${popup.invoker}${popup.content} toggles.</label>
186+
${popup.invoker}${popup.content}
177187
</div>
178188
`;
179189
})
180-
.add('trapsKeyboardFocus', () => {
190+
.add('trapsKeyboardFocus with nodes', () => {
191+
const invokerNode = document.createElement('button');
192+
invokerNode.innerHTML = 'Invoker Button';
193+
194+
const contentNode = document.createElement('div');
195+
contentNode.classList.add('demo-popup');
196+
const contentButton = document.createElement('button');
197+
contentButton.innerHTML = 'Content Button';
198+
const contentInput = document.createElement('input');
199+
contentNode.appendChild(contentButton);
200+
contentNode.appendChild(contentInput);
201+
181202
const popup = overlays.add(
182203
new LocalOverlayController({
183204
hidesOnEsc: true,
184205
hidesOnOutsideClick: true,
185206
trapsKeyboardFocus: true,
186-
contentTemplate: () => html`
187-
<div class="demo-popup">
188-
<button id="el1">Button</button>
189-
<a id="el2" href="#">Anchor</a>
190-
<div id="el3" tabindex="0">Tabindex</div>
191-
<input id="el4" placeholder="Input" />
192-
<div id="el5" contenteditable>Content editable</div>
193-
<textarea id="el6">Textarea</textarea>
194-
<select id="el7">
195-
<option>1</option>
196-
</select>
197-
</div>
198-
`,
199-
invokerTemplate: () =>
200-
html`
201-
<button @click=${() => popup.show()}>UK</button>
202-
`,
207+
contentNode,
208+
invokerNode,
203209
}),
204210
);
211+
212+
invokerNode.addEventListener('click', () => {
213+
popup.toggle();
214+
});
205215
return html`
206216
<style>
207217
${popupDemoStyle}

0 commit comments

Comments
 (0)