[feat] Style directives (#5923)

* add Style node, interfaces

* style-directives: add parser and runtime test

* style-directives: push styles in to styles array on Element

* style-directives: minimal ssr work

* style-directives: ssr add_styles

* style-directive: tests

* style-directives: work in progress

* obviously incorrect hard-coded color

* tweak

* style directive interface

* style-directives: get text from info in Style node

* style-directives: add_styles func in ElementWrapper

* style-directives: ssr rendering

* handle text-only style directive in tag.ts

* style-directives: handle spread styles in ssr

* style-directives: more parser tests

* style-directives: more inline tests

* style-directives: remove solo tests

* style-directives: cleanup

* style-directives: tweak spread ssr internal

* style-directives: push updater into update chunks; add dynamic test;

* remove .solo

* check for dynamic dependencies before adding update chunk

* add test of multiple styles; remove null styles;

* style-directives: docs; more tests of multiple styles

* style-directives: use camelcase

* style-directives: cleanup

* style-directives: fix mustache template case with template literal

* style-directives: use ternary

* style-directives: linting

* style-directives: remove "text" from interface

* style-directives: actually, remove StyleDirective interface entriely

* add more test, fix test for ssr

* fix lint and tidy up

* add test for css variables

* fix linting errors

Co-authored-by: pmurray73 <pmurray73@bloomberg.net>
Co-authored-by: Tan Li Hau <lhtan93@gmail.com>
pull/7113/head
Paul Murray 3 years ago committed by GitHub
parent deed340cf5
commit 8a47b5ec9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -821,6 +821,46 @@ A `class:` directive provides a shorter way of toggling a class on an element.
<div class:active class:inactive={!active} class:isAdmin>...</div>
```
#### 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
<!-- These are equivalent -->
<div style:color="red">...</div>
<div style="color: red;">...</div>
<!-- Variables can be used -->
<div style:color={myColor}>...</div>
<!-- Shorthand, for when property and variable name match -->
<div style:color>...</div>
<!-- Multiple styles can be included -->
<div style:color style:width="12rem" style:background-color={darkMode ? "black" : "white"}>...</div>
```
---
When `style:` directives are combined with `style` attributes, the directives will take precedence:
```sv
<div style="color: blue;" style:color="red">This will be red</div>
```
#### use:*action*

@ -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';
@ -180,6 +181,7 @@ export default class Element extends Node {
actions: Action[] = [];
bindings: Binding[] = [];
classes: Class[] = [];
styles: Style[] = [];
handlers: EventHandler[] = [];
lets: Let[] = [];
intro?: Transition = null;
@ -263,6 +265,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;

@ -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;
}
}

@ -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

@ -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);`;

@ -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})`);
}
}

@ -34,6 +34,7 @@ export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'Style'
| 'EventHandler'
| 'Let'
| 'Ref'

@ -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<Text | MustacheTag>): 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<string>) {
const start = parser.index;
@ -365,9 +396,17 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
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<Text | MustacheTag>) : 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<string>) {
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<string>) {
directive.outro = direction === 'out' || direction === 'transition';
}
if (!directive.expression && (type === 'Binding' || type === 'Class')) {
// Directive name is expression, e.g. <p class:isRed />
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;

@ -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) {

@ -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 = {
'"': '&quot;',
"'": '&#39;',
@ -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}"` : '';
}

@ -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": []
}
]
}
}

@ -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": []
}
]
}
}

@ -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": []
}
]
}
}

@ -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": []
}
]
}
}

@ -0,0 +1 @@
<div style="color: red;">red</div>

@ -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"
}
]
}
]
}
}

@ -0,0 +1,13 @@
export default {
html: `
<p style="height: 40px; color: red;"></p>
`,
test({ assert, target, window }) {
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.color, 'red');
assert.equal(styles.height, '40px');
}
};

@ -0,0 +1,5 @@
<script>
export let color = 'red';
</script>
<p style="height: 40px; color: blue;" style:color={color} />

@ -0,0 +1,13 @@
export default {
html: `
<p style="height: 40px; color: red;"></p>
`,
test({ assert, target, window }) {
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.color, 'red');
assert.equal(styles.height, '40px');
}
};

@ -0,0 +1,5 @@
<script>
export let color = 'red';
</script>
<p style="height: 40px;" style:color={color} />

@ -0,0 +1,9 @@
export default {
html: '<p style="--border-color: red;"></p>',
test({ assert, component, target }) {
component.myColor = 'blue';
assert.htmlEqual(target.innerHTML, '<p style="--border-color: blue;"></p>');
}
};

@ -0,0 +1,5 @@
<script>
export let myColor = "red";
</script>
<p style:--border-color={myColor} />

@ -0,0 +1,10 @@
export default {
html: `
<p style="color: red;"></p>
`,
test({ assert, component, target }) {
component.myColor = 'blue';
assert.htmlEqual(target.innerHTML, '<p style="color: blue;"></p>');
}
};

