Merge branch 'master' of github.com:sveltejs/svelte

pull/7738/head
Richard Harris 6 years ago
commit e0bd4ad4c9

@ -44,6 +44,6 @@ In `mapbox.js` you'll see this line:
const key = {};
```
We can use anything as a key — we could do `setContext('mapbox', ...)` for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance, even when you have multiple different contexts operating across many component layers.
We can use anything as a key — we could do `setContext('mapbox', ...)` for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has referential equality to itself, i.e. `{} !== {}` whereas `"x" === "x"`), even when you have multiple different contexts operating across many component layers.
> Remember that context is not inherently reactive. If you need context values to be reactive then you can pass a store into context, which *will* be reactive.

@ -83,16 +83,16 @@ export default class EachBlock extends AbstractBlock {
this.scope.add(context.key.name, this.expression.dependencies, this);
});
this.key = info.key
? new Expression(component, this, this.scope, info.key)
: null;
if (this.index) {
// index can only change if this is a keyed each block
const dependencies = this.key ? this.expression.dependencies : new Set([]);
const dependencies = info.key ? this.expression.dependencies : new Set([]);
this.scope.add(this.index, dependencies, this);
}
this.key = info.key
? new Expression(component, this, this.scope, info.key)
: null;
this.has_animation = false;
this.children = map_children(component, this, this.scope, info.children);

@ -703,16 +703,20 @@ export default class Element extends Node {
return this.name === 'audio' || this.name === 'video';
}
add_css_class(class_name = this.component.stylesheet.id) {
add_css_class() {
const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
(class_attribute.chunks[0] as Text).data += ` ${id}`;
} else {
(class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${class_name}`
data: ` ${id}`,
synthetic: true
})
);
}
@ -721,7 +725,7 @@ export default class Element extends Node {
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: class_name }]
value: [{ type: 'Text', data: id, synthetic: true }]
})
);
}

@ -6,9 +6,11 @@ import { INode } from './interfaces';
export default class Text extends Node {
type: 'Text';
data: string;
synthetic: boolean;
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info);
this.data = info.data;
this.synthetic = info.synthetic || false;
}
}

@ -5,6 +5,7 @@ import ElementWrapper from './index';
import { stringify } from '../../../utils/stringify';
import deindent from '../../../utils/deindent';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
export default class AttributeWrapper {
node: Attribute;
@ -84,19 +85,13 @@ export default class AttributeWrapper {
value = (this.node.chunks[0] as Expression).render(block);
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.node.chunks[0].type === 'Text' ? '' : `"" + `) +
this.node.chunks
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
return chunk.get_precedence() <= 13
? `(${chunk.render()})`
: chunk.render();
}
})
.join(' + ');
const prefix = this.node.chunks[0].type === 'Text' ? '' : `"" + `;
const text = this.node.name === 'class'
? this.get_class_name_text()
: this.render_chunks().join(' + ');
value = `${prefix}${text}`;
}
const is_select_value_attribute =
@ -210,6 +205,31 @@ export default class AttributeWrapper {
}
}
get_class_name_text() {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks();
if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined}
rendered[0] = `@null_to_empty(${rendered[0]})`;
}
return rendered.join(' + ');
}
render_chunks() {
return this.node.chunks.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
}
const rendered = chunk.render();
return chunk.get_precedence() <= 13
? `(${rendered})`
: rendered;
});
}
stringify() {
if (this.node.is_true) return '';

@ -816,29 +816,4 @@ export default class ElementWrapper extends Wrapper {
}
});
}
// todo: looks to be dead code copypasted from Element.add_css_class in src/compile/nodes/Element.ts
// add_css_class(class_name = this.component.stylesheet.id) {
// const class_attribute = this.attributes.find(a => a.name === 'class');
// if (class_attribute && !class_attribute.is_true) {
// if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
// (class_attribute.chunks[0] as Text).data += ` ${class_name}`;
// } else {
// (class_attribute.chunks as Node[]).push(
// new Text(this.component, this, this.scope, {
// type: 'Text',
// data: ` ${class_name}`
// })
// );
// }
// } else {
// this.attributes.push(
// new Attribute(this.component, this, this.scope, {
// type: 'Attribute',
// name: 'class',
// value: [{ type: 'Text', data: class_name }]
// })
// );
// }
// }
}

