mirror of https://github.com/sveltejs/svelte
parent
0ca1dcd33c
commit
fa7c780bad
@ -0,0 +1,19 @@
|
||||
import Expression from "./shared/Expression";
|
||||
import map_children from "./shared/map_children";
|
||||
import AbstractBlock from "./shared/AbstractBlock";
|
||||
|
||||
export default class KeyBlock extends AbstractBlock {
|
||||
type: "KeyBlock";
|
||||
|
||||
expression: Expression;
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
this.expression = new Expression(component, this, scope, info.expression);
|
||||
|
||||
this.children = map_children(component, this, scope, info.children);
|
||||
|
||||
this.warn_if_empty_block();
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
import Wrapper from "./shared/Wrapper";
|
||||
import Renderer from "../Renderer";
|
||||
import Block from "../Block";
|
||||
import EachBlock from "../../nodes/EachBlock";
|
||||
import KeyBlock from "../../nodes/KeyBlock";
|
||||
import create_debugging_comment from "./shared/create_debugging_comment";
|
||||
import FragmentWrapper from "./Fragment";
|
||||
import { b, x } from "code-red";
|
||||
import { Identifier } from "estree";
|
||||
|
||||
export default class KeyBlockWrapper extends Wrapper {
|
||||
node: KeyBlock;
|
||||
fragment: FragmentWrapper;
|
||||
block: Block;
|
||||
dependencies: string[];
|
||||
var: Identifier = { type: "Identifier", name: "key_block" };
|
||||
|
||||
constructor(
|
||||
renderer: Renderer,
|
||||
block: Block,
|
||||
parent: Wrapper,
|
||||
node: EachBlock,
|
||||
strip_whitespace: boolean,
|
||||
next_sibling: Wrapper
|
||||
) {
|
||||
super(renderer, block, parent, node);
|
||||
|
||||
this.cannot_use_innerhtml();
|
||||
this.not_static_content();
|
||||
|
||||
this.dependencies = node.expression.dynamic_dependencies();
|
||||
|
||||
if (this.dependencies.length) {
|
||||
block = block.child({
|
||||
comment: create_debugging_comment(node, renderer.component),
|
||||
name: renderer.component.get_unique_name("create_key_block"),
|
||||
type: "key"
|
||||
});
|
||||
renderer.blocks.push(block);
|
||||
}
|
||||
|
||||
this.block = block;
|
||||
this.fragment = new FragmentWrapper(
|
||||
renderer,
|
||||
this.block,
|
||||
node.children,
|
||||
parent,
|
||||
strip_whitespace,
|
||||
next_sibling
|
||||
);
|
||||
}
|
||||
|
||||
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
|
||||
if (this.dependencies.length === 0) {
|
||||
this.render_static_key(block, parent_node, parent_nodes);
|
||||
} else {
|
||||
this.render_dynamic_key(block, parent_node, parent_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
render_static_key(_block: Block, parent_node: Identifier, parent_nodes: Identifier) {
|
||||
this.fragment.render(this.block, parent_node, parent_nodes);
|
||||
}
|
||||
|
||||
render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
|
||||
this.fragment.render(
|
||||
this.block,
|
||||
null,
|
||||
(x`#nodes` as unknown) as Identifier
|
||||
);
|
||||
|
||||
const has_transitions = !!(
|
||||
this.block.has_intro_method || this.block.has_outro_method
|
||||
);
|
||||
const dynamic = this.block.has_update_method;
|
||||
|
||||
const previous_key = block.get_unique_name('previous_key');
|
||||
const snippet = this.node.expression.manipulate(block);
|
||||
block.add_variable(previous_key, snippet);
|
||||
|
||||
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
|
||||
const condition = x`${this.renderer.dirty(this.dependencies)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`;
|
||||
|
||||
block.chunks.init.push(b`
|
||||
let ${this.var} = ${this.block.name}(#ctx);
|
||||
`);
|
||||
block.chunks.create.push(b`${this.var}.c();`);
|
||||
if (this.renderer.options.hydratable) {
|
||||
block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`);
|
||||
}
|
||||
block.chunks.mount.push(
|
||||
b`${this.var}.m(${parent_node || "#target"}, ${
|
||||
parent_node ? "null" : "#anchor"
|
||||
});`
|
||||
);
|
||||
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
|
||||
const body = b`
|
||||
${
|
||||
has_transitions
|
||||
? b`
|
||||
@group_outros();
|
||||
@transition_out(${this.var}, 1, 1, @noop);
|
||||
@check_outros();
|
||||
`
|
||||
: b`${this.var}.d(1);`
|
||||
}
|
||||
${this.var} = ${this.block.name}(#ctx);
|
||||
${this.var}.c();
|
||||
${has_transitions && b`@transition_in(${this.var})`}
|
||||
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
|
||||
`;
|
||||
|
||||
if (dynamic) {
|
||||
block.chunks.update.push(b`
|
||||
if (${condition}) {
|
||||
${body}
|
||||
} else {
|
||||
${this.var}.p(#ctx, #dirty);
|
||||
}
|
||||
`);
|
||||
} else {
|
||||
block.chunks.update.push(b`
|
||||
if (${condition}) {
|
||||
${body}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
if (has_transitions) {
|
||||
block.chunks.intro.push(b`@transition_in(${this.var})`);
|
||||
block.chunks.outro.push(b`@transition_out(${this.var})`);
|
||||
}
|
||||
|
||||
block.chunks.destroy.push(b`${this.var}.d(detaching)`);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import KeyBlock from '../../nodes/KeyBlock';
|
||||
import Renderer, { RenderOptions } from '../Renderer';
|
||||
|
||||
export default function(node: KeyBlock, renderer: Renderer, options: RenderOptions) {
|
||||
renderer.render(node.children, options);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// with reactive content beside `key`
|
||||
export default {
|
||||
html: `<div>00</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
const div = target.querySelector('div');
|
||||
component.reactive = 2;
|
||||
assert.htmlEqual(target.innerHTML, `<div>02</div>`);
|
||||
assert.strictEqual(div, target.querySelector('div'));
|
||||
|
||||
component.value = 5;
|
||||
assert.htmlEqual(target.innerHTML, `<div>52</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector('div'));
|
||||
}
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
export let reactive = 0;
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<div>{value}{reactive}</div>
|
||||
{/key}
|
@ -0,0 +1,11 @@
|
||||
// key is not used in the template
|
||||
export default {
|
||||
html: `<div></div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
const div = target.querySelector('div');
|
||||
|
||||
component.value = 5;
|
||||
assert.htmlEqual(target.innerHTML, `<div></div>`);
|
||||
assert.notStrictEqual(div, target.querySelector('div'));
|
||||
}
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<div />
|
||||
{/key}
|
@ -0,0 +1,15 @@
|
||||
export default {
|
||||
html: `<div>1</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
let div = target.querySelector("div");
|
||||
await component.append(2);
|
||||
assert.htmlEqual(target.innerHTML, `<div>1</div>`);
|
||||
assert.strictEqual(div, target.querySelector("div"));
|
||||
|
||||
div = target.querySelector("div");
|
||||
|
||||
component.array = [3, 4];
|
||||
assert.htmlEqual(target.innerHTML, `<div>3,4</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
}
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
<svelte:options immutable />
|
||||
|
||||
<script>
|
||||
export let array = [1];
|
||||
|
||||
export function append(value) {
|
||||
array.push(value);
|
||||
array = array;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key array}
|
||||
<div>{array.join(',')}</div>
|
||||
{/key}
|
@ -0,0 +1,15 @@
|
||||
export default {
|
||||
html: `<div>1</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
let div = target.querySelector("div");
|
||||
await component.append(2);
|
||||
assert.htmlEqual(target.innerHTML, `<div>1,2</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
|
||||
div = target.querySelector("div");
|
||||
|
||||
component.array = [3, 4];
|
||||
assert.htmlEqual(target.innerHTML, `<div>3,4</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
}
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
<script>
|
||||
export let array = [1];
|
||||
|
||||
export function append(value) {
|
||||
array.push(value);
|
||||
array = array;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key array}
|
||||
<div>{array.join(',')}</div>
|
||||
{/key}
|
@ -0,0 +1,18 @@
|
||||
export default {
|
||||
html: `<div>3</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
const div = target.querySelector("div");
|
||||
|
||||
await component.mutate();
|
||||
assert.htmlEqual(target.innerHTML, `<div>5</div>`);
|
||||
assert.strictEqual(div, target.querySelector("div"));
|
||||
|
||||
await component.reassign();
|
||||
assert.htmlEqual(target.innerHTML, `<div>7</div>`);
|
||||
assert.strictEqual(div, target.querySelector("div"));
|
||||
|
||||
await component.changeKey();
|
||||
assert.htmlEqual(target.innerHTML, `<div>7</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
}
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
let obj = { key: 1, value: 3 };
|
||||
|
||||
export function mutate() {
|
||||
obj.value = 5;
|
||||
}
|
||||
export function reassign() {
|
||||
obj = { key: 1, value: 7 };
|
||||
}
|
||||
export function changeKey() {
|
||||
obj.key = 3;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key obj.key}
|
||||
<div>{obj.value}</div>
|
||||
{/key}
|
@ -0,0 +1,28 @@
|
||||
export default {
|
||||
html: `<div>000</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
let div = target.querySelector("div");
|
||||
component.value = 2;
|
||||
assert.htmlEqual(target.innerHTML, `<div>200</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
|
||||
div = target.querySelector("div");
|
||||
|
||||
component.anotherValue = 5;
|
||||
assert.htmlEqual(target.innerHTML, `<div>250</div>`);
|
||||
assert.notStrictEqual(div, target.querySelector("div"));
|
||||
|
||||
div = target.querySelector("div");
|
||||
|
||||
component.thirdValue = 9;
|
||||
assert.htmlEqual(target.innerHTML, `<div>259</div>`);
|
||||
assert.strictEqual(div, target.querySelector("div"));
|
||||
|
||||
// make dirty while maintain the value of `value + anotherValue`
|
||||
// should update the content, but not recreate the elements
|
||||
await component.$set({ value: 4, anotherValue: 3 });
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<div>439</div>`);
|
||||
assert.strictEqual(div, target.querySelector("div"));
|
||||
}
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
export let anotherValue = 0;
|
||||
export let thirdValue = 0;
|
||||
</script>
|
||||
|
||||
{#key value + anotherValue}
|
||||
<div>{value}{anotherValue}{thirdValue}</div>
|
||||
{/key}
|
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
html: `<div>00</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
const div = target.querySelector('div');
|
||||
component.anotherValue = 2;
|
||||
assert.htmlEqual(target.innerHTML, `<div>02</div>`);
|
||||
assert.strictEqual(div, target.querySelector('div'));
|
||||
}
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
let value = 0;
|
||||
export let anotherValue = 0;
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<div>{value}{anotherValue}</div>
|
||||
{/key}
|
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
html: '<div>0</div>',
|
||||
async test({ assert, component, target, window, raf }) {
|
||||
component.value = 2;
|
||||
|
||||
const [div1, div2] = target.querySelectorAll('div');
|
||||
|
||||
assert.htmlEqual(div1.outerHTML, '<div>0</div>');
|
||||
assert.htmlEqual(div2.outerHTML, '<div>2</div>');
|
||||
|
||||
raf.tick(0);
|
||||
|
||||
assert.equal(div1.foo, 1);
|
||||
assert.equal(div1.oof, 0);
|
||||
|
||||
assert.equal(div2.foo, 0);
|
||||
assert.equal(div2.oof, 1);
|
||||
|
||||
raf.tick(200);
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<div>2</div>');
|
||||
assert.equal(div2, target.querySelector('div'));
|
||||
}
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
|
||||
function foo(node, params) {
|
||||
return {
|
||||
duration: 100,
|
||||
tick: (t, u) => {
|
||||
node.foo = t;
|
||||
node.oof = u;
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<div transition:foo>{value}</div>
|
||||
{/key}
|
@ -0,0 +1,17 @@
|
||||
export default {
|
||||
html: `<div>0</div><div>0</div>`,
|
||||
async test({ assert, component, target, window }) {
|
||||
let [div1, div2] = target.querySelectorAll('div');
|
||||
|
||||
component.value = 5;
|
||||
assert.htmlEqual(target.innerHTML, `<div>5</div><div>0</div>`);
|
||||
assert.notStrictEqual(div1, target.querySelectorAll('div')[0]);
|
||||
assert.strictEqual(div2, target.querySelectorAll('div')[1]);
|
||||
[div1, div2] = target.querySelectorAll('div');
|
||||
|
||||
component.reactive = 10;
|
||||
assert.htmlEqual(target.innerHTML, `<div>5</div><div>10</div>`);
|
||||
assert.strictEqual(div1, target.querySelectorAll('div')[0]);
|
||||
assert.strictEqual(div2, target.querySelectorAll('div')[1]);
|
||||
}
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
export let reactive = 0;
|
||||
</script>
|
||||
|
||||
{#key value}
|
||||
<div>{value}</div>
|
||||
{/key}
|
||||
|
||||
<div>{reactive}</div>
|
Loading…
Reference in new issue