Преглед изворни кода

fix v-model android composition events handling (vuejs/Discussion#162)

Evan You пре 11 година
родитељ
комит
cef724a5f8
4 измењених фајлова са 102 додато и 106 уклоњено
  1. 1 1
      component.json
  2. 3 3
      src/directives/model/index.js
  3. 64 52
      src/directives/model/text.js
  4. 34 50
      src/util/env.js

+ 1 - 1
component.json

@@ -33,10 +33,10 @@
     "src/directives/if.js",
     "src/directives/index.js",
     "src/directives/model/checkbox.js",
-    "src/directives/model/default.js",
     "src/directives/model/index.js",
     "src/directives/model/radio.js",
     "src/directives/model/select.js",
+    "src/directives/model/text.js",
     "src/directives/on.js",
     "src/directives/prop.js",
     "src/directives/ref.js",

+ 3 - 3
src/directives/model/index.js

@@ -1,7 +1,7 @@
 var _ = require('../../util')
 
 var handlers = {
-  _default: require('./default'),
+  text: require('./text'),
   radio: require('./radio'),
   select: require('./select'),
   checkbox: require('./checkbox')
@@ -39,11 +39,11 @@ module.exports = {
     var tag = el.tagName
     var handler
     if (tag === 'INPUT') {
-      handler = handlers[el.type] || handlers._default
+      handler = handlers[el.type] || handlers.text
     } else if (tag === 'SELECT') {
       handler = handlers.select
     } else if (tag === 'TEXTAREA') {
-      handler = handlers._default
+      handler = handlers.text
     } else {
       _.warn('v-model does not support element type: ' + tag)
       return

+ 64 - 52
src/directives/model/default.js → src/directives/model/text.js

@@ -15,23 +15,29 @@ module.exports = {
     var debounce = parseInt(this._checkParam('debounce'), 10)
 
     // handle composition events.
-    // http://blog.evanyou.me/2014/01/03/composition-event/
-    var cpLocked = false
-    this.cpLock = function () {
-      cpLocked = true
-    }
-    this.cpUnlock = function () {
-      cpLocked = false
-      // in IE11 the "compositionend" event fires AFTER
-      // the "input" event, so the input handler is blocked
-      // at the end... have to call it here.
-      set()
+    //   http://blog.evanyou.me/2014/01/03/composition-event/
+    // skip this for Android because it handles composition
+    // events quite differently. Android doesn't trigger
+    // composition events for language input methods e.g.
+    // Chinese, but instead triggers them for spelling
+    // suggestions... (see Discussion/#162)
+    var composing = false
+    if (!_.isAndroid) {
+      this.onComposeStart = function () {
+        composing = true
+      }
+      this.onComposeEnd = function () {
+        composing = false
+        // in IE11 the "compositionend" event fires AFTER
+        // the "input" event, so the input handler is blocked
+        // at the end... have to call it here.
+        self.listener()
+      }
+      _.on(el,'compositionstart', this.onComposeStart)
+      _.on(el,'compositionend', this.onComposeEnd)
     }
-    _.on(el,'compositionstart', this.cpLock)
-    _.on(el,'compositionend', this.cpUnlock)
 
-    // shared setter
-    function set () {
+    function syncToModel () {
       var val = number
         ? _.toNumber(el.value)
         : el.value
@@ -43,46 +49,50 @@ module.exports = {
     // the input with the filtered value.
     // also force update for type="range" inputs to enable
     // "lock in range" (see #506)
-    var hasReadFilter = this.filters && this.filters.read
-    this.listener = hasReadFilter || el.type === 'range'
-      ? function textInputListener () {
-          if (cpLocked) return
-          var charsOffset
-          // some HTML5 input types throw error here
-          try {
-            // record how many chars from the end of input
-            // the cursor was at
-            charsOffset = el.value.length - el.selectionStart
-          } catch (e) {}
-          // Fix IE10/11 infinite update cycle
-          // https://github.com/yyx990803/vue/issues/592
-          /* istanbul ignore if */
-          if (charsOffset < 0) {
-            return
-          }
-          set()
-          _.nextTick(function () {
-            // force a value update, because in
-            // certain cases the write filters output the
-            // same result for different input values, and
-            // the Observer set events won't be triggered.
-            var newVal = self._watcher.value
-            self.update(newVal)
-            if (charsOffset != null) {
-              var cursorPos =
-                _.toString(newVal).length - charsOffset
-              el.setSelectionRange(cursorPos, cursorPos)
-            }
-          })
-        }
-      : function textInputListener () {
-          if (cpLocked) return
-          set()
+    if ((this.filters && this.filters.read) || el.type === 'range') {
+      this.listener = function () {
+        if (composing) return
+        var charsOffset
+        // some HTML5 input types throw error here
+        try {
+          // record how many chars from the end of input
+          // the cursor was at
+          charsOffset = el.value.length - el.selectionStart
+        } catch (e) {}
+        // Fix IE10/11 infinite update cycle
+        // https://github.com/yyx990803/vue/issues/592
+        /* istanbul ignore if */
+        if (charsOffset < 0) {
+          return
         }
+        syncToModel()
+        _.nextTick(function () {
+          // force a value update, because in
+          // certain cases the write filters output the
+          // same result for different input values, and
+          // the Observer set events won't be triggered.
+          var newVal = self._watcher.value
+          self.update(newVal)
+          if (charsOffset != null) {
+            var cursorPos =
+              _.toString(newVal).length - charsOffset
+            el.setSelectionRange(cursorPos, cursorPos)
+          }
+        })
+      }
+    } else {
+      this.listener = function () {
+        if (composing) return
+        syncToModel()
+      }
+    }
 
     if (debounce) {
       this.listener = _.debounce(this.listener, debounce)
     }
+
+    // Now attach the main listener
+
     this.event = lazy ? 'change' : 'input'
     // Support jQuery events, since jQuery.trigger() doesn't
     // trigger native events in some cases and some plugins
@@ -137,8 +147,10 @@ module.exports = {
     } else {
       _.off(el, this.event, this.listener)
     }
-    _.off(el,'compositionstart', this.cpLock)
-    _.off(el,'compositionend', this.cpUnlock)
+    if (this.onComposeStart) {
+      _.off(el, 'compositionstart', this.onComposeStart)
+      _.off(el, 'compositionend', this.onComposeEnd)
+    }
     if (this.onCut) {
       _.off(el,'cut', this.onCut)
       _.off(el,'keyup', this.onDel)

+ 34 - 50
src/util/env.js

@@ -1,21 +1,40 @@
-/**
- * Can we use __proto__?
- *
- * @type {Boolean}
- */
-
+// can we use __proto__?
 exports.hasProto = '__proto__' in {}
 
-/**
- * Indicates we have a window
- *
- * @type {Boolean}
- */
-
-var toString = Object.prototype.toString
+// Browser environment sniffing
 var inBrowser = exports.inBrowser =
   typeof window !== 'undefined' &&
-  toString.call(window) !== '[object Object]'
+  Object.prototype.toString.call(window) !== '[object Object]'
+
+exports.isIE9 =
+  inBrowser &&
+  navigator.userAgent.toLowerCase().indexOf('msie 9.0') > 0
+
+exports.isAndroid =
+  inBrowser &&
+  navigator.userAgent.toLowerCase().indexOf('android') > 0
+
+// Transition property/event sniffing
+if (inBrowser && !exports.isIE9) {
+  var isWebkitTrans =
+    window.ontransitionend === undefined &&
+    window.onwebkittransitionend !== undefined
+  var isWebkitAnim =
+    window.onanimationend === undefined &&
+    window.onwebkitanimationend !== undefined
+  exports.transitionProp = isWebkitTrans
+    ? 'WebkitTransition'
+    : 'transition'
+  exports.transitionEndEvent = isWebkitTrans
+    ? 'webkitTransitionEnd'
+    : 'transitionend'
+  exports.animationProp = isWebkitAnim
+    ? 'WebkitAnimation'
+    : 'animation'
+  exports.animationEndEvent = isWebkitAnim
+    ? 'webkitAnimationEnd'
+    : 'animationend'
+}
 
 /**
  * Defer a task to execute it asynchronously. Ideally this
@@ -63,39 +82,4 @@ exports.nextTick = (function () {
     pending = true
     timerFunc(handle, 0)
   }
-})()
-
-/**
- * Detect if we are in IE9...
- *
- * @type {Boolean}
- */
-
-exports.isIE9 =
-  inBrowser &&
-  navigator.userAgent.indexOf('MSIE 9.0') > 0
-
-/**
- * Sniff transition/animation events
- */
-
-if (inBrowser && !exports.isIE9) {
-  var isWebkitTrans =
-    window.ontransitionend === undefined &&
-    window.onwebkittransitionend !== undefined
-  var isWebkitAnim =
-    window.onanimationend === undefined &&
-    window.onwebkitanimationend !== undefined
-  exports.transitionProp = isWebkitTrans
-    ? 'WebkitTransition'
-    : 'transition'
-  exports.transitionEndEvent = isWebkitTrans
-    ? 'webkitTransitionEnd'
-    : 'transitionend'
-  exports.animationProp = isWebkitAnim
-    ? 'WebkitAnimation'
-    : 'animation'
-  exports.animationEndEvent = isWebkitAnim
-    ? 'webkitAnimationEnd'
-    : 'animationend'
-}
+})()