Skip to content

Programmic slot access/api #4604

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/compiler/compile/render_dom/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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}
}`;
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
29 changes: 24 additions & 5 deletions src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ export default function dom(
);
}

const uses_slots = component.var_lookup.has('$$slots');
let slots = null;
let slots_update = null;

if (uses_slots) {
slots = b`let { $$slots, update: #update_$$slots } = @create_slots_accessor(#slots, $$scope)`;
slots_update = b`
if (${renderer.dirty(['$$scope'], true)}) {
#update_$$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`;
Expand All @@ -83,7 +97,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))`)}
Expand All @@ -92,7 +106,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`)};`}
}
`
Expand Down Expand Up @@ -420,12 +434,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`[]`)}];`}

Expand All @@ -441,8 +458,10 @@ export default function dom(

${/* before reactive declarations */ props_inject}

${reactive_declarations.length > 0 && b`
${(reactive_declarations.length > 0 || uses_slots) && b`
$$self.$$.update = () => {
${slots_update}

${reactive_declarations}
};
`}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[]> = [];
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/wrappers/Slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
`);
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compile/render_dom/wrappers/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}")`,
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/utils/reserved_keywords.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/internal/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/runtime/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
83 changes: 83 additions & 0 deletions src/runtime/internal/slots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { onDestroy } from './lifecycle';
import { assign, noop } 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 update_list = [];
function update(scope) {
update_list.forEach(fn => fn(scope));
}

const $$slots = {};
for (const key in slots) {
$$slots[key] = function (ctx, callback = noop) {
const definition = slots[key];
const slot = create_slot(definition, null, scope, () => ctx);
const content = slot.c();

function local_update (scope, ctx_fn, dirty_fn) {
slot.p(
get_slot_context(definition, null, scope, ctx_fn),
get_slot_changes(definition, scope, 0, dirty_fn)
);
callback(content);
}

if (slot.d) onDestroy(slot.d);
if (slot.p) update_list.push(local_update);

return {
content,
mount: slot.m,
update: props => local_update(
scope,
() => assign(ctx, props),
() => Object.keys(props).reduce((o, k) => (o[k] = true, o), {})
),
destroy: () => {
slot.d();
const i = update_list.indexOf(local_update);
if (i !== -1) update_list.splice(i, 1);
}
};
};
}

return { $$slots, update };
}
39 changes: 1 addition & 38 deletions src/runtime/internal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}
}
4 changes: 2 additions & 2 deletions test/js/samples/capture-inject-state/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function instance($$self, $$props, $$invalidate) {
$$subscribe_prop = () => ($$unsubscribe_prop(), $$unsubscribe_prop = subscribe(prop, $$value => $$invalidate(2, $prop = $$value)), prop);

$$self.$$.on_destroy.push(() => $$unsubscribe_prop());
let { $$slots: slots = {}, $$scope } = $$props;
let { prop } = $$props;
validate_store(prop, "prop");
$$subscribe_prop();
Expand All @@ -115,8 +116,7 @@ function instance($$self, $$props, $$invalidate) {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
});

let { $$slots = {}, $$scope } = $$props;
validate_slots("Component", $$slots, []);
validate_slots("Component", slots, []);

$$self.$set = $$props => {
if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop));
Expand Down
4 changes: 2 additions & 2 deletions test/js/samples/debug-empty/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ function create_fragment(ctx) {
}

function instance($$self, $$props, $$invalidate) {
let { $$slots: slots = {}, $$scope } = $$props;
let { name } = $$props;
const writable_props = ["name"];

Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
});

let { $$slots = {}, $$scope } = $$props;
validate_slots("Component", $$slots, []);
validate_slots("Component", slots, []);

$$self.$set = $$props => {
if ("name" in $$props) $$invalidate(0, name = $$props.name);
Expand Down
4 changes: 2 additions & 2 deletions test/js/samples/debug-foo-bar-baz-things/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ function create_fragment(ctx) {
}

function instance($$self, $$props, $$invalidate) {
let { $$slots: slots = {}, $$scope } = $$props;
let { things } = $$props;
let { foo } = $$props;
let { bar } = $$props;
Expand All @@ -180,8 +181,7 @@ function instance($$self, $$props, $$invalidate) {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
});

let { $$slots = {}, $$scope } = $$props;
validate_slots("Component", $$slots, []);
validate_slots("Component", slots, []);

$$self.$set = $$props => {
if ("things" in $$props) $$invalidate(0, things = $$props.things);
Expand Down
4 changes: 2 additions & 2 deletions test/js/samples/debug-foo/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ function create_fragment(ctx) {
}

function instance($$self, $$props, $$invalidate) {
let { $$slots: slots = {}, $$scope } = $$props;
let { things } = $$props;
let { foo } = $$props;
const writable_props = ["things", "foo"];
Expand All @@ -172,8 +173,7 @@ function instance($$self, $$props, $$invalidate) {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
});

let { $$slots = {}, $$scope } = $$props;
validate_slots("Component", $$slots, []);
validate_slots("Component", slots, []);

$$self.$set = $$props => {
if ("things" in $$props) $$invalidate(0, things = $$props.things);
Expand Down
4 changes: 2 additions & 2 deletions test/js/samples/debug-hoisted/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function create_fragment(ctx) {
}

function instance($$self, $$props, $$invalidate) {
let { $$slots: slots = {}, $$scope } = $$props;
let obj = { x: 5 };
let kobzol = 5;
const writable_props = [];
Expand All @@ -57,8 +58,7 @@ function instance($$self, $$props, $$invalidate) {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
});

let { $$slots = {}, $$scope } = $$props;
validate_slots("Component", $$slots, []);
validate_slots("Component", slots, []);
$$self.$capture_state = () => ({ obj, kobzol });

$$self.$inject_state = $$props => {
Expand Down
Loading