add slots option to component constructor options object

pull/5687/head
TruongSinh Tran-Nguyen 5 years ago
parent 391455c58e
commit 7e65579817

@ -7,6 +7,7 @@ module.exports = {
'svelte/internal',
'svelte/store',
'svelte/easing',
'svelte/slot',
'estree'
],
'svelte3/compiler': require('./compiler')

1
.gitignore vendored

@ -14,6 +14,7 @@ node_modules
/motion
/transition
/animate
/slot
/scratch/
/coverage/
/coverage.lcov

6
package-lock.json generated

@ -4547,9 +4547,9 @@
"dev": true
},
"typescript": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
},
"uri-js": {

@ -131,7 +131,7 @@
"sourcemap-codec": "^1.4.8",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",
"typescript": "^3.5.3"
"typescript": "^3.9.7"
},
"nyc": {
"include": [

@ -906,6 +906,7 @@ The following initialisation options can be provided:
| `props` | `{}` | An object of properties to supply to the component
| `hydrate` | `false` | See below
| `intro` | `false` | If `true`, will play transitions on initial render, rather than waiting for subsequent state changes
| `slots` | `{}` | An object with keys - slot names, values - element or array of elements
Existing children of `target` are left where they are.

@ -13,3 +13,5 @@ export {
SvelteComponentDev as SvelteComponent,
SvelteComponentTyped
} from 'svelte/internal';
export { createSlot, slot } from 'svelte/slot';

@ -4,27 +4,30 @@ import { blank_object, is_empty, is_function, run, run_all, noop } from './utils
import { children, detach } from './dom';
import { transition_in } from './transitions';
interface Fragment {
key: string|null;
first: null;
export interface FragmentMinimal {
/* create */ c: () => void;
/* claim */ l: (nodes: any) => void;
/* mount */ m: (target: HTMLElement, anchor: HTMLElement) => void;
/* destroy */ d: (detaching: 0|1) => void;
}
interface Fragment extends FragmentMinimal {
key: string|null;
first: null;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any) => void;
/* update */ p: (ctx: any, dirty: any) => void;
/* measure */ r: () => void;
/* fix */ f: () => void;
/* animate */ a: () => void;
/* intro */ i: (local: any) => void;
/* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0|1) => void;
}
interface T$$ {
dirty: number[];
ctx: null|any;
ctx?: any;
bound: any;
update: () => void;
callbacks: any;
callbacks: Record<string, CallableFunction[]>;
after_update: any[];
props: Record<string, 0 | string>;
fragment: null|false|Fragment;
@ -36,7 +39,7 @@ interface T$$ {
skip_bound: boolean;
}
export function bind(component, name, callback) {
export function bind(component: SvelteComponent, name, callback) {
const index = component.$$.props[name];
if (index !== undefined) {
component.$$.bound[index] = callback;
@ -52,7 +55,7 @@ export function claim_component(block, parent_nodes) {
block && block.l(parent_nodes);
}
export function mount_component(component, target, anchor) {
export function mount_component(component: SvelteComponent, target, anchor) {
const { fragment, on_mount, on_destroy, after_update } = component.$$;
fragment && fragment.m(target, anchor);
@ -73,7 +76,7 @@ export function mount_component(component, target, anchor) {
after_update.forEach(add_render_callback);
}
export function destroy_component(component, detaching) {
export function destroy_component(component: SvelteComponent, detaching) {
const $$ = component.$$;
if ($$.fragment !== null) {
run_all($$.on_destroy);
@ -87,7 +90,7 @@ export function destroy_component(component, detaching) {
}
}
function make_dirty(component, i) {
function make_dirty(component: SvelteComponent, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
@ -96,11 +99,33 @@ function make_dirty(component, i) {
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {
export type Props = Record<string, any>;
export type SvelteSlotOptions = {
props?: Props;
}
export type SvelteComponentOptions = {
target: Element;
anchor?: Element;
hydrate?: boolean;
intro?: boolean;
slots?: unknown;
} & SvelteSlotOptions;
export type SvelteComponentOptionsPrivate = {
target?: Element;
$$inline?: boolean;
} & SvelteComponentOptions;
export function init(component: SvelteComponent, options: SvelteComponentOptions, instance, create_fragment, not_equal, props: Props, dirty = [-1]) {
const parent_component = current_component;
set_current_component(component);
const prop_values = options.props || {};
if (options.slots) {
prop_values.$$slots = options.slots;
}
const $$: T$$ = component.$$ = {
fragment: null,
@ -164,9 +189,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
set_current_component(parent_component);
}
export let SvelteElement;
if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
export class SvelteElement extends HTMLElement {
$$: T$$;
$$set?: ($$props: any) => void;
constructor() {
@ -209,7 +232,6 @@ if (typeof HTMLElement === 'function') {
this.$$.skip_bound = false;
}
}
};
}
/**
@ -224,7 +246,7 @@ export class SvelteComponent {
this.$destroy = noop;
}
$on(type, callback) {
$on(type: string, callback: CallableFunction) {
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);

@ -1,5 +1,5 @@
import { custom_event, append, insert, detach, listen, attr } from './dom';
import { SvelteComponent } from './Component';
import { SvelteComponent, Props, SvelteComponentOptions, SvelteComponentOptionsPrivate } from './Component';
export function dispatch_dev<T=any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
@ -97,7 +97,6 @@ export function validate_slots(name, slot, keys) {
}
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;
$on(event: string, callback: (event: any) => void): () => void;
@ -116,15 +115,9 @@ export class SvelteComponentDev extends SvelteComponent {
*/
$$prop_def: Props;
constructor(options: {
target: Element;
anchor?: Element;
props?: Props;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}) {
if (!options || (!options.target && !options.$$inline)) {
constructor(options: SvelteComponentOptions) {
const privateOptions: SvelteComponentOptionsPrivate = options;
if (!privateOptions || (!privateOptions.target && !privateOptions.$$inline)) {
throw new Error("'target' is a required option");
}

@ -79,10 +79,10 @@ export function create_slot(definition, ctx, $$scope, fn) {
}
}
export function get_slot_context(definition, ctx, $$scope, fn) {
export function get_slot_context(definition, ctx, $$scope: {ctx: unknown[]} | undefined, fn) {
return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
? assign($$scope?.ctx.slice(), definition[1](fn(ctx)))
: $$scope?.ctx;
}
export function get_slot_changes(definition, $$scope, dirty, fn) {

@ -0,0 +1,41 @@
import { noop, insert, detach, FragmentMinimal, SvelteSlotOptions, SvelteComponentOptionsPrivate } from 'svelte/internal';
import { SvelteComponent } from '..';
function create_root_slot_fn(elements: Node[]) {
return function (): FragmentMinimal {
return {
c: noop,
m: function mount(target, anchor) {
elements.forEach(element => {
insert(target, element, anchor);
});
},
d: function destroy(detaching) {
if (detaching) {
elements.forEach(detach);
}
},
l: noop
};
};
}
export function createSlot(input: Record<string, Node | Node[]>) {
const slots: Record<string, Array<ReturnType<typeof create_root_slot_fn>>> = {};
for (const key in input) {
const nodeOrNodeList = input[key];
const nodeList = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
slots[key] = [create_root_slot_fn(nodeList)];
}
return slots;
}
export function slot(componentClass: typeof SvelteComponent, options: SvelteSlotOptions): Element[] {
const wrapper = document.createElement('div');
new componentClass({...options, target: wrapper} as SvelteComponentOptionsPrivate) as any;
// @TODO this is a workaround until src/compiler/compile/render_dom/Block.ts is extended to expose created HTML element
return Array.from(wrapper.children);
}

@ -61,6 +61,7 @@ global.navigator = window.navigator;
global.getComputedStyle = window.getComputedStyle;
global.requestAnimationFrame = null; // placeholder, filled in using set_raf
global.window = window;
global.HTMLElement = window.HTMLElement;
// add missing ecmascript globals to window
for (const key of Object.getOwnPropertyNames(global)) {

@ -0,0 +1,100 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
append,
create_slot,
detach,
element,
init,
insert,
safe_not_equal,
space,
transition_in,
transition_out,
update_slot
} from "svelte/internal";
const get_slot1_slot_changes = dirty => ({});
const get_slot1_slot_context = ctx => ({});
function create_fragment(ctx) {
let div;
let t;
let current;
const default_slot_template = /*#slots*/ ctx[1].default;
const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[0], null);
const slot1_slot_template = /*#slots*/ ctx[1].slot1;
const slot1_slot = create_slot(slot1_slot_template, ctx, /*$$scope*/ ctx[0], get_slot1_slot_context);
return {
c() {
div = element("div");
if (default_slot) default_slot.c();
t = space();
if (slot1_slot) slot1_slot.c();
},
m(target, anchor) {
insert(target, div, anchor);
if (default_slot) {
default_slot.m(div, null);
}
append(div, t);
if (slot1_slot) {
slot1_slot.m(div, null);
}
current = true;
},
p(ctx, [dirty]) {
if (default_slot) {
if (default_slot.p && dirty & /*$$scope*/ 1) {
update_slot(default_slot, default_slot_template, ctx, /*$$scope*/ ctx[0], dirty, null, null);
}
}
if (slot1_slot) {
if (slot1_slot.p && dirty & /*$$scope*/ 1) {
update_slot(slot1_slot, slot1_slot_template, ctx, /*$$scope*/ ctx[0], dirty, get_slot1_slot_changes, get_slot1_slot_context);
}
}
},
i(local) {
if (current) return;
transition_in(default_slot, local);
transition_in(slot1_slot, local);
current = true;
},
o(local) {
transition_out(default_slot, local);
transition_out(slot1_slot, local);
current = false;
},
d(detaching) {
if (detaching) detach(div);
if (default_slot) default_slot.d(detaching);
if (slot1_slot) slot1_slot.d(detaching);
}
};
}
function instance($$self, $$props, $$invalidate) {
let { $$slots: slots = {}, $$scope } = $$props;
$$self.$$set = $$props => {
if ("$$scope" in $$props) $$invalidate(0, $$scope = $$props.$$scope);
};
return [$$scope, slots];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,4 @@
<div>
<slot />
<slot name="slot1" />
</div>

@ -24,8 +24,8 @@ let compile = null;
const sveltePath = process.cwd().split('\\').join('/');
let unhandled_rejection = false;
process.on('unhandledRejection', err => {
let unhandled_rejection: Error | null = null;
process.on('unhandledRejection', (err: Error) => {
unhandled_rejection = err;
});
@ -159,12 +159,13 @@ describe('runtime', () => {
warnings.push(warning);
};
const configOptions = typeof config.options === 'function' ? config.options(window) : config.options;
const options = Object.assign({}, {
target,
hydrate,
props: config.props,
intro: config.intro
}, config.options || {});
}, configOptions || {});
const component = new SvelteComponent(options);

@ -0,0 +1,52 @@
import { createSlot, slot } from 'svelte';
export default {
options(window) {
const default_el = window.document.createElement('div');
default_el.innerHTML = 'default slot custom content';
const my_slot_el1 = window.document.createElement('div');
my_slot_el1.innerHTML = 'first my slot element';
const my_slot_el2 = window.document.createElement('div');
my_slot_el2.innerHTML = 'second my slot element';
const my_slot_els = [my_slot_el1, my_slot_el2];
const another_slot_el = window.document.createTextNode('another slot');
const conditional_slot_el = window.document.createElement('div');
conditional_slot_el.innerHTML = 'conditional slot';
const Nested = require('./nested.svelte').default;
return {
slots: createSlot({
default: default_el,
'my-slot': my_slot_els,
'another-slot-with-content': another_slot_el,
'conditional-slot': conditional_slot_el,
'slot-with-content-from-nested': slot(Nested)
})
};
},
test({ assert, component, target }) {
const expectedHtmlWhenSlotIsVisible = `
default slot: <div>default slot custom content</div>
named slot: <div>first my slot element</div><div>second my slot element</div>
slot with default content: default content
another slot with content: another slot
slot with content from nested: <div>this div is in nested.svelte</div><span>this span is in nested.svelte</span>
conditional slot: <div>conditional slot</div>
conditional slot with content: default content`;
assert.htmlEqual(target.innerHTML, expectedHtmlWhenSlotIsVisible);
component.is_slot_visible = false;
assert.htmlEqual(target.innerHTML, `
default slot: <div>default slot custom content</div>
named slot: <div>first my slot element</div><div>second my slot element</div>
slot with default content: default content
another slot with content: another slot
slot with content from nested: <div>this div is in nested.svelte</div><span>this span is in nested.svelte</span>`);
component.is_slot_visible = true;
assert.htmlEqual(target.innerHTML, expectedHtmlWhenSlotIsVisible);
// @TODO once src/compiler/compile/render_dom/Block.ts is extended to expose created HTML elements
// and nested component can be referenced directly, test mutating nested child props
}
};

@ -0,0 +1,12 @@
default slot: <slot />
named slot: <slot name="my-slot" />
slot with default content: <slot name="slot-with-content">default content</slot>
another slot with content: <slot name="another-slot-with-content">default content</slot>
slot with content from nested: <slot name="slot-with-content-from-nested">default content</slot>
{#if is_slot_visible}
conditional slot: <slot name="conditional-slot" />
conditional slot with content: <slot name="conditional-slot-with-content">default content</slot>
{/if}
<script>
export let is_slot_visible = true;
</script>

@ -0,0 +1,2 @@
<div>this div is in nested.svelte</div>
<span>this span is in nested.svelte</span>
Loading…
Cancel
Save