vue3-core/packages/runtime-dom/src/patchProp.ts

150 lines
4.2 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 {
camelize,
isFunction,
isModelListener,
isOn,
isString,
} from '@vue/shared'
import type { RendererOptions } from '@vue/runtime-core'
import type { VueElement } from './apiCustomElement'
const isNativeOn = (key: string) =>
key.charCodeAt(0) === 111 /* o */ &&
key.charCodeAt(1) === 110 /* n */ &&
// lowercase letter
key.charCodeAt(2) > 96 &&
key.charCodeAt(2) < 123
type DOMRendererOptions = RendererOptions<Node, Element>
export const patchProp: DOMRendererOptions['patchProp'] = (
el,
key,
prevValue,
nextValue,
namespace,
parentComponent,
) => {
const isSVG = namespace === 'svg'
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, parentComponent)
// #6007 also set form state as attributes so they work with
// <input type="reset"> or libs / extensions that expect attributes
// #11163 custom elements may use value as an prop and set it as object
if (
!el.tagName.includes('-') &&
(key === 'value' || key === 'checked' || key === 'selected')
) {
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== 'value')
}
} else if (
// #11081 force set props for possible async custom element
(el as VueElement)._isVueCE &&
(/[A-Z]/.test(key) || !isString(nextValue))
) {
patchDOMProp(el, camelize(key), nextValue, parentComponent, key)
} 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 && isNativeOn(key) && isFunction(value)) {
return true
}
return false
}
// these are enumerated 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' ||
key === 'translate' ||
key === 'autocorrect'
) {
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
}
// #8780 the width or height of embedded tags must be set as attribute
if (key === 'width' || key === 'height') {
const tag = el.tagName
if (
tag === 'IMG' ||
tag === 'VIDEO' ||
tag === 'CANVAS' ||
tag === 'SOURCE'
) {
return false
}
}
// native onclick with string value, must be set as attribute
if (isNativeOn(key) && isString(value)) {
return false
}
return key in el
}