From c603bb0956911c5c3f6a1a9a939ccba04a793288 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Wed, 4 Mar 2026 09:02:22 +0100 Subject: [PATCH 1/7] Completer.js: Trigger autosubmit on focus out for manual input if the not instrumented Ensure the form submits automatically when: - The completer is not part of term input or instrumented - The completer has the autosubmit dataset attribute - No suggestion is explicitly selected on manual input --- asset/js/widget/Completer.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index a11548a3f..b8070efd4 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -6,6 +6,7 @@ define(["../notjQuery"], function ($) { constructor(input, instrumented = false) { this.input = input; this.instrumented = instrumented; + this.hasNotBeenCompleted = false; // Flag to identify if the input has been completed at least once. this.selectionStartInput = null; this.selectionActive = false; this.mouseSelectionActive = false; @@ -268,6 +269,7 @@ define(["../notjQuery"], function ($) { complete(input, value, data) { $(input).focus({ scripted: true }); + this.hasNotBeenCompleted = false; if (this.instrumented) { if (! Object.keys(data).length) { @@ -476,7 +478,7 @@ define(["../notjQuery"], function ($) { } onFocusOut(event) { - if (this.completedInput === null) { + if (this.completedInput === null && (this.instrumented || ! this.hasNotBeenCompleted)) { // If there are multiple instances of Completer bound to the same suggestion container // all of them try to handle the event. Though, only one of them is responsible and // that's the one which has a completed input set. @@ -492,12 +494,20 @@ define(["../notjQuery"], function ($) { && ! this.termSuggestions.contains(document.activeElement) ) { // Hide the suggestions if the user doesn't navigate them - if (input !== completedInput) { + if (completedInput !== null && input !== completedInput) { // Restore input if a suggestion lost focus this.suggest(completedInput, this.completedValue); } this.hideSuggestions(); + + // Autosubmit when the user leaves without selecting a suggestion on manual input. + // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle + // autosubmit themselves via BaseInput.autoSubmit() with proper term data. + if (! this.instrumented && this.shouldAutoSubmit()) { + this.hasNotBeenCompleted = false; + $(input.form).trigger('submit', { submittedBy: input }); + } } }, 250); } @@ -712,6 +722,7 @@ define(["../notjQuery"], function ($) { onInput(event) { let input = event.target; + this.hasNotBeenCompleted = true; if (input.minLength > 0 && input.value.length < input.minLength) { return; From 68994ba2a12a0d2e3f14bc6a00bf4d46036efb1e Mon Sep 17 00:00:00 2001 From: raviks789 Date: Fri, 6 Mar 2026 13:04:45 +0100 Subject: [PATCH 2/7] fix: resolve review comments - rename property hasNotBeenCompleted to hasBeenManuallyChanged - Separate autosubmit logic from suggestionkiller --- asset/js/widget/Completer.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index b8070efd4..1fb85045f 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -6,7 +6,7 @@ define(["../notjQuery"], function ($) { constructor(input, instrumented = false) { this.input = input; this.instrumented = instrumented; - this.hasNotBeenCompleted = false; // Flag to identify if the input has been completed at least once. + this.hasBeenManuallyChanged = false; // Flag to identify if the input has been manually changed. this.selectionStartInput = null; this.selectionActive = false; this.mouseSelectionActive = false; @@ -269,7 +269,7 @@ define(["../notjQuery"], function ($) { complete(input, value, data) { $(input).focus({ scripted: true }); - this.hasNotBeenCompleted = false; + this.hasBeenManuallyChanged = false; if (this.instrumented) { if (! Object.keys(data).length) { @@ -478,7 +478,18 @@ define(["../notjQuery"], function ($) { } onFocusOut(event) { - if (this.completedInput === null && (this.instrumented || ! this.hasNotBeenCompleted)) { + // Autosubmit when the user leaves the input without selecting a suggestion on manually changing the value. + // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle + // autosubmit themselves via BaseInput.autoSubmit() with proper term data. + if (! this.instrumented && this.hasBeenManuallyChanged && this.shouldAutoSubmit()) { + this.hasBeenManuallyChanged = false; + let input = event.target; + setTimeout(() => { + $(input.form).trigger('submit', { submittedBy: input }); + }, 250); + } + + if (this.completedInput === null) { // If there are multiple instances of Completer bound to the same suggestion container // all of them try to handle the event. Though, only one of them is responsible and // that's the one which has a completed input set. @@ -494,20 +505,12 @@ define(["../notjQuery"], function ($) { && ! this.termSuggestions.contains(document.activeElement) ) { // Hide the suggestions if the user doesn't navigate them - if (completedInput !== null && input !== completedInput) { + if (input !== completedInput) { // Restore input if a suggestion lost focus this.suggest(completedInput, this.completedValue); } this.hideSuggestions(); - - // Autosubmit when the user leaves without selecting a suggestion on manual input. - // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle - // autosubmit themselves via BaseInput.autoSubmit() with proper term data. - if (! this.instrumented && this.shouldAutoSubmit()) { - this.hasNotBeenCompleted = false; - $(input.form).trigger('submit', { submittedBy: input }); - } } }, 250); } @@ -722,7 +725,7 @@ define(["../notjQuery"], function ($) { onInput(event) { let input = event.target; - this.hasNotBeenCompleted = true; + this.hasBeenManuallyChanged = true; if (input.minLength > 0 && input.value.length < input.minLength) { return; From 709a7193428aad9f30df632f308507dc59f8abc4 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Fri, 6 Mar 2026 15:16:01 +0100 Subject: [PATCH 3/7] fix: autosubmit only if the suggestion is closed --- asset/js/widget/Completer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index 1fb85045f..e1b0de4a9 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -478,7 +478,7 @@ define(["../notjQuery"], function ($) { } onFocusOut(event) { - // Autosubmit when the user leaves the input without selecting a suggestion on manually changing the value. + // Autosubmit if the user leaves the input and the input has been manually changed. // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle // autosubmit themselves via BaseInput.autoSubmit() with proper term data. if (! this.instrumented && this.hasBeenManuallyChanged && this.shouldAutoSubmit()) { @@ -486,7 +486,7 @@ define(["../notjQuery"], function ($) { let input = event.target; setTimeout(() => { $(input.form).trigger('submit', { submittedBy: input }); - }, 250); + }, 300); } if (this.completedInput === null) { @@ -511,6 +511,7 @@ define(["../notjQuery"], function ($) { } this.hideSuggestions(); + this.hasBeenManuallyChanged = true; } }, 250); } @@ -668,6 +669,7 @@ define(["../notjQuery"], function ($) { break; case 'Tab': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); + this.hasBeenManuallyChanged = false; if (suggestions.length === 1) { event.preventDefault(); let input = event.target; @@ -689,6 +691,7 @@ define(["../notjQuery"], function ($) { break; case 'Escape': if (this.hasSuggestions()) { + this.hasBeenManuallyChanged = true; this.hideSuggestions() event.preventDefault(); } @@ -696,6 +699,7 @@ define(["../notjQuery"], function ($) { break; case 'ArrowUp': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); + this.hasBeenManuallyChanged = false; if (suggestions.length) { event.preventDefault(); this.moveToSuggestion(true); @@ -704,6 +708,7 @@ define(["../notjQuery"], function ($) { break; case 'ArrowDown': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); + this.hasBeenManuallyChanged = false; if (suggestions.length) { event.preventDefault(); this.moveToSuggestion(); From 2c758e74c95d9eb7681341f6c014ae6a3f01e9f5 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Fri, 20 Mar 2026 13:21:28 +0100 Subject: [PATCH 4/7] Completer: Move autosubmit logic into the timer `onFocusOut` --- asset/js/widget/Completer.js | 51 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index e1b0de4a9..31f788919 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -269,6 +269,7 @@ define(["../notjQuery"], function ($) { complete(input, value, data) { $(input).focus({ scripted: true }); + // Disable autosubmit for the input in non-instrumented mode if the input is completed. this.hasBeenManuallyChanged = false; if (this.instrumented) { @@ -323,8 +324,12 @@ define(["../notjQuery"], function ($) { if (! stopAtEdge && this.completedValue !== null) { if (input === this.completedInput) { + // Re-enable autosubmit for the input in non-instrumented mode when the user moves from suggestions back to the input. + this.hasBeenManuallyChanged = true; this.suggest(this.completedInput, this.completedValue); } else { + // Disable the autosubmit for the input in non-instrumented mode while moving through suggestions. + this.hasBeenManuallyChanged = false; this.suggest(this.completedInput, input.value, { ...input.dataset }); } } @@ -478,16 +483,22 @@ define(["../notjQuery"], function ($) { } onFocusOut(event) { - // Autosubmit if the user leaves the input and the input has been manually changed. - // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle - // autosubmit themselves via BaseInput.autoSubmit() with proper term data. - if (! this.instrumented && this.hasBeenManuallyChanged && this.shouldAutoSubmit()) { - this.hasBeenManuallyChanged = false; - let input = event.target; - setTimeout(() => { - $(input.form).trigger('submit', { submittedBy: input }); - }, 300); - } + setTimeout(() => { + // Autosubmit if the user leaves the input and the input has been manually changed. + // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle + // autosubmit themselves via BaseInput.autoSubmit() with proper term data. + if ( + ! this.instrumented + && this.hasBeenManuallyChanged + && ! this.hasSuggestions() + && this.shouldAutoSubmit() + ) { + // Reset this flag since the user has navigated away from the input and the submit event + // will be triggered by the input's form submit event handler. + this.hasBeenManuallyChanged = false; + $(this.input.form).trigger('submit', {submittedBy: this.input}); + } + }, 300); if (this.completedInput === null) { // If there are multiple instances of Completer bound to the same suggestion container @@ -511,6 +522,9 @@ define(["../notjQuery"], function ($) { } this.hideSuggestions(); + + // This triggers the autosubmit if the user navigates away from the input for non-instrumented mode. + // as the input is reset to the manually changed value once suggestions are hidden. this.hasBeenManuallyChanged = true; } }, 250); @@ -645,6 +659,11 @@ define(["../notjQuery"], function ($) { onKeyDown(event) { let suggestions; + const keys = ['Tab', 'ArrowDown', 'ArrowUp']; + if (keys.includes(event.key) && (event.target === this.input && this.hasSuggestions())) { + // Disable the autosubmit if the user navigates away from the input but is within the suggestions. + this.hasBeenManuallyChanged = false; + } switch (event.key) { case ' ': @@ -669,7 +688,6 @@ define(["../notjQuery"], function ($) { break; case 'Tab': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); - this.hasBeenManuallyChanged = false; if (suggestions.length === 1) { event.preventDefault(); let input = event.target; @@ -691,15 +709,16 @@ define(["../notjQuery"], function ($) { break; case 'Escape': if (this.hasSuggestions()) { + this.hideSuggestions(); + // This triggers the autosubmit if the user navigates away from the input for non-instrumented mode. + // as the input has the manually changed value. this.hasBeenManuallyChanged = true; - this.hideSuggestions() event.preventDefault(); } break; case 'ArrowUp': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); - this.hasBeenManuallyChanged = false; if (suggestions.length) { event.preventDefault(); this.moveToSuggestion(true); @@ -708,7 +727,6 @@ define(["../notjQuery"], function ($) { break; case 'ArrowDown': suggestions = this.termSuggestions.querySelectorAll('[type="button"]'); - this.hasBeenManuallyChanged = false; if (suggestions.length) { event.preventDefault(); this.moveToSuggestion(); @@ -730,8 +748,6 @@ define(["../notjQuery"], function ($) { onInput(event) { let input = event.target; - this.hasBeenManuallyChanged = true; - if (input.minLength > 0 && input.value.length < input.minLength) { return; } @@ -749,6 +765,9 @@ define(["../notjQuery"], function ($) { dataElement.value = input.value; } + // This flag triggers the autosubmit if the user navigates away from the input for non-instrumented mode + // and the input has the manually changed value. + this.hasBeenManuallyChanged = true; let [value, data] = this.prepareCompletionData(input); this.completedInput = input; this.completedValue = value; From 5de4099aef436be05c482f74ad3251aa083bfa45 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Mon, 23 Mar 2026 14:43:35 +0100 Subject: [PATCH 5/7] Set the flag hasBeenManuallyChanged on Escape in onSuggestionKeyDown --- asset/js/widget/Completer.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index 31f788919..86e10c688 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -519,13 +519,12 @@ define(["../notjQuery"], function ($) { if (input !== completedInput) { // Restore input if a suggestion lost focus this.suggest(completedInput, this.completedValue); + // This triggers the autosubmit if the user navigates away from the input for non-instrumented mode. + // as the input is reset to the manually changed value once suggestions are hidden. + this.hasBeenManuallyChanged = true; } this.hideSuggestions(); - - // This triggers the autosubmit if the user navigates away from the input for non-instrumented mode. - // as the input is reset to the manually changed value once suggestions are hidden. - this.hasBeenManuallyChanged = true; } }, 250); } @@ -597,6 +596,8 @@ define(["../notjQuery"], function ($) { $(this.completedInput).focus({ scripted: true }); this.suggest(this.completedInput, this.completedValue); this.clearSelection(); + this.hasBeenManuallyChanged = true; + break; case 'Tab': event.preventDefault(); @@ -710,9 +711,6 @@ define(["../notjQuery"], function ($) { case 'Escape': if (this.hasSuggestions()) { this.hideSuggestions(); - // This triggers the autosubmit if the user navigates away from the input for non-instrumented mode. - // as the input has the manually changed value. - this.hasBeenManuallyChanged = true; event.preventDefault(); } From 58553c11120a1d9732898a1f0f292a7de0ea5a41 Mon Sep 17 00:00:00 2001 From: Ravi Srinivasa Date: Thu, 30 Apr 2026 10:30:25 +0200 Subject: [PATCH 6/7] Completer.js: Use instrumented flag to change the hasBeenManuallyChanged flag in `onKeyDown` --- asset/js/widget/Completer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index 86e10c688..5c9dbee91 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -661,7 +661,11 @@ define(["../notjQuery"], function ($) { onKeyDown(event) { let suggestions; const keys = ['Tab', 'ArrowDown', 'ArrowUp']; - if (keys.includes(event.key) && (event.target === this.input && this.hasSuggestions())) { + if ( + ! this.instrumented + && keys.includes(event.key) + && this.hasSuggestions() + ) { // Disable the autosubmit if the user navigates away from the input but is within the suggestions. this.hasBeenManuallyChanged = false; } From d983aeae6a3dfbf80675da43cfd9c2175556e6a1 Mon Sep 17 00:00:00 2001 From: Ravi Srinivasa Date: Thu, 30 Apr 2026 14:03:42 +0200 Subject: [PATCH 7/7] Do not push callback onto the event stack for non-instrumented, non-autosubmit completers --- asset/js/widget/Completer.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/asset/js/widget/Completer.js b/asset/js/widget/Completer.js index 5c9dbee91..1596d331d 100644 --- a/asset/js/widget/Completer.js +++ b/asset/js/widget/Completer.js @@ -483,22 +483,22 @@ define(["../notjQuery"], function ($) { } onFocusOut(event) { - setTimeout(() => { - // Autosubmit if the user leaves the input and the input has been manually changed. - // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle - // autosubmit themselves via BaseInput.autoSubmit() with proper term data. - if ( - ! this.instrumented - && this.hasBeenManuallyChanged - && ! this.hasSuggestions() - && this.shouldAutoSubmit() - ) { - // Reset this flag since the user has navigated away from the input and the submit event - // will be triggered by the input's form submit event handler. - this.hasBeenManuallyChanged = false; - $(this.input.form).trigger('submit', {submittedBy: this.input}); - } - }, 300); + if (! this.instrumented && this.shouldAutoSubmit()) { + setTimeout(() => { + // Autosubmit if the user leaves the input and the input has been manually changed. + // Only for non-instrumented mode — instrumented inputs (e.g. TermInput) handle + // autosubmit themselves via BaseInput.autoSubmit() with proper term data. + if ( + this.hasBeenManuallyChanged + && ! this.hasSuggestions() + ) { + // Reset this flag since the user has navigated away from the input and the submit event + // will be triggered by the input's form submit event handler. + this.hasBeenManuallyChanged = false; + $(this.input.form).trigger('submit', {submittedBy: this.input}); + } + }, 300); + } if (this.completedInput === null) { // If there are multiple instances of Completer bound to the same suggestion container