@ -0,0 +1,5 @@
<script>
export let myColor = "red";
</script>
<p style:color={myColor} />

@ -0,0 +1,27 @@
export default {
html: `
<p style="color: red; width: 65px; font-weight: 700;"></p>
`,
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,
'<p style="color: pink; width: 100vh; font-weight: 100; position: absolute;"></p>'
);
assert.equal(styles.color, 'pink');
assert.equal(styles.width, '100vh');
assert.equal(styles.fontWeight, '100');
assert.equal(styles.position, 'absolute');
}
};

@ -0,0 +1,13 @@
<script>
export let myColor = "red";
export let width = "65px";
export let absolute = false;
export let bold = true;
</script>
<p
style:color={myColor}
style:width
style:position={absolute ? "absolute" : null}
style:font-weight={bold ? 700 : 100}
/>

@ -0,0 +1,18 @@
export default {
html: `
<p style="color: red;"></p>
`,
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, '<p style="color: blue;"></p>');
styles = window.getComputedStyle(p);
assert.equal(styles.color, 'blue');
}
};

@ -0,0 +1,5 @@
<script>
export let color = "red";
</script>
<p style:color />

@ -0,0 +1,5 @@
export default {
html: `
<p style=""></p>
`
};

@ -0,0 +1,11 @@
<script>
export let spread = { style: 'color: red;' };
export let color = null;
export let style = 'color: blue';
</script>
<p
style:color
{...spread}
{style}
/>

@ -0,0 +1,43 @@
export default {
html: `
<p style="color: green;"></p>
`,
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, '<p style=""></p>');
styles = window.getComputedStyle(p);
assert.equal(styles.color, '');
component.spread = { style: 'color: yellow; padding: 30px;' };
assert.htmlEqual(target.innerHTML, '<p style="padding: 30px;"></p>');
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,
'<p style="background-color: green;"></p>'
);
styles = window.getComputedStyle(p);
assert.equal(styles.color, '');
assert.equal(styles.backgroundColor, 'green');
component.color = 'purple';
assert.htmlEqual(
target.innerHTML,
'<p style="background-color: green; color: purple;"></p>'
);
styles = window.getComputedStyle(p);
assert.equal(styles.color, 'purple');
assert.equal(styles.backgroundColor, 'green');
}
};

@ -0,0 +1,11 @@
<script>
export let spread = { style: 'color: red;' };
export let color = 'green';
export let style = 'color: blue;';
</script>
<p
style:color
{style}
{...spread}
/>

@ -0,0 +1,39 @@
export default {
html: `
<p id="my-id" style="width: 65px; color: blue;"></p>
`,
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,
'<p id="my-id" style="width: 65px; color: red;"></p>'
);
component.obj = { style: 'height: 72px;' };
assert.htmlEqual(
target.innerHTML,
'<p style="height: 72px; color: red;"></p>'
);
component.obj = { style: 'border-radius: 2px; color: orange' };
assert.htmlEqual(
target.innerHTML,
'<p style="border-radius: 2px; color: red;"></p>'
);
component.obj = {};
assert.htmlEqual(target.innerHTML, '<p style="color: red;"></p>');
}
};

@ -0,0 +1,5 @@
<script>
export let color = 'blue';
export let obj = { id: 'my-id', style: 'width: 65px' };
</script>
<p {...obj} style:color={color} />

@ -0,0 +1,14 @@
export default {
html: `
<p id="my-id" style="width: 65px; color: blue;"></p>
`,
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');
}
};

@ -0,0 +1 @@
<p {...{ id: "my-id", style: "width: 65px" }} style:color="blue" />

@ -0,0 +1,23 @@
export default {
html: `
<p style="color: green; transform: translateX(45px); border: 100px solid pink;"></p>
`,
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,
'<p style="color: green; transform: translateX(100%); border: 20px solid yellow;"></p>'
);
}
};

@ -0,0 +1,7 @@
<script>
export let translate_x = "45px";
export let border_width = 100;
export let border_color;
</script>
<p style:color={"green"} style:transform="translateX({translate_x})" style:border="{border_width}px solid {border_color || 'pink'}" />

@ -0,0 +1,12 @@
export default {
html: `
<p style="color: red;"></p>
`,
test({ assert, target, window }) {
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.color, 'red');
}
};

@ -0,0 +1,12 @@
export default {
html: `
<p style="color: red;"></p>
`,
test({ assert, target, window }) {
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.color, 'red');
}
};

@ -0,0 +1,5 @@
<script>
export let myColor = "red";
</script>
<p style:color={myColor} />

@ -0,0 +1,12 @@
export default {
html: `
<div style="color: red;"></div>
`,
test({ assert, component, target, window }) {
const p = target.querySelector('div');
const styles = window.getComputedStyle(p);
assert.equal(styles.color, 'red');
}
};

@ -0,0 +1 @@
<div style="color: red;"></div>
Loading…
Cancel
Save