Skip to content

Commit cef724a

Browse files
committed
fix v-model android composition events handling (vuejs/Discussion#162)
1 parent 7ed5f84 commit cef724a

File tree

4 files changed

+102
-106
lines changed

4 files changed

+102
-106
lines changed

component.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
"src/directives/if.js",
3434
"src/directives/index.js",
3535
"src/directives/model/checkbox.js",
36-
"src/directives/model/default.js",
3736
"src/directives/model/index.js",
3837
"src/directives/model/radio.js",
3938
"src/directives/model/select.js",
39+
"src/directives/model/text.js",
4040
"src/directives/on.js",
4141
"src/directives/prop.js",
4242
"src/directives/ref.js",

src/directives/model/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
var _ = require('../../util')
22

33
var handlers = {
4-
_default: require('./default'),
4+
text: require('./text'),
55
radio: require('./radio'),
66
select: require('./select'),
77
checkbox: require('./checkbox')
@@ -39,11 +39,11 @@ module.exports = {
3939
var tag = el.tagName
4040
var handler
4141
if (tag === 'INPUT') {
42-
handler = handlers[el.type] || handlers._default
42+
handler = handlers[el.type] || handlers.text
4343
} else if (tag === 'SELECT') {
4444
handler = handlers.select
4545
} else if (tag === 'TEXTAREA') {
46-
handler = handlers._default
46+
handler = handlers.text
4747
} else {
4848
_.warn('v-model does not support element type: ' + tag)
4949
return

src/directives/model/default.js renamed to src/directives/model/text.js

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,29 @@ module.exports = {
1515
var debounce = parseInt(this._checkParam('debounce'), 10)
1616

1717
// handle composition events.
18-
// http://blog.evanyou.me/2014/01/03/composition-event/
19-
var cpLocked = false
20-
this.cpLock = function () {
21-
cpLocked = true
22-
}
23-
this.cpUnlock = function () {
24-
cpLocked = false
25-
// in IE11 the "compositionend" event fires AFTER
26-
// the "input" event, so the input handler is blocked
27-
// at the end... have to call it here.
28-
set()
18+
// http://blog.evanyou.me/2014/01/03/composition-event/
19+
// skip this for Android because it handles composition
20+
// events quite differently. Android doesn't trigger
21+
// composition events for language input methods e.g.
22+
// Chinese, but instead triggers them for spelling
23+
// suggestions... (see Discussion/#162)
24+
var composing = false
25+
if (!_.isAndroid) {
26+
this.onComposeStart = function () {
27+
composing = true
28+
}
29+
this.onComposeEnd = function () {
30+
composing = false
31+
// in IE11 the "compositionend" event fires AFTER
32+
// the "input" event, so the input handler is blocked
33+
// at the end... have to call it here.
34+
self.listener()
35+
}
36+
_.on(el,'compositionstart', this.onComposeStart)
37+
_.on(el,'compositionend', this.onComposeEnd)
2938
}
30-
_.on(el,'compositionstart', this.cpLock)
31-
_.on(el,'compositionend', this.cpUnlock)
3239

33-
// shared setter
34-
function set () {
40+
function syncToModel () {
3541
var val = number
3642
? _.toNumber(el.value)
3743
: el.value
@@ -43,46 +49,50 @@ module.exports = {
4349
// the input with the filtered value.
4450
// also force update for type="range" inputs to enable
4551
// "lock in range" (see #506)
46-
var hasReadFilter = this.filters && this.filters.read
47-
this.listener = hasReadFilter || el.type === 'range'
48-
? function textInputListener () {
49-
if (cpLocked) return
50-
var charsOffset
51-
// some HTML5 input types throw error here
52-
try {
53-
// record how many chars from the end of input
54-
// the cursor was at
55-
charsOffset = el.value.length - el.selectionStart
56-
} catch (e) {}
57-
// Fix IE10/11 infinite update cycle
58-
// https://github.com/yyx990803/vue/issues/592
59-
/* istanbul ignore if */
60-
if (charsOffset < 0) {
61-
return
62-
}
63-
set()
64-
_.nextTick(function () {
65-
// force a value update, because in
66-
// certain cases the write filters output the
67-
// same result for different input values, and
68-
// the Observer set events won't be triggered.
69-
var newVal = self._watcher.value
70-
self.update(newVal)
71-
if (charsOffset != null) {
72-
var cursorPos =
73-
_.toString(newVal).length - charsOffset
74-
el.setSelectionRange(cursorPos, cursorPos)
75-
}
76-
})
77-
}
78-
: function textInputListener () {
79-
if (cpLocked) return
80-
set()
52+
if ((this.filters && this.filters.read) || el.type === 'range') {
53+
this.listener = function () {
54+
if (composing) return
55+
var charsOffset
56+
// some HTML5 input types throw error here
57+
try {
58+
// record how many chars from the end of input
59+
// the cursor was at
60+
charsOffset = el.value.length - el.selectionStart
61+
} catch (e) {}
62+
// Fix IE10/11 infinite update cycle
63+
// https://github.com/yyx990803/vue/issues/592
64+
/* istanbul ignore if */
65+
if (charsOffset < 0) {
66+
return
8167
}
68+
syncToModel()
69+
_.nextTick(function () {
70+
// force a value update, because in
71+
// certain cases the write filters output the
72+
// same result for different input values, and
73+
// the Observer set events won't be triggered.
74+
var newVal = self._watcher.value
75+
self.update(newVal)
76+
if (charsOffset != null) {
77+
var cursorPos =
78+
_.toString(newVal).length - charsOffset
79+
el.setSelectionRange(cursorPos, cursorPos)
80+
}
81+
})
82+
}
83+
} else {
84+
this.listener = function () {
85+
if (composing) return
86+
syncToModel()
87+
}
88+
}
8289

8390
if (debounce) {
8491
this.listener = _.debounce(this.listener, debounce)
8592
}
93+
94+
// Now attach the main listener
95+
8696
this.event = lazy ? 'change' : 'input'
8797
// Support jQuery events, since jQuery.trigger() doesn't
8898
// trigger native events in some cases and some plugins
@@ -137,8 +147,10 @@ module.exports = {
137147
} else {
138148
_.off(el, this.event, this.listener)
139149
}
140-
_.off(el,'compositionstart', this.cpLock)
141-
_.off(el,'compositionend', this.cpUnlock)
150+
if (this.onComposeStart) {
151+
_.off(el, 'compositionstart', this.onComposeStart)
152+
_.off(el, 'compositionend', this.onComposeEnd)
153+
}
142154
if (this.onCut) {
143155
_.off(el,'cut', this.onCut)
144156
_.off(el,'keyup', this.onDel)

src/util/env.js

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
1-
/**
2-
* Can we use __proto__?
3-
*
4-
* @type {Boolean}
5-
*/
6-
1+
// can we use __proto__?
72
exports.hasProto = '__proto__' in {}
83

9-
/**
10-
* Indicates we have a window
11-
*
12-
* @type {Boolean}
13-
*/
14-
15-
var toString = Object.prototype.toString
4+
// Browser environment sniffing
165
var inBrowser = exports.inBrowser =
176
typeof window !== 'undefined' &&
18-
toString.call(window) !== '[object Object]'
7+
Object.prototype.toString.call(window) !== '[object Object]'
8+
9+
exports.isIE9 =
10+
inBrowser &&
11+
navigator.userAgent.toLowerCase().indexOf('msie 9.0') > 0
12+
13+
exports.isAndroid =
14+
inBrowser &&
15+
navigator.userAgent.toLowerCase().indexOf('android') > 0
16+
17+
// Transition property/event sniffing
18+
if (inBrowser && !exports.isIE9) {
19+
var isWebkitTrans =
20+
window.ontransitionend === undefined &&
21+
window.onwebkittransitionend !== undefined
22+
var isWebkitAnim =
23+
window.onanimationend === undefined &&
24+
window.onwebkitanimationend !== undefined
25+
exports.transitionProp = isWebkitTrans
26+
? 'WebkitTransition'
27+
: 'transition'
28+
exports.transitionEndEvent = isWebkitTrans
29+
? 'webkitTransitionEnd'
30+
: 'transitionend'
31+
exports.animationProp = isWebkitAnim
32+
? 'WebkitAnimation'
33+
: 'animation'
34+
exports.animationEndEvent = isWebkitAnim
35+
? 'webkitAnimationEnd'
36+
: 'animationend'
37+
}
1938

2039
/**
2140
* Defer a task to execute it asynchronously. Ideally this
@@ -63,39 +82,4 @@ exports.nextTick = (function () {
6382
pending = true
6483
timerFunc(handle, 0)
6584
}
66-
})()
67-
68-
/**
69-
* Detect if we are in IE9...
70-
*
71-
* @type {Boolean}
72-
*/
73-
74-
exports.isIE9 =
75-
inBrowser &&
76-
navigator.userAgent.indexOf('MSIE 9.0') > 0
77-
78-
/**
79-
* Sniff transition/animation events
80-
*/
81-
82-
if (inBrowser && !exports.isIE9) {
83-
var isWebkitTrans =
84-
window.ontransitionend === undefined &&
85-
window.onwebkittransitionend !== undefined
86-
var isWebkitAnim =
87-
window.onanimationend === undefined &&
88-
window.onwebkitanimationend !== undefined
89-
exports.transitionProp = isWebkitTrans
90-
? 'WebkitTransition'
91-
: 'transition'
92-
exports.transitionEndEvent = isWebkitTrans
93-
? 'webkitTransitionEnd'
94-
: 'transitionend'
95-
exports.animationProp = isWebkitAnim
96-
? 'WebkitAnimation'
97-
: 'animation'
98-
exports.animationEndEvent = isWebkitAnim
99-
? 'webkitAnimationEnd'
100-
: 'animationend'
101-
}
85+
})()

0 commit comments

Comments
 (0)