Skip to content

Commit

Permalink
fix(runtime-vapor): patch prop
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Jan 20, 2024
1 parent 775491e commit 35334fd
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 64 deletions.
66 changes: 3 additions & 63 deletions packages/runtime-vapor/src/dom.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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 = {}
Expand Down
139 changes: 139 additions & 0 deletions packages/runtime-vapor/src/dom/patchProp.ts
Original file line number Diff line number Diff line change
@@ -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
}
41 changes: 40 additions & 1 deletion playground/src/todo-mvc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

0 comments on commit 35334fd

Please sign in to comment.