patchProp.ts 4.2 KB

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