Skip to content

Commit

Permalink
feat(popup-menu): trap keyboard (ESC, navigation) without search
Browse files Browse the repository at this point in the history
Closes #701
  • Loading branch information
nikku committed Nov 22, 2022
1 parent c91bc8e commit 5ff152f
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 7 deletions.
25 changes: 20 additions & 5 deletions lib/features/popup-menu/PopupMenuComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
} from '../../ui';

import {
closest as domClosest
closest as domClosest,
matches as domMatches
} from 'min-dom';

import PopupMenuList from './PopupMenuList';
Expand Down Expand Up @@ -99,7 +100,6 @@ export default function PopupMenuComponent(props) {
const handleKeyDown = event => {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();

return onClose();
}
Expand Down Expand Up @@ -159,14 +159,18 @@ export default function PopupMenuComponent(props) {
}, [ onSelect, onClose, selectedEntry, keyboardSelect ]);

const handleKey = useCallback(event => {
setValue(() => event.target.value);
if (domMatches(event.target, 'input')) {
setValue(() => event.target.value);
}
}, [ setValue ]);

const displayHeader = props.title || Object.keys(headerEntries).length > 0;

return html`
<${PopupMenuWrapper}
onClose=${ onClose }
onKeyup=${ handleKey }
onKeydown=${ handleKeyDown }
className=${ className }
position=${position}
width=${ width }
Expand Down Expand Up @@ -200,8 +204,6 @@ export default function PopupMenuComponent(props) {
<input
ref=${ inputRef }
type="text"
onKeyup=${ handleKey }
onKeydown=${ handleKeyDown }
/>
</div>
` }
Expand Down Expand Up @@ -229,11 +231,15 @@ export default function PopupMenuComponent(props) {
function PopupMenuWrapper(props) {
const {
onClose,
onKeydown,
onKeyup,
className,
children,
position: positionGetter
} = props;

const popupRef = useRef();

const checkClose = useCallback((event) => {

const overlay = domClosest(event.target, '.djs-popup .djs-popup-overlay', true);
Expand All @@ -259,10 +265,19 @@ function PopupMenuWrapper(props) {
overlayEl.style.top = `${position.y}px`;
}, [ overlayRef.current, positionGetter ]);

// focus popup initially, on mount
useLayoutEffect(() => {
popupRef.current && popupRef.current.focus();
}, []);

return html`
<div
class=${ classNames('djs-popup', className) }
onClick=${ checkClose }
onKeydown=${ onKeydown }
onKeyup=${ onKeyup }
ref=${ popupRef }
tabIndex="-1"
>
<div
class="djs-popup-overlay"
Expand Down
65 changes: 63 additions & 2 deletions test/spec/features/popup-menu/PopupMenuComponentSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,51 @@ describe('features/popup-menu - <PopupMenu>', function() {
}));


describe('should focus', function() {

it('with search', inject(function() {

// when
createPopupMenu({
container,
search: true,
entries: [
{ id: '1', label: 'Entry 1' },
{ id: '2', label: 'Entry 2' },
{ id: '3', label: 'Entry 3' },
{ id: '4', label: 'Entry 4' },
{ id: '5', label: 'Entry 5' },
{ id: '6', label: 'Last' }
]
});

const searchInputEl = domQuery(
'.djs-popup-search input', container
);

// then
expect(document.activeElement).to.equal(searchInputEl);
}));


it('without search', inject(function() {

// when
createPopupMenu({
container
});

const popupEl = domQuery(
'.djs-popup', container
);

// then
expect(document.activeElement).to.equal(popupEl);
}));

});


it('should apply custom width', inject(function() {

// when
Expand Down Expand Up @@ -141,7 +186,7 @@ describe('features/popup-menu - <PopupMenu>', function() {

describe('body', function() {

it('should focus first entry', inject(function() {
it('should select first entry', inject(function() {
const entries = [
{ id: '1', label: 'Entry 1' },
{ id: '2', label: 'Entry 2' }
Expand Down Expand Up @@ -186,7 +231,7 @@ describe('features/popup-menu - <PopupMenu>', function() {
];


it('should filter entries + focus first', inject(async function() {
it('should filter entries + select first', inject(async function() {

// given
createPopupMenu({ container, entries, search: true });
Expand Down Expand Up @@ -319,6 +364,22 @@ describe('features/popup-menu - <PopupMenu>', function() {
}));


it('on popup', inject(function() {

// given
const onClose = sinon.spy();
createPopupMenu({ container, entries, onClose, search: true });

const popupEl = domQuery('.djs-popup', container);

// when
popupEl.dispatchEvent(keyDown('Escape'));

// then
expect(onClose).to.be.calledOnce;
}));


it('global', inject(async function() {

// given
Expand Down

0 comments on commit 5ff152f

Please sign in to comment.