From 497e3d58ec674c80b755683747a57274c326c262 Mon Sep 17 00:00:00 2001 From: inottn Date: Sat, 13 Dec 2025 21:39:39 +0800 Subject: [PATCH] fix(Field): improve cursor position handling for emoji input and formatting (#13711) --- packages/vant/src/field/Field.tsx | 8 ++--- packages/vant/src/field/test/index.spec.js | 36 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/vant/src/field/Field.tsx b/packages/vant/src/field/Field.tsx index 6b2dd156c..f79b8eadd 100644 --- a/packages/vant/src/field/Field.tsx +++ b/packages/vant/src/field/Field.tsx @@ -329,8 +329,7 @@ export default defineComponent({ // When the value length exceeds maxlength, // record the excess length for correcting the cursor position. // https://github.com/youzan/vant/issues/11289 - const limitDiffLen = - getStringLength(originalValue) - getStringLength(value); + const limitDiffLen = originalValue.length - value.length; // https://github.com/youzan/vant/issues/13058 if (props.type === 'number' || props.type === 'digit') { @@ -368,8 +367,7 @@ export default defineComponent({ const bcoVal = cutString(originalValue, selectionEnd!); // Record the length change of `bcoVal` after formatting, // which is used to correct the cursor position. - formatterDiffLen = - getStringLength(formatter(bcoVal)) - getStringLength(bcoVal); + formatterDiffLen = formatter(bcoVal).length - bcoVal.length; } } @@ -380,7 +378,7 @@ export default defineComponent({ inputRef.value.value = value; if (isDef(selectionStart) && isDef(selectionEnd)) { - const valueLen = getStringLength(value); + const valueLen = value.length; if (limitDiffLen) { selectionStart -= limitDiffLen; diff --git a/packages/vant/src/field/test/index.spec.js b/packages/vant/src/field/test/index.spec.js index eed9f8138..ca171d8bc 100644 --- a/packages/vant/src/field/test/index.spec.js +++ b/packages/vant/src/field/test/index.spec.js @@ -608,3 +608,39 @@ test("should not be set label's for attribute when using input slot", async () = wrapper.find('.van-field__label label').attributes('for'), ).toBeUndefined(); }); + +test('should update selection range correctly when inputting text into string with emoji', async () => { + const wrapper = mount(Field, { + props: { + maxlength: 2, + modelValue: '😀😀', + }, + }); + + const input = wrapper.find('input'); + await input.trigger('focus'); + + input.element.value = '😀😀😀'; + input.element.selectionEnd = 6; + input.trigger('input'); + + expect(input.element.selectionEnd).toEqual(4); +}); + +test('should update selection range correctly when using formatter with emoji', async () => { + const wrapper = mount(Field, { + props: { + modelValue: '', + formatter: (val) => val.replace('1', '😀😀'), + }, + }); + + const input = wrapper.find('input'); + await input.trigger('focus'); + + input.element.value = '1'; + input.element.selectionEnd = 1; + input.trigger('input'); + + expect(input.element.selectionEnd).toEqual(4); +});