fix(runtime-vapor): patch prop

This commit is contained in:
三咲智子 Kevin Deng 2024-01-20 13:38:20 +08:00
parent 775491e46d
commit 35334fd33e
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
3 changed files with 182 additions and 64 deletions

View File

@ -1,11 +1,8 @@
import {
isArray,
normalizeClass,
normalizeStyle,
toDisplayString,
} from '@vue/shared'
import { isArray, toDisplayString } from '@vue/shared'
import type { Block, ParentBlock } from './render'
export * from './dom/patchProp'
export function insert(block: Block, parent: Node, anchor: Node | null = null) {
// if (!isHydrating) {
if (block instanceof Node) {
@ -62,63 +59,6 @@ export function setHtml(el: Element, oldVal: any, newVal: any) {
}
}
export function setClass(el: Element, oldVal: any, newVal: any) {
if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
el.className = newVal
}
}
export function setStyle(el: HTMLElement, oldVal: any, newVal: any) {
if ((newVal = normalizeStyle(newVal)) !== oldVal && (newVal || oldVal)) {
if (typeof newVal === 'string') {
el.style.cssText = newVal
} else {
// TODO
}
}
}
export function setAttr(el: Element, key: string, oldVal: any, newVal: any) {
if (newVal !== oldVal) {
if (newVal != null) {
el.setAttribute(key, newVal)
} else {
el.removeAttribute(key)
}
}
}
export function setDOMProp(el: any, key: string, oldVal: any, newVal: any) {
// TODO special checks
if (newVal !== oldVal) {
el[key] = newVal
}
}
export function setDynamicProp(
el: Element,
key: string,
oldVal: any,
newVal: any,
) {
if (key === 'class') {
setClass(el, oldVal, newVal)
} else if (key === 'style') {
setStyle(el as HTMLElement, oldVal, newVal)
} else if (
key[0] === '.'
? ((key = key.slice(1)), true)
: key[0] === '^'
? ((key = key.slice(1)), false)
: key in el
) {
setDOMProp(el, key, oldVal, newVal)
} else {
// TODO special checks
setAttr(el, key, oldVal, newVal)
}
}
type Children = Record<number, [ChildNode, Children]>
export function children(n: Node): Children {
const result: Children = {}

View File

@ -0,0 +1,139 @@
import {
isFunction,
isString,
normalizeClass,
normalizeStyle,
} from '@vue/shared'
export function setClass(el: Element, oldVal: any, newVal: any) {
if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
el.className = newVal
}
}
export function setStyle(el: HTMLElement, oldVal: any, newVal: any) {
if ((newVal = normalizeStyle(newVal)) !== oldVal && (newVal || oldVal)) {
if (typeof newVal === 'string') {
el.style.cssText = newVal
} else {
// TODO
}
}
}
export function setAttr(el: Element, key: string, oldVal: any, newVal: any) {
if (newVal !== oldVal) {
if (newVal != null) {
el.setAttribute(key, newVal)
} else {
el.removeAttribute(key)
}
}
}
export function setDOMProp(el: any, key: string, oldVal: any, newVal: any) {
// TODO special checks
if (newVal !== oldVal) {
el[key] = newVal
}
}
export function setDynamicProp(
el: Element,
key: string,
oldVal: any,
newVal: any,
) {
// TODO
const isSVG = false
if (key === 'class') {
setClass(el, oldVal, newVal)
} else if (key === 'style') {
setStyle(el as HTMLElement, oldVal, newVal)
} else if (
key[0] === '.'
? ((key = key.slice(1)), true)
: key[0] === '^'
? ((key = key.slice(1)), false)
: shouldSetAsProp(el, key, newVal, isSVG)
) {
setDOMProp(el, key, oldVal, newVal)
} else {
// TODO special case for <input v-model type="checkbox">
setAttr(el, key, oldVal, newVal)
}
}
// TODO copied from runtime-dom
const isNativeOn = (key: string) =>
key.charCodeAt(0) === 111 /* o */ &&
key.charCodeAt(1) === 110 /* n */ &&
// lowercase letter
key.charCodeAt(2) > 96 &&
key.charCodeAt(2) < 123
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') {
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
}

View File

@ -6,6 +6,45 @@ interface Task {
completed: boolean
}
const tasks = ref<Task[]>([])
const value = ref('hello')
function handleAdd() {
tasks.value.push({
title: value.value,
completed: false,
})
// TODO: clear input
value.value = ''
}
</script>
<template>{{ tasks }}</template>
<template>
<ul>
<!-- TODO: v-for -->
<li>
<!-- TODO checked=false -->
<input type="checkbox" :checked="tasks[0]?.completed" />
{{ tasks[0]?.title }}
</li>
<li>
<input type="checkbox" :checked="tasks[1]?.completed" />
{{ tasks[1]?.title }}
</li>
<li>
<input type="checkbox" :checked="tasks[2]?.completed" />
{{ tasks[2]?.title }}
</li>
<li>
<input type="checkbox" :checked="tasks[3]?.completed" />
{{ tasks[3]?.title }}
</li>
<li>
<input
type="text"
:value="value"
@input="evt => (value = evt.target.value)"
/>
<button @click="handleAdd">Add</button>
</li>
</ul>
</template>