diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 77cb2e062d..458ebc9112 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -10,6 +10,7 @@ import Class from './Class'; import Text from './Text'; import { namespaces } from '../../utils/namespaces'; import map_children from './shared/map_children'; +import { isBindingContenteditable, getContenteditableAttr } from '../utils/contenteditable'; import { dimensions } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; @@ -769,19 +770,15 @@ export default class Element extends Node { }); } } else if ( - name === 'textContent' || - name === 'innerHTML' + isBindingContenteditable(binding) ) { - const contenteditable = this.attributes.find( - (attribute: Attribute) => attribute.name === 'contenteditable' - ); - + const contenteditable = getContenteditableAttr(this); if (!contenteditable) { component.error(binding, { code: 'missing-contenteditable-attribute', - message: '\'contenteditable\' attribute is required for textContent and innerHTML two-way bindings' + message: '\'contenteditable\' attribute is required for textContent/innerHTML/innerText two-way bindings' }); - } else if (contenteditable && !contenteditable.is_static) { + } else if (!contenteditable.is_static) { component.error(contenteditable, { code: 'dynamic-contenteditable-attribute', message: '\'contenteditable\' attribute cannot be dynamic if element uses two-way binding' diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 33febebaa2..a74783f880 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,5 +1,5 @@ import { is_void } from '../../../utils/names'; -import isContenteditable, { isBindingContenteditable } from '../../utils/contenteditable'; +import { isBindingContenteditable, isContenteditable } from '../../utils/contenteditable'; import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import { boolean_attributes } from './shared/boolean_attributes'; import Renderer, { RenderOptions } from '../Renderer'; diff --git a/src/compiler/compile/utils/__test__.ts b/src/compiler/compile/utils/__test__.ts index c4c8b24212..8a8a653345 100644 --- a/src/compiler/compile/utils/__test__.ts +++ b/src/compiler/compile/utils/__test__.ts @@ -1,11 +1,12 @@ import * as assert from 'assert'; import get_name_from_filename from './get_name_from_filename'; import { - default as isContenteditable, + isContenteditable, isInputOrTextarea, isAttrContentEditable, hasContentEditableAttr, isBindingContenteditable, + getContenteditableAttr, CONTENTEDITABLE_ATTR, CONTENTEDITABLE_BINDINGS, } from './contenteditable'; @@ -99,5 +100,17 @@ describe('contenteditable', () => { assert.equal(isBindingContenteditable(binding), false); }); }); + + describe('getContenteditableAttr', () => { + it('returns the contenteditable Attribute if it exists', () => { + const attr = { name: CONTENTEDITABLE_ATTR } as Attribute; + const node = { name: 'div', attributes: [attr] } as Element; + assert.equal(getContenteditableAttr(node), attr); + }); + it('returns undefined if contenteditable attribute cannot be found', () => { + const node = { name: 'div', attributes: [] } as Element; + assert.equal(getContenteditableAttr(node), undefined); + }); + }); }); diff --git a/src/compiler/compile/utils/contenteditable.ts b/src/compiler/compile/utils/contenteditable.ts index 1ecc0242a2..ae27f20798 100644 --- a/src/compiler/compile/utils/contenteditable.ts +++ b/src/compiler/compile/utils/contenteditable.ts @@ -14,7 +14,7 @@ export const CONTENTEDITABLE_BINDINGS = [ /** * Returns true if node is an 'input' or 'textarea' - * @param node The element to be checked + * @param {Element} node The element to be checked */ export function isInputOrTextarea(node: Element): boolean { return node.name === 'textarea' || node.name === 'input'; @@ -22,7 +22,7 @@ export function isInputOrTextarea(node: Element): boolean { /** * Check if a given attribute is 'contenteditable' - * @param attribute A node.attribute + * @param {Attribute} attribute A node.attribute */ export function isAttrContentEditable(attribute: Attribute): boolean { return attribute.name === CONTENTEDITABLE_ATTR; @@ -30,7 +30,7 @@ export function isAttrContentEditable(attribute: Attribute): boolean { /** * Check if any of a node's attributes are 'contentenditable' - * @param node The element to be checked + * @param {Element} node The element to be checked */ export function hasContentEditableAttr(node: Element): boolean { return node.attributes.some(isAttrContentEditable); @@ -38,17 +38,26 @@ export function hasContentEditableAttr(node: Element): boolean { /** * Returns true if node is not textarea or input, but has 'contenteditable' attribute - * @param node The element to be tested + * @param {Element} node The element to be tested */ -export default function isContenteditable(node: Element): boolean { +export function isContenteditable(node: Element): boolean { return !isInputOrTextarea(node) && hasContentEditableAttr(node); } /** * Returns true if a given bindings should be contenteditable * - * @param binding A node's binding to be checked + * @param {Binding} binding A node's binding to be checked */ -export function isBindingContenteditable(binding: Binding) { +export function isBindingContenteditable(binding: Binding): boolean { return CONTENTEDITABLE_BINDINGS.includes(binding.name); } + +/** + * Returns the contenteditable attribute from the node (if it exists) + * + * @param {Element} node The element to get the attribute from + */ +export function getContenteditableAttr(node: Element): Attribute | undefined { + return node.attributes.find(isAttrContentEditable); +}