/**
|
* Not type checking this file because flow doesn't like attaching
|
* properties to Elements.
|
*/
|
|
import { isTextInputType } from 'web/util/element'
|
import { looseEqual, looseIndexOf } from 'shared/util'
|
import { mergeVNodeHook } from 'core/vdom/helpers/index'
|
import { warn, isIE9, isIE, isEdge } from 'core/util/index'
|
|
/* 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')
|
}
|
})
|
}
|
|
const directive = {
|
inserted (el, binding, vnode, oldVnode) {
|
if (vnode.tag === 'select') {
|
// #6903
|
if (oldVnode.elm && !oldVnode.elm._vOptions) {
|
mergeVNodeHook(vnode, 'postpatch', () => {
|
directive.componentUpdated(el, binding, vnode)
|
})
|
} else {
|
setSelected(el, binding, vnode.context)
|
}
|
el._vOptions = [].map.call(el.options, getValue)
|
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
|
el._vModifiers = binding.modifiers
|
if (!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
|
}
|
}
|
}
|
},
|
|
componentUpdated (el, binding, vnode) {
|
if (vnode.tag === 'select') {
|
setSelected(el, binding, vnode.context)
|
// in case the options rendered by v-for have changed,
|
// it's possible that the value is out-of-sync with the rendered options.
|
// detect such cases and filter out values that no longer has a matching
|
// option in the DOM.
|
const prevOptions = el._vOptions
|
const curOptions = el._vOptions = [].map.call(el.options, getValue)
|
if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) {
|
// trigger change event if
|
// no matching option found for at least one value
|
const needReset = el.multiple
|
? binding.value.some(v => hasNoMatchingOption(v, curOptions))
|
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions)
|
if (needReset) {
|
trigger(el, 'change')
|
}
|
}
|
}
|
}
|
}
|
|
function setSelected (el, binding, vm) {
|
actuallySetSelected(el, binding, vm)
|
/* istanbul ignore if */
|
if (isIE || isEdge) {
|
setTimeout(() => {
|
actuallySetSelected(el, binding, vm)
|
}, 0)
|
}
|
}
|
|
function actuallySetSelected (el, binding, vm) {
|
const value = binding.value
|
const isMultiple = el.multiple
|
if (isMultiple && !Array.isArray(value)) {
|
process.env.NODE_ENV !== 'production' && warn(
|
`<select multiple v-model="${binding.expression}"> ` +
|
`expects an Array value for its binding, but got ${
|
Object.prototype.toString.call(value).slice(8, -1)
|
}`,
|
vm
|
)
|
return
|
}
|
let selected, option
|
for (let i = 0, l = el.options.length; i < l; i++) {
|
option = el.options[i]
|
if (isMultiple) {
|
selected = looseIndexOf(value, getValue(option)) > -1
|
if (option.selected !== selected) {
|
option.selected = selected
|
}
|
} else {
|
if (looseEqual(getValue(option), value)) {
|
if (el.selectedIndex !== i) {
|
el.selectedIndex = i
|
}
|
return
|
}
|
}
|
}
|
if (!isMultiple) {
|
el.selectedIndex = -1
|
}
|
}
|
|
function hasNoMatchingOption (value, options) {
|
return options.every(o => !looseEqual(o, value))
|
}
|
|
function getValue (option) {
|
return '_value' in option
|
? option._value
|
: option.value
|
}
|
|
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)
|
}
|
|
export default directive
|