patchProp.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { patchClass } from './modules/class'
  2. import { patchStyle } from './modules/style'
  3. import { patchAttr } from './modules/attrs'
  4. import { patchDOMProp } from './modules/props'
  5. import { patchEvent } from './modules/events'
  6. import {
  7. camelize,
  8. isFunction,
  9. isModelListener,
  10. isNativeOn,
  11. isOn,
  12. isString,
  13. } from '@vue/shared'
  14. import type { RendererOptions } from '@vue/runtime-core'
  15. import type { VueElement } from './apiCustomElement'
  16. type DOMRendererOptions = RendererOptions<Node, Element>
  17. export const patchProp: DOMRendererOptions['patchProp'] = (
  18. el,
  19. key,
  20. prevValue,
  21. nextValue,
  22. namespace,
  23. parentComponent,
  24. ) => {
  25. const isSVG = namespace === 'svg'
  26. if (key === 'class') {
  27. patchClass(el, nextValue, isSVG)
  28. } else if (key === 'style') {
  29. patchStyle(el, prevValue, nextValue)
  30. } else if (isOn(key)) {
  31. // ignore v-model listeners
  32. if (!isModelListener(key)) {
  33. patchEvent(el, key, prevValue, nextValue, parentComponent)
  34. }
  35. } else if (
  36. key[0] === '.'
  37. ? ((key = key.slice(1)), true)
  38. : key[0] === '^'
  39. ? ((key = key.slice(1)), false)
  40. : shouldSetAsProp(el, key, nextValue, isSVG)
  41. ) {
  42. patchDOMProp(el, key, nextValue, parentComponent)
  43. // #6007 also set form state as attributes so they work with
  44. // <input type="reset"> or libs / extensions that expect attributes
  45. // #11163 custom elements may use value as an prop and set it as object
  46. if (
  47. !el.tagName.includes('-') &&
  48. (key === 'value' || key === 'checked' || key === 'selected')
  49. ) {
  50. patchAttr(el, key, nextValue, isSVG, parentComponent, key !== 'value')
  51. }
  52. } else if (
  53. // #11081 force set props for possible async custom element
  54. (el as VueElement)._isVueCE &&
  55. (/[A-Z]/.test(key) || !isString(nextValue))
  56. ) {
  57. patchDOMProp(el, camelize(key), nextValue, parentComponent)
  58. } else {
  59. // special case for <input v-model type="checkbox"> with
  60. // :true-value & :false-value
  61. // store value as dom properties since non-string values will be
  62. // stringified.
  63. if (key === 'true-value') {
  64. ;(el as any)._trueValue = nextValue
  65. } else if (key === 'false-value') {
  66. ;(el as any)._falseValue = nextValue
  67. }
  68. patchAttr(el, key, nextValue, isSVG, parentComponent)
  69. }
  70. }
  71. function shouldSetAsProp(
  72. el: Element,
  73. key: string,
  74. value: unknown,
  75. isSVG: boolean,
  76. ) {
  77. if (isSVG) {
  78. // most keys must be set as attribute on svg elements to work
  79. // ...except innerHTML & textContent
  80. if (key === 'innerHTML' || key === 'textContent') {
  81. return true
  82. }
  83. // or native onclick with function values
  84. if (key in el && isNativeOn(key) && isFunction(value)) {
  85. return true
  86. }
  87. return false
  88. }
  89. // these are enumerated attrs, however their corresponding DOM properties
  90. // are actually booleans - this leads to setting it with a string "false"
  91. // value leading it to be coerced to `true`, so we need to always treat
  92. // them as attributes.
  93. // Note that `contentEditable` doesn't have this problem: its DOM
  94. // property is also enumerated string values.
  95. if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
  96. return false
  97. }
  98. // #1787, #2840 form property on form elements is readonly and must be set as
  99. // attribute.
  100. if (key === 'form') {
  101. return false
  102. }
  103. // #1526 <input list> must be set as attribute
  104. if (key === 'list' && el.tagName === 'INPUT') {
  105. return false
  106. }
  107. // #2766 <textarea type> must be set as attribute
  108. if (key === 'type' && el.tagName === 'TEXTAREA') {
  109. return false
  110. }
  111. // #8780 the width or height of embedded tags must be set as attribute
  112. if (key === 'width' || key === 'height') {
  113. const tag = el.tagName
  114. if (
  115. tag === 'IMG' ||
  116. tag === 'VIDEO' ||
  117. tag === 'CANVAS' ||
  118. tag === 'SOURCE'
  119. ) {
  120. return false
  121. }
  122. }
  123. // native onclick with string value, must be set as attribute
  124. if (isNativeOn(key) && isString(value)) {
  125. return false
  126. }
  127. return key in el
  128. }