From 35334fd33e75068f2e95b66b5bb7bd5c1b75d758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sat, 20 Jan 2024 13:38:20 +0800 Subject: [PATCH] fix(runtime-vapor): patch prop --- packages/runtime-vapor/src/dom.ts | 66 +--------- packages/runtime-vapor/src/dom/patchProp.ts | 139 ++++++++++++++++++++ playground/src/todo-mvc.vue | 41 +++++- 3 files changed, 182 insertions(+), 64 deletions(-) create mode 100644 packages/runtime-vapor/src/dom/patchProp.ts diff --git a/packages/runtime-vapor/src/dom.ts b/packages/runtime-vapor/src/dom.ts index 595349df0..0c8a53222 100644 --- a/packages/runtime-vapor/src/dom.ts +++ b/packages/runtime-vapor/src/dom.ts @@ -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 export function children(n: Node): Children { const result: Children = {} diff --git a/packages/runtime-vapor/src/dom/patchProp.ts b/packages/runtime-vapor/src/dom/patchProp.ts new file mode 100644 index 000000000..daeb5b683 --- /dev/null +++ b/packages/runtime-vapor/src/dom/patchProp.ts @@ -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 + 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 must be set as attribute + if (key === 'list' && el.tagName === 'INPUT') { + return false + } + + // #2766