Skip to content
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

add slots option to component constructor options object #5687

Closed
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'svelte/internal',
'svelte/store',
'svelte/easing',
'svelte/slot',
'estree'
],
'svelte3/compiler': require('./compiler')
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ node_modules
/motion
/transition
/animate
/slot
/scratch/
/coverage/
/coverage.lcov
Expand Down
58 changes: 41 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0",
"@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-sucrase": "^3.1.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you rebase against master you should be able to drop your changes to this file and package-lock.json. I submitted those as a separate PR in #5758 because that was a big enough change by itself that it took some review. Hopefully that'll make review of this one a bit easier by making this one a bit smaller

"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.6.0",
Expand Down Expand Up @@ -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": [
Expand Down
1 change: 1 addition & 0 deletions site/content/docs/03-run-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export {
SvelteComponentDev as SvelteComponent,
SvelteComponentTyped
} from 'svelte/internal';

export { createSlot, slot } from 'svelte/slot';
58 changes: 40 additions & 18 deletions src/runtime/internal/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -209,8 +232,7 @@ if (typeof HTMLElement === 'function') {
this.$$.skip_bound = false;
}
}
};
}
}

/**
* Base class for Svelte components. Used when dev=false.
Expand All @@ -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);

Expand Down
15 changes: 4 additions & 11 deletions src/runtime/internal/dev.ts
Original file line number Diff line number Diff line change
@@ -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 }));
Expand Down Expand Up @@ -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;
Expand All @@ -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");
}

Expand Down
6 changes: 3 additions & 3 deletions src/runtime/internal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions src/runtime/slot/index.ts
Original file line number Diff line number Diff line change
@@ -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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried but did not know how to modify src/compiler/compile/render_dom/Block.ts to expose created HTML element

return Array.from(wrapper.children);
}
1 change: 1 addition & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Loading