diff --git a/components/_util/antInputDirective.js b/components/_util/antInputDirective.js new file mode 100644 index 000000000..c6812322e --- /dev/null +++ b/components/_util/antInputDirective.js @@ -0,0 +1,74 @@ +/** + * Not type checking this file because flow doesn't like attaching + * properties to Elements. + */ + +export const inBrowser = typeof window !== 'undefined' +export const UA = inBrowser && window.navigator.userAgent.toLowerCase() +export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 +function makeMap ( + str, + expectsLowerCase +) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} +const isTextInputType = makeMap('text,number,password,search,email,tel,url') + +function onCompositionStart (e) { + e.target.composing = true +} + +function onCompositionEnd (e) { + // prevent triggering an input event for no reason + if (!e.target.composing) return + e.target.composing = false + trigger(e.target, 'input') +} + +function trigger (el, type) { + const e = document.createEvent('HTMLEvents') + e.initEvent(type, true, true) + el.dispatchEvent(e) +} + +/* istanbul ignore if */ +if (isIE9) { + // http://www.matts411.com/post/internet-explorer-9-oninput/ + document.addEventListener('selectionchange', () => { + const el = document.activeElement + if (el && el.vmodel) { + trigger(el, 'input') + } + }) +} + +export default { + install: (Vue, options) => { + Vue.directive('ant-input', { + inserted (el, binding, vnode, oldVnode) { + if (vnode.tag === 'textarea' || isTextInputType(el.type)) { + if (!binding.modifiers || !binding.modifiers.lazy) { + el.addEventListener('compositionstart', onCompositionStart) + el.addEventListener('compositionend', onCompositionEnd) + // Safari < 10.2 & UIWebView doesn't fire compositionend when + // switching focus before confirming composition choice + // this also fixes the issue where some browsers e.g. iOS Chrome + // fires "change" instead of "input" on autocomplete. + el.addEventListener('change', onCompositionEnd) + /* istanbul ignore if */ + if (isIE9) { + el.vmodel = true + } + } + } + }, + }) + }, +} diff --git a/components/input/Input.jsx b/components/input/Input.jsx index 2364f58aa..dd7fa76a3 100644 --- a/components/input/Input.jsx +++ b/components/input/Input.jsx @@ -52,7 +52,9 @@ export default { } else { this.$forceUpdate() } - this.$emit('change.value', e.target.value) + if (!e.target.composing) { + this.$emit('change.value', e.target.value) + } this.$emit('change', e) this.$emit('input', e) }, @@ -185,6 +187,9 @@ export default { class: classNames(getInputClassName(), getClass(this)), ref: 'input', } + if ($listeners['change.value']) { + inputProps.directives = [{ name: 'ant-input' }] + } return this.renderLabeledIcon( } diff --git a/components/input/TextArea.jsx b/components/input/TextArea.jsx index 2fdd2800d..1ff5a6cde 100644 --- a/components/input/TextArea.jsx +++ b/components/input/TextArea.jsx @@ -96,9 +96,11 @@ export default { }) } else { this.$forceUpdate() + } + if (!e.target.composing) { this.$emit('change.value', e.target.value) - this.$emit('change', e) } + this.$emit('change', e) this.$emit('input', e) }, @@ -133,6 +135,9 @@ export default { input: handleTextareaChange, }, } + if ($listeners['change.value']) { + textareaProps.directives = [{ name: 'ant-input' }] + } return (