vModel.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import {
  2. transformModel as baseTransform,
  3. DirectiveTransform,
  4. ElementTypes,
  5. findProp,
  6. NodeTypes,
  7. hasDynamicKeyVBind
  8. } from '@vue/compiler-core'
  9. import { createDOMCompilerError, DOMErrorCodes } from '../errors'
  10. import {
  11. V_MODEL_CHECKBOX,
  12. V_MODEL_RADIO,
  13. V_MODEL_SELECT,
  14. V_MODEL_TEXT,
  15. V_MODEL_DYNAMIC,
  16. V_MODEL_DETAILS,
  17. V_MODEL_DIALOG
  18. } from '../runtimeHelpers'
  19. export const transformModel: DirectiveTransform = (dir, node, context) => {
  20. const baseResult = baseTransform(dir, node, context)
  21. // base transform has errors OR component v-model (only need props)
  22. if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
  23. return baseResult
  24. }
  25. if (dir.arg) {
  26. context.onError(
  27. createDOMCompilerError(
  28. DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
  29. dir.arg.loc
  30. )
  31. )
  32. }
  33. function checkDuplicatedValue() {
  34. const value = findProp(node, 'value')
  35. if (value) {
  36. context.onError(
  37. createDOMCompilerError(
  38. DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
  39. value.loc
  40. )
  41. )
  42. }
  43. }
  44. const { tag } = node
  45. const isCustomElement = context.isCustomElement(tag)
  46. if (
  47. tag === 'input' ||
  48. tag === 'textarea' ||
  49. tag === 'select' ||
  50. tag === 'details' ||
  51. tag === 'dialog' ||
  52. isCustomElement
  53. ) {
  54. let directiveToUse = V_MODEL_TEXT
  55. let isInvalidType = false
  56. if (tag === 'input' || isCustomElement) {
  57. const type = findProp(node, `type`)
  58. if (type) {
  59. if (type.type === NodeTypes.DIRECTIVE) {
  60. // :type="foo"
  61. directiveToUse = V_MODEL_DYNAMIC
  62. } else if (type.value) {
  63. switch (type.value.content) {
  64. case 'radio':
  65. directiveToUse = V_MODEL_RADIO
  66. break
  67. case 'checkbox':
  68. directiveToUse = V_MODEL_CHECKBOX
  69. break
  70. case 'file':
  71. isInvalidType = true
  72. context.onError(
  73. createDOMCompilerError(
  74. DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
  75. dir.loc
  76. )
  77. )
  78. break
  79. default:
  80. // text type
  81. __DEV__ && checkDuplicatedValue()
  82. break
  83. }
  84. }
  85. } else if (hasDynamicKeyVBind(node)) {
  86. // element has bindings with dynamic keys, which can possibly contain
  87. // "type".
  88. directiveToUse = V_MODEL_DYNAMIC
  89. } else {
  90. // text type
  91. __DEV__ && checkDuplicatedValue()
  92. }
  93. } else if (tag === 'select') {
  94. directiveToUse = V_MODEL_SELECT
  95. } else if (tag === 'dialog') {
  96. directiveToUse = V_MODEL_DIALOG
  97. } else if (tag === 'details') {
  98. directiveToUse = V_MODEL_DETAILS
  99. } else {
  100. // textarea
  101. __DEV__ && checkDuplicatedValue()
  102. }
  103. // inject runtime directive
  104. // by returning the helper symbol via needRuntime
  105. // the import will replaced a resolveDirective call.
  106. if (!isInvalidType) {
  107. baseResult.needRuntime = context.helper(directiveToUse)
  108. }
  109. } else {
  110. context.onError(
  111. createDOMCompilerError(
  112. DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
  113. dir.loc
  114. )
  115. )
  116. }
  117. // native vmodel doesn't need the `modelValue` props since they are also
  118. // passed to the runtime as `binding.value`. removing it reduces code size.
  119. baseResult.props = baseResult.props.filter(
  120. p =>
  121. !(
  122. p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
  123. p.key.content === 'modelValue'
  124. )
  125. )
  126. return baseResult
  127. }