/* @flow */ 
 | 
  
 | 
import config from 'core/config' 
 | 
import { addHandler, addProp, getBindingAttr } from 'compiler/helpers' 
 | 
import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' 
 | 
  
 | 
let warn 
 | 
  
 | 
// in some cases, the event used has to be determined at runtime 
 | 
// so we used some reserved tokens during compile. 
 | 
export const RANGE_TOKEN = '__r' 
 | 
export const CHECKBOX_RADIO_TOKEN = '__c' 
 | 
  
 | 
export default function model ( 
 | 
  el: ASTElement, 
 | 
  dir: ASTDirective, 
 | 
  _warn: Function 
 | 
): ?boolean { 
 | 
  warn = _warn 
 | 
  const value = dir.value 
 | 
  const modifiers = dir.modifiers 
 | 
  const tag = el.tag 
 | 
  const type = el.attrsMap.type 
 | 
  
 | 
  if (process.env.NODE_ENV !== 'production') { 
 | 
    // inputs with type="file" are read only and setting the input's 
 | 
    // value will throw an error. 
 | 
    if (tag === 'input' && type === 'file') { 
 | 
      warn( 
 | 
        `<${el.tag} v-model="${value}" type="file">:\n` + 
 | 
        `File inputs are read only. Use a v-on:change listener instead.`, 
 | 
        el.rawAttrsMap['v-model'] 
 | 
      ) 
 | 
    } 
 | 
  } 
 | 
  
 | 
  if (el.component) { 
 | 
    genComponentModel(el, value, modifiers) 
 | 
    // component v-model doesn't need extra runtime 
 | 
    return false 
 | 
  } else if (tag === 'select') { 
 | 
    genSelect(el, value, modifiers) 
 | 
  } else if (tag === 'input' && type === 'checkbox') { 
 | 
    genCheckboxModel(el, value, modifiers) 
 | 
  } else if (tag === 'input' && type === 'radio') { 
 | 
    genRadioModel(el, value, modifiers) 
 | 
  } else if (tag === 'input' || tag === 'textarea') { 
 | 
    genDefaultModel(el, value, modifiers) 
 | 
  } else if (!config.isReservedTag(tag)) { 
 | 
    genComponentModel(el, value, modifiers) 
 | 
    // component v-model doesn't need extra runtime 
 | 
    return false 
 | 
  } else if (process.env.NODE_ENV !== 'production') { 
 | 
    warn( 
 | 
      `<${el.tag} v-model="${value}">: ` + 
 | 
      `v-model is not supported on this element type. ` + 
 | 
      'If you are working with contenteditable, it\'s recommended to ' + 
 | 
      'wrap a library dedicated for that purpose inside a custom component.', 
 | 
      el.rawAttrsMap['v-model'] 
 | 
    ) 
 | 
  } 
 | 
  
 | 
  // ensure runtime directive metadata 
 | 
  return true 
 | 
} 
 | 
  
 | 
function genCheckboxModel ( 
 | 
  el: ASTElement, 
 | 
  value: string, 
 | 
  modifiers: ?ASTModifiers 
 | 
) { 
 | 
  const number = modifiers && modifiers.number 
 | 
  const valueBinding = getBindingAttr(el, 'value') || 'null' 
 | 
  const trueValueBinding = getBindingAttr(el, 'true-value') || 'true' 
 | 
  const falseValueBinding = getBindingAttr(el, 'false-value') || 'false' 
 | 
  addProp(el, 'checked', 
 | 
    `Array.isArray(${value})` + 
 | 
    `?_i(${value},${valueBinding})>-1` + ( 
 | 
      trueValueBinding === 'true' 
 | 
        ? `:(${value})` 
 | 
        : `:_q(${value},${trueValueBinding})` 
 | 
    ) 
 | 
  ) 
 | 
  addHandler(el, 'change', 
 | 
    `var $$a=${value},` + 
 | 
        '$$el=$event.target,' + 
 | 
        `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` + 
 | 
    'if(Array.isArray($$a)){' + 
 | 
      `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` + 
 | 
          '$$i=_i($$a,$$v);' + 
 | 
      `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` + 
 | 
      `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` + 
 | 
    `}else{${genAssignmentCode(value, '$$c')}}`, 
 | 
    null, true 
 | 
  ) 
 | 
} 
 | 
  
 | 
function genRadioModel ( 
 | 
  el: ASTElement, 
 | 
  value: string, 
 | 
  modifiers: ?ASTModifiers 
 | 
) { 
 | 
  const number = modifiers && modifiers.number 
 | 
  let valueBinding = getBindingAttr(el, 'value') || 'null' 
 | 
  valueBinding = number ? `_n(${valueBinding})` : valueBinding 
 | 
  addProp(el, 'checked', `_q(${value},${valueBinding})`) 
 | 
  addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true) 
 | 
} 
 | 
  
 | 
function genSelect ( 
 | 
  el: ASTElement, 
 | 
  value: string, 
 | 
  modifiers: ?ASTModifiers 
 | 
) { 
 | 
  const number = modifiers && modifiers.number 
 | 
  const selectedVal = `Array.prototype.filter` + 
 | 
    `.call($event.target.options,function(o){return o.selected})` + 
 | 
    `.map(function(o){var val = "_value" in o ? o._value : o.value;` + 
 | 
    `return ${number ? '_n(val)' : 'val'}})` 
 | 
  
 | 
  const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]' 
 | 
  let code = `var $$selectedVal = ${selectedVal};` 
 | 
  code = `${code} ${genAssignmentCode(value, assignment)}` 
 | 
  addHandler(el, 'change', code, null, true) 
 | 
} 
 | 
  
 | 
function genDefaultModel ( 
 | 
  el: ASTElement, 
 | 
  value: string, 
 | 
  modifiers: ?ASTModifiers 
 | 
): ?boolean { 
 | 
  const type = el.attrsMap.type 
 | 
  
 | 
  // warn if v-bind:value conflicts with v-model 
 | 
  // except for inputs with v-bind:type 
 | 
  if (process.env.NODE_ENV !== 'production') { 
 | 
    const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value'] 
 | 
    const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'] 
 | 
    if (value && !typeBinding) { 
 | 
      const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value' 
 | 
      warn( 
 | 
        `${binding}="${value}" conflicts with v-model on the same element ` + 
 | 
        'because the latter already expands to a value binding internally', 
 | 
        el.rawAttrsMap[binding] 
 | 
      ) 
 | 
    } 
 | 
  } 
 | 
  
 | 
  const { lazy, number, trim } = modifiers || {} 
 | 
  const needCompositionGuard = !lazy && type !== 'range' 
 | 
  const event = lazy 
 | 
    ? 'change' 
 | 
    : type === 'range' 
 | 
      ? RANGE_TOKEN 
 | 
      : 'input' 
 | 
  
 | 
  let valueExpression = '$event.target.value' 
 | 
  if (trim) { 
 | 
    valueExpression = `$event.target.value.trim()` 
 | 
  } 
 | 
  if (number) { 
 | 
    valueExpression = `_n(${valueExpression})` 
 | 
  } 
 | 
  
 | 
  let code = genAssignmentCode(value, valueExpression) 
 | 
  if (needCompositionGuard) { 
 | 
    code = `if($event.target.composing)return;${code}` 
 | 
  } 
 | 
  
 | 
  addProp(el, 'value', `(${value})`) 
 | 
  addHandler(el, event, code, null, true) 
 | 
  if (trim || number) { 
 | 
    addHandler(el, 'blur', '$forceUpdate()') 
 | 
  } 
 | 
} 
 |