mirror of https://github.com/sveltejs/svelte
parent
a1b0295fc3
commit
d99359a03a
@ -0,0 +1,86 @@
|
||||
import Expression from './shared/Expression';
|
||||
import map_children from './shared/map_children';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
import AbstractBlock from './shared/AbstractBlock';
|
||||
import { x } from 'code-red';
|
||||
import { Node, Identifier, RestElement } from 'estree';
|
||||
|
||||
interface Context {
|
||||
key: Identifier;
|
||||
name?: string;
|
||||
modifier: (node: Node) => Node;
|
||||
}
|
||||
|
||||
function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
|
||||
if (!node) return;
|
||||
|
||||
if (node.type === 'Identifier' || (node as any).type === 'RestIdentifier') { // TODO is this right? not RestElement?
|
||||
contexts.push({
|
||||
key: node as Identifier,
|
||||
modifier
|
||||
});
|
||||
} else if (node.type === 'ArrayPattern') {
|
||||
node.elements.forEach((element, i) => {
|
||||
if (element && (element as any).type === 'RestIdentifier') {
|
||||
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
|
||||
} else {
|
||||
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
|
||||
}
|
||||
});
|
||||
} else if (node.type === 'ObjectPattern') {
|
||||
const used_properties = [];
|
||||
|
||||
node.properties.forEach((property, i) => {
|
||||
if ((property as any).kind === 'rest') { // TODO is this right?
|
||||
const replacement: RestElement = {
|
||||
type: 'RestElement',
|
||||
argument: property.key as Identifier
|
||||
};
|
||||
|
||||
node.properties[i] = replacement as any;
|
||||
|
||||
unpack_destructuring(
|
||||
contexts,
|
||||
property.value,
|
||||
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
|
||||
);
|
||||
} else {
|
||||
used_properties.push(x`"${(property.key as Identifier).name}"`);
|
||||
|
||||
unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class WithBlock extends AbstractBlock {
|
||||
type: 'WithBlock';
|
||||
|
||||
expression: Expression;
|
||||
context_node: Node;
|
||||
|
||||
context: string;
|
||||
scope: TemplateScope;
|
||||
contexts: Context[];
|
||||
has_binding = false;
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
this.expression = new Expression(component, this, scope, info.expression);
|
||||
this.context = info.context.name || 'with'; // TODO this is used to facilitate binding; currently fails with destructuring
|
||||
this.context_node = info.context;
|
||||
this.scope = scope.child();
|
||||
|
||||
this.contexts = [];
|
||||
unpack_destructuring(this.contexts, info.context, node => node);
|
||||
|
||||
this.contexts.forEach(context => {
|
||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
||||
});
|
||||
|
||||
this.children = map_children(component, this, this.scope, info.children);
|
||||
|
||||
this.warn_if_empty_block();
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
import Renderer from '../Renderer';
|
||||
import Block from '../Block';
|
||||
import Wrapper from './shared/Wrapper';
|
||||
import create_debugging_comment from './shared/create_debugging_comment';
|
||||
import FragmentWrapper from './Fragment';
|
||||
import { b, x } from 'code-red';
|
||||
import WithBlock from '../../nodes/WithBlock';
|
||||
import { Node, Identifier } from 'estree';
|
||||
|
||||
export default class WithBlockWrapper extends Wrapper {
|
||||
block: Block;
|
||||
node: WithBlock;
|
||||
fragment: FragmentWrapper;
|
||||
vars: {
|
||||
create_with_block: Identifier;
|
||||
with_block_value: Identifier;
|
||||
with_block: Identifier;
|
||||
get_with_context: Identifier;
|
||||
}
|
||||
|
||||
dependencies: Set<string>;
|
||||
|
||||
var: Identifier = { type: 'Identifier', name: 'with' };
|
||||
|
||||
constructor(
|
||||
renderer: Renderer,
|
||||
block: Block,
|
||||
parent: Wrapper,
|
||||
node: WithBlock,
|
||||
strip_whitespace: boolean,
|
||||
next_sibling: Wrapper
|
||||
) {
|
||||
super(renderer, block, parent, node);
|
||||
this.cannot_use_innerhtml();
|
||||
this.not_static_content();
|
||||
|
||||
block.add_dependencies(node.expression.dependencies);
|
||||
|
||||
this.node.contexts.forEach(context => {
|
||||
renderer.add_to_context(context.key.name, true);
|
||||
});
|
||||
|
||||
this.block = block.child({
|
||||
comment: create_debugging_comment(this.node, this.renderer.component),
|
||||
name: renderer.component.get_unique_name('create_with_block'),
|
||||
type: 'with',
|
||||
// @ts-ignore todo: probably error
|
||||
key: node.key as string,
|
||||
|
||||
bindings: new Map(block.bindings)
|
||||
});
|
||||
|
||||
const with_block_value = renderer.component.get_unique_name(`${this.var.name}_value`);
|
||||
renderer.add_to_context(with_block_value.name, true);
|
||||
|
||||
this.vars = {
|
||||
create_with_block: this.block.name,
|
||||
with_block_value,
|
||||
with_block: block.get_unique_name(`${this.var.name}_block`),
|
||||
get_with_context: renderer.component.get_unique_name(`get_${this.var.name}_context`),
|
||||
};
|
||||
|
||||
const store =
|
||||
node.expression.node.type === 'Identifier' &&
|
||||
node.expression.node.name[0] === '$'
|
||||
? node.expression.node.name.slice(1)
|
||||
: null;
|
||||
|
||||
node.contexts.forEach(prop => {
|
||||
this.block.bindings.set(prop.key.name, {
|
||||
object: with_block_value,
|
||||
modifier: prop.modifier,
|
||||
snippet: prop.modifier(x`${with_block_value}` as Node),
|
||||
store
|
||||
});
|
||||
});
|
||||
|
||||
renderer.blocks.push(this.block);
|
||||
|
||||
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling);
|
||||
|
||||
block.add_dependencies(this.block.dependencies);
|
||||
}
|
||||
|
||||
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
|
||||
if (this.fragment.nodes.length === 0) return;
|
||||
|
||||
const { renderer } = this;
|
||||
const {
|
||||
create_with_block,
|
||||
with_block,
|
||||
with_block_value,
|
||||
get_with_context
|
||||
} = this.vars;
|
||||
|
||||
const needs_anchor = this.next
|
||||
? !this.next.is_dom_node() :
|
||||
!parent_node || !this.parent.is_dom_node();
|
||||
|
||||
const context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`value`)};`);
|
||||
if (this.node.has_binding) context_props.push(b`child_ctx[${renderer.context_lookup.get(with_block_value.name).index}] = value;`);
|
||||
|
||||
const snippet = this.node.expression.manipulate(block);
|
||||
block.chunks.init.push(b`let ${with_block_value} = ${snippet};`);
|
||||
|
||||
renderer.blocks.push(b`
|
||||
function ${get_with_context}(#ctx, value) {
|
||||
const child_ctx = #ctx.slice();
|
||||
${context_props}
|
||||
return child_ctx;
|
||||
}
|
||||
`);
|
||||
|
||||
const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : 'anchor' };
|
||||
const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' };
|
||||
const update_anchor_node = needs_anchor
|
||||
? block.get_unique_name(`${this.var.name}_anchor`)
|
||||
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
|
||||
const update_mount_node: Identifier = this.get_update_mount_node((update_anchor_node as Identifier));
|
||||
|
||||
const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
|
||||
this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
|
||||
all_dependencies.add(dependency);
|
||||
});
|
||||
this.dependencies = all_dependencies;
|
||||
|
||||
block.chunks.init.push(b`
|
||||
let ${with_block} = ${create_with_block}(${get_with_context}(#ctx, ${with_block_value}));
|
||||
`);
|
||||
|
||||
block.chunks.create.push(b`
|
||||
${with_block}.c();
|
||||
`);
|
||||
|
||||
if (parent_nodes && this.renderer.options.hydratable) {
|
||||
block.chunks.claim.push(b`
|
||||
${with_block}.l(${parent_nodes});
|
||||
`);
|
||||
}
|
||||
|
||||
block.chunks.mount.push(b`
|
||||
${with_block}.m(${initial_mount_node}, ${initial_anchor_node});
|
||||
`);
|
||||
|
||||
if (this.dependencies.size) {
|
||||
const update = this.block.has_update_method
|
||||
? b`
|
||||
if (${with_block}) {
|
||||
${with_block}.p(child_ctx, #dirty);
|
||||
} else {
|
||||
${with_block} = ${create_with_block}(child_ctx);
|
||||
${with_block}.c();
|
||||
${with_block}.m(${update_mount_node}, ${update_anchor_node});
|
||||
}`
|
||||
: b`
|
||||
if (!${with_block}) {
|
||||
${with_block} = ${create_with_block}(child_ctx);
|
||||
${with_block}.c();
|
||||
${with_block}.m(${update_mount_node}, ${update_anchor_node});
|
||||
}`;
|
||||
block.chunks.update.push(b`
|
||||
if (${block.renderer.dirty(Array.from(all_dependencies))}) {
|
||||
${with_block_value} = ${snippet};
|
||||
const child_ctx = ${get_with_context}(#ctx, ${with_block_value});
|
||||
${update}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
block.chunks.destroy.push(b`${with_block}.d(detaching);`);
|
||||
|
||||
if (needs_anchor) {
|
||||
block.add_element(
|
||||
update_anchor_node as Identifier,
|
||||
x`@empty()`,
|
||||
parent_nodes && x`@empty()`,
|
||||
parent_node
|
||||
);
|
||||
}
|
||||
|
||||
this.fragment.render(this.block, null, x`#nodes` as Identifier);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import Renderer, { RenderOptions } from '../Renderer';
|
||||
import WithBlock from '../../nodes/WithBlock';
|
||||
import { x } from 'code-red';
|
||||
|
||||
export default function(node: WithBlock, renderer: Renderer, options: RenderOptions) {
|
||||
const args = [node.context_node];
|
||||
|
||||
renderer.push();
|
||||
renderer.render(node.children, options);
|
||||
const result = renderer.pop();
|
||||
renderer.add_expression(x`((${args}) => ${result})(${node.expression.node})`);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
export default {
|
||||
props: {
|
||||
people: { name: { first: 'Doctor', last: 'Who' } },
|
||||
},
|
||||
|
||||
html: `
|
||||
<input>
|
||||
<input>
|
||||
<p>Doctor Who</p>
|
||||
`,
|
||||
|
||||
ssrHtml: `
|
||||
<input value=Doctor>
|
||||
<input value=Who>
|
||||
<p>Doctor Who</p>
|
||||
`,
|
||||
|
||||
async test({ assert, component, target, window }) {
|
||||
const inputs = target.querySelectorAll('input');
|
||||
|
||||
inputs[1].value = 'Oz';
|
||||
await inputs[1].dispatchEvent(new window.Event('input'));
|
||||
|
||||
const { people } = component;
|
||||
|
||||
assert.deepEqual(people, {
|
||||
name: { first: 'Doctor', last: 'Oz' }
|
||||
});
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<input>
|
||||
<input>
|
||||
<p>Doctor Oz</p>
|
||||
`);
|
||||
|
||||
people.name.first = 'Frank';
|
||||
component.people = people;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<input>
|
||||
<input>
|
||||
<p>Frank Oz</p>
|
||||
`);
|
||||
},
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
export let people;
|
||||
</script>
|
||||
|
||||
{#with people as { name: { first: f, last: l } } }
|
||||
<input bind:value={f}>
|
||||
<input bind:value={l}>
|
||||
<p>{f} {l}</p>
|
||||
{/with}
|
@ -0,0 +1,16 @@
|
||||
export default {
|
||||
html: `
|
||||
<button>1</button>
|
||||
`,
|
||||
|
||||
async test({ assert, target, window, }) {
|
||||
const btn = target.querySelector('button');
|
||||
const clickEvent = new window.MouseEvent('click');
|
||||
|
||||
await btn.dispatchEvent(clickEvent);
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<button>2</button>
|
||||
`);
|
||||
}
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
let a = '1'
|
||||
</script>
|
||||
|
||||
{#with a as b }
|
||||
<button on:click={e => a = '2'}>{b}</button>
|
||||
{/with}
|
Loading…
Reference in new issue