Skip to content

Commit

Permalink
fix(feel-popup): do not close popup on escape during auto suggest
Browse files Browse the repository at this point in the history
Closes #279
  • Loading branch information
Niklas Kiefer committed Sep 15, 2023
1 parent 6163c8f commit dcbb1e6
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 20 deletions.
7 changes: 6 additions & 1 deletion src/components/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,39 @@ const noop = () => {};
* @param {Object} props
* @param {HTMLElement} [props.container]
* @param {string} [props.className]
* @param {boolean} [props.delayInitialFocus]
* @param {{x: number, y: number}} [props.position]
* @param {number} [props.width]
* @param {number} [props.height]
* @param {Function} props.onClose
* @param {Function} [props.onPostActivate]
* @param {Function} [props.onPostDeactivate]
* @param {boolean} [props.returnFocus]
* @param {boolean} [props.closeOnEscape]
* @param {string} props.title
*/
export function Popup(props) {

const {
container,
className,
delayInitialFocus,
position,
width,
height,
onClose,
onPostActivate = noop,
onPostDeactivate = noop,
returnFocus = true,
closeOnEscape = true,
title
} = props;

const focusTrapRef = useRef(null);
const popupRef = useRef(null);

const handleKeyPress = event => {
if (event.key === 'Escape') {
if (closeOnEscape && event.key === 'Escape') {
onClose();
}
};
Expand Down Expand Up @@ -101,6 +105,7 @@ export function Popup(props) {
if (popupRef.current) {
focusTrapRef.current = focusTrap.createFocusTrap(popupRef.current, {
clickOutsideDeactivates: true,
delayInitialFocus,
fallbackFocus: popupRef.current,
onPostActivate,
onPostDeactivate,
Expand Down
38 changes: 32 additions & 6 deletions src/components/entries/FEEL/FeelPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,34 @@ function FeelPopupComponent(props) {

const editorRef = useRef();

const isAutoCompletionOpen = useRef(false);

const handleSetReturnFocus = () => {
sourceElement && sourceElement.focus();
};

useEffect(() => {
const editor = editorRef.current;
const onKeyDownCapture = (event) => {

// we use capture here to make sure we handle the event before the editor does
if (event.key === 'Escape') {
isAutoCompletionOpen.current = autoCompletionOpen(event.target);
}
};

if (editor) {
editor.focus();
const onKeyDown = (event) => {

if (event.key === 'Escape') {

// close popup only if auto completion is not open
// we need to do check this because the editor is not
// stop propagating the keydown event
// cf. https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322/5
if (!isAutoCompletionOpen.current) {
onClose();
isAutoCompletionOpen.current = false;
}
}
}, [ editorRef, id ]);
};

return (
<Popup
Expand All @@ -114,6 +131,8 @@ function FeelPopupComponent(props) {

// handle focus manually on deactivate
returnFocus={ false }
closeOnEscape={ false }
delayInitialFocus={ false }
onPostDeactivate={ handleSetReturnFocus }
height={ FEEL_POPUP_HEIGHT }
width={ FEEL_POPUP_WIDTH }
Expand All @@ -122,7 +141,10 @@ function FeelPopupComponent(props) {
title={ title }
draggable />
<Popup.Body>
<div class="bio-properties-panel-feel-popup__body">
<div
onKeyDownCapture={ onKeyDownCapture }
onKeyDown={ onKeyDown }
class="bio-properties-panel-feel-popup__body">

{
type === 'feel' && (
Expand Down Expand Up @@ -171,4 +193,8 @@ function FeelPopupComponent(props) {

function prefixId(id) {
return `bio-properties-panel-${id}`;
}

function autoCompletionOpen(element) {
return element.closest('.cm-editor').querySelector('.cm-tooltip-autocomplete');
}
180 changes: 167 additions & 13 deletions test/spec/components/FeelPopup.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
act,
fireEvent,
render,
waitFor
} from '@testing-library/preact/pure';
Expand Down Expand Up @@ -29,7 +30,6 @@ const noopElement = {
};



describe('<FeelPopup>', function() {

let container;
Expand Down Expand Up @@ -116,15 +116,15 @@ describe('<FeelPopup>', function() {
});

// assume
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.exist;
expect(getFeelEditor(document.body)).to.exist;

// and when
await act(() => {
result.rerender({ element: { id: 'bar', type: 'bar' } });
});

// then
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.not.exist;
expect(getFeelEditor(document.body)).to.not.exist;
});


Expand All @@ -139,19 +139,68 @@ describe('<FeelPopup>', function() {
const btn = domQuery('button', childComponent);

// assume
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.not.exist;
expect(getFeelEditor(document.body)).to.not.exist;

// when
await act(() => {
btn.click();
});

// then
expect(getFeelEditor(document.body)).to.exist;
});


it('should focus <feel> editor', async function() {

// given
createFeelPopup({ type: 'feel' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

// when
await act(() => {
btn.click();
});

const editor = getFeelEditor(document.body);

// then
expect(document.activeElement).to.eql(domQuery('.cm-content', editor));
});


it('should autosuggest in <feel> editor', async function() {

// given
createFeelPopup({ type: 'feel' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

await act(() => {
btn.click();
});

const editor = getFeelEditor(document.body);

// assume
expect(editor).to.exist;

// when
fireEvent.keyDown(document.activeElement, { key: ' ', ctrlKey: true });

// then
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.exist;
let suggestions;
await waitFor(() => {
suggestions = domQuery('.cm-tooltip-autocomplete > ul', editor);
expect(suggestions).to.exist;
});
});


it('should close <feel> editor', async function() {
it('should NOT close <feel> editor on ESC (autosuggest)', async function() {

// given
createFeelPopup({ type: 'feel' }, container);
Expand All @@ -163,8 +212,63 @@ describe('<FeelPopup>', function() {
btn.click();
});

const editor = getFeelEditor(document.body);

expect(editor).to.exist;

fireEvent.keyDown(domQuery('.cm-content', editor), { key: ' ', ctrlKey: true });

// assume
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.exist;
let suggestions;
await waitFor(() => {
suggestions = domQuery('.cm-tooltip-autocomplete > ul', editor);
expect(suggestions).to.exist;
});

// when
fireEvent.keyDown(domQuery('.cm-content', editor), { key: 'Escape' });

// then
expect(getFeelEditor(document.body)).to.exist;
});


it('should close <feel> editor on ESC (no autosuggest)', async function() {

// given
createFeelPopup({ type: 'feel' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

await act(() => {
btn.click();
});

const editor = getFeelEditor(document.body);

// when
fireEvent.keyDown(domQuery('.cm-content', editor), { key: 'Escape' });

// then
expect(getFeelEditor(document.body)).to.not.exist;
});


it('should close <feel> editor on btn click', async function() {

// given
createFeelPopup({ type: 'feel' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

await act(() => {
btn.click();
});

// assume
expect(getFeelEditor(document.body)).to.exist;

const closeBtn = domQuery('.bio-properties-panel-feel-popup__close-btn', document.body);

Expand All @@ -174,7 +278,7 @@ describe('<FeelPopup>', function() {
});

// then
expect(domQuery('.bio-properties-panel-feel-editor-container', document.body)).to.not.exist;
expect(getFeelEditor(document.body)).to.not.exist;
});

});
Expand All @@ -191,19 +295,61 @@ describe('<FeelPopup>', function() {
const btn = domQuery('button', childComponent);

// assume
expect(domQuery('.bio-properties-panel-feelers-editor-container', document.body)).to.not.exist;
expect(getFeelersEditor(document.body)).to.not.exist;

// when
await act(() => {
btn.click();
});

// then
expect(domQuery('.bio-properties-panel-feelers-editor-container', document.body)).to.exist;
expect(getFeelersEditor(document.body)).to.exist;
});


it('should close <feelers> editor', async function() {
it('should focus <feelers> editor', async function() {

// given
createFeelPopup({ type: 'feelers' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

// when
await act(() => {
btn.click();
});

const editor = getFeelersEditor(document.body);

// then
expect(document.activeElement).to.eql(domQuery('.cm-content', editor));
});


it('should close <feelers> editor on ESC (no autosuggest)', async function() {

// given
createFeelPopup({ type: 'feelers' }, container);

const childComponent = domQuery('.child-component', container);
const btn = domQuery('button', childComponent);

await act(() => {
btn.click();
});

const editor = getFeelersEditor(document.body);

// when
fireEvent.keyDown(domQuery('.cm-content', editor), { key: 'Escape' });

// then
expect(getFeelersEditor(document.body)).to.not.exist;
});


it('should close <feelers> editor on btn click', async function() {

// given
createFeelPopup({ type: 'feelers' }, container);
Expand All @@ -216,7 +362,7 @@ describe('<FeelPopup>', function() {
});

// assume
expect(domQuery('.bio-properties-panel-feelers-editor-container', document.body)).to.exist;
expect(getFeelersEditor(document.body)).to.exist;

const closeBtn = domQuery('.bio-properties-panel-feel-popup__close-btn', document.body);

Expand All @@ -226,7 +372,7 @@ describe('<FeelPopup>', function() {
});

// then
expect(domQuery('.bio-properties-panel-feelers-editor-container', document.body)).to.not.exist;
expect(getFeelersEditor(document.body)).to.not.exist;
});

});
Expand Down Expand Up @@ -328,4 +474,12 @@ function ChildComponent(props) {
return <div class="child-component">
<button ref={ btnRef } onClick={ onClick }>Open popup</button>
</div>;
}

function getFeelEditor(container) {
return domQuery('.bio-properties-panel-feel-editor-container', container);
}

function getFeelersEditor(container) {
return domQuery('.bio-properties-panel-feelers-editor-container', container);
}
Loading

0 comments on commit dcbb1e6

Please sign in to comment.