]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-vapor): patch prop
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 20 Jan 2024 05:38:20 +0000 (13:38 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 20 Jan 2024 05:38:20 +0000 (13:38 +0800)
packages/runtime-vapor/src/dom.ts
packages/runtime-vapor/src/dom/patchProp.ts [new file with mode: 0644]
playground/src/todo-mvc.vue

index 595349df017b82d1e258014657579d1b50859189..0c8a53222b1da0c7f255f14d83c6acc97da4d9db 100644 (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 = {}
diff --git a/packages/runtime-vapor/src/dom/patchProp.ts b/packages/runtime-vapor/src/dom/patchProp.ts
new file mode 100644 (file)
index 0000000..daeb5b6
--- /dev/null
@@ -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
+}
index 1d9ccbd806585fd75a0e9668191a3f2922104d0d..6ebac01f58f1887742a72a2396aabaeade95a709 100644 (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>