e7b0a9d3cf
fix #4296
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import { patchClass } from './modules/class'
|
|
import { patchStyle } from './modules/style'
|
|
import { patchAttr } from './modules/attrs'
|
|
import { patchDOMProp } from './modules/props'
|
|
import { patchEvent } from './modules/events'
|
|
import { isOn, isString, isFunction, isModelListener } from '@vue/shared'
|
|
import { RendererOptions } from '@vue/runtime-core'
|
|
|
|
const nativeOnRE = /^on[a-z]/
|
|
|
|
type DOMRendererOptions = RendererOptions<Node, Element>
|
|
|
|
export const patchProp: DOMRendererOptions['patchProp'] = (
|
|
el,
|
|
key,
|
|
prevValue,
|
|
nextValue,
|
|
isSVG = false,
|
|
prevChildren,
|
|
parentComponent,
|
|
parentSuspense,
|
|
unmountChildren
|
|
) => {
|
|
if (key === 'class') {
|
|
patchClass(el, nextValue, isSVG)
|
|
} else if (key === 'style') {
|
|
patchStyle(el, prevValue, nextValue)
|
|
} else if (isOn(key)) {
|
|
// ignore v-model listeners
|
|
if (!isModelListener(key)) {
|
|
patchEvent(el, key, prevValue, nextValue, parentComponent)
|
|
}
|
|
} else if (
|
|
key[0] === '.'
|
|
? ((key = key.slice(1)), true)
|
|
: key[0] === '^'
|
|
? ((key = key.slice(1)), false)
|
|
: shouldSetAsProp(el, key, nextValue, isSVG)
|
|
) {
|
|
patchDOMProp(
|
|
el,
|
|
key,
|
|
nextValue,
|
|
prevChildren,
|
|
parentComponent,
|
|
parentSuspense,
|
|
unmountChildren
|
|
)
|
|
} else {
|
|
// special case for <input v-model type="checkbox"> with
|
|
// :true-value & :false-value
|
|
// store value as dom properties since non-string values will be
|
|
// stringified.
|
|
if (key === 'true-value') {
|
|
;(el as any)._trueValue = nextValue
|
|
} else if (key === 'false-value') {
|
|
;(el as any)._falseValue = nextValue
|
|
}
|
|
patchAttr(el, key, nextValue, isSVG, parentComponent)
|
|
}
|
|
}
|
|
|
|
function shouldSetAsProp(
|
|
el: Element,
|
|
key: string,
|
|
value: unknown,
|
|
isSVG: boolean
|
|
) {
|
|
if (isSVG) {
|
|
// most keys must be set as attribute on svg elements to work
|
|
// ...except innerHTML & textContent
|
|
if (key === 'innerHTML' || key === 'textContent') {
|
|
return true
|
|
}
|
|
// or native onclick with function values
|
|
if (key in el && nativeOnRE.test(key) && isFunction(value)) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// spellcheck and draggable are numerated attrs, however their
|
|
// corresponding DOM properties are actually booleans - this leads to
|
|
// setting it with a string "false" value leading it to be coerced to
|
|
// `true`, so we need to always treat them as attributes.
|
|
// Note that `contentEditable` doesn't have this problem: its DOM
|
|
// property is also enumerated string values.
|
|
if (key === 'spellcheck' || key === 'draggable') {
|
|
return false
|
|
}
|
|
|
|
// #1787, #2840 form property on form elements is readonly and must be set as
|
|
// attribute.
|
|
if (key === 'form') {
|
|
return false
|
|
}
|
|
|
|
// #1526 <input list> must be set as attribute
|
|
if (key === 'list' && el.tagName === 'INPUT') {
|
|
return false
|
|
}
|
|
|
|
// #2766 <textarea type> must be set as attribute
|
|
if (key === 'type' && el.tagName === 'TEXTAREA') {
|
|
return false
|
|
}
|
|
|
|
// native onclick with string value, must be set as attribute
|
|
if (nativeOnRE.test(key) && isString(value)) {
|
|
return false
|
|
}
|
|
|
|
return key in el
|
|
}
|