Skip to content

Commit

Permalink
key block
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Sep 15, 2020
1 parent 0449876 commit fa94a5f
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 1 deletion.
35 changes: 35 additions & 0 deletions src/compiler/compile/nodes/KeyBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Expression from "./shared/Expression";
import map_children from "./shared/map_children";
import AbstractBlock from "./shared/AbstractBlock";
import Element from "./Element";

export default class KeyBlock extends AbstractBlock {
type: "KeyBlock";

expression: Expression;
has_animation: boolean;

constructor(component, parent, scope, info) {
super(component, parent, scope, info);

this.expression = new Expression(component, this, scope, info.expression);

this.has_animation = false;

this.children = map_children(component, this, scope, info.children);

if (this.has_animation) {
if (this.children.length !== 1) {
const child = this.children.find(
(child) => !!(child as Element).animation
);
component.error((child as Element).animation, {
code: `invalid-animation`,
message: `An element that use the animate directive must be the sole child of a key block`,
});
}
}

this.warn_if_empty_block();
}
}
2 changes: 2 additions & 0 deletions src/compiler/compile/nodes/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Fragment from './Fragment';
import Head from './Head';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
import KeyBlock from './KeyBlock';
import Let from './Let';
import MustacheTag from './MustacheTag';
import Options from './Options';
Expand Down Expand Up @@ -50,6 +51,7 @@ export type INode = Action
| Head
| IfBlock
| InlineComponent
| KeyBlock
| Let
| MustacheTag
| Options
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compile/nodes/shared/map_children.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Element from '../Element';
import Head from '../Head';
import IfBlock from '../IfBlock';
import InlineComponent from '../InlineComponent';
import KeyBlock from '../KeyBlock';
import MustacheTag from '../MustacheTag';
import Options from '../Options';
import RawMustacheTag from '../RawMustacheTag';
Expand All @@ -28,6 +29,7 @@ function get_constructor(type) {
case 'Head': return Head;
case 'IfBlock': return IfBlock;
case 'InlineComponent': return InlineComponent;
case 'KeyBlock': return KeyBlock;
case 'MustacheTag': return MustacheTag;
case 'Options': return Options;
case 'RawMustacheTag': return RawMustacheTag;
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compile/render_dom/wrappers/Fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EachBlock from './EachBlock';
import Element from './Element/index';
import Head from './Head';
import IfBlock from './IfBlock';
import KeyBlock from './KeyBlock';
import InlineComponent from './InlineComponent/index';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
Expand All @@ -30,6 +31,7 @@ const wrappers = {
Head,
IfBlock,
InlineComponent,
KeyBlock,
MustacheTag,
Options: null,
RawMustacheTag,
Expand Down
120 changes: 120 additions & 0 deletions src/compiler/compile/render_dom/wrappers/KeyBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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();

this.block = block.child({
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name("create_key_block"),
type: "key",
});

this.fragment = new FragmentWrapper(
renderer,
this.block,
node.children,
parent,
strip_whitespace,
next_sibling
);

renderer.blocks.push(this.block);
}

render(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;

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

if (this.dependencies.length) {
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 (${this.renderer.dirty(this.dependencies)}) {
${body}
} else {
${this.var}.p(#ctx, #dirty);
}
`);
} else {
block.chunks.update.push(b`
if (${this.renderer.dirty(this.dependencies)}) {
${body}
}
`);
}
} else if (dynamic) {
block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`);
}

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)`);
}
}
2 changes: 2 additions & 0 deletions src/compiler/compile/render_ssr/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Head from './handlers/Head';
import HtmlTag from './handlers/HtmlTag';
import IfBlock from './handlers/IfBlock';
import InlineComponent from './handlers/InlineComponent';
import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
Expand All @@ -30,6 +31,7 @@ const handlers: Record<string, Handler> = {
Head,
IfBlock,
InlineComponent,
KeyBlock,
MustacheTag: Tag, // TODO MustacheTag is an anachronism
Options: noop,
RawMustacheTag: HtmlTag,
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/compile/render_ssr/handlers/KeyBlock.ts
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 5 additions & 1 deletion src/compiler/parse/state/mustache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function mustache(parser: Parser) {

parser.allow_whitespace();

// {/if}, {/each} or {/await}
// {/if}, {/each}, {/await} or {/key}
if (parser.eat('/')) {
let block = parser.current();
let expected;
Expand All @@ -63,6 +63,8 @@ export default function mustache(parser: Parser) {
expected = 'each';
} else if (block.type === 'AwaitBlock') {
expected = 'await';
} else if (block.type === 'KeyBlock') {
expected = 'key';
} else {
parser.error({
code: `unexpected-block-close`,
Expand Down Expand Up @@ -221,6 +223,8 @@ export default function mustache(parser: Parser) {
type = 'EachBlock';
} else if (parser.eat('await')) {
type = 'AwaitBlock';
} else if (parser.eat('key')) {
type = 'KeyBlock';
} else {
parser.error({
code: `expected-block-type`,
Expand Down
21 changes: 21 additions & 0 deletions test/runtime/samples/key-block-multiple/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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'));
}
};
9 changes: 9 additions & 0 deletions test/runtime/samples/key-block-multiple/main.svelte
Original file line number Diff line number Diff line change
@@ -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}
9 changes: 9 additions & 0 deletions test/runtime/samples/key-block-static/_config.js
Original file line number Diff line number Diff line change
@@ -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'));
}
};
8 changes: 8 additions & 0 deletions test/runtime/samples/key-block-static/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
let value = 0;
export let anotherValue = 0;
</script>

{#key value}
<div>{value}{anotherValue}</div>
{/key}
24 changes: 24 additions & 0 deletions test/runtime/samples/key-block-transition/_config.js
Original file line number Diff line number Diff line change
@@ -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'));
},
};
17 changes: 17 additions & 0 deletions test/runtime/samples/key-block-transition/main.svelte
Original file line number Diff line number Diff line change
@@ -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}
13 changes: 13 additions & 0 deletions test/runtime/samples/key-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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'));
}
};
8 changes: 8 additions & 0 deletions test/runtime/samples/key-block/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
export let value = 0;
export let reactive = 0;
</script>

{#key value}
<div>{value}{reactive}</div>
{/key}

0 comments on commit fa94a5f

Please sign in to comment.