@ -2,7 +2,7 @@ import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../..
import Attribute from '../../nodes/Attribute';
import Class from '../../nodes/Class';
import { snip } from '../../utils/snip';
import { stringify_attribute } from '../../utils/stringify_attribute';
import { stringify_attribute, stringify_class_attribute } from '../../utils/stringify_attribute';
import { get_slot_scope } from './shared/get_slot_scope';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
@ -111,9 +111,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
args.push(`{ ${quote_name_if_necessary(attribute.name)}: ${snip(attribute.chunks[0])} }`);
} else if (attribute.name === 'class' && class_expression) {
// Add class expression
args.push(`{ ${quote_name_if_necessary(attribute.name)}: [\`${stringify_attribute(attribute, true)}\`, \`\${${class_expression}}\`].join(' ').trim() }`);
args.push(`{ ${quote_name_if_necessary(attribute.name)}: [\`${stringify_class_attribute(attribute)}\`, \`\${${class_expression}}\`].join(' ').trim() }`);
} else {
args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${stringify_attribute(attribute, true)}\` }`);
args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}\` }`);
}
}
});
@ -136,14 +136,13 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
opening_tag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && class_expression) {
add_class_attribute = false;
opening_tag += ` class="\${[\`${stringify_attribute(attribute, true)}\`, ${class_expression}].join(' ').trim() }"`;
opening_tag += ` class="\${[\`${stringify_class_attribute(attribute)}\`, ${class_expression}].join(' ').trim() }"`;
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const { name } = attribute;
const snippet = snip(attribute.chunks[0]);
opening_tag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}';
opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', ' + (boolean_attributes.has(name) ? 1 : 0) + ')}';
} else {
opening_tag += ` ${attribute.name}="${stringify_attribute(attribute, true)}"`;
opening_tag += ` ${attribute.name}="${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}"`;
}
});
}
@ -165,7 +164,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
node_contents = '${(' + snippet + ') || ""}';
} else {
const snippet = snip(expression);
opening_tag += '${@add_attribute("' + name + '", ' + snippet + ')}';
opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', 1)}';
}
});

@ -1,6 +1,16 @@
import Attribute from '../nodes/Attribute';
import { escape_template, escape } from './stringify';
import { snip } from './snip';
import Text from '../nodes/Text';
export function stringify_class_attribute(attribute: Attribute) {
// handle special case — `class={possiblyUndefined}` with scoped CSS
if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
return '${@escape(@null_to_empty(' + snip(attribute.chunks[0]) + '))}' + (attribute.chunks[1] as Text).data;
}
return stringify_attribute(attribute, true);
}
export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
return attribute.chunks

@ -120,9 +120,9 @@ export function create_ssr_component(fn) {
};
}
export function add_attribute(name, value) {
if (!value) return '';
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(value) : `"${value}"`}`}`;
export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return '';
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
}
export function add_classes(classes) {

@ -96,3 +96,7 @@ export function once(fn) {
fn.call(this, ...args);
};
}
export function null_to_empty(value) {
return value == null ? '' : value;
}

@ -0,0 +1,3 @@
export default {
html: `<div class="false"></div>`,
};

@ -0,0 +1,42 @@
export default {
props: {
testName: "testClassName"
},
html: `<div class="testClassName"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'testClassName');
component.testName = null;
assert.equal(div.className, '');
component.testName = undefined;
assert.equal(div.className, '');
component.testName = undefined + '';
assert.equal(div.className, 'undefined');
component.testName = null + '';
assert.equal(div.className, 'null');
component.testName = 1;
assert.equal(div.className, '1');
component.testName = 0;
assert.equal(div.className, '0');
component.testName = false;
assert.equal(div.className, 'false');
component.testName = true;
assert.equal(div.className, 'true');
component.testName = {};
assert.equal(div.className, '[object Object]');
component.testName = '';
assert.equal(div.className, '');
}
};

@ -0,0 +1,5 @@
<script>
export let testName;
</script>
<div class={testName}></div>

@ -0,0 +1,40 @@
export default {
html: `<div class=" svelte-x1o6ra"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
component.testName = null;
assert.equal(div.className, ' svelte-x1o6ra');
component.testName = undefined;
assert.equal(div.className, ' svelte-x1o6ra');
component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra');
component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra');
component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra');
component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra');
component.testName = {};
assert.equal(div.className, '[object Object] svelte-x1o6ra');
component.testName = '';
assert.equal(div.className, ' svelte-x1o6ra');
component.testName = 'testClassName';
assert.equal(div.className, 'testClassName svelte-x1o6ra');
}
};

@ -0,0 +1,11 @@
<script>
export let testName;
</script>
<style>
div {
color: purple;
}
</style>
<div class={testName}></div>

@ -0,0 +1,45 @@
export default {
props: {
testName1: "test1",
testName2: "test2",
},
html: `<div class="test1test2"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'test1test2');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0');
component.testName1 = null;
component.testName2 = "test";
assert.equal(div.className, 'nulltest');
component.testName1 = undefined;
component.testName2 = "test";
assert.equal(div.className, 'undefinedtest');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0');
component.testName1 = undefined;
component.testName2 = 0;
assert.equal(div.className, 'NaN');
}
};

@ -0,0 +1,6 @@
<script>
export let testName1;
export let testName2;
</script>
<div class={testName1 + testName2}></div>

@ -0,0 +1,45 @@
export default {
props: {
testName1: "test1",
testName2: "test2",
},
html: `<div class="test1test2 svelte-x1o6ra"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'test1test2 svelte-x1o6ra');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName1 = null;
component.testName2 = "test";
assert.equal(div.className, 'nulltest svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = "test";
assert.equal(div.className, 'undefinedtest svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = 0;
assert.equal(div.className, 'NaN svelte-x1o6ra');
}
};

