Fix keyboard and space handling in the rich text editor suggestions popup
What does this MR do and why?
In the rich text editor, typing a colon after a space (standard French typography, and common in English too) opens the emoji popup and leaves it armed for the rest of the sentence. While it is open, Enter does nothing instead of starting a new paragraph, Tab does nothing, and Enter inserts the highlighted match over the colon. In the create work item modal, Escape closes the whole modal and discards the draft.
Root cause, in one line each: the dropdown returned "handled" for Enter and Tab even with nothing highlighted; ProseMirror calls preventDefault but not stopPropagation, so a consumed Escape still bubbled to the modal's own handler; and every trigger was configured with allowSpaces: true, so the : query kept growing past the space and matched every emoji whose description contains one.
Three changes:
- Enter and Tab fall through to the editor when no item is highlighted, and the popup hides on fall-through.
- Escape stops propagating once the popup consumes it, so it dismisses the popup and not the surrounding dialog. A second Escape reaches the form as before.
- A space typed immediately after a trigger character dismisses the popup, for every trigger. No autocompleted name starts with a space, so that space means prose rather than a query. Spaces later in the query still search, so
:smilingnarrows the smiling faces and#login pagestill finds an issue by title.
Unchanged: multi-word search for every trigger (@john doe, ~"UX Paper Cuts", %Next release), arrow-key navigation, :smile plus Enter, and the rule that a colon inside a URL or a time never opens a popup.
The plain text editor already behaves this way: at.js hides its list and lets Enter or Tab through when nothing is highlighted, and the modal's cancel handler already skips itself while at.js is open. This MR gives the rich text popup the equivalent.
One difference is deliberate. at.js dismisses at any space, because GfmAutoComplete.defaultMatcher reads only the last whitespace-delimited token, which is why the plain text editor cannot search a reference by multi-word title. The rich text editor can, so it dismisses on a leading space only and keeps that capability.
Resolves #602797 (closed)
Related to #458075
Evidence
- Jest: 58 of 223 examples across the three touched specs fail at the merge base and pass here (Vue 2). Whole
content_editortree green: Vue 2 1340 examples across 126 suites, Vue 3 1322 passed with 18 skipped. The new plugin-level spec is the skipped suite under Vue 3 (VueRenderer.updatePropscannot mutate props under@vue/compat), the gapspec/frontend/vue3migration/tiptap_spec.jsdocuments. - Feature examples: four added (three to the shared autocomplete group, one to the work item create modal spec). All four fail on
origin/masterwith the two source files swapped, each at the assertion naming the defect. Predictive RSpec may not map this JavaScript change to them, so they first run at tier-2 or withpipeline:run-all-rspec. - Screenshots and recordings below: 13 matrix rows and 2 GIF stories, real headless Chrome,
origin/master26225e7e against this branch.
Trigger configuration after this MR (every trigger behaves the same way)
| Trigger | Node | Spaces inside the query | Space right after the trigger |
|---|---|---|---|
@ |
reference (user) | searched (@john doe) |
dismisses |
#, [issue:, [work_item: |
reference (issue, work item) | searched (title) | dismisses |
! |
reference (merge request) | searched (title) | dismisses |
&, [epic: |
reference (epic) | searched (title) | dismisses |
$ |
reference (snippet) | searched (title) | dismisses |
[vulnerability: |
reference (vulnerability) | searched (title) | dismisses |
~ |
referenceLabel | searched (~"UX Paper Cuts") |
dismisses |
% |
reference (milestone) | searched (title) | dismisses |
*iteration: |
reference (iteration) | searched (title) | dismisses |
" |
reference (status, type) | searched ("In progress") |
dismisses |
: |
emoji | searched (:smiling ) |
dismisses |
[[ |
link (wiki) | searched (page titles) | dismisses |
/ |
reference (command), startOfLine |
searched (title) | dismisses |
Screenshots or screen recordings
In rows 06 to 13 content, popup state and serialized markdown match on both refs; the only differing pixels are the project avatar tile (its colour derives from the project id) and stray edge pixels.
Recordings
Three beats per side; the first beat is the same state on both refs and the stories diverge at the second beat.
How to set up and validate locally
- On any new issue page, switch the description to rich text.
- Type
Steps to reproduce :then Enter, thenOpen the page. Before: the text lands on the same line. After: a new paragraph. - Type
Statut :then a space. Before: the popup stays open withA button (blood type)highlighted, and Enter inserts it. After: the popup closes at the space. - Type
Bonjour !then a space. Before: the merge request list stays open for the rest of the sentence. After: it closes at the space. - From the sidebar + menu choose New work item, switch to rich text, type
Draft description :and press Escape. Before: the modal closes and the draft is gone. After: only the popup closes; a second Escape runs the cancel flow as before. - Regressions:
:smile,@,~with a space,%with a space and/laball still complete on Enter, and multi-word search still works for every trigger; a colon inhttps://gitlab.comor10:30-11:00opens no popup.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.





























