diff --git a/Changelog.md b/Changelog.md index 74607198b..4a1ca75e7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -21,6 +21,7 @@ ### Fixed +- Auto-complete select error when input is empty (TypeError: Cannot read properties of undefined (reading 'length')) - Minus is being deleted with the first digit #2860 - Problems with deleting static chars in alternator mask #2648 - Backspace on controlled input adds "backspace" text to the value #2865 diff --git a/lib/eventhandlers.js b/lib/eventhandlers.js index 66d0415d4..b1ecccb6f 100644 --- a/lib/eventhandlers.js +++ b/lib/eventhandlers.js @@ -508,9 +508,18 @@ const EventHandlers = { }, 0); break; case "deleteContentBackward": - var keydown = new $.Event("keydown"); - keydown.key = keys.Backspace; - EventHandlers.keyEvent.call(input, keydown); + if (e.inputType && e.inputType.startsWith("insert")) { + // e.inputType indicates an insert operation (e.g. browser autocomplete + // with insertReplacementText) but analyseChanges incorrectly detected + // a deletion due to caret position / buffer length mismatch. + // Apply the value directly instead of dispatching a backspace event. + applyInputValue(input, inputValue); + caret.call(inputmask, input, caretPos.begin, caretPos.end, true); + } else { + var keydown = new $.Event("keydown"); + keydown.key = keys.Backspace; + EventHandlers.keyEvent.call(input, keydown); + } break; default: applyInputValue(input, inputValue); diff --git a/qunit/simulator.js b/qunit/simulator.js index d4875a6b4..9b9f2b321 100644 --- a/qunit/simulator.js +++ b/qunit/simulator.js @@ -249,4 +249,18 @@ export default function ($, Inputmask) { } $(input).trigger("input"); }; + + // Simulate a browser autocomplete selection on a masked input. + // Sets the native value and fires an input event with inputType "insertReplacementText", + // matching the browser behavior when the user selects a suggestion from the autocomplete list. + $.fn.autocomplete = function (value, caretBegin, caretEnd) { + const input = this.nodeName ? this : this[0]; + input.inputmask.__valueSet.call(input, value); + if (caretBegin !== undefined) { + $.caret(input, caretBegin, caretEnd !== undefined ? caretEnd : caretBegin); + } + const evt = $.Event("input"); + evt.inputType = "insertReplacementText"; + $(input).trigger(evt); + }; } diff --git a/qunit/tests_inputeventonly.js b/qunit/tests_inputeventonly.js index a1f3ddb17..0ad809664 100644 --- a/qunit/tests_inputeventonly.js +++ b/qunit/tests_inputeventonly.js @@ -204,4 +204,42 @@ export default function (qunit, Inputmask) { done(); }, 0); }); + + // Regression test: selecting a browser autocomplete suggestion on an empty masked input + // must not throw TypeError. analyseChanges can misdetect the insertion as a deletion + // (deleteContentBackward) when caret is at 0 and the autocomplete value is shorter than + // the mask template. The fix guards against this contradiction by checking e.inputType. + qunit.test( + "(999) 999-9999 - autocomplete on empty input (insertReplacementText)", + function (assert) { + const done = assert.async(), + $fixture = $("#qunit-fixture"); + $fixture.append(''); + const testmask = document.getElementById("testmask"); + Inputmask("(999) 999-9999", { inputEventOnly: true }).mask(testmask); + + testmask.focus(); + setTimeout(function () { + // Simulate browser autocomplete: native value set to "1231231234", caret at 0, + // input event fires with inputType "insertReplacementText". + assert.ok( + (function () { + try { + $(testmask).autocomplete("1231231234", 0, 0); + return true; + } catch (e) { + return false; + } + })(), + "No error thrown on autocomplete" + ); + assert.equal( + testmask.value, + "(123) 123-1234", + "Result " + testmask.value + ); + done(); + }, 0); + } + ); }