vModel.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import {
  2. type DirectiveTransform,
  3. ElementTypes,
  4. NodeTypes,
  5. transformModel as baseTransform,
  6. findDir,
  7. findProp,
  8. hasDynamicKeyVBind,
  9. isStaticArgOf,
  10. } from '@vue/compiler-core'
  11. import { DOMErrorCodes, createDOMCompilerError } from '../errors'
  12. import {
  13. V_MODEL_CHECKBOX,
  14. V_MODEL_DYNAMIC,
  15. V_MODEL_RADIO,
  16. V_MODEL_SELECT,
  17. V_MODEL_TEXT,
  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 = findDir(node, 'bind')
  35. if (value && isStaticArgOf(value.arg, '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. isCustomElement
  51. ) {
  52. let directiveToUse = V_MODEL_TEXT
  53. let isInvalidType = false
  54. if (tag === 'input' || isCustomElement) {
  55. const type = findProp(node, `type`)
  56. if (type) {
  57. if (type.type === NodeTypes.DIRECTIVE) {
  58. // :type="foo"
  59. directiveToUse = V_MODEL_DYNAMIC
  60. } else if (type.value) {
  61. switch (type.value.content) {
  62. case 'radio':
  63. directiveToUse = V_MODEL_RADIO
  64. break
  65. case 'checkbox':
  66. directiveToUse = V_MODEL_CHECKBOX
  67. break
  68. case 'file':
  69. isInvalidType = true
  70. context.onError(
  71. createDOMCompilerError(
  72. DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
  73. dir.loc,
  74. ),
  75. )
  76. break
  77. default:
  78. // text type
  79. __DEV__ && checkDuplicatedValue()
  80. break
  81. }
  82. }
  83. } else if (hasDynamicKeyVBind(node)) {
  84. // element has bindings with dynamic keys, which can possibly contain
  85. // "type".
  86. directiveToUse = V_MODEL_DYNAMIC
  87. } else {
  88. // text type
  89. __DEV__ && checkDuplicatedValue()
  90. }
  91. } else if (tag === 'select') {
  92. directiveToUse = V_MODEL_SELECT
  93. } else {
  94. // textarea
  95. __DEV__ && checkDuplicatedValue()
  96. }
  97. // inject runtime directive
  98. // by returning the helper symbol via needRuntime
  99. // the import will replaced a resolveDirective call.
  100. if (!isInvalidType) {
  101. baseResult.needRuntime = context.helper(directiveToUse)
  102. }
  103. } else {
  104. context.onError(
  105. createDOMCompilerError(
  106. DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
  107. dir.loc,
  108. ),
  109. )
  110. }
  111. // native vmodel doesn't need the `modelValue` props since they are also
  112. // passed to the runtime as `binding.value`. removing it reduces code size.
  113. baseResult.props = baseResult.props.filter(
  114. p =>
  115. !(
  116. p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
  117. p.key.content === 'modelValue'
  118. ),
  119. )
  120. return baseResult
  121. }