$$slots accessor

pull/4604/head
Timothy Johnson 6 years ago
parent aa3dcc06d6
commit 4e525d9e11

@ -75,6 +75,8 @@ export default class Block {
variables: Map<string, { id: Identifier; init?: Node }> = new Map();
get_unique_name: (name: string) => Identifier;
root_nodes: Identifier[] = [];
has_update_method = false;
autofocus: string;
@ -269,9 +271,14 @@ export default class Block {
: this.chunks.hydrate
);
const return_value = this.type === 'slot'
? b`return [${this.root_nodes}]`
: null;
properties.create = x`function #create() {
${this.chunks.create}
${hydrate}
${return_value}
}`;
}

@ -59,7 +59,7 @@ export default class Renderer {
if (component.slots.size > 0) {
this.add_to_context('$$scope');
this.add_to_context('$$slots');
this.add_to_context('#slots');
}
if (this.binding_groups.length > 0) {

@ -70,6 +70,14 @@ export default function dom(
);
}
const uses_slots = component.var_lookup.has('$$slots');
let slots = null
if (uses_slots) {
slots = b`let { $$slots, update: #update_$$slots } = @create_slots_accessor(#slots, $$scope)`
renderer.add_to_context('$$scope');
}
const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? `$$new_props` : `$$props`;
@ -83,7 +91,7 @@ export default function dom(
let $$restProps = ${compute_rest};
` : null;
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0)
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0 || uses_slots)
? x`
${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)}
@ -92,7 +100,7 @@ export default function dom(
${writable_props.map(prop =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
)}
${component.slots.size > 0 &&
${(component.slots.size > 0 || uses_slots) &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`}
}
`
@ -420,12 +428,15 @@ export default function dom(
${resubscribable_reactive_store_unsubscribers}
${component.slots.size || uses_slots || component.compile_options.dev ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null}
${slots}
${instance_javascript}
${unknown_props_check}
${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null}
${component.compile_options.dev && b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
${renderer.binding_groups.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`}
@ -441,8 +452,12 @@ export default function dom(
${/* before reactive declarations */ props_inject}
${reactive_declarations.length > 0 && b`
${(reactive_declarations.length > 0 || uses_slots) && b`
$$self.$$.update = () => {
if (${renderer.dirty(['$$scope'], true)}) {
#update_$$slots($$scope, $$self.$$.dirty)
}
${reactive_declarations}
};
`}

@ -320,6 +320,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.destroy.push(b`@detach(${node});`);
}
} else {
block.root_nodes.push(node);
block.chunks.mount.push(b`@insert(#target, ${node}, anchor);`);
// TODO we eventually need to consider what happens to elements

@ -133,6 +133,8 @@ export default class InlineComponentWrapper extends Wrapper {
const name = this.var;
if (parent_node === null) block.root_nodes.push(name);
const component_opts = x`{}` as ObjectExpression;
const statements: Array<Node | Node[]> = [];

@ -128,7 +128,7 @@ export default class SlotWrapper extends Wrapper {
const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot;
block.chunks.init.push(b`
const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name};
const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn});
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
`);

@ -44,6 +44,8 @@ export default class TextWrapper extends Wrapper {
if (this.skip) return;
const use_space = this.use_space();
if (parent_node === null) block.root_nodes.push(this.var);
block.add_element(
this.var,
use_space ? x`@space()` : x`@text("${this.data}")`,

@ -1,4 +1,4 @@
export const reserved_keywords = new Set(["$$props", "$$restProps"]);
export const reserved_keywords = new Set(["$$props", "$$restProps", "$$slots"]);
export function is_reserved_keyword(name) {
return reserved_keywords.has(name);

@ -7,7 +7,7 @@ import { transition_in } from './transitions';
interface Fragment {
key: string|null;
first: null;
/* create */ c: () => void;
/* create */ c: () => void|any[];
/* claim */ l: (nodes: any) => void;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any) => void;

@ -7,6 +7,7 @@ export * from './keyed_each';
export * from './lifecycle';
export * from './loop';
export * from './scheduler';
export * from './slots';
export * from './spread';
export * from './ssr';
export * from './transitions';

@ -0,0 +1,69 @@
import { onDestroy } from './lifecycle';
import { assign } from './utils'
export function create_slot(definition, ctx, $$scope, fn) {
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);
return definition[0](slot_ctx);
}
}
export function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
}
export function get_slot_changes(definition, $$scope, dirty, fn) {
if (definition[2] && fn) {
const lets = definition[2](fn(dirty));
if ($$scope.dirty === undefined) {
return lets;
}
if (typeof lets === 'object') {
const merged = [];
const len = Math.max($$scope.dirty.length, lets.length);
for (let i = 0; i < len; i += 1) {
merged[i] = $$scope.dirty[i] | lets[i];
}
return merged;
}
return $$scope.dirty | lets;
}
return $$scope.dirty;
}
export function create_slots_accessor(slots, scope) {
const slot_list = [];
function update(scope, dirty) {
slot_list.forEach(({ slot, definition }) =>
slot.p(
get_slot_context(definition, [], scope, null),
get_slot_changes(definition, scope, dirty, null)
)
);
}
const $$slots = {};
for (const key in slots) {
$$slots[key] = function () {
let definition = slots[key];
let slot = create_slot(definition, [], scope, null);
if (slot.d) onDestroy(slot.d);
if (slot.p) slot_list.push({ definition, slot });
return {
content: slot.c(),
mount: slot.m
};
};
}
return { $$slots, update };
}

@ -66,43 +66,6 @@ export function component_subscribe(component, store, callback) {
component.$$.on_destroy.push(subscribe(store, callback));
}
export function create_slot(definition, ctx, $$scope, fn) {
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);
return definition[0](slot_ctx);
}
}
export function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
}
export function get_slot_changes(definition, $$scope, dirty, fn) {
if (definition[2] && fn) {
const lets = definition[2](fn(dirty));
if ($$scope.dirty === undefined) {
return lets;
}
if (typeof lets === 'object') {
const merged = [];
const len = Math.max($$scope.dirty.length, lets.length);
for (let i = 0; i < len; i += 1) {
merged[i] = $$scope.dirty[i] | lets[i];
}
return merged;
}
return $$scope.dirty | lets;
}
return $$scope.dirty;
}
export function exclude_internal_props(props) {
const result = {};
for (const k in props) if (k[0] !== '$') result[k] = props[k];
@ -138,4 +101,4 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj,
export function action_destroyer(action_result) {
return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;
}
}

@ -0,0 +1,15 @@
<script>
let target;
const slot = $$slots.default();
$: {
if (target) {
slot.content[0].setAttribute('attr', 'value');
slot.content[2].$set({ prop: 'bar' })
slot.mount(target);
}
}
</script>
<div bind:this={target} />

@ -0,0 +1,5 @@
<script>
export let prop
</script>
<p>{prop}</p>

@ -0,0 +1,18 @@
export default {
async test({ assert, target, window, }) {
assert.htmlEqual(target.innerHTML, `
<button>Click me</button>
<div><p attr="value">Value: a</p><p>bar</p></div>
`)
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<button>Click me</button>
<div><p attr="value">Value: b</p><p>bar</p></div>
`);
}
};

@ -0,0 +1,12 @@
<script>
import Child from './Child.svelte'
import Component from './Component.svelte'
let value = 'a';
</script>
<button on:click={e => value = 'b'}>Click me</button>
<Child>
<p>Value: {value}</p>
<Component prop="foo" />
</Child>
Loading…
Cancel
Save