Fix for `<textarea>`

pull/7280/head
yosuke ota 4 years ago
parent 69b70e6421
commit ece59b4d11

@ -11,7 +11,7 @@ import StyleDirective from './StyleDirective';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import { dimensions } from '../../utils/patterns'; import { dimensions, start_newline } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Let from './Let'; import Let from './Let';
@ -208,6 +208,12 @@ export default class Element extends Node {
// this is an egregious hack, but it's the easiest way to get <textarea> // this is an egregious hack, but it's the easiest way to get <textarea>
// children treated the same way as a value attribute // children treated the same way as a value attribute
const first = info.children[0];
if (first && first.type === 'Text') {
// The leading newline character should be stripped.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
first.data = first.data.replace(start_newline, '');
}
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
name: 'value', name: 'value',

@ -992,7 +992,13 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// element // element
state.quasi.value.raw += `<${wrapper.node.name}`; state.quasi.value.raw += `<${wrapper.node.name}`;
const is_empty_textarea = wrapper.node.name === 'textarea' && wrapper.fragment.nodes.length === 0;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => { (wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
if (is_empty_textarea && attr.node.name === 'value') {
// The value attribute of <textarea> renders as content.
return;
}
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`; state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach(chunk => { attr.node.chunks.forEach(chunk => {
@ -1024,6 +1030,32 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
} }
} }
if (is_empty_textarea) {
// The <textarea> renders the value attribute as content because the content is stored in the value attribute.
const value_attribute = wrapper.attributes.find(attr => attr.node.name === 'value');
if (value_attribute) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = value_attribute.node.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
state.quasi.value.raw += '\n';
}
value_attribute.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
});
}
}
to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state); to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
state.quasi.value.raw += `</${wrapper.node.name}>`; state.quasi.value.raw += `</${wrapper.node.name}>`;

@ -40,7 +40,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
const { name, expression: { node: expression } } = style_directive; const { name, expression: { node: expression } } = style_directive;
return p`"${name}": ${expression}`; return p`"${name}": ${expression}`;
}); });
const style_expression = const style_expression =
style_expression_list.length > 0 && style_expression_list.length > 0 &&
x`{ ${style_expression_list} }`; x`{ ${style_expression_list} }`;
@ -164,6 +164,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`);
} else { } else {
if (node.name === 'textarea') {
// Restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
renderer.add_string('\n');
}
renderer.add_expression(node_contents); renderer.add_expression(node_contents);
} }

@ -9,9 +9,9 @@ export default {
test({ assert, component, target }) { test({ assert, component, target }) {
const textarea = target.querySelector( 'textarea' ); const textarea = target.querySelector( 'textarea' );
assert.strictEqual( textarea.value, '\n\t<p>not actually an element. 42</p>\n' ); assert.strictEqual( textarea.value, '\t<p>not actually an element. 42</p>\n' );
component.foo = 43; component.foo = 43;
assert.strictEqual( textarea.value, '\n\t<p>not actually an element. 43</p>\n' ); assert.strictEqual( textarea.value, '\t<p>not actually an element. 43</p>\n' );
} }
}; };

@ -0,0 +1,2 @@
[main.svelte]
trim_trailing_whitespace = unset

@ -0,0 +1,34 @@
export default {
test({ assert, target }) {
// Test for <textarea> tag
const elementTextarea = target.querySelector('#textarea');
// Test for <textarea> tag in non <textarea> tag
const elementDivWithTextarea = target.querySelector('#div-with-textarea');
// Test for <textarea> tag with leading newline
const elementTextareaWithLeadingNewline = target.querySelector('#textarea-with-leading-newline');
const elementTextareaWithoutLeadingNewline = target.querySelector('#textarea-without-leading-newline');
const elementTextareaWithMultipleLeadingNewline = target.querySelector('#textarea-with-multiple-leading-newlines');
const elementDivWithTextareaWithMultipleLeadingNewline = target.querySelector('#div-with-textarea-with-multiple-leading-newlines');
assert.equal(
elementTextarea.value,
` A
B
`
);
assert.equal(
elementDivWithTextarea.children[0].value,
` A
B
`
);
assert.equal(elementTextareaWithLeadingNewline.children[0].value, 'leading newline');
assert.equal(elementTextareaWithLeadingNewline.children[1].value, ' leading newline and spaces');
assert.equal(elementTextareaWithLeadingNewline.children[2].value, '\nleading newlines');
assert.equal(elementTextareaWithoutLeadingNewline.children[0].value, 'without spaces');
assert.equal(elementTextareaWithoutLeadingNewline.children[1].value, ' with spaces ');
assert.equal(elementTextareaWithoutLeadingNewline.children[2].value, ' \nnewline after leading space');
assert.equal(elementTextareaWithMultipleLeadingNewline.value, '\n\nmultiple leading newlines');
assert.equal(elementDivWithTextareaWithMultipleLeadingNewline.children[0].value, '\n\nmultiple leading newlines');
}
};

@ -0,0 +1,40 @@
<textarea id="textarea">
A
B
</textarea>
<div id="div-with-textarea">
<textarea>
A
B
</textarea>
</div>
<div id="textarea-with-leading-newline">
<textarea>
leading newline</textarea>
<textarea>
leading newline and spaces</textarea>
<textarea>
leading newlines</textarea>
</div>
<div id="textarea-without-leading-newline">
<textarea>without spaces</textarea>
<textarea> with spaces </textarea>
<textarea>
newline after leading space</textarea>
</div>
<textarea id="textarea-with-multiple-leading-newlines">
multiple leading newlines</textarea>
<div id="div-with-textarea-with-multiple-leading-newlines">
<textarea>
multiple leading newlines</textarea>
</div>

@ -0,0 +1,2 @@
[{main.svelte,_expected.html}]
trim_trailing_whitespace = unset

@ -0,0 +1,3 @@
export default {
withoutNormalizeHtml: true
};

@ -0,0 +1,35 @@
<textarea id="textarea">
A
B
</textarea>
<div id="div-with-textarea"><textarea>
A
B
</textarea></div>
<div id="textarea-with-leading-newline"><textarea>
leading newline</textarea>
<textarea>
leading newline and spaces</textarea>
<textarea>
leading newlines</textarea></div>
<div id="textarea-without-leading-newline"><textarea>
without spaces</textarea>
<textarea>
with spaces </textarea>
<textarea>
newline after leading space</textarea></div>
<textarea id="textarea-with-multiple-leading-newlines">
multiple leading newlines</textarea>
<div id="div-with-textarea-with-multiple-leading-newlines"><textarea>
multiple leading newlines</textarea></div>

@ -0,0 +1,40 @@
<textarea id="textarea">
A
B
</textarea>
<div id="div-with-textarea">
<textarea>
A
B
</textarea>
</div>
<div id="textarea-with-leading-newline">
<textarea>
leading newline</textarea>
<textarea>
leading newline and spaces</textarea>
<textarea>
leading newlines</textarea>
</div>
<div id="textarea-without-leading-newline">
<textarea>without spaces</textarea>
<textarea> with spaces </textarea>
<textarea>
newline after leading space</textarea>
</div>
<textarea id="textarea-with-multiple-leading-newlines">
multiple leading newlines</textarea>
<div id="div-with-textarea-with-multiple-leading-newlines">
<textarea>
multiple leading newlines</textarea>
</div>
Loading…
Cancel
Save