Merge branch 'master' into resize-observer

pull/5963/head
Jacob Wright 6 years ago
commit a6a5bc793f

@ -1,5 +1,10 @@
# Svelte changelog
## 3.35.0
* Implement slotted components and `<svelte:fragment slot="...">` ([#1037](https://github.com/sveltejs/svelte/issues/1037), [#2079](https://github.com/sveltejs/svelte/issues/2079))
* Fix reactivity bug where `slot="..."` is specified after attributes that should be reactive ([#5626](https://github.com/sveltejs/svelte/issues/5626))
## 3.34.0
* Add a `cssHash` option for controlling the classname used for CSS scoping ([#570](https://github.com/sveltejs/svelte/issues/570))

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.34.0",
"version": "3.35.0",
"lockfileVersion": 1,
"requires": true,
"packages": {

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.34.0",
"version": "3.35.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",

@ -0,0 +1,104 @@
---
title: What's new in Svelte: March 2021
description: Call for Svelte Summit Speakers! Improved SSR, non-HTML5 compilation targets, and ESLint TypeScript support
author: Daniel Sandoval
authorURL: https://desandoval.net
---
Lots to cover this month with releases from across the Svelte ecosystem. Most importantly, Svelte Summit Spring 2021 has an [Open Call for Speakers](https://sessionize.com/svelte-summit-spring-2021). **The deadline is March 14** so if you have an idea for a talk, submit it now!
Let's dive into the news 🐬
## What's new in `sveltejs/svelte`
* SSR store handling has been reworked to subscribe and unsubscribe as in DOM mode. SSR stores should work much more consistently now (**3.31.2**, see [custom stores](https://svelte.dev/examples#custom-stores) and [Server-side component API ](https://svelte.dev/docs#Server-side_component_API))
* Multiple instances of the same action are now allowed on an element (**3.32.0**, [example](https://svelte.dev/repl/01a14375951749dab9579cb6860eccde?version=3.32.0))
* The new `foreign` namespace should make it easier for alternative compile targets (like Svelte Native and SvelteGUI) by disabling certain HTML5-specific behaviour and checks (**3.32.0**, [more info](https://github.com/sveltejs/svelte/pull/5652))
* Support for inline comment sourcemaps in code from preprocessors (**3.32.0**)
* Destructured defaults are now allowed to refer to other variables (**3.33.0**, [example](https://svelte.dev/repl/0ee7227e1b45465b9b47d7a5ae2d1252?version=3.33.0))
* Custom elements will now call `onMount` functions when connecting and clean up when disconnecting (**3.33.0**, checkout [this PR](https://github.com/sveltejs/svelte/pull/4522) for an interesting conversation on how folks are using Svelte with Web Components)
* A `cssHash` option has been added to the compiler options to control the classname used for CSS scoping (**3.34.0**, [docs](https://svelte.dev/docs#svelte_compile))
* Continued improvement to Typescript definitions
For a complete list of changes, including bug fixes and links to PRs, check out [the CHANGELOG](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
## New from `sveltejs/language-tools`
- For language server clients that don't support `didChangeWatchedFiles`, a fallback file watcher will be used instead
- New highlighting rules for store accessors and element directives (like `bind:` and `class:`)
- HTML tags can now be renamed together
- Mustache tags parsing is now more robust and will provide better intellisense in more situations
Haven't tried the language-tools yet? Check out [Svelte Extension for VSCode](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) or find a plugin for your favorite IDE!
## Other changes from `sveltejs/*`
- [eslint-plugin-svelte3](https://github.com/sveltejs/eslint-plugin-svelte3) now supports TypeScript as of 3.1.0
- [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte/) released a number of minor versions to address whitespace and comment trimming issues.
- [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess/) bug fixes this month include fixes to postcss transformations and support for both v2 and v3 of `postcss-load-config`
- [sapper](https://github.com/sveltejs/sapper/)'s 0.29.1 release fixes some bad imports in type definitions, updates typings to be compatible with express/polka, and restores hashing of all CSS file names.
---
## Community Showcase
**Apps & Sites**
- [Tracking the Coronavirus](https://www.nytimes.com/interactive/2021/us/new-york-city-new-york-covid-cases.html) from NYTimes is an example of SvelteKit in production
- [Budibase](https://github.com/Budibase/budibase) is an open-source low-code platform, helping developers and IT professionals build, automate, and ship internal tools 50x faster on their own infrastructure.
- [Track the Parcel](https://tracktheparcel.com/) is a one-stop tool for tracking parcel status with all major package shippers.
- [Memo](https://sendmemo.app/features/) is a replacement for email that uses Svelte for modern messaging
- [Userscripts Safari](https://github.com/quoid/userscripts) is an open-source userscript editor for Safari... a native Svelte app for Mac OS!
- [SVGX](https://svgx.app/) is "the desktop SVG asset manager designers and developers wished they had."
- [Armoria](https://azgaar.github.io/Armoria/) is a procedural heraldry generator and editor
- [FictionBoard](https://www.fictionboard.com) is a virtual table top (VTT) platform that just released fillable and responsive character sheets
- [Castles & Crusades Treasure Generator](https://treasure.playaheadgames.com/) is a treasure generator for the table top RPG: Castles and Crusades.
- [NESBit Studio](https://jensa.org/NESBitStudio-web/graphics/spritesheets) is a toolkit to help the development of homebrew NES games
- [ElectroBlocks](https://electroblocks.org/) is an online Arduino IDE with a built-in simulator (Chrome Only)
- [Goblin.life](https://store.steampowered.com/app/552180/GoblinLife/) is a 3D world builder whose UI is built with Svelte
- [farmbox](https://farmbox.ae/) is a UAE-based grocery delivery services
- [heroeswearmasks.fun](https://heroeswearmasks.fun/) is a client-side machine learning tool that determines whether or not you're wearing a mask.
- [weatherify](https://brdtheo-weatherify.netlify.app/) is a very pretty (and [open source](https://github.com/brdtheo/weatherify)) weather app
- [DSN Live](https://dsn-live.netlify.app/#/) lets you monitor connections between NASA/JPL and interplanetary spacecraft missions in real time.
**Demos, Libraries, Tools & Components**
- [spc](https://github.com/khang-nd/spc) is a special characters picker component for the web
- [svelte-injector](https://www.npmjs.com/package/svelte-injector) lets you inject Svelte components in React, Angular, Vue, jQuery, Vanilla JS.
- [Felte](https://felte.dev/) is a form library for Svelte with simple validation reporting.
- [svelte-use-form](https://github.com/noahsalvi/svelte-use-form#readme) is form library that "is easy to use and has 0 boilerplate."
- [Formula](https://formula.svelte.codes/) provides "Zero Configuration Reactive Forms for Svelte."
- [Houdini](https://github.com/AlecAivazis/houdini) is "the disappearing GraphQL client built for Sapper and Sveltekit."
- [svelte-split-pane](https://www.reddit.com/r/sveltejs/comments/leoe33/sveltesplitpane/) is a draggable split pane component
- [svelte-virtualized-auto-sizer](https://github.com/micha-lmxt/svelte-virtualized-auto-sizer) is a high-order component that automatically adjusts the width and height of a single child.
- [svelte-window](https://github.com/micha-lmxt/svelte-window) are components for efficiently rendering large, scrollable lists and tabular data.
- [Svelte Persistent store](https://github.com/MacFJA/svelte-persistent-store) is a Svelte store that keep its value through pages and reloads
- [Svelte Dark](https://marketplace.visualstudio.com/items?itemName=NickScialli.svelte-dark) is a VSCode theme inspired by the svelte.dev REPL
- [Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) has been updated to support Svelte libraries and help developers keep their bundle size under control.
- [Tree-sitter-svelte](https://github.com/Himujjal/tree-sitter-svelte) provides tree-sitter grammar for svelte
- [Svelte Ripple](https://svelte.dev/repl/b73224a0fd4248178e3eab41943d41a9?version=3.31.2) is a Material Design ripple effect that doesn't depend on `@material/ripple` (made by @karakara in the Svelte Discord)
- [Analog SVG Clock](https://svelte.dev/repl/270e83f43e7a48918d8f2d497760904f?version=3.32.1) is a great example of easing functions (made by @tonmcg in the Svelte Discord)
- [Console Log Styler](https://svelte.dev/repl/11f609d0d90746f08da6d3d90bba84fc?version=3.32.0) lets you generate a styled console log using pseudo HTML and CSS (made by @EmNudge in the Svelte Discord)
- [svelte-heroicons](https://github.com/martinse/svelte-heroicons) is a handy wrapper for the Heroicons icon library
- [supabase-ui-svelte](https://github.com/joshnuss/supabase-ui-svelte) are UI components for Supabase authentication
**Have your own Svelte Component to share?** Check out the [Components](https://sveltesociety.dev/components) page on the Svelte Society site. You can contribute by making [a PR to this file](https://github.com/svelte-society/sveltesociety.dev/blob/master/src/pages/components/components.json).
**Learning Resources & Starters**
- [The **unofficial** SvelteKit docs](https://sk-incognito.vercel.app/learn/what-is-sveltekit) were built using SvelteKit and are [open for contributions](https://github.com/GrygrFlzr/kit-docs)
- [📦 Svelte Store](https://www.youtube.com/playlist?list=PLoKaNN3BjQX3fG-XOSwsPHtnV8FUY6lgK) course by lihautan covers the basics of Svelte Stores and best practices.
- [Svelte Events](https://www.youtube.com/watch?v=cbxxbBofjAw&feature=youtu.be) by WebJeda explains how directives like `on:` can be used to listen to DOM events.
- [How to Set Up Protected Routes in Your Svelte Application](https://www.webtips.dev/how-to-set-up-protected-routes-in-your-svelte-application) describes how to authenticate your users to access your routes
- [Using Fauna's streaming feature to build a chat with Svelte](https://dev.to/fauna/using-fauna-s-streaming-feature-to-build-a-chat-with-svelte-1gkd) demonstrates how to setup and configure Fauna to build a real-time chat interface with Svelte
- [Using TakeShape with Sapper](https://www.takeshape.io/articles/using-takeshape-with-sapper/) demonstrates how to connect the TakeShape CMS with Sapper
- [YastPack](https://github.com/rodabt/yastpack) is Yet Another Snowpack-Svelte-TailwindCss-Routify Template Pack
- [S2T2](https://ralphbliu.medium.com/s2t2-snowpack-svelte-tailwindcss-typescript-8928caa5af6c) is a Snowpack + Svelte + TailwindCSS + Typescript template
- [tonyketcham/sapper-tailwind2-template](https://github.com/tonyketcham/sapper-tailwind2-template) is a Sapper Template w/ Tailwind 2.0, TypeScript, ESLint, and Prettier
## See you next month!
Got something to add? Join us on [Svelte Society](https://sveltesociety.dev/), [Reddit](https://www.reddit.com/r/sveltejs/) and [Discord](https://discord.com/invite/yy75DKs)!

@ -0,0 +1,9 @@
---
question: Where can I get support?
---
If your question is about certain syntax, the [API page](https://svelte.dev/docs) is a good place to start.
Stack Overflow is a popular forum to ask code-level questions or if youre stuck with a specific error. Read through the existing questions tagged with [Svelte](https://stackoverflow.com/questions/tagged/svelte+or+svelte-3) or [ask your own](https://stackoverflow.com/questions/ask?tags=svelte)!
There are online forums and chats which are a great place for discussion about best practices, application architecture or just to get to know fellow Svelte users. [Our Discord](https://svelte.dev/chat) or [the Reddit channel](https://www.reddit.com/r/sveltejs/) are examples of that. If you have an answerable code-level question, Stack Overflow is usually a better fit.

@ -0,0 +1,28 @@
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import Node from './shared/Node';
import Let from './Let';
import { INode } from './interfaces';
export default class DefaultSlotTemplate extends Node {
type: 'SlotTemplate';
scope: TemplateScope;
children: INode[];
lets: Let[] = [];
slot_template_name = 'default';
constructor(
component: Component,
parent: INode,
scope: TemplateScope,
info: any,
lets: Let[],
children: INode[]
) {
super(component, parent, scope, info);
this.type = 'SlotTemplate';
this.children = children;
this.scope = scope;
this.lets = lets;
}
}

@ -301,7 +301,7 @@ export default class Element extends Node {
component.slot_outlets.add(name);
}
if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) {
if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) {
component.error(attribute, {
code: 'invalid-slotted-content',
message: 'Element with a slot=\'...\' attribute must be a child of a component or a descendant of a custom element'
@ -906,6 +906,10 @@ export default class Element extends Node {
);
}
}
get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
}
}
function should_have_attribute(

@ -46,12 +46,6 @@ export default class InlineComponent extends Node {
});
case 'Attribute':
if (node.name === 'slot') {
component.error(node, {
code: 'invalid-prop',
message: "'slot' is reserved for future use in named slots"
});
}
// fallthrough
case 'Spread':
this.attributes.push(new Attribute(component, this, scope, node));
@ -112,6 +106,57 @@ export default class InlineComponent extends Node {
});
});
this.children = map_children(component, this, this.scope, info.children);
const children = [];
for (let i=info.children.length - 1; i >= 0; i--) {
const child = info.children[i];
if (child.type === 'SlotTemplate') {
children.push(child);
info.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
const slot_template = {
start: child.start,
end: child.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: [child]
};
// transfer attributes
for (let i=child.attributes.length - 1; i >= 0; i--) {
const attribute = child.attributes[i];
if (attribute.type === 'Let') {
slot_template.attributes.push(attribute);
child.attributes.splice(i, 1);
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
slot_template.attributes.push(attribute);
}
}
children.push(slot_template);
info.children.splice(i, 1);
}
}
if (info.children.some(node => not_whitespace_text(node))) {
children.push({
start: info.start,
end: info.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: info.children
});
}
this.children = map_children(component, this, this.scope, children);
}
get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
}
}
function not_whitespace_text(node) {
return !(node.type === 'Text' && /^\s+$/.test(node.data));
}

@ -0,0 +1,82 @@
import map_children from './shared/map_children';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import Node from './shared/Node';
import Let from './Let';
import Attribute from './Attribute';
import { INode } from './interfaces';
export default class SlotTemplate extends Node {
type: 'SlotTemplate';
scope: TemplateScope;
children: INode[];
lets: Let[] = [];
slot_attribute: Attribute;
slot_template_name: string = 'default';
constructor(
component: Component,
parent: INode,
scope: TemplateScope,
info: any
) {
super(component, parent, scope, info);
this.validate_slot_template_placement();
const has_let = info.attributes.some((node) => node.type === 'Let');
if (has_let) {
scope = scope.child();
}
info.attributes.forEach((node) => {
switch (node.type) {
case 'Let': {
const l = new Let(component, this, scope, node);
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach((name) => {
scope.add(name, dependencies, this);
});
break;
}
case 'Attribute': {
if (node.name === 'slot') {
this.slot_attribute = new Attribute(component, this, scope, node);
if (!this.slot_attribute.is_static) {
component.error(node, {
code: 'invalid-slot-attribute',
message: 'slot attribute cannot have a dynamic value'
});
}
const value = this.slot_attribute.get_static_value();
if (typeof value === 'boolean') {
component.error(node, {
code: 'invalid-slot-attribute',
message: 'slot attribute value is missing'
});
}
this.slot_template_name = value as string;
break;
}
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
}
default:
throw new Error(`Not implemented: ${node.type}`);
}
});
this.scope = scope;
this.children = map_children(component, this, this.scope, info.children);
}
validate_slot_template_placement() {
if (this.parent.type !== 'InlineComponent') {
this.component.error(this, {
code: 'invalid-slotted-content',
message: '<svelte:fragment> must be a child of a component'
});
}
}
}

@ -30,7 +30,7 @@ export default class Text extends Node {
should_skip() {
if (/\S/.test(this.data)) return false;
const parent_element = this.find_nearest(/(?:Element|InlineComponent|Head)/);
const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/);
if (!parent_element) return false;
if (parent_element.type === 'Head') return true;

@ -25,6 +25,8 @@ import Options from './Options';
import PendingBlock from './PendingBlock';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import SlotTemplate from './SlotTemplate';
import DefaultSlotTemplate from './DefaultSlotTemplate';
import Text from './Text';
import ThenBlock from './ThenBlock';
import Title from './Title';
@ -58,6 +60,8 @@ export type INode = Action
| PendingBlock
| RawMustacheTag
| Slot
| SlotTemplate
| DefaultSlotTemplate
| Tag
| Text
| ThenBlock

@ -3,8 +3,9 @@ import ThenBlock from '../ThenBlock';
import CatchBlock from '../CatchBlock';
import InlineComponent from '../InlineComponent';
import Element from '../Element';
import SlotTemplate from '../SlotTemplate';
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element;
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate;
export default class TemplateScope {
names: Set<string>;
@ -40,7 +41,7 @@ export default class TemplateScope {
is_let(name: string) {
const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate');
}
is_await(name: string) {

@ -12,6 +12,7 @@ import Options from '../Options';
import RawMustacheTag from '../RawMustacheTag';
import DebugTag from '../DebugTag';
import Slot from '../Slot';
import SlotTemplate from '../SlotTemplate';
import Text from '../Text';
import Title from '../Title';
import Window from '../Window';
@ -35,6 +36,7 @@ function get_constructor(type) {
case 'RawMustacheTag': return RawMustacheTag;
case 'DebugTag': return DebugTag;
case 'Slot': return Slot;
case 'SlotTemplate': return SlotTemplate;
case 'Text': return Text;
case 'Title': return Title;
case 'Window': return Window;

@ -1,61 +0,0 @@
import ElementWrapper from './index';
import SlotWrapper from '../Slot';
import Block from '../../Block';
import { sanitize } from '../../../../utils/names';
import InlineComponentWrapper from '../InlineComponent';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_slot_definition } from '../shared/get_slot_definition';
export default function create_slot_block(attribute, element: ElementWrapper | SlotWrapper, block: Block) {
const owner = find_slot_owner(element.parent);
if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.get_static_value() as string;
if (!((owner as unknown) as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({
comment: create_debugging_comment(element.node, element.renderer.component),
name: element.renderer.component.get_unique_name(
`create_${sanitize(name)}_slot`
),
type: 'slot'
});
const { scope, lets } = element.node;
const seen = new Set(lets.map(l => l.name.name));
((owner as unknown) as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
((owner as unknown) as InlineComponentWrapper).slots.set(
name,
get_slot_definition(child_block, scope, lets)
);
element.renderer.blocks.push(child_block);
}
element.slot_block = ((owner as unknown) as InlineComponentWrapper).slots.get(
name
).block;
return element.slot_block;
}
return block;
}
function find_slot_owner(owner) {
while (owner) {
if (owner.node.type === 'InlineComponent') {
break;
}
if (owner.node.type === 'Element' && /-/.test(owner.node.name)) {
break;
}
owner = owner.parent;
}
return owner;
}

@ -25,7 +25,6 @@ import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action';
import MustacheTagWrapper from '../MustacheTag';
import RawMustacheTagWrapper from '../RawMustacheTag';
import create_slot_block from './create_slot_block';
import is_dynamic from '../shared/is_dynamic';
interface BindingGroup {
@ -147,7 +146,6 @@ export default class ElementWrapper extends Wrapper {
event_handlers: EventHandler[];
class_dependencies: string[];
slot_block: Block;
select_binding_dependencies?: Set<string>;
var: any;
@ -180,9 +178,6 @@ export default class ElementWrapper extends Wrapper {
}
this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') {
block = create_slot_block(attribute, this, block);
}
if (attribute.name === 'style') {
return new StyleAttributeWrapper(this, block, attribute);
}
@ -237,15 +232,6 @@ export default class ElementWrapper extends Wrapper {
}
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
if (this.slot_block) {
block.parent.add_dependencies(block.dependencies);
// appalling hack
const index = block.parent.wrappers.indexOf(this);
block.parent.wrappers.splice(index, 1);
block.wrappers.push(this);
}
}
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
@ -253,10 +239,6 @@ export default class ElementWrapper extends Wrapper {
if (this.node.name === 'noscript') return;
if (this.slot_block) {
block = this.slot_block;
}
const node = this.var;
const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;

@ -11,6 +11,7 @@ import InlineComponent from './InlineComponent/index';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import SlotTemplate from './SlotTemplate';
import Text from './Text';
import Title from './Title';
import Window from './Window';
@ -36,6 +37,7 @@ const wrappers = {
Options: null,
RawMustacheTag,
Slot,
SlotTemplate,
Text,
Title,
Window

@ -4,12 +4,11 @@ import Renderer from '../../Renderer';
import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent';
import FragmentWrapper from '../Fragment';
import SlotTemplateWrapper from '../SlotTemplate';
import { sanitize } from '../../../../utils/names';
import add_to_set from '../../../utils/add_to_set';
import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_slot_definition } from '../shared/get_slot_definition';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this';
@ -18,12 +17,16 @@ import EventHandler from '../Element/EventHandler';
import { extract_names } from 'periscopic';
import mark_each_block_bindings from '../shared/mark_each_block_bindings';
import { string_to_member_expression } from '../../../utils/string_to_member_expression';
import SlotTemplate from '../../../nodes/SlotTemplate';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
export default class InlineComponentWrapper extends Wrapper {
var: Identifier;
slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
slots: Map<string, SlotDefinition> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;
children: Array<Wrapper | FragmentWrapper> = [];
constructor(
renderer: Renderer,
@ -76,32 +79,22 @@ export default class InlineComponentWrapper extends Wrapper {
});
});
const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name('create_default_slot'),
type: 'slot'
});
this.renderer.blocks.push(default_slot);
this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling);
const dependencies: Set<string> = new Set();
// TODO is this filtering necessary? (I *think* so)
default_slot.dependencies.forEach(name => {
if (!this.node.scope.is_let(name)) {
dependencies.add(name);
}
});
block.add_dependencies(dependencies);
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
}
block.add_outro();
}
set_slot(name: string, slot_definition: SlotDefinition) {
if (this.slots.has(name)) {
if (name === 'default') {
throw new Error('Found elements without slot attribute when using slot="default"');
}
throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`);
}
this.slots.set(name, slot_definition);
}
warn_if_reactive() {
const { name } = this.node;
const variable = this.renderer.component.var_lookup.get(name);
@ -135,14 +128,10 @@ export default class InlineComponentWrapper extends Wrapper {
const statements: Array<Node | Node[]> = [];
const updates: Array<Node | Node[]> = [];
if (this.fragment) {
this.children.forEach((child) => {
this.renderer.add_to_context('$$scope', true);
const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => {
child.render(default_slot.block, null, x`#nodes` as unknown as Identifier);
});
}
child.render(block, null, x`#nodes` as Identifier);
});
let props;
const name_changes = block.get_unique_name(`${name.name}_changes`);
@ -194,7 +183,7 @@ export default class InlineComponentWrapper extends Wrapper {
component_opts.properties.push(p`$$inline: true`);
}
const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []);
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []);
this.slots.forEach(slot => {
slot.block.dependencies.forEach(name => {
const is_let = slot.scope.is_let(name);

@ -11,7 +11,6 @@ import { is_reserved_keyword } from '../../utils/reserved_keywords';
import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree';
import create_debugging_comment from './shared/create_debugging_comment';
import create_slot_block from './Element/create_slot_block';
export default class SlotWrapper extends Wrapper {
node: Slot;
@ -43,10 +42,6 @@ export default class SlotWrapper extends Wrapper {
renderer.blocks.push(this.fallback);
}
if (this.node.values.has('slot')) {
block = create_slot_block(this.node.values.get('slot'), this, block);
}
this.fragment = new FragmentWrapper(
renderer,
this.fallback,

@ -0,0 +1,80 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import FragmentWrapper from './Fragment';
import create_debugging_comment from './shared/create_debugging_comment';
import { get_slot_definition } from './shared/get_slot_definition';
import { x } from 'code-red';
import { sanitize } from '../../../utils/names';
import { Identifier } from 'estree';
import InlineComponentWrapper from './InlineComponent';
import { extract_names } from 'periscopic';
import { INode } from '../../nodes/interfaces';
import Let from '../../nodes/Let';
import TemplateScope from '../../nodes/shared/TemplateScope';
type NodeWithLets = INode & {
scope: TemplateScope;
lets: Let[];
slot_template_name: string;
};
export default class SlotTemplateWrapper extends Wrapper {
node: NodeWithLets;
fragment: FragmentWrapper;
block: Block;
parent: InlineComponentWrapper;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: NodeWithLets,
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
const { scope, lets, slot_template_name } = this.node;
lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true);
});
});
this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component),
name: this.renderer.component.get_unique_name(
`create_${sanitize(slot_template_name)}_slot`
),
type: 'slot'
});
this.renderer.blocks.push(this.block);
const seen = new Set(lets.map(l => l.name.name));
this.parent.node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
this.parent.set_slot(
slot_template_name,
get_slot_definition(this.block, scope, lets)
);
this.fragment = new FragmentWrapper(
renderer,
this.block,
node.type === 'SlotTemplate' ? node.children : [node],
this,
strip_whitespace,
next_sibling
);
this.block.parent.add_dependencies(this.block.dependencies);
}
render() {
this.fragment.render(this.block, null, x`#nodes` as Identifier);
}
}

@ -15,7 +15,7 @@ export default function create_debugging_comment(
let d;
if (node.type === 'InlineComponent' || node.type === 'Element') {
if (node.type === 'InlineComponent' || node.type === 'Element' || node.type === 'SlotTemplate') {
if (node.children.length) {
d = node.children[0].start;
while (source[d - 1] !== '>') d -= 1;

@ -9,6 +9,7 @@ import IfBlock from './handlers/IfBlock';
import InlineComponent from './handlers/InlineComponent';
import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot';
import SlotTemplate from './handlers/SlotTemplate';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
import Title from './handlers/Title';
@ -36,6 +37,7 @@ const handlers: Record<string, Handler> = {
Options: noop,
RawMustacheTag: HtmlTag,
Slot,
SlotTemplate,
Text,
Title,
Window: noop

@ -1,6 +1,5 @@
import { is_void } from '../../../utils/names';
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { get_slot_scope } from './shared/get_slot_scope';
import { boolean_attributes } from './shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
@ -8,9 +7,7 @@ import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children';
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
export default function(node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next);
@ -23,13 +20,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/);
if (slot && nearest_inline_component) {
renderer.push();
}
renderer.add_string(`<${node.name}`);
const class_expression_list = node.classes.map(class_directive => {
@ -148,24 +138,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (!is_void(node.name)) {
renderer.add_string(`</${node.name}>`);
}
} else if (slot && nearest_inline_component) {
renderer.render(children, options);
if (!is_void(node.name)) {
renderer.add_string(`</${node.name}>`);
}
const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));
nearest_inline_component.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
} else {
renderer.render(children, options);

@ -1,8 +1,6 @@
import { string_literal } from '../../utils/stringify';
import Renderer, { RenderOptions } from '../Renderer';
import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import remove_whitespace_children from './utils/remove_whitespace_children';
import { p, x } from 'code-red';
function get_prop_value(attribute) {
@ -68,28 +66,19 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
const slot_fns = [];
const children = remove_whitespace_children(node.children, node.next);
const children = node.children;
if (children.length) {
const slot_scopes = new Map();
renderer.push();
renderer.render(children, Object.assign({}, options, {
slot_scopes
}));
slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
slot_scopes.forEach(({ input, output }, name) => {
if (!is_empty_template_literal(output)) {
slot_fns.push(
p`${name}: (${input}) => ${output}`
);
}
slot_fns.push(
p`${name}: (${input}) => ${output}`
);
});
}
@ -99,11 +88,3 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
}
function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === ''
);
}

@ -0,0 +1,45 @@
import Renderer, { RenderOptions } from '../Renderer';
import SlotTemplate from '../../nodes/SlotTemplate';
import remove_whitespace_children from './utils/remove_whitespace_children';
import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import Element from '../../nodes/Element';
export default function(node: SlotTemplate | Element | InlineComponent, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next);
renderer.push();
renderer.render(children, options);
const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));
parent_inline_component.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
const slot_fragment_content = renderer.pop();
if (!is_empty_template_literal(slot_fragment_content)) {
if (options.slot_scopes.has(node.slot_template_name)) {
if (node.slot_template_name === 'default') {
throw new Error('Found elements without slot attribute when using slot="default"');
}
throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`);
}
options.slot_scopes.set(node.slot_template_name, {
input: get_slot_scope(node.lets),
output: slot_fragment_content
});
}
}
function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === ''
);
}

@ -18,7 +18,7 @@ const meta_tags = new Map([
['svelte:body', 'Body']
]);
const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component');
const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment');
const specials = new Map([
[
@ -39,6 +39,7 @@ const specials = new Map([
const SELF = /^svelte:self(?=[\s/>])/;
const COMPONENT = /^svelte:component(?=[\s/>])/;
const SLOT = /^svelte:fragment(?=[\s/>])/;
function parent_is_head(stack) {
let i = stack.length;
@ -107,8 +108,9 @@ export default function tag(parser: Parser) {
const type = meta_tags.has(name)
? meta_tags.get(name)
: (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
: name === 'svelte:fragment' ? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
const element: TemplateNode = {
start,
@ -265,6 +267,8 @@ function read_tag_name(parser: Parser) {
if (parser.read(COMPONENT)) return 'svelte:component';
if (parser.read(SLOT)) return 'svelte:fragment';
const name = parser.read_until(/(\s|\/|>)/);
if (meta_tags.has(name)) return name;

@ -1,6 +1,6 @@
{
"code": "invalid-tag-name",
"message": "Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self or svelte:component",
"message": "Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self, svelte:component or svelte:fragment",
"pos": 10,
"start": {
"character": 10,

@ -0,0 +1,15 @@
export default {
html: `
<button>Disable</button>
<button slot="footer">Button</button>
<button slot="footer">Button</button>
`,
async test({ assert, component, target, window }) {
const [btn, btn1, btn2] = target.querySelectorAll('button');
await btn.dispatchEvent(new window.MouseEvent('click'));
assert.equal(btn1.disabled, true);
assert.equal(btn2.disabled, true);
}
};

@ -0,0 +1,15 @@
<script>
import Component from './Component.svelte';
let disabled = false;
</script>
<button on:click={() => disabled = !disabled}>Disable</button>
<Component>
<button slot="footer" disabled={disabled}>Button</button>
</Component>
<Component>
<button disabled={disabled} slot="footer">Button</button>
</Component>

@ -0,0 +1,5 @@
<script>
export let name;
</script>
<span>Hello {name}</span>

@ -0,0 +1,7 @@
export default {
preserveIdentifiers: true,
html: `
<span>Hello world</span>
`
};

@ -0,0 +1,10 @@
<script>
import Nested from './Nested.svelte';
import Hello from './Hello.svelte';
let name = 'world';
</script>
<Nested>
<Hello slot="name" {name} />
</Nested>

@ -0,0 +1,6 @@
export default {
html: `
<span>Hello</span>
<span>world</span>
`
};

@ -0,0 +1,13 @@
<script>
import Nested from './Nested.svelte';
import Hello from './Hello.svelte';
import World from './World.svelte';
</script>
<Nested>
<Hello slot="name" />
</Nested>
<Nested>
<World slot="name" />
</Nested>

@ -0,0 +1,5 @@
<div>
<slot/>
<slot name='bar'/>
<slot name='foo'/>
</div>

@ -0,0 +1,9 @@
export default {
html: `
<div>
Hello
<p>bar</p>
<p>foo</p>
</div>
`
};

@ -0,0 +1,12 @@
<script>
import Nested from './Nested.svelte';
import Foo from './Foo.svelte';
import Bar from './Bar.svelte';
</script>
<Nested>
Hello
<Foo slot='foo' />
<Bar slot='bar' />
</Nested>

@ -0,0 +1,3 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="foo">{value}</svelte:fragment>
<svelte:fragment slot="foo">{value}</svelte:fragment>
</Nested>

@ -0,0 +1,3 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="foo">{value}</svelte:fragment>
<p slot="foo">{value}</p>
</Nested>

@ -0,0 +1,3 @@
export default {
error: 'Found elements without slot attribute when using slot="default"'
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="default">value</svelte:fragment>
<p>value</p>
</Nested>

@ -0,0 +1,3 @@
export default {
error: 'Duplicate slot name "foo" in <Nested>'
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<p slot="foo">{value}</p>
<p slot="foo">{value}</p>
</Nested>

@ -3,7 +3,6 @@ export default {
dev: true
},
warnings: [
'<Nested> received an unexpected slot "default".',
'<Nested> received an unexpected slot "slot1".'
]
};

@ -0,0 +1,6 @@
export default {
html: `
<span>Hello</span>
<span>world</span>
`
};

@ -0,0 +1,15 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="name">
<span>Hello</span>
</svelte:fragment>
</Nested>
<Nested>
<svelte:fragment slot="name">
<span>world</span>
</svelte:fragment>
</Nested>

@ -0,0 +1,9 @@
<script>
export let things;
</script>
<div>
{#each things as thing}
<slot name="main" {thing}/>
{/each}
</div>

@ -0,0 +1,24 @@
export default {
props: {
things: [1, 2, 3]
},
html: `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>`,
test({ assert, component, target }) {
component.things = [1, 2, 3, 4];
assert.htmlEqual(target.innerHTML, `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
</div>
`);
}
};

@ -0,0 +1,11 @@
<script>
import Nested from './Nested.svelte';
export let things;
</script>
<Nested {things} let:thing={x}>
<svelte:fragment slot="main">
<span>{x}</span>
</svelte:fragment>
</Nested>

@ -0,0 +1,6 @@
<script>
let count = 0;
</script>
<button on:click="{() => count += 1}">+1</button>
<slot name="main" count={count}/>

@ -0,0 +1,18 @@
export default {
html: `
<button>+1</button>
<span>0</span>
`,
async test({ assert, target, window }) {
const button = target.querySelector('button');
const click = new window.MouseEvent('click');
await button.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<button>+1</button>
<span>1</span>
`);
}
};

@ -0,0 +1,9 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested let:count>
<svelte:fragment slot="main">
<span>{count}</span>
</svelte:fragment>
</Nested>

@ -0,0 +1,6 @@
<script>
let count = 0;
</script>
<button on:click="{() => count += 1}">+1</button>
<slot name='main' c={count}/>

@ -0,0 +1,18 @@
export default {
html: `
<button>+1</button>
<span>0 (undefined)</span>
`,
async test({ assert, target, window }) {
const button = target.querySelector('button');
const click = new window.MouseEvent('click');
await button.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<button>+1</button>
<span>1 (undefined)</span>
`);
}
};

@ -0,0 +1,9 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="main" let:c let:count>
<span>{c} ({count})</span>
</svelte:fragment>
</Nested>

@ -0,0 +1,7 @@
<script>
let foo = 'a';
</script>
<div on:click="{() => foo = 'b'}">
<slot name="main" {foo}></slot>
</div>

@ -0,0 +1,20 @@
export default {
html: `
<div>
<p>a</p>
</div>
`,
async test({ assert, target, window }) {
const div = target.querySelector('div');
const click = new window.MouseEvent('click');
await div.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<div>
<p>b</p>
</div>
`);
}
};

@ -0,0 +1,9 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<svelte:fragment slot="main" let:foo={bar}>
<p>{bar}</p>
</svelte:fragment>
</Nested>

@ -0,0 +1,5 @@
<script>
export let props;
</script>
<slot name="main" value={props} data={Array.isArray(props) ? props[0] : props.a} />

@ -0,0 +1,68 @@
export default {
html: `
<div>
hello world 0 hello
<button>Increment</button>
</div>
<div>
hello world 0 hello
<button>Increment</button>
</div>
<div>
hello world 0 hello
<button>Increment</button>
</div>
`,
async test({ assert, component, target, window }) {
const [button1, button2, button3] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
await button1.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<div>
hello world 1 hello
<button>Increment</button>
</div>
<div>
hello world 0 hello
<button>Increment</button>
</div>
<div>
hello world 0 hello
<button>Increment</button>
</div>
`);
await button2.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<div>
hello world 1 hello
<button>Increment</button>
</div>
<div>
hello world 1 hello
<button>Increment</button>
</div>
<div>
hello world 0 hello
<button>Increment</button>
</div>
`);
await button3.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<div>
hello world 1 hello
<button>Increment</button>
</div>
<div>
hello world 1 hello
<button>Increment</button>
</div>
<div>
hello world 1 hello
<button>Increment</button>
</div>
`);
}
};

@ -0,0 +1,34 @@
<script>
import Nested from "./Nested.svelte";
let c = 0, d = 0, e = 0;
</script>
<div>
<Nested props={['hello', 'world']}>
<svelte:fragment slot="main" let:value={pair} let:data={foo}>
{pair[0]} {pair[1]} {c} {foo}
</svelte:fragment>
</Nested>
<button on:click={() => { c += 1; }}>Increment</button>
</div>
<div>
<Nested props={['hello', 'world']}>
<svelte:fragment slot="main" let:value={[a, b]} let:data={foo}>
{a} {b} {d} {foo}
</svelte:fragment>
</Nested>
<button on:click={() => { d += 1; }}>Increment</button>
</div>
<div>
<Nested props={{ a: 'hello', b: 'world' }}>
<svelte:fragment slot="main" let:value={{ a, b }} let:data={foo}>
{a} {b} {e} {foo}
</svelte:fragment>
</Nested>
<button on:click={() => { e += 1; }}>Increment</button>
</div>

@ -0,0 +1,9 @@
<script>
export let things;
</script>
<div>
{#each things as thing}
<slot name="item" {thing}/>
{/each}
</div>

@ -0,0 +1,34 @@
export default {
props: {
things: [
{ num: 1 },
{ num: 2 },
{ num: 3 }
]
},
html: `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>`,
test({ assert, component, target }) {
component.things = [
{ num: 1 },
{ num: 2 },
{ num: 3 },
{ num: 4 }
];
assert.htmlEqual(target.innerHTML, `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
</div>
`);
}
};

@ -0,0 +1,11 @@
<script>
import Nested from './Nested.svelte';
export let things;
</script>
<Nested {things}>
<svelte:fragment slot="item" let:thing="{{ num }}">
<span>{num}</span>
</svelte:fragment>
</Nested>

@ -0,0 +1,11 @@
<script>
import B from './B.svelte';
export let x;
</script>
<B {x} let:reflected>
<svelte:fragment slot="main">
<span>{reflected}</span>
<slot name="main" {reflected} />
</svelte:fragment>
</B>

@ -0,0 +1,5 @@
<script>
export let x;
</script>
<slot name="main" reflected={x}/>

@ -0,0 +1,19 @@
export default {
html: `
<span>1</span>
<span>1</span>
<span>1</span>
<span>1</span>
`,
async test({ assert, target, component }) {
component.x = 2;
assert.htmlEqual(target.innerHTML, `
<span>2</span>
<span>2</span>
<span>2</span>
<span>2</span>
`);
}
};

@ -0,0 +1,16 @@
<script>
import A from './A.svelte';
export let x = 1;
</script>
<A {x}>
<svelte:fragment slot="main" let:reflected>
<span>{reflected}</span>
</svelte:fragment>
</A>
<A {x} let:reflected>
<svelte:fragment slot="main">
<span>{reflected}</span>
</svelte:fragment>
</A>

@ -0,0 +1,5 @@
<script>
export let x;
</script>
<slot name="foo" reflected={x}/>

@ -0,0 +1,22 @@
export default {
html: `
<span class="1">1</span>
0
`,
async test({ assert, target, component, window }) {
component.x = 2;
assert.htmlEqual(target.innerHTML, `
<span class="2">2</span>
0
`);
const span = target.querySelector('span');
await span.dispatchEvent(new window.MouseEvent('click'));
assert.htmlEqual(target.innerHTML, `
<span class="2">2</span>
2
`);
}
};

@ -0,0 +1,17 @@
<script>
import A from './A.svelte';
export let x = 1;
let y = 0;
</script>
<A {x}>
<svelte:fragment slot="foo" let:reflected>
<span
on:click={() => y = reflected}
class={reflected}
>
{reflected}
</span>
</svelte:fragment>
</A>
{ y }

@ -0,0 +1,9 @@
<script>
export let items;
</script>
<div>
{#each items as item, index}
<slot name="main" {index}/>
{/each}
</div>

@ -0,0 +1,26 @@
export default {
html: `
<div>
<label>1: <input></label>
<label>2: <input></label>
<label>3: <input></label>
</div>
`,
ssrHtml: `
<div>
<label>1: <input value="a"></label>
<label>2: <input value="b"></label>
<label>3: <input value="c"></label>
</div>
`,
async test({ assert, component, target, window }) {
const inputs = target.querySelectorAll('input');
inputs[2].value = 'd';
await inputs[2].dispatchEvent(new window.Event('input'));
assert.deepEqual(component.letters, ['a', 'b', 'd']);
}
};

@ -0,0 +1,13 @@
<script>
import Nested from './Nested.svelte';
export let letters = ['a', 'b', 'c'];
</script>
<Nested items={letters}>
<svelte:fragment slot="main" let:index>
<label>
{index + 1}: <input bind:value={letters[index]}>
</label>
</svelte:fragment>
</Nested>

@ -0,0 +1,5 @@
<script>
export let prop
</script>
<slot name="main" value={prop} />

@ -0,0 +1,12 @@
export default {
props: {
prop: 'a'
},
html: 'a',
test({ assert, component, target }) {
component.prop = 'b';
assert.htmlEqual( target.innerHTML, 'b' );
}
};

@ -0,0 +1,16 @@
<script>
import Outer from './Outer.svelte'
import Inner from './Inner.svelte'
export let prop
</script>
<Outer {prop}>
<svelte:fragment slot="main" let:value>
<Inner>
<svelte:fragment slot="main">
{value}
</svelte:fragment>
</Inner>
</svelte:fragment>
</Outer>

@ -0,0 +1,9 @@
<script>
export let things;
</script>
<div>
{#each things as thing}
<slot name="foo" {thing}/>
{/each}
</div>

@ -0,0 +1,24 @@
export default {
props: {
things: [1, 2, 3]
},
html: `
<div>
<div slot="foo"><span>1</span></div>
<div slot="foo"><span>2</span></div>
<div slot="foo"><span>3</span></div>
</div>`,
test({ assert, component, target }) {
component.things = [1, 2, 3, 4];
assert.htmlEqual(target.innerHTML, `
<div>
<div slot="foo"><span>1</span></div>
<div slot="foo"><span>2</span></div>
<div slot="foo"><span>3</span></div>
<div slot="foo"><span>4</span></div>
</div>
`);
}
};

@ -0,0 +1,11 @@
<script>
import Nested from './Nested.svelte';
export let things;
</script>
<Nested {things}>
<div slot="foo" let:thing>
<span>{thing}</span>
</div>
</Nested>

@ -0,0 +1,3 @@
export default {
html: '<p>Hi</p>'
};

@ -0,0 +1,7 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested let:value>
<p>{value}</p>
</Nested>

@ -0,0 +1,9 @@
<script>
export let things;
</script>
<div>
{#each things as thing}
<slot {thing}/>
{/each}
</div>

@ -0,0 +1,24 @@
export default {
props: {
things: [1, 2, 3]
},
html: `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>`,
test({ assert, component, target }) {
component.things = [1, 2, 3, 4];
assert.htmlEqual(target.innerHTML, `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
</div>
`);
}
};

@ -0,0 +1,9 @@
<script>
import Nested from './Nested.svelte';
export let things;
</script>
<Nested {things} let:thing>
<span>{thing}</span>
</Nested>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save