Skip to content

Commit 44a9731

Browse files
committed
add slots option to component constructor options object
2 parents 391455c + b4dd7f8 commit 44a9731

File tree

17 files changed

+272
-39
lines changed

17 files changed

+272
-39
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
'svelte/internal',
88
'svelte/store',
99
'svelte/easing',
10+
'svelte/slot',
1011
'estree'
1112
],
1213
'svelte3/compiler': require('./compiler')

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ node_modules
1414
/motion
1515
/transition
1616
/animate
17+
/slot
1718
/scratch/
1819
/coverage/
1920
/coverage.lcov

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
"sourcemap-codec": "^1.4.8",
132132
"tiny-glob": "^0.2.6",
133133
"tslib": "^1.10.0",
134-
"typescript": "^3.5.3"
134+
"typescript": "^3.9.7"
135135
},
136136
"nyc": {
137137
"include": [

site/content/docs/03-run-time.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@ The following initialisation options can be provided:
906906
| `props` | `{}` | An object of properties to supply to the component
907907
| `hydrate` | `false` | See below
908908
| `intro` | `false` | If `true`, will play transitions on initial render, rather than waiting for subsequent state changes
909+
| `slots` | `{}` | An object with keys - slot names, values - element or array of elements
909910

910911
Existing children of `target` are left where they are.
911912

src/runtime/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export {
1313
SvelteComponentDev as SvelteComponent,
1414
SvelteComponentTyped
1515
} from 'svelte/internal';
16+
17+
export { createSlot, slot } from 'svelte/slot';

src/runtime/internal/Component.ts

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,30 @@ import { blank_object, is_empty, is_function, run, run_all, noop } from './utils
44
import { children, detach } from './dom';
55
import { transition_in } from './transitions';
66

7-
interface Fragment {
8-
key: string|null;
9-
first: null;
7+
export interface FragmentMinimal {
108
/* create */ c: () => void;
119
/* claim */ l: (nodes: any) => void;
10+
/* mount */ m: (target: HTMLElement, anchor: HTMLElement) => void;
11+
/* destroy */ d: (detaching: 0|1) => void;
12+
13+
}
14+
interface Fragment extends FragmentMinimal {
15+
key: string|null;
16+
first: null;
1217
/* hydrate */ h: () => void;
13-
/* mount */ m: (target: HTMLElement, anchor: any) => void;
1418
/* update */ p: (ctx: any, dirty: any) => void;
1519
/* measure */ r: () => void;
1620
/* fix */ f: () => void;
1721
/* animate */ a: () => void;
1822
/* intro */ i: (local: any) => void;
1923
/* outro */ o: (local: any) => void;
20-
/* destroy */ d: (detaching: 0|1) => void;
2124
}
2225
interface T$$ {
2326
dirty: number[];
24-
ctx: null|any;
27+
ctx?: any;
2528
bound: any;
2629
update: () => void;
27-
callbacks: any;
30+
callbacks: Record<string, CallableFunction[]>;
2831
after_update: any[];
2932
props: Record<string, 0 | string>;
3033
fragment: null|false|Fragment;
@@ -36,7 +39,7 @@ interface T$$ {
3639
skip_bound: boolean;
3740
}
3841

39-
export function bind(component, name, callback) {
42+
export function bind(component: SvelteComponent, name, callback) {
4043
const index = component.$$.props[name];
4144
if (index !== undefined) {
4245
component.$$.bound[index] = callback;
@@ -52,7 +55,7 @@ export function claim_component(block, parent_nodes) {
5255
block && block.l(parent_nodes);
5356
}
5457

55-
export function mount_component(component, target, anchor) {
58+
export function mount_component(component: SvelteComponent, target, anchor) {
5659
const { fragment, on_mount, on_destroy, after_update } = component.$$;
5760

5861
fragment && fragment.m(target, anchor);
@@ -73,7 +76,7 @@ export function mount_component(component, target, anchor) {
7376
after_update.forEach(add_render_callback);
7477
}
7578

76-
export function destroy_component(component, detaching) {
79+
export function destroy_component(component: SvelteComponent, detaching) {
7780
const $$ = component.$$;
7881
if ($$.fragment !== null) {
7982
run_all($$.on_destroy);
@@ -87,7 +90,7 @@ export function destroy_component(component, detaching) {
8790
}
8891
}
8992

90-
function make_dirty(component, i) {
93+
function make_dirty(component: SvelteComponent, i) {
9194
if (component.$$.dirty[0] === -1) {
9295
dirty_components.push(component);
9396
schedule_update();
@@ -96,11 +99,33 @@ function make_dirty(component, i) {
9699
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
97100
}
98101

99-
export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {
102+
export type Props = Record<string, any>;
103+
104+
export type SvelteSlotOptions = {
105+
props?: Props;
106+
}
107+
108+
export type SvelteComponentOptions = {
109+
target: Element;
110+
anchor?: Element;
111+
hydrate?: boolean;
112+
intro?: boolean;
113+
slots?: unknown;
114+
} & SvelteSlotOptions;
115+
116+
export type SvelteComponentOptionsPrivate = {
117+
target?: Element;
118+
$$inline?: boolean;
119+
} & SvelteComponentOptions;
120+
121+
export function init(component: SvelteComponent, options: SvelteComponentOptions, instance, create_fragment, not_equal, props: Props, dirty = [-1]) {
100122
const parent_component = current_component;
101123
set_current_component(component);
102124

103125
const prop_values = options.props || {};
126+
if (options.slots) {
127+
prop_values.$$slots = options.slots;
128+
}
104129

105130
const $$: T$$ = component.$$ = {
106131
fragment: null,
@@ -164,9 +189,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
164189
set_current_component(parent_component);
165190
}
166191

167-
export let SvelteElement;
168-
if (typeof HTMLElement === 'function') {
169-
SvelteElement = class extends HTMLElement {
192+
export class SvelteElement extends HTMLElement {
170193
$$: T$$;
171194
$$set?: ($$props: any) => void;
172195
constructor() {
@@ -209,8 +232,7 @@ if (typeof HTMLElement === 'function') {
209232
this.$$.skip_bound = false;
210233
}
211234
}
212-
};
213-
}
235+
}
214236

215237
/**
216238
* Base class for Svelte components. Used when dev=false.
@@ -224,7 +246,7 @@ export class SvelteComponent {
224246
this.$destroy = noop;
225247
}
226248

227-
$on(type, callback) {
249+
$on(type: string, callback: CallableFunction) {
228250
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
229251
callbacks.push(callback);
230252

src/runtime/internal/dev.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { custom_event, append, insert, detach, listen, attr } from './dom';
2-
import { SvelteComponent } from './Component';
2+
import { SvelteComponent, Props, SvelteComponentOptions, SvelteComponentOptionsPrivate } from './Component';
33

44
export function dispatch_dev<T=any>(type: string, detail?: T) {
55
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
@@ -97,7 +97,6 @@ export function validate_slots(name, slot, keys) {
9797
}
9898
}
9999

100-
type Props = Record<string, any>;
101100
export interface SvelteComponentDev {
102101
$set(props?: Props): void;
103102
$on(event: string, callback: (event: any) => void): () => void;
@@ -116,15 +115,9 @@ export class SvelteComponentDev extends SvelteComponent {
116115
*/
117116
$$prop_def: Props;
118117

119-
constructor(options: {
120-
target: Element;
121-
anchor?: Element;
122-
props?: Props;
123-
hydrate?: boolean;
124-
intro?: boolean;
125-
$$inline?: boolean;
126-
}) {
127-
if (!options || (!options.target && !options.$$inline)) {
118+
constructor(options: SvelteComponentOptions) {
119+
const privateOptions: SvelteComponentOptionsPrivate = options;
120+
if (!privateOptions || (!privateOptions.target && !privateOptions.$$inline)) {
128121
throw new Error("'target' is a required option");
129122
}
130123

src/runtime/internal/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ export function create_slot(definition, ctx, $$scope, fn) {
7979
}
8080
}
8181

82-
export function get_slot_context(definition, ctx, $$scope, fn) {
82+
export function get_slot_context(definition, ctx, $$scope: {ctx: unknown[]} | undefined, fn) {
8383
return definition[1] && fn
84-
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
85-
: $$scope.ctx;
84+
? assign($$scope?.ctx.slice(), definition[1](fn(ctx)))
85+
: $$scope?.ctx;
8686
}
8787

8888
export function get_slot_changes(definition, $$scope, dirty, fn) {

src/runtime/slot/index.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { noop, insert, detach, FragmentMinimal, SvelteSlotOptions, SvelteComponentOptionsPrivate } from 'svelte/internal';
2+
import { SvelteComponent } from '..';
3+
4+
function create_root_slot_fn(elements: Node[]) {
5+
return function (): FragmentMinimal {
6+
return {
7+
c: noop,
8+
9+
m: function mount(target, anchor) {
10+
elements.forEach(element => {
11+
insert(target, element, anchor);
12+
});
13+
},
14+
15+
d: function destroy(detaching) {
16+
if (detaching) {
17+
elements.forEach(detach);
18+
}
19+
},
20+
21+
l: noop
22+
};
23+
};
24+
}
25+
26+
export function createSlot(input: Record<string, Node | Node[]>) {
27+
const slots: Record<string, Array<ReturnType<typeof create_root_slot_fn>>> = {};
28+
for (const key in input) {
29+
const nodeOrNodeList = input[key];
30+
const nodeList = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
31+
slots[key] = [create_root_slot_fn(nodeList)];
32+
}
33+
return slots;
34+
}
35+
36+
export function slot(componentClass: typeof SvelteComponent, options: SvelteSlotOptions): Element[] {
37+
const wrapper = document.createElement('div');
38+
new componentClass({...options, target: wrapper} as SvelteComponentOptionsPrivate) as any;
39+
// @TODO this is a workaround until src/compiler/compile/render_dom/Block.ts is extended to expose created HTML element
40+
return Array.from(wrapper.children);
41+
}

test/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ global.navigator = window.navigator;
6161
global.getComputedStyle = window.getComputedStyle;
6262
global.requestAnimationFrame = null; // placeholder, filled in using set_raf
6363
global.window = window;
64+
global.HTMLElement = window.HTMLElement;
6465

6566
// add missing ecmascript globals to window
6667
for (const key of Object.getOwnPropertyNames(global)) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/* generated by Svelte vX.Y.Z */
2+
import {
3+
SvelteComponent,
4+
append,
5+
create_slot,
6+
detach,
7+
element,
8+
init,
9+
insert,
10+
safe_not_equal,
11+
space,
12+
transition_in,
13+
transition_out,
14+
update_slot
15+
} from "svelte/internal";
16+
17+
const get_slot1_slot_changes = dirty => ({});
18+
const get_slot1_slot_context = ctx => ({});
19+
20+
function create_fragment(ctx) {
21+
let div;
22+
let t;
23+
let current;
24+
const default_slot_template = /*#slots*/ ctx[1].default;
25+
const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[0], null);
26+
const slot1_slot_template = /*#slots*/ ctx[1].slot1;
27+
const slot1_slot = create_slot(slot1_slot_template, ctx, /*$$scope*/ ctx[0], get_slot1_slot_context);
28+
29+
return {
30+
c() {
31+
div = element("div");
32+
if (default_slot) default_slot.c();
33+
t = space();
34+
if (slot1_slot) slot1_slot.c();
35+
},
36+
m(target, anchor) {
37+
insert(target, div, anchor);
38+
39+
if (default_slot) {
40+
default_slot.m(div, null);
41+
}
42+
43+
append(div, t);
44+
45+
if (slot1_slot) {
46+
slot1_slot.m(div, null);
47+
}
48+
49+
current = true;
50+
},
51+
p(ctx, [dirty]) {
52+
if (default_slot) {
53+
if (default_slot.p && dirty & /*$$scope*/ 1) {
54+
update_slot(default_slot, default_slot_template, ctx, /*$$scope*/ ctx[0], dirty, null, null);
55+
}
56+
}
57+
58+
if (slot1_slot) {
59+
if (slot1_slot.p && dirty & /*$$scope*/ 1) {
60+
update_slot(slot1_slot, slot1_slot_template, ctx, /*$$scope*/ ctx[0], dirty, get_slot1_slot_changes, get_slot1_slot_context);
61+
}
62+
}
63+
},
64+
i(local) {
65+
if (current) return;
66+
transition_in(default_slot, local);
67+
transition_in(slot1_slot, local);
68+
current = true;
69+
},
70+
o(local) {
71+
transition_out(default_slot, local);
72+
transition_out(slot1_slot, local);
73+
current = false;
74+
},
75+
d(detaching) {
76+
if (detaching) detach(div);
77+
if (default_slot) default_slot.d(detaching);
78+
if (slot1_slot) slot1_slot.d(detaching);
79+
}
80+
};
81+
}
82+
83+
function instance($$self, $$props, $$invalidate) {
84+
let { $$slots: slots = {}, $$scope } = $$props;
85+
86+
$$self.$$set = $$props => {
87+
if ("$$scope" in $$props) $$invalidate(0, $$scope = $$props.$$scope);
88+
};
89+
90+
return [$$scope, slots];
91+
}
92+
93+
class Component extends SvelteComponent {
94+
constructor(options) {
95+
super();
96+
init(this, options, instance, create_fragment, safe_not_equal, {});
97+
}
98+
}
99+
100+
export default Component;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div>
2+
<slot />
3+
<slot name="slot1" />
4+
</div>

0 commit comments

Comments
 (0)