Pārlūkot izejas kodu

Merge pull request #403 from 03juan/fix-cursor-selection-when-binding-value

Fixes #401. Preserve text selection on applicable inputs
Caleb Porzio 5 gadi atpakaļ
vecāks
revīzija
5e136c4095
5 mainītis faili ar 65 papildinājumiem un 12 dzēšanām
  1. 5 3
      dist/alpine-ie11.js
  2. 5 3
      dist/alpine.js
  3. 1 1
      package-lock.json
  4. 7 3
      src/directives/bind.js
  5. 47 2
      test/bind.spec.js

+ 5 - 3
dist/alpine-ie11.js

@@ -5995,11 +5995,13 @@
         updateSelect(el, value);
       } else {
         // Cursor position should be restored back to origin due to a safari bug
-        var cursorPosition = el.selectionStart;
+        var selectionStart = el.selectionStart;
+        var selectionEnd = el.selectionEnd;
+        var selectionDirection = el.selectionDirection;
         el.value = value;
 
-        if (el === document.activeElement) {
-          el.setSelectionRange(cursorPosition, cursorPosition);
+        if (el === document.activeElement && selectionStart !== null) {
+          el.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
         }
       }
     } else if (attrName === 'class') {

+ 5 - 3
dist/alpine.js

@@ -558,11 +558,13 @@
         updateSelect(el, value);
       } else {
         // Cursor position should be restored back to origin due to a safari bug
-        const cursorPosition = el.selectionStart;
+        const selectionStart = el.selectionStart;
+        const selectionEnd = el.selectionEnd;
+        const selectionDirection = el.selectionDirection;
         el.value = value;
 
-        if (el === document.activeElement) {
-          el.setSelectionRange(cursorPosition, cursorPosition);
+        if (el === document.activeElement && selectionStart !== null) {
+          el.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
         }
       }
     } else if (attrName === 'class') {

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
     "name": "alpinejs",
-    "version": "2.2.2",
+    "version": "2.3.0",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {

+ 7 - 3
src/directives/bind.js

@@ -44,10 +44,14 @@ export function handleAttributeBindingDirective(component, el, attrName, express
             updateSelect(el, value)
         } else {
             // Cursor position should be restored back to origin due to a safari bug
-            const cursorPosition = el.selectionStart 
+            const selectionStart = el.selectionStart
+            const selectionEnd = el.selectionEnd
+            const selectionDirection = el.selectionDirection
+
             el.value = value
-            if(el === document.activeElement) { 
-                el.setSelectionRange(cursorPosition, cursorPosition)
+
+            if (el === document.activeElement && selectionStart !== null) {
+                el.setSelectionRange(selectionStart, selectionEnd, selectionDirection)
             }
         }
     } else if (attrName === 'class') {

+ 47 - 2
test/bind.spec.js

@@ -1,5 +1,5 @@
 import Alpine from 'alpinejs'
-import { wait } from '@testing-library/dom'
+import { fireEvent, wait } from '@testing-library/dom'
 
 global.MutationObserver = class {
     observe() {}
@@ -372,6 +372,51 @@ test('classes are removed before being added', async () => {
     await wait(() => {
         expect(document.querySelector('span').classList.contains('block')).toBeFalsy()
         expect(document.querySelector('span').classList.contains('hidden')).toBeTruthy()
-        expect(document.querySelector('span').classList.contains('text-red')).toBeTruthy
+        expect(document.querySelector('span').classList.contains('text-red')).toBeTruthy()
+    })
+});
+
+test('cursor position is preserved on selectable text input', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ foo: 'bar' }">
+            <input type="text" x-model="foo" @select="foo = 'baz'">
+        </div>
+    `
+
+    Alpine.start()
+
+    document.querySelector('input').focus()
+
+    expect(document.querySelector('input').value).toEqual('bar')
+    expect(document.querySelector('input').selectionStart).toEqual(0)
+    expect(document.querySelector('input').selectionEnd).toEqual(0)
+    expect(document.querySelector('input').selectionDirection).toEqual('none')
+
+    document.querySelector('input').setSelectionRange(0, 3, 'backward')
+
+    await wait(() => {
+        expect(document.querySelector('input').value).toEqual('baz')
+        expect(document.querySelector('input').selectionStart).toEqual(0)
+        expect(document.querySelector('input').selectionEnd).toEqual(3)
+        expect(document.querySelector('input').selectionDirection).toEqual('backward')
+    })
+})
+
+// input elements that are not 'text', 'search', 'url', 'password' types
+// will throw an exception when calling their setSelectionRange() method
+// see issues #401 #404 #405
+test('setSelectionRange is not called for inapplicable input types', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ foo: 'bar' }">
+            <input type="hidden" x-model="foo">
+        </div>
+    `
+
+    Alpine.start()
+
+    fireEvent.input(document.querySelector('input'), { target: { value: 'baz' } })
+
+    await wait(() => {
+        expect(document.querySelector('input').value).toEqual('baz')
     })
 })