diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index a5974f9485..5e831becd7 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -107,6 +107,20 @@ export default function visitElement ( generator: DomGenerator, block: Block, st } if ( node.name !== 'select' ) { + if ( node.name === 'textarea' ) { + // this is an egregious hack, but it's the easiest way to get ' ); + parser.read( /<\/textarea>/ ); + element.end = parser.index; } else { // don't push self-closing elements onto the stack parser.stack.push( element ); @@ -280,11 +284,41 @@ function readAttribute ( parser: Parser, uniqueNames ) { } function readAttributeValue ( parser: Parser ) { - let quoteMark; + const quoteMark = ( + parser.eat( `'` ) ? `'` : + parser.eat( `"` ) ? `"` : + null + ); + + const regex = ( + quoteMark === `'` ? /'/ : + quoteMark === `"` ? /"/ : + /[\s"'=<>\/`]/ + ); + + const value = readSequence( parser, () => regex.test( parser.template[ parser.index ] ) ); + + if ( quoteMark ) parser.index += 1; + return value; +} - if ( parser.eat( `'` ) ) quoteMark = `'`; - if ( parser.eat( `"` ) ) quoteMark = `"`; +function getShorthandValue ( start: number, name: string ) { + const end = start + name.length; + return [{ + type: 'AttributeShorthand', + start, + end, + expression: { + type: 'Identifier', + start, + end, + name + } + }]; +} + +function readSequence ( parser: Parser, done: () => boolean ) { let currentChunk: Node = { start: parser.index, end: null, @@ -292,16 +326,24 @@ function readAttributeValue ( parser: Parser ) { data: '' }; - const done = quoteMark ? - char => char === quoteMark : - char => invalidUnquotedAttributeCharacters.test( char ); - const chunks = []; while ( parser.index < parser.template.length ) { const index = parser.index; - if ( parser.eat( '{{' ) ) { + if ( done() ) { + currentChunk.end = parser.index; + + if ( currentChunk.data ) chunks.push( currentChunk ); + + chunks.forEach( chunk => { + if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); + }); + + return chunks; + } + + else if ( parser.eat( '{{' ) ) { if ( currentChunk.data ) { currentChunk.end = index; chunks.push( currentChunk ); @@ -328,39 +370,10 @@ function readAttributeValue ( parser: Parser ) { }; } - else if ( done( parser.template[ parser.index ] ) ) { - currentChunk.end = parser.index; - if ( quoteMark ) parser.index += 1; - - if ( currentChunk.data ) chunks.push( currentChunk ); - - chunks.forEach( chunk => { - if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); - }); - - return chunks; - } - else { currentChunk.data += parser.template[ parser.index++ ]; } } parser.error( `Unexpected end of input` ); -} - -function getShorthandValue ( start: number, name: string ) { - const end = start + name.length; - - return [{ - type: 'AttributeShorthand', - start, - end, - expression: { - type: 'Identifier', - start, - end, - name - } - }]; -} +} \ No newline at end of file diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index 6610e7ee69..18c906b298 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -77,6 +77,14 @@ export default function validateElement ( validator: Validator, node: Node ) { validator.error( `Missing transition '${attribute.name}'`, attribute.start ); } } + + else if ( attribute.type === 'Attribute' ) { + if ( attribute.name === 'value' && node.name === 'textarea' ) { + if ( node.children.length ) { + validator.error( `A \ No newline at end of file diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json new file mode 100644 index 0000000000..4b67ac108a --- /dev/null +++ b/test/parser/samples/textarea-children/output.json @@ -0,0 +1,44 @@ +{ + "hash": 3618147195, + "html": { + "start": 0, + "end": 63, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 63, + "type": "Element", + "name": "textarea", + "attributes": [], + "children": [ + { + "start": 10, + "end": 40, + "type": "Text", + "data": "\n\t

not actually an element. " + }, + { + "start": 40, + "end": 47, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 42, + "end": 45, + "name": "foo" + } + }, + { + "start": 47, + "end": 52, + "type": "Text", + "data": "

\n" + } + ] + } + ] + }, + "css": null, + "js": null +} \ No newline at end of file diff --git a/test/runtime/samples/textarea-children/_config.js b/test/runtime/samples/textarea-children/_config.js new file mode 100644 index 0000000000..f21519b26a --- /dev/null +++ b/test/runtime/samples/textarea-children/_config.js @@ -0,0 +1,17 @@ +export default { + 'skip-ssr': true, // SSR behaviour is awkwardly different + + data: { + foo: 42 + }, + + html: ``, + + test ( assert, component, target ) { + const textarea = target.querySelector( 'textarea' ); + assert.strictEqual( textarea.value, `\n\t

not actually an element. 42

\n` ); + + component.set({ foo: 43 }); + assert.strictEqual( textarea.value, `\n\t

not actually an element. 43

\n` ); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/textarea-children/main.html b/test/runtime/samples/textarea-children/main.html new file mode 100644 index 0000000000..de50c807d6 --- /dev/null +++ b/test/runtime/samples/textarea-children/main.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/runtime/samples/textarea-value/_config.js b/test/runtime/samples/textarea-value/_config.js new file mode 100644 index 0000000000..89ff63508c --- /dev/null +++ b/test/runtime/samples/textarea-value/_config.js @@ -0,0 +1,17 @@ +export default { + 'skip-ssr': true, // SSR behaviour is awkwardly different + + data: { + foo: 42 + }, + + html: ``, + + test ( assert, component, target ) { + const textarea = target.querySelector( 'textarea' ); + assert.strictEqual( textarea.value, '42' ); + + component.set({ foo: 43 }); + assert.strictEqual( textarea.value, '43' ); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/textarea-value/main.html b/test/runtime/samples/textarea-value/main.html new file mode 100644 index 0000000000..215166ab10 --- /dev/null +++ b/test/runtime/samples/textarea-value/main.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-children/main.html b/test/server-side-rendering/samples/textarea-children/main.html new file mode 100644 index 0000000000..7209683448 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-children/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-value/_expected.html b/test/server-side-rendering/samples/textarea-value/_expected.html new file mode 100644 index 0000000000..cd39aabde5 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-value/_expected.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-value/main.html b/test/server-side-rendering/samples/textarea-value/main.html new file mode 100644 index 0000000000..33375b5785 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-value/main.html @@ -0,0 +1,9 @@ + \ No newline at end of file