From edb6ad00b851d58a105344fe86eddccefbe2b82d Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Thu, 1 Oct 2020 11:15:32 -0400 Subject: [PATCH] Add keytraps to keep focus within modal windows (re: #8004) --- css/80_app.css | 9 +++++++++ modules/ui/modal.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/css/80_app.css b/css/80_app.css index d50f3a1d3..15512dd96 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -234,6 +234,15 @@ textarea.mixed::placeholder { font-style: italic; } +/* keytraps need to be invisible yet not be display:none or visibility:hidden */ +.keytrap { + width: 0; + height: 0; + padding: 0; + margin: 0; + border: 0; +} + /* tables */ table { background-color: #fff; diff --git a/modules/ui/modal.js b/modules/ui/modal.js index 5b783a7b0..9b3d0b475 100644 --- a/modules/ui/modal.js +++ b/modules/ui/modal.js @@ -40,6 +40,11 @@ export function uiModal(selection, blocking) { .append('div') .attr('class', 'modal fillL'); + modal + .append('input') + .attr('class', 'keytrap keytrap-first') + .on('focus.keytrap', moveFocusToLast); + if (!blocking) { shaded.on('click.remove-modal', () => { if (d3_event.target === this) { @@ -65,6 +70,11 @@ export function uiModal(selection, blocking) { .append('div') .attr('class', 'content'); + modal + .append('input') + .attr('class', 'keytrap keytrap-last') + .on('focus.keytrap', moveFocusToFirst); + if (animate) { shaded.transition().style('opacity', 1); } else { @@ -72,4 +82,30 @@ export function uiModal(selection, blocking) { } return shaded; + + + function moveFocusToFirst() { + let node = modal + // there are additional rules about what's focusable, but this suits our purposes + .select('a, button, input:not(.keytrap), select, textarea') + .node(); + + if (node) { + node.focus(); + } else { + d3_select(this).node().blur(); + } + } + + function moveFocusToLast() { + let nodes = modal + .selectAll('a, button, input:not(.keytrap), select, textarea') + .nodes(); + + if (nodes.length) { + nodes[nodes.length - 1].focus(); + } else { + d3_select(this).node().blur(); + } + } }