Browse Source

fix: allow more enumerated values for contenteditable

close #9397
Evan You 7 years ago
parent
commit
e632e9a075

+ 3 - 2
src/platforms/web/runtime/modules/attrs.js

@@ -14,7 +14,8 @@ import {
   getXlinkProp,
   isBooleanAttr,
   isEnumeratedAttr,
-  isFalsyAttrValue
+  isFalsyAttrValue,
+  convertEnumeratedValue
 } from 'web/util/index'
 
 function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
@@ -75,7 +76,7 @@ function setAttr (el: Element, key: string, value: any) {
       el.setAttribute(key, value)
     }
   } else if (isEnumeratedAttr(key)) {
-    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
+    el.setAttribute(key, convertEnumeratedValue(key, value))
   } else if (isXlink(key)) {
     if (isFalsyAttrValue(value)) {
       el.removeAttributeNS(xlinkNS, getXlinkProp(key))

+ 3 - 2
src/platforms/web/server/modules/attrs.js

@@ -11,7 +11,8 @@ import {
 import {
   isBooleanAttr,
   isEnumeratedAttr,
-  isFalsyAttrValue
+  isFalsyAttrValue,
+  convertEnumeratedValue
 } from 'web/util/attrs'
 
 import { isSSRUnsafeAttr } from 'web/server/util'
@@ -54,7 +55,7 @@ export function renderAttr (key: string, value: string): string {
       return ` ${key}="${key}"`
     }
   } else if (isEnumeratedAttr(key)) {
-    return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"`
+    return ` ${key}="${escape(convertEnumeratedValue(key, value))}"`
   } else if (!isFalsyAttrValue(value)) {
     return ` ${key}="${escape(String(value))}"`
   }

+ 11 - 0
src/platforms/web/util/attrs.js

@@ -19,6 +19,17 @@ export const mustUseProp = (tag: string, type: ?string, attr: string): boolean =
 
 export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck')
 
+const isValidContentEditableValue = makeMap('events,caret,typing,plaintext-only')
+
+export const convertEnumeratedValue = (key: string, value: any) => {
+  return isFalsyAttrValue(value) || value === 'false'
+    ? 'false'
+    // allow arbitrary string value for contenteditable
+    : key === 'contenteditable' && isValidContentEditableValue(value)
+      ? value
+      : 'true'
+}
+
 export const isBooleanAttr = makeMap(
   'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +
   'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +

+ 8 - 8
test/unit/features/directives/bind.spec.js

@@ -69,25 +69,25 @@ describe('Directive v-bind', () => {
 
   it('enumerated attr', done => {
     const vm = new Vue({
-      template: '<div><span :draggable="foo">hello</span></div>',
+      template: '<div><span :contenteditable="foo">hello</span></div>',
       data: { foo: true }
     }).$mount()
-    expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')
-    vm.foo = 'again'
+    expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('true')
+    vm.foo = 'plaintext-only' // allow special values
     waitForUpdate(() => {
-      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')
+      expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('plaintext-only')
       vm.foo = null
     }).then(() => {
-      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')
+      expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('false')
       vm.foo = ''
     }).then(() => {
-      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')
+      expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('true')
       vm.foo = false
     }).then(() => {
-      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')
+      expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('false')
       vm.foo = 'false'
     }).then(() => {
-      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')
+      expect(vm.$el.firstChild.getAttribute('contenteditable')).toBe('false')
     }).then(done)
   })