diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md
index 3e1cc2374b9e..59d88dd73dbf 100644
--- a/site/content/docs/02-template-syntax.md
+++ b/site/content/docs/02-template-syntax.md
@@ -821,6 +821,46 @@ A `class:` directive provides a shorter way of toggling a class on an element.
...
```
+#### style:*property*
+
+```sv
+style:property={value}
+```
+```sv
+style:property="value"
+```
+```sv
+style:property
+```
+
+---
+
+The `style:` directive provides a shorthand for setting multiple styles on an element.
+
+```sv
+
+...
+...
+
+
+...
+
+
+...
+
+
+...
+```
+
+---
+
+When `style:` directives are combined with `style` attributes, the directives will take precedence:
+
+```sv
+This will be red
+```
+
+
#### use:*action*
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index 837a0296d097..80fc0fb987e1 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -7,6 +7,7 @@ import Transition from './Transition';
import Animation from './Animation';
import Action from './Action';
import Class from './Class';
+import Style from './Style';
import Text from './Text';
import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children';
@@ -123,6 +124,7 @@ export default class Element extends Node {
actions: Action[] = [];
bindings: Binding[] = [];
classes: Class[] = [];
+ styles: Style[] = [];
handlers: EventHandler[] = [];
lets: Let[] = [];
intro?: Transition = null;
@@ -206,6 +208,10 @@ export default class Element extends Node {
this.classes.push(new Class(component, this, scope, node));
break;
+ case 'Style':
+ this.styles.push(new Style(component, this, scope, node));
+ break;
+
case 'EventHandler':
this.handlers.push(new EventHandler(component, this, scope, node));
break;
diff --git a/src/compiler/compile/nodes/Style.ts b/src/compiler/compile/nodes/Style.ts
new file mode 100644
index 000000000000..e922f6f5783a
--- /dev/null
+++ b/src/compiler/compile/nodes/Style.ts
@@ -0,0 +1,22 @@
+import Node from './shared/Node';
+import Expression from './shared/Expression';
+import { TemplateNode } from '../../interfaces';
+import TemplateScope from './shared/TemplateScope';
+import Component from '../Component';
+
+export default class Style extends Node {
+ type: 'Style';
+ name: string;
+ expression: Expression;
+ should_cache: boolean;
+
+ constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
+ super(component, parent, scope, info);
+
+ this.name = info.name;
+
+ this.expression = new Expression(component, this, scope, info.expression);
+
+ this.should_cache = info.expression.type === 'TemplateLiteral' && info.expression.expressions.length > 0;
+ }
+}
diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts
index a98c21511fb7..2e2fa8b5e05f 100644
--- a/src/compiler/compile/nodes/interfaces.ts
+++ b/src/compiler/compile/nodes/interfaces.ts
@@ -8,6 +8,7 @@ import Binding from './Binding';
import Body from './Body';
import CatchBlock from './CatchBlock';
import Class from './Class';
+import Style from './Style';
import Comment from './Comment';
import DebugTag from './DebugTag';
import EachBlock from './EachBlock';
@@ -62,6 +63,7 @@ export type INode = Action
| Slot
| SlotTemplate
| DefaultSlotTemplate
+| Style
| Tag
| Text
| ThenBlock
diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts
index 9ec36b12d7da..f2db1482f363 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts
@@ -340,6 +340,7 @@ export default class ElementWrapper extends Wrapper {
this.add_transitions(block);
this.add_animation(block);
this.add_classes(block);
+ this.add_styles(block);
this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) {
@@ -914,6 +915,43 @@ export default class ElementWrapper extends Wrapper {
});
}
+ add_styles(block: Block) {
+ const has_spread = this.node.attributes.some(attr => attr.is_spread);
+ this.node.styles.forEach((style_directive) => {
+ const { name, expression, should_cache } = style_directive;
+
+ const snippet = expression.manipulate(block);
+ let cached_snippet;
+ if (should_cache) {
+ cached_snippet = block.get_unique_name(`style_${name}`);
+ block.add_variable(cached_snippet, snippet);
+ }
+
+ const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, false)`;
+
+ block.chunks.hydrate.push(updater);
+
+ const dependencies = expression.dynamic_dependencies();
+ if (has_spread) {
+ block.chunks.update.push(updater);
+ } else if (dependencies.length > 0) {
+ if (should_cache) {
+ block.chunks.update.push(b`
+ if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) {
+ ${updater}
+ }
+ `);
+ } else {
+ block.chunks.update.push(b`
+ if (${block.renderer.dirty(dependencies)}) {
+ ${updater}
+ }
+ `);
+ }
+ }
+ });
+ }
+
add_manual_style_scoping(block) {
if (this.node.needs_manual_style_scoping) {
const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`;
diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts
index c0bf826a8ecc..dd096ae79b07 100644
--- a/src/compiler/compile/render_ssr/handlers/Element.ts
+++ b/src/compiler/compile/render_ssr/handlers/Element.ts
@@ -3,7 +3,7 @@ import { get_attribute_expression, get_attribute_value, get_class_attribute_valu
import { boolean_attributes } from './shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
-import { x } from 'code-red';
+import { p, x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
@@ -36,6 +36,15 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
+ const style_expression_list = node.styles.map(style_directive => {
+ const { name, expression: { node: expression } } = style_directive;
+ return p`"${name}": ${expression}`;
+ });
+
+ const style_expression =
+ style_expression_list.length > 0 &&
+ x`{ ${style_expression_list} }`;
+
if (node.attributes.some(attr => attr.is_spread)) {
// TODO dry this out
const args = [];
@@ -65,9 +74,10 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
});
- renderer.add_expression(x`@spread([${args}], ${class_expression})`);
+ renderer.add_expression(x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`);
} else {
let add_class_attribute = !!class_expression;
+ let add_style_attribute = !!style_expression;
node.attributes.forEach(attribute => {
const name = attribute.name.toLowerCase();
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name);
@@ -88,6 +98,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_string(` ${attr_name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`);
renderer.add_string('"');
+ } else if (name === 'style' && style_expression) {
+ add_style_attribute = false;
+ renderer.add_expression(x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
@@ -98,7 +111,10 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
});
if (add_class_attribute) {
- renderer.add_expression(x`@add_classes([${class_expression}].join(' ').trim())`);
+ renderer.add_expression(x`@add_classes((${class_expression}).trim())`);
+ }
+ if (add_style_attribute) {
+ renderer.add_expression(x`@add_styles(${style_expression})`);
}
}
diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts
index b999fbd803dd..3e54ea55a382 100644
--- a/src/compiler/interfaces.ts
+++ b/src/compiler/interfaces.ts
@@ -34,6 +34,7 @@ export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
+| 'Style'
| 'EventHandler'
| 'Let'
| 'Ref'
diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts
index 832f47aaccc6..81d8fff1c476 100644
--- a/src/compiler/parse/state/tag.ts
+++ b/src/compiler/parse/state/tag.ts
@@ -1,10 +1,11 @@
+import { TemplateLiteral, TemplateElement, Expression } from 'estree';
import read_expression from '../read/expression';
import read_script from '../read/script';
import read_style from '../read/style';
import { decode_character_references, closing_tag_omitted } from '../utils/html';
import { is_void } from '../../utils/names';
import { Parser } from '../index';
-import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
+import { Directive, DirectiveType, TemplateNode, Text, MustacheTag } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore';
import parser_errors from '../errors';
@@ -270,6 +271,36 @@ function read_tag_name(parser: Parser) {
return name;
}
+function node_to_template_literal(value: Array): TemplateLiteral {
+ let quasi: TemplateElement = {
+ type: 'TemplateElement',
+ value: { raw: '', cooked: null },
+ tail: false
+ };
+ const literal: TemplateLiteral = {
+ type: 'TemplateLiteral',
+ expressions: [],
+ quasis: []
+ };
+
+ value.forEach((node) => {
+ if (node.type === 'Text') {
+ quasi.value.raw += node.raw;
+ } else if (node.type === 'MustacheTag') {
+ literal.quasis.push(quasi);
+ literal.expressions.push(node.expression as Expression);
+ quasi = {
+ type: 'TemplateElement',
+ value: { raw: '', cooked: null },
+ tail: false
+ };
+ }
+ });
+ quasi.tail = true;
+ literal.quasis.push(quasi);
+ return literal;
+}
+
function read_attribute(parser: Parser, unique_names: Set) {
const start = parser.index;
@@ -365,9 +396,17 @@ function read_attribute(parser: Parser, unique_names: Set) {
parser.error(parser_errors.invalid_ref_directive(directive_name), start);
}
- if (value[0]) {
- if ((value as any[]).length > 1 || value[0].type === 'Text') {
- parser.error(parser_errors.invalid_directive_value, value[0].start);
+ const first_value = value[0];
+ let expression = null;
+
+ if (first_value) {
+ const attribute_contains_text = (value as any[]).length > 1 || first_value.type === 'Text';
+ if (type === 'Style') {
+ expression = attribute_contains_text ? node_to_template_literal(value as Array) : first_value.expression;
+ } else if (attribute_contains_text) {
+ parser.error(parser_errors.invalid_directive_value, first_value.start);
+ } else {
+ expression = first_value.expression;
}
}
@@ -377,7 +416,7 @@ function read_attribute(parser: Parser, unique_names: Set) {
type,
name: directive_name,
modifiers,
- expression: (value[0] && value[0].expression) || null
+ expression
};
if (type === 'Transition') {
@@ -386,7 +425,8 @@ function read_attribute(parser: Parser, unique_names: Set) {
directive.outro = direction === 'out' || direction === 'transition';
}
- if (!directive.expression && (type === 'Binding' || type === 'Class')) {
+ // Directive name is expression, e.g.
+ if (!directive.expression && (type === 'Binding' || type === 'Class' || type === 'Style')) {
directive.expression = {
start: directive.start + colon_index + 1,
end: directive.end,
@@ -414,6 +454,7 @@ function get_directive_type(name: string): DirectiveType {
if (name === 'animate') return 'Animation';
if (name === 'bind') return 'Binding';
if (name === 'class') return 'Class';
+ if (name === 'style') return 'Style';
if (name === 'on') return 'EventHandler';
if (name === 'let') return 'Let';
if (name === 'ref') return 'Ref';
@@ -471,6 +512,8 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
data: null
};
+ const chunks: TemplateNode[] = [];
+
function flush(end: number) {
if (current_chunk.raw) {
current_chunk.data = decode_character_references(current_chunk.raw);
@@ -479,8 +522,6 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
}
}
- const chunks: TemplateNode[] = [];
-
while (parser.index < parser.template.length) {
const index = parser.index;
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
index eb3389e3f863..a8b542053b47 100644
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -530,7 +530,11 @@ export function set_input_type(input, type) {
}
export function set_style(node, key, value, important) {
- node.style.setProperty(key, value, important ? 'important' : '');
+ if (value === null) {
+ node.style.removeProperty(key);
+ } else {
+ node.style.setProperty(key, value, important ? 'important' : '');
+ }
}
export function select_option(select, value) {
diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index fd72aa154833..ab3770807163 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -6,15 +6,29 @@ export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFF
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter
-export function spread(args, classes_to_add) {
+export function spread(args, attrs_to_add) {
const attributes = Object.assign({}, ...args);
- if (classes_to_add) {
- if (attributes.class == null) {
- attributes.class = classes_to_add;
- } else {
- attributes.class += ' ' + classes_to_add;
+ if (attrs_to_add) {
+ const classes_to_add = attrs_to_add.classes;
+ const styles_to_add = attrs_to_add.styles;
+
+ if (classes_to_add) {
+ if (attributes.class == null) {
+ attributes.class = classes_to_add;
+ } else {
+ attributes.class += ' ' + classes_to_add;
+ }
+ }
+
+ if (styles_to_add) {
+ if (attributes.style == null) {
+ attributes.style = style_object_to_string(styles_to_add);
+ } else {
+ attributes.style = style_object_to_string(merge_ssr_styles(attributes.style, styles_to_add));
+ }
}
}
+
let str = '';
Object.keys(attributes).forEach(name => {
@@ -32,6 +46,28 @@ export function spread(args, classes_to_add) {
return str;
}
+export function merge_ssr_styles(style_attribute, style_directive) {
+ const style_object = {};
+ for (const individual_style of style_attribute.split(';')) {
+ const colon_index = individual_style.indexOf(':');
+ const name = individual_style.slice(0, colon_index).trim();
+ const value = individual_style.slice(colon_index + 1).trim();
+ if (!name) continue;
+ style_object[name] = value;
+ }
+
+ for (const name in style_directive) {
+ const value = style_directive[name];
+ if (value) {
+ style_object[name] = value;
+ } else {
+ delete style_object[name];
+ }
+ }
+
+ return style_object;
+}
+
export const escaped = {
'"': '"',
"'": ''',
@@ -147,3 +183,16 @@ export function add_attribute(name, value, boolean) {
export function add_classes(classes) {
return classes ? ` class="${classes}"` : '';
}
+
+function style_object_to_string(style_object) {
+ return Object.keys(style_object)
+ .filter(key => style_object[key])
+ .map(key => `${key}: ${style_object[key]};`)
+ .join(' ');
+}
+
+export function add_styles(style_object) {
+ const styles = style_object_to_string(style_object);
+
+ return styles ? ` style="${styles}"` : '';
+}
diff --git a/test/parser/samples/attribute-class-directive/input.svelte b/test/parser/samples/attribute-class-directive/input.svelte
new file mode 100644
index 000000000000..629df2e29fce
--- /dev/null
+++ b/test/parser/samples/attribute-class-directive/input.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/parser/samples/attribute-class-directive/output.json b/test/parser/samples/attribute-class-directive/output.json
new file mode 100644
index 000000000000..abb2e0c0becd
--- /dev/null
+++ b/test/parser/samples/attribute-class-directive/output.json
@@ -0,0 +1,41 @@
+{
+ "html": {
+ "start": 0,
+ "end": 29,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 29,
+ "type": "Element",
+ "name": "div",
+ "attributes": [
+ {
+ "start": 5,
+ "end": 22,
+ "type": "Class",
+ "name": "foo",
+ "modifiers": [],
+ "expression": {
+ "type": "Identifier",
+ "start": 16,
+ "end": 21,
+ "loc": {
+ "start": {
+ "line": 1,
+ "column": 16
+ },
+ "end": {
+ "line": 1,
+ "column": 21
+ }
+ },
+ "name": "isFoo"
+ }
+ }
+ ],
+ "children": []
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style-directive-shorthand/input.svelte b/test/parser/samples/attribute-style-directive-shorthand/input.svelte
new file mode 100644
index 000000000000..3e2c66f21808
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive-shorthand/input.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style-directive-shorthand/output.json b/test/parser/samples/attribute-style-directive-shorthand/output.json
new file mode 100644
index 000000000000..d4e81b6e4be2
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive-shorthand/output.json
@@ -0,0 +1,31 @@
+{
+ "html": {
+ "start": 0,
+ "end": 23,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 23,
+ "type": "Element",
+ "name": "div",
+ "attributes": [
+ {
+ "start": 5,
+ "end": 16,
+ "type": "Style",
+ "name": "color",
+ "modifiers": [],
+ "expression": {
+ "start": 11,
+ "end": 16,
+ "name": "color",
+ "type": "Identifier"
+ }
+ }
+ ],
+ "children": []
+ }
+ ]
+ }
+}
diff --git a/test/parser/samples/attribute-style-directive-string/input.svelte b/test/parser/samples/attribute-style-directive-string/input.svelte
new file mode 100644
index 000000000000..b2eb6bfef8ce
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive-string/input.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style-directive-string/output.json b/test/parser/samples/attribute-style-directive-string/output.json
new file mode 100644
index 000000000000..4cf2bd30c2e5
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive-string/output.json
@@ -0,0 +1,39 @@
+{
+ "html": {
+ "start": 0,
+ "end": 29,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 29,
+ "type": "Element",
+ "name": "div",
+ "attributes": [
+ {
+ "start": 5,
+ "end": 22,
+ "type": "Style",
+ "name": "color",
+ "modifiers": [],
+ "expression": {
+ "type": "TemplateLiteral",
+ "expressions": [],
+ "quasis": [
+ {
+ "type": "TemplateElement",
+ "value": {
+ "raw": "red",
+ "cooked": null
+ },
+ "tail": true
+ }
+ ]
+ }
+ }
+ ],
+ "children": []
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style-directive/input.svelte b/test/parser/samples/attribute-style-directive/input.svelte
new file mode 100644
index 000000000000..536d162326c0
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive/input.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style-directive/output.json b/test/parser/samples/attribute-style-directive/output.json
new file mode 100644
index 000000000000..36906045ce16
--- /dev/null
+++ b/test/parser/samples/attribute-style-directive/output.json
@@ -0,0 +1,41 @@
+{
+ "html": {
+ "start": 0,
+ "end": 33,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 33,
+ "type": "Element",
+ "name": "div",
+ "attributes": [
+ {
+ "start": 5,
+ "end": 26,
+ "type": "Style",
+ "name": "color",
+ "modifiers": [],
+ "expression": {
+ "type": "Identifier",
+ "start": 18,
+ "end": 25,
+ "loc": {
+ "start": {
+ "line": 1,
+ "column": 18
+ },
+ "end": {
+ "line": 1,
+ "column": 25
+ }
+ },
+ "name": "myColor"
+ }
+ }
+ ],
+ "children": []
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style/input.svelte b/test/parser/samples/attribute-style/input.svelte
new file mode 100644
index 000000000000..12872605ad66
--- /dev/null
+++ b/test/parser/samples/attribute-style/input.svelte
@@ -0,0 +1 @@
+red
\ No newline at end of file
diff --git a/test/parser/samples/attribute-style/output.json b/test/parser/samples/attribute-style/output.json
new file mode 100644
index 000000000000..95c0c8a7a460
--- /dev/null
+++ b/test/parser/samples/attribute-style/output.json
@@ -0,0 +1,41 @@
+{
+ "html": {
+ "start": 0,
+ "end": 34,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 34,
+ "type": "Element",
+ "name": "div",
+ "attributes": [
+ {
+ "start": 5,
+ "end": 24,
+ "type": "Attribute",
+ "name": "style",
+ "value": [
+ {
+ "start": 12,
+ "end": 23,
+ "type": "Text",
+ "raw": "color: red;",
+ "data": "color: red;"
+ }
+ ]
+ }
+ ],
+ "children": [
+ {
+ "start": 25,
+ "end": 28,
+ "type": "Text",
+ "raw": "red",
+ "data": "red"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/runtime/samples/inline-style-directive-and-style-attr-merged/_config.js b/test/runtime/samples/inline-style-directive-and-style-attr-merged/_config.js
new file mode 100644
index 000000000000..f5780daf80eb
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-and-style-attr-merged/_config.js
@@ -0,0 +1,13 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+ assert.equal(styles.height, '40px');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-and-style-attr-merged/main.svelte b/test/runtime/samples/inline-style-directive-and-style-attr-merged/main.svelte
new file mode 100644
index 000000000000..6f3e86b9bfed
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-and-style-attr-merged/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-and-style-attr/_config.js b/test/runtime/samples/inline-style-directive-and-style-attr/_config.js
new file mode 100644
index 000000000000..f5780daf80eb
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-and-style-attr/_config.js
@@ -0,0 +1,13 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+ assert.equal(styles.height, '40px');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-and-style-attr/main.svelte b/test/runtime/samples/inline-style-directive-and-style-attr/main.svelte
new file mode 100644
index 000000000000..e2ba0fa591cb
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-and-style-attr/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-css-vars/_config.js b/test/runtime/samples/inline-style-directive-css-vars/_config.js
new file mode 100644
index 000000000000..c8b239a42bcd
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-css-vars/_config.js
@@ -0,0 +1,9 @@
+export default {
+ html: '',
+
+ test({ assert, component, target }) {
+ component.myColor = 'blue';
+
+ assert.htmlEqual(target.innerHTML, '');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-css-vars/main.svelte b/test/runtime/samples/inline-style-directive-css-vars/main.svelte
new file mode 100644
index 000000000000..689a2333f500
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-css-vars/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-dynamic/_config.js b/test/runtime/samples/inline-style-directive-dynamic/_config.js
new file mode 100644
index 000000000000..a2214fd872ab
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-dynamic/_config.js
@@ -0,0 +1,10 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target }) {
+ component.myColor = 'blue';
+ assert.htmlEqual(target.innerHTML, '');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-dynamic/main.svelte b/test/runtime/samples/inline-style-directive-dynamic/main.svelte
new file mode 100644
index 000000000000..1d60aefa6fd3
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-dynamic/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-multiple/_config.js b/test/runtime/samples/inline-style-directive-multiple/_config.js
new file mode 100644
index 000000000000..725d03c4feb3
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-multiple/_config.js
@@ -0,0 +1,27 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('p');
+
+ let styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+
+ component.myColor = 'pink';
+ component.width = '100vh';
+ component.absolute = true;
+ component.bold = false;
+
+ styles = window.getComputedStyle(p);
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+ assert.equal(styles.color, 'pink');
+ assert.equal(styles.width, '100vh');
+ assert.equal(styles.fontWeight, '100');
+ assert.equal(styles.position, 'absolute');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-multiple/main.svelte b/test/runtime/samples/inline-style-directive-multiple/main.svelte
new file mode 100644
index 000000000000..06f03206d54d
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-multiple/main.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-shorthand/_config.js b/test/runtime/samples/inline-style-directive-shorthand/_config.js
new file mode 100644
index 000000000000..64043e52698f
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-shorthand/_config.js
@@ -0,0 +1,18 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('p');
+
+ let styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+
+ component.color = 'blue';
+ assert.htmlEqual(target.innerHTML, '');
+
+ styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'blue');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-shorthand/main.svelte b/test/runtime/samples/inline-style-directive-shorthand/main.svelte
new file mode 100644
index 000000000000..65bbff910b91
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-shorthand/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-spread-and-attr-empty/_config.js b/test/runtime/samples/inline-style-directive-spread-and-attr-empty/_config.js
new file mode 100644
index 000000000000..f3dcbc8823ff
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-and-attr-empty/_config.js
@@ -0,0 +1,5 @@
+export default {
+ html: `
+
+ `
+};
diff --git a/test/runtime/samples/inline-style-directive-spread-and-attr-empty/main.svelte b/test/runtime/samples/inline-style-directive-spread-and-attr-empty/main.svelte
new file mode 100644
index 000000000000..c2d5cb4d3f27
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-and-attr-empty/main.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-spread-and-attr/_config.js b/test/runtime/samples/inline-style-directive-spread-and-attr/_config.js
new file mode 100644
index 000000000000..b3660bd5caa8
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-and-attr/_config.js
@@ -0,0 +1,43 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('p');
+
+ let styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'green');
+
+ component.color = null;
+ assert.htmlEqual(target.innerHTML, '');
+ styles = window.getComputedStyle(p);
+ assert.equal(styles.color, '');
+
+ component.spread = { style: 'color: yellow; padding: 30px;' };
+
+ assert.htmlEqual(target.innerHTML, '');
+ styles = window.getComputedStyle(p);
+ assert.equal(styles.color, '');
+ assert.equal(styles.padding, '30px');
+
+ component.spread = {};
+ component.style = 'color: blue; background-color: green;';
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+ styles = window.getComputedStyle(p);
+ assert.equal(styles.color, '');
+ assert.equal(styles.backgroundColor, 'green');
+
+ component.color = 'purple';
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+ styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'purple');
+ assert.equal(styles.backgroundColor, 'green');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-spread-and-attr/main.svelte b/test/runtime/samples/inline-style-directive-spread-and-attr/main.svelte
new file mode 100644
index 000000000000..a9696df67573
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-and-attr/main.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-spread-dynamic/_config.js b/test/runtime/samples/inline-style-directive-spread-dynamic/_config.js
new file mode 100644
index 000000000000..152e4036a07a
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-dynamic/_config.js
@@ -0,0 +1,39 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'blue');
+ assert.equal(styles.width, '65px');
+ assert.equal(p.id, 'my-id');
+
+ component.color = 'red';
+
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+
+ component.obj = { style: 'height: 72px;' };
+
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+
+ component.obj = { style: 'border-radius: 2px; color: orange' };
+
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+
+ component.obj = {};
+
+ assert.htmlEqual(target.innerHTML, '');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-spread-dynamic/main.svelte b/test/runtime/samples/inline-style-directive-spread-dynamic/main.svelte
new file mode 100644
index 000000000000..a514e4856f83
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread-dynamic/main.svelte
@@ -0,0 +1,5 @@
+
+
diff --git a/test/runtime/samples/inline-style-directive-spread/_config.js b/test/runtime/samples/inline-style-directive-spread/_config.js
new file mode 100644
index 000000000000..adb8a2654484
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread/_config.js
@@ -0,0 +1,14 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'blue');
+ assert.equal(styles.width, '65px');
+ assert.equal(p.id, 'my-id');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-spread/main.svelte b/test/runtime/samples/inline-style-directive-spread/main.svelte
new file mode 100644
index 000000000000..51291146c79a
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-spread/main.svelte
@@ -0,0 +1 @@
+
diff --git a/test/runtime/samples/inline-style-directive-string-variable/_config.js b/test/runtime/samples/inline-style-directive-string-variable/_config.js
new file mode 100644
index 000000000000..411a42db8e1d
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-string-variable/_config.js
@@ -0,0 +1,23 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'green');
+ assert.equal(styles.transform, 'translateX(45px)');
+ assert.equal(styles.border, '100px solid pink');
+
+ component.translate_x = '100%';
+ component.border_width = 20;
+ component.border_color = 'yellow';
+
+ assert.htmlEqual(
+ target.innerHTML,
+ ''
+ );
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-string-variable/main.svelte b/test/runtime/samples/inline-style-directive-string-variable/main.svelte
new file mode 100644
index 000000000000..1f0b5019f400
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-string-variable/main.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/test/runtime/samples/inline-style-directive-string/_config.js b/test/runtime/samples/inline-style-directive-string/_config.js
new file mode 100644
index 000000000000..ec32e157e51b
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-string/_config.js
@@ -0,0 +1,12 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive-string/main.svelte b/test/runtime/samples/inline-style-directive-string/main.svelte
new file mode 100644
index 000000000000..4e7ce22b2266
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive-string/main.svelte
@@ -0,0 +1 @@
+
diff --git a/test/runtime/samples/inline-style-directive/_config.js b/test/runtime/samples/inline-style-directive/_config.js
new file mode 100644
index 000000000000..ec32e157e51b
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive/_config.js
@@ -0,0 +1,12 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, target, window }) {
+ const p = target.querySelector('p');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+ }
+};
diff --git a/test/runtime/samples/inline-style-directive/main.svelte b/test/runtime/samples/inline-style-directive/main.svelte
new file mode 100644
index 000000000000..1d60aefa6fd3
--- /dev/null
+++ b/test/runtime/samples/inline-style-directive/main.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/runtime/samples/inline-style/_config.js b/test/runtime/samples/inline-style/_config.js
new file mode 100644
index 000000000000..3e984d4c6992
--- /dev/null
+++ b/test/runtime/samples/inline-style/_config.js
@@ -0,0 +1,12 @@
+export default {
+ html: `
+
+ `,
+
+ test({ assert, component, target, window }) {
+ const p = target.querySelector('div');
+
+ const styles = window.getComputedStyle(p);
+ assert.equal(styles.color, 'red');
+ }
+};
diff --git a/test/runtime/samples/inline-style/main.svelte b/test/runtime/samples/inline-style/main.svelte
new file mode 100644
index 000000000000..0f37a04c994f
--- /dev/null
+++ b/test/runtime/samples/inline-style/main.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file