@ -0,0 +1,12 @@
<script>
export let testName1;
export let testName2;
</script>
<style>
div {
color: purple;
}
</style>
<div class={testName1 + testName2}></div>

@ -0,0 +1,42 @@
export default {
props: {
testName: "testClassName"
},
html: `<div class="testClassName"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'testClassName');
component.testName = null;
assert.equal(div.className, '');
component.testName = undefined;
assert.equal(div.className, '');
component.testName = undefined + '';
assert.equal(div.className, 'undefined');
component.testName = null + '';
assert.equal(div.className, 'null');
component.testName = 1;
assert.equal(div.className, '1');
component.testName = 0;
assert.equal(div.className, '0');
component.testName = false;
assert.equal(div.className, 'false');
component.testName = true;
assert.equal(div.className, 'true');
component.testName = {};
assert.equal(div.className, '[object Object]');
component.testName = '';
assert.equal(div.className, '');
}
};

@ -0,0 +1,9 @@
<script>
export let testName;
function myHelper(testName) {
return testName;
}
</script>
<div class={myHelper(testName)}></div>

@ -0,0 +1,42 @@
export default {
props: {
testName: "testClassName"
},
html: `<div class="testClassName svelte-x1o6ra"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'testClassName svelte-x1o6ra');
component.testName = null;
assert.equal(div.className, ' svelte-x1o6ra');
component.testName = undefined;
assert.equal(div.className, ' svelte-x1o6ra');
component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra');
component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra');
component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra');
component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra');
component.testName = {};
assert.equal(div.className, '[object Object] svelte-x1o6ra');
component.testName = '';
assert.equal(div.className, ' svelte-x1o6ra');
}
};

@ -0,0 +1,15 @@
<script>
export let testName;
function myHelper(testName) {
return testName;
}
</script>
<style>
div {
color: purple;
}
</style>
<div class={myHelper(testName)}></div>

@ -0,0 +1,45 @@
export default {
props: {
testName1: "test1",
testName2: "test2",
},
html: `<div class="test1test2"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'test1test2');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0');
component.testName1 = null;
component.testName2 = "test";
assert.equal(div.className, 'nulltest');
component.testName1 = undefined;
component.testName2 = "test";
assert.equal(div.className, 'undefinedtest');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0');
component.testName1 = undefined;
component.testName2 = 0;
assert.equal(div.className, 'NaN');
}
};

@ -0,0 +1,11 @@
<script>
export let testName1;
export let testName2;
function myHelper(testName) {
return testName;
}
</script>
<div class={myHelper(testName1) + myHelper(testName2)}></div>

@ -0,0 +1,45 @@
export default {
props: {
testName1: "test1",
testName2: "test2",
},
html: `<div class="test1test2 svelte-x1o6ra"></div>`,
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div.className, 'test1test2 svelte-x1o6ra');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName1 = null;
component.testName2 = "test";
assert.equal(div.className, 'nulltest svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = "test";
assert.equal(div.className, 'undefinedtest svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
component.testName1 = undefined;
component.testName2 = 0;
assert.equal(div.className, 'NaN svelte-x1o6ra');
}
};

@ -0,0 +1,17 @@
<script>
export let testName1;
export let testName2;
function myHelper(testName) {
return testName;
}
</script>
<style>
div {
color: purple;
}
</style>
<div class={myHelper(testName1) + myHelper(testName2)}></div>

@ -0,0 +1,3 @@
export default {
html: `<div></div>`,
};

@ -0,0 +1,3 @@
export default {
html: `<div></div>`,
};

@ -1,6 +1,6 @@
<script>
// script block prevents auto-declaration
</script>
<p>{potato}</p>
<p>{Math.max(1, 2)}</p>
<p>{Math.max(1, 2)}</p>
{#each window.something as foo, i (foo.x + i)}
hello
{/each}

@ -1,15 +1,15 @@
[{
"code": "missing-declaration",
"message": "'potato' is not defined",
"pos": 67,
"message": "'potato' is not defined. Consider adding a <script> block with 'export let potato' to declare a prop",
"pos": 4,
"start": {
"line": 5,
"line": 1,
"column": 4,
"character": 67
"character": 4
},
"end": {
"line": 5,
"line": 1,
"column": 10,
"character": 73
"character": 10
}
}]
Loading…
Cancel
Save