From b4f0a8748d5dbfbe97f526734ef537f52db2c660 Mon Sep 17 00:00:00 2001 From: Renato Atilio Date: Fri, 3 Jan 2025 16:27:44 -0300 Subject: [PATCH] UX: avoid triggering the autocomplete mid-word (#30042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the previous logic, autocompletes were being opened mid-word, which was annoying and didn't properly work – selecting an option would generate an invalid format, mixing existing with new. This PR makes a simple change: only ever trigger an autocomplete on word ends, and close them when arrow-navigating out of the word-end boundary. ref /t/-/143169 --- .../discourse/app/lib/autocomplete.js | 6 ++- .../tests/unit/lib/autocomplete-test.js | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/lib/autocomplete.js b/app/assets/javascripts/discourse/app/lib/autocomplete.js index 7f2c9fde7a2..56e3c0bdbb6 100644 --- a/app/assets/javascripts/discourse/app/lib/autocomplete.js +++ b/app/assets/javascripts/discourse/app/lib/autocomplete.js @@ -478,7 +478,11 @@ export default function (options) { } prevTerm = term; - if (term.length !== 0 && term.trim().length === 0) { + if ( + (term.length !== 0 && term.trim().length === 0) || + // close unless the caret is at the end of a word, like #line|<- + options.textHandler.value[options.textHandler.getCaretPosition()]?.trim() + ) { closeAutocomplete(); return null; } else { diff --git a/app/assets/javascripts/discourse/tests/unit/lib/autocomplete-test.js b/app/assets/javascripts/discourse/tests/unit/lib/autocomplete-test.js index 92538da136f..8af5d481c02 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/autocomplete-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/autocomplete-test.js @@ -1,3 +1,4 @@ +import { triggerKeyEvent } from "@ember/test-helpers"; import { setupTest } from "ember-qunit"; import { compile } from "handlebars"; import $ from "jquery"; @@ -169,4 +170,45 @@ module("Unit | Utility | autocomplete", function (hooks) { assert.strictEqual(element.value, "@t"); }); + + test("Autocomplete does not trigger with right-left arrow keys", async function (assert) { + const element = textArea(); + + $(element).autocomplete({ + key: ":", + template, + transformComplete: (e) => e.slice(1), + dataSource: () => [":smile:"], + }); + + await simulateKeys(element, ":smi\t"); + + assert.dom(element).hasValue(":smile: "); + + await triggerArrowKey(element, "ArrowLeft"); + await triggerArrowKey(element, "ArrowLeft"); + await triggerArrowKey(element, "ArrowLeft"); + await triggerArrowKey(element, "ArrowRight"); + + assert.strictEqual(element.selectionStart, 6); + + // This is passing, but it's a false positive + // triggerArrowKey isn't triggering the event the autocomplete listens to + assert.dom("#ac-testing").doesNotExist(); + + await triggerArrowKey(element, "ArrowRight"); + await simulateKey(element, "\b"); + + assert.dom(element).hasValue(":smile "); + assert.dom("#ac-testing").exists(); + }); }); + +async function triggerArrowKey(element, key) { + await triggerKeyEvent(element, "keydown", key, { code: key }); + await triggerKeyEvent(element, "keyup", key, { code: key }); + + const pos = element.selectionStart; + const direction = key === "ArrowLeft" ? -1 : 1; + element.setSelectionRange(pos + 1 * direction, pos + 1 * direction); +}