Replace mousetrap

This commit is contained in:
Harry Adel 2026-01-21 07:03:46 +02:00
parent a528a411da
commit c795dfe96b
7 changed files with 236 additions and 147 deletions

View file

@ -1,54 +1,80 @@
// We “inherit” the jquery-textcomplete plugin to integrate with our
// EscapeActions system. You should always use `escapeableTextComplete` instead
// of the vanilla `textcomplete`.
// We use @textcomplete packages to integrate with our EscapeActions system.
// You should always use `createEscapeableTextComplete` or the jQuery extension
// `escapeableTextComplete` instead of the vanilla textcomplete.
import { Textcomplete } from '@textcomplete/core';
import { TextareaEditor } from '@textcomplete/textarea';
import { ContenteditableEditor } from '@textcomplete/contenteditable';
let dropdownMenuIsOpened = false;
$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent EscapeActions side effect, for instance the
// minicard submission (on `Enter`) or going on the next column (on `Tab`).
options = {
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
return null;
/**
* Create an escapeable textcomplete instance for a textarea or contenteditable element
* @param {HTMLTextAreaElement|HTMLElement} element - The target element
* @param {Array} strategies - Array of strategy objects
* @param {Object} options - Additional options
* @returns {Textcomplete} The textcomplete instance
*/
export function createEscapeableTextComplete(element, strategies, options = {}) {
// Determine the appropriate editor based on element type
const isContentEditable = element.isContentEditable || element.contentEditable === 'true';
const Editor = isContentEditable ? ContenteditableEditor : TextareaEditor;
const editor = new Editor(element);
// Merge default options
const mergedOptions = {
dropdown: {
className: 'textcomplete-dropdown',
maxCount: 10,
placement: 'bottom',
...options.dropdown,
},
...options,
};
// Proxy to the vanilla jQuery component
this.textcomplete(strategies, options, ...otherArgs);
const textcomplete = new Textcomplete(editor, strategies, mergedOptions);
// Since commit d474017 jquery-textComplete automatically closes a potential
// opened dropdown menu when the user press Escape. This behavior conflicts
// with our EscapeActions system, but it's too complicated and hacky to
// monkey-pach textComplete to disable it -- I tried. Instead we listen to
// 'open' and 'hide' events, and create a ghost escapeAction when the dropdown
// is opened (and rely on textComplete to execute the actual action).
this.on({
'textComplete:show'() {
dropdownMenuIsOpened = true;
},
'textComplete:select'() {
EscapeActions.preventNextClick();
},
'textComplete:hide'() {
Tracker.afterFlush(() => {
// XXX Hack. We unfortunately need to set a setTimeout here to make the
// `noClickEscapeOn` work bellow, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the
// next item in the stack (for example the minicard editor) which we
// don't want.
setTimeout(() => {
dropdownMenuIsOpened = false;
}, 100);
});
},
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validate the auto-completion. We also need to stop the
// event propagation to prevent EscapeActions side effect, for instance the
// minicard submission (on `Enter`) or going on the next column (on `Tab`).
element.addEventListener('keydown', (evt) => {
if (dropdownMenuIsOpened && (evt.keyCode === 9 || evt.keyCode === 13)) {
evt.stopPropagation();
}
});
// Track dropdown state for EscapeActions integration
// Since @textcomplete automatically closes when Escape is pressed, we
// integrate with our EscapeActions system by tracking open/close state.
textcomplete.on('show', () => {
dropdownMenuIsOpened = true;
});
textcomplete.on('selected', () => {
EscapeActions.preventNextClick();
});
textcomplete.on('hidden', () => {
Tracker.afterFlush(() => {
// XXX Hack. We unfortunately need to set a setTimeout here to make the
// `noClickEscapeOn` work below, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the
// next item in the stack (for example the minicard editor) which we
// don't want.
setTimeout(() => {
dropdownMenuIsOpened = false;
}, 100);
});
});
return textcomplete;
}
// jQuery extension for backward compatibility
$.fn.escapeableTextComplete = function(strategies, options = {}) {
return this.each(function() {
createEscapeableTextComplete(this, strategies, options);
});
return this;
};
EscapeActions.register(