Merge branch 'sveltejs:master' into bugfix/destroy-empty-component

pull/7492/head
qinmu 4 years ago committed by GitHub
commit 64064231c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,16 +1,20 @@
# Svelte changelog
## Unreleased
## 3.48.0
* Allow creating cancelable custom events with `createEventDispatcher` ([#4623](https://github.com/sveltejs/svelte/issues/4623))
* Support `{@const}` tag in `{#if}` blocks [#7241](https://github.com/sveltejs/svelte/issues/7241)
* Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427)
* Fix `{@const}` tag not working inside Component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189)
* Ignore comments in `{#each}` blocks when containing elements with `animate:` ([#3999](https://github.com/sveltejs/svelte/issues/3999))
* Add a third parameter to the returned function of `createEventDispatcher` that allows passing an object of `{ cancelable: true }` to create a cancelable custom event. The returned function when called will also return a boolean depending on whether the event is cancelled ([#7064](https://github.com/sveltejs/svelte/pull/7064))
* Allow comments inside `{#each}` blocks when using `animate:` ([#3999](https://github.com/sveltejs/svelte/issues/3999))
* Fix `|local` transitions in `{#key}` blocks ([#5950](https://github.com/sveltejs/svelte/issues/5950))
* Support svg namespace for `{@html}` ([#7002](https://github.com/sveltejs/svelte/issues/7002), [#7450](https://github.com/sveltejs/svelte/issues/7450))
* Fix `{@const}` tag not working inside a component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189)
* Remove extraneous leading newline inside `<pre>` and `<textarea>` ([#7264](https://github.com/sveltejs/svelte/issues/7264))
* Fix erroneous setting of `textContent` for `<template>` elements ([#7297](https://github.com/sveltejs/svelte/pull/7297))
* Fix value of `let:` bindings not updating in certain cases ([#7440](https://github.com/sveltejs/svelte/issues/7440))
* Strip leading newline after `<pre>` and `<textarea>` ([#7264](https://github.com/sveltejs/svelte/issues/7264))
* Fix local transition for `{#key}` block ([#5950](https://github.com/sveltejs/svelte/issues/5950))
* Fix errorneous setting `textContent` for `<template>` element ([#7297](https://github.com/sveltejs/svelte/pull/7297))
* Support svg namespace for `{@html}` ([#7450](https://github.com/sveltejs/svelte/issues/7450), [#7002](https://github.com/sveltejs/svelte/issues/7002))
* Fix handling of void tags in `<svelte:element>` ([#7449](https://github.com/sveltejs/svelte/issues/7449))
* Fix handling of boolean attributes in `<svelte:element>` ([#7478](https://github.com/sveltejs/svelte/issues/7478))
* Add special style scoping handling of `[open]` selectors on `<dialog>` elements ([#7495](https://github.com/sveltejs/svelte/issues/7494))
## 3.47.0

28
package-lock.json generated

@ -1,11 +1,12 @@
{
"name": "svelte",
"version": "3.47.0",
"version": "3.48.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "3.47.0",
"name": "svelte",
"version": "3.48.0",
"license": "MIT",
"devDependencies": {
"@ampproject/remapping": "^0.3.0",
@ -4476,6 +4477,19 @@
"node": ">=6"
}
},
"node_modules/string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
@ -8616,6 +8630,16 @@
"strip-ansi": "^5.1.0"
}
},
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",

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

@ -0,0 +1,89 @@
---
title: "What's new in Svelte: May 2022"
description: "Dynamically switch between HTML element types with `<svelte:element>`"
author: Daniel Sandoval
authorURL: https://desandoval.net
---
With yesterday's Svelte Summit behind us, we've got a lot of news to share! Check out all of the recordings on the [Svelte Society YouTube Channel](https://www.youtube.com/sveltesociety) and the rest of this month's updates below...
## What's new in Svelte
- The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful, for example, when rendering rich text content from a CMS. Check out the [docs](https://svelte.dev/docs#template-syntax-svelte-element) or the [tutorial](https://svelte.dev/tutorial/svelte-element) for more info (**3.47.0**)!
## Language Tools updates
- `svelte:element` and `sveltekit:reload` are now supported
- Invalid Svelte import paths will now be automatically detected - see PR for getting back the old behavior ([#1448](https://github.com/sveltejs/language-tools/pull/1448))
- `source.sortImports` lets you sort imports without deleting unused imports ([#1338](https://github.com/sveltejs/language-tools/issues/1338))
- Hovering over HTML attributes will now show HTML hover info instead of the TS hover info - resulting in much more useful information ([#1447](https://github.com/sveltejs/language-tools/pull/1447))
- In VS Code, you can now wrap existing blocks of code in control flow tags using the `Insert Snippet` command ([#1373](https://github.com/sveltejs/language-tools/pull/1373))
## What's new in SvelteKit
- Files and directories can now be named `__tests__` and `__test__` in the routes directory ([#4438](https://github.com/sveltejs/kit/pull/4438))
- Netlify Edge Functions ([#4657](https://github.com/sveltejs/kit/pull/4657)) and the Vercel build output API ([#4663](https://github.com/sveltejs/kit/pull/4663)) are now supported
- Custom `load` dependencies, array of strings representing URLs the page depends on, are now available when loading routes ([Docs](https://kit.svelte.dev/docs/loading#output-dependencies), [#4536](https://github.com/sveltejs/kit/pull/4536))
**Breaking Changes**
- Validators are now called "matchers" ([Docs](https://kit.svelte.dev/docs/routing#advanced-routing-matching), [#4358](https://github.com/sveltejs/kit/pull/4358))
- `__layout.reset` has been replaced by named layouts - which have much configurability for shared layout elements ([Docs](https://kit.svelte.dev/docs/layouts#named-layouts), [#4388](https://github.com/sveltejs/kit/pull/4388))
- Prerendering is now skipped for `rel="external"` links ([#4545](https://github.com/sveltejs/kit/pull/4545))
- `maxage` is now `cache` in `LoadOutput` ([#4690](https://github.com/sveltejs/kit/pull/4690))
---
## Community Showcase
**Apps & Sites built with Svelte**
- [polySpectra AR](https://ar.polyspectra.com/) lets you prototype faster 3D Printing with seamless AR file handoffs ([video demo](https://www.youtube.com/watch?v=VhYCeVGcG3E))
- [Pixel Art Together](https://github.com/liveblocks/pixel-art-together) is a free multiplayer pixel art editor powered by Liveblocks
- [Tooling Manager](https://tooling-manager.netlify.app/) lets you compare your JavaScript tech stack against industry standard boilerplates
- [Easy Portfolio](https://easy-portfolio.com/) generates a portfolio based on your GitHub profile
- [FLOAT](https://github.com/muttoni/float) is an attendance tracking program for events
- [The Coin Perspective](https://thecoinperspective.com/) is a cryptocurrency price tracker and portfolio management tool
- [Locutionis](https://github.com/pbouillon/locutionis) is a small, online reference of figures of speech (en français)
- [ASM Editor](https://asm-editor.specy.app/) is an all in one web editor for M68K and MIPS
- [Otium](https://github.com/alombi/otium) is a free and open source book manager and bookshelf organiser, that helps you managing your books and the ones you would like to read
- [Sinwaver](https://github.com/Hugo-Dz/Sinwaver) is an SVG sine wave generator
Want to contribute to a modern SvelteKit website? [Help build the Svelte Society site](https://github.com/svelte-society/sveltesociety.dev/issues)!
**Learning Resources**
_To Read_
- [4 tips for cleaner Svelte components](https://geoffrich.net/posts/clean-component-tips/) by Geoff Rich
- [Building a Clubhouse clone with Svelte and 100ms](https://www.100ms.live/blog/clubhouse-clone-with-svelte) By Seun Taiwo
- [SvelteKit uvu Testing: Fast Component Unit Tests](https://rodneylab.com/sveltekit-uvu-testing/) by Rodney Lab
- [SvelteKit JWT authentication tutorial](https://dev.to/pilcrowonpaper/sveltekit-jwt-authentication-tutorial-2m34) by pilcrowOnPaper
- [Converting a Rollup-based Svelte SPA to SvelteKit](https://github.com/sveltejs/kit/discussions/4595) by Simon H
- [Add Commitint, Commitizen, Standard Version, and Husky to SvelteKit Project](https://davipon.hashnode.dev/add-commitint-commitizen-standard-version-and-husky-to-sveltekit-project) by David Peng
_To Watch or Hear_
- [Rich Harris - The Road to SvelteKit 1.0 (Svelte Society NYC)](https://www.youtube.com/watch?v=s6a1pbTVcUs) by Svelte Society
- [Svelte Fundamentals - Intro to Svelte](https://codingcat.dev/course/intro-to-svelte) by Coding Cat
- [Svelte Components Using Custom Markdown Renderers - Weekly Svelte](https://www.youtube.com/watch?v=ZiEROAqobwM) by LevelUpTuts
- [Implementing {@const} in if block](https://www.youtube.com/watch?v=f5iReGqjmG0) by lihautan
- [Svelte and Contributing to Open-Source with Geoff Rich](https://podcast.20minjs.com/1952066/10417700-episode-6-svelte-and-contributing-to-open-source-with-geoff-rich) by 20minJS
**Libraries, Tools & Components**
- [KitDocs](https://github.com/svelteness/kit-docs) is a documentation integration for SvelteKit - like VitePress for Svelte.
- [Svelte Copy](https://github.com/ghostdevv/svelte-copy) is a click/tap-to-copy library that makes it easy to copy to the clipboard
- [Svend3r](https://github.com/oslabs-beta/svend3r) provides beautiful visualizations that harness the power of D3 to bring your data to life while abstracting away its imperative-style code
- [Svelte Hamburgers](https://github.com/ghostdevv/svelte-hamburgers) is the easy to use Hamburger menu component for Svelte
- [Svelte Droplet](https://github.com/probablykasper/svelte-droplet) is a file dropzone for Svelte
- [Svelte MP3](https://www.npmjs.com/package/svelte-mp3) is a light blazingly fast yet simple minimalistic audio player for Svelte
- [SvelteUI](https://github.com/Brisklemonade/svelteui) is a component library for building fully functional & accessible web applications faster than ever
- [svelte-spotlight](https://github.com/beynar/svelte-spotlight) is a headless spotlight component to help you build your site's global search box in minutes
- [svelte-pdf-simple](https://github.com/gspasov/svelte-pdf-simple) is a simple svelte library for displaying PDFs and giving you all the control
- [persistent-svelte-store](https://github.com/omer-g/persistent-svelte-store) is a generic persistent writable store, built from scratch in TypeScript according to the Svelte store contract
- [svelte-exmarkdown](https://github.com/ssssota/svelte-exmarkdown) is Svelte component to render markdown dynamically
- [Bookit](https://github.com/leveluptuts/bookit) is a storybook-like component rendering environment - finely tuned to work directly within your SvelteKit projects
Join us on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs) to continue the conversation.
If you'd prefer to join us in person, Svelte Summit is finally transitioning properly into the real world. Come join us for two days of awesome Svelte content! [Get your tickets now!](https://ti.to/svelte/svelte-summit-fall-edition)
See y'all next month!

@ -474,7 +474,7 @@ The `{@const ...}` tag defines a local constant.
{/each}
```
`{@const}` is only allowed as direct child of `{#each}`, `{:then}`, `{:catch}`, `<Component />` or `<svelte:fragment />`.
`{@const}` is only allowed as direct child of `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<Component />` or `<svelte:fragment />`.
### Element directives
@ -1641,6 +1641,8 @@ The only supported binding is `bind:this`, since the element type specific bindi
If `this` has a nullish value, the element and its children will not be rendered.
If `this` is the name of a void tag (e.g., `br`) and `<svelte:element>` has child elements, a runtime error will be thrown in development mode.
```sv
<script>
let tag = 'div';

@ -2,9 +2,9 @@
title: Dynamic attributes
---
Just like you can use curly braces to control text, you can use them to control element attributes.
You can use curly braces to control element attributes, just like you use them to control text.
Our image is missing a `src` — let's add one:
Our image is missing a `src` attribute — let's add one:
```html
<img src={src}>
@ -24,7 +24,6 @@ In this case, we're missing the `alt` attribute that describes the image for peo
We can use curly braces *inside* attributes. Try changing it to `"{name} dances."` — remember to declare a `name` variable in the `<script>` block.
## Shorthand attributes
It's not uncommon to have an attribute where the name and value are the same, like `src={src}`. Svelte gives us a convenient shorthand for these cases:
@ -32,4 +31,3 @@ It's not uncommon to have an attribute where the name and value are the same, li
```html
<img {src} alt="A man dances.">
```

@ -260,7 +260,7 @@ export default {
},
invalid_const_placement: {
code: 'invalid-const-placement',
message: '{@const} must be the immediate child of {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>'
message: '{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>'
},
invalid_const_declaration: (name: string) => ({
code: 'invalid-const-declaration',

@ -21,7 +21,8 @@ enum NodeExist {
}
const whitelist_attribute_selector = new Map([
['details', new Set(['open'])]
['details', new Set(['open'])],
['dialog', new Set(['open'])]
]);
export default class Selector {

@ -11,7 +11,7 @@ import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors';
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate']);
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']);
export default class ConstTag extends Node {
type: 'ConstTag';

@ -1,4 +1,4 @@
import { is_void } from '../../utils/names';
import { is_void } from '../../../shared/utils/names';
import Node from './shared/Node';
import Attribute from './Attribute';
import Binding from './Binding';

@ -1,16 +1,21 @@
import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';
import Node from './shared/Node';
import ConstTag from './ConstTag';
import get_const_tags from './shared/get_const_tags';
export default class ElseBlock extends AbstractBlock {
type: 'ElseBlock';
scope: TemplateScope;
const_tags: ConstTag[];
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.children = map_children(component, this, scope, info.children);
this.scope = scope.child();
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));
this.warn_if_empty_block();
}

@ -1,22 +1,26 @@
import ElseBlock from './ElseBlock';
import Expression from './shared/Expression';
import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';
import Node from './shared/Node';
import ConstTag from './ConstTag';
import get_const_tags from './shared/get_const_tags';
export default class IfBlock extends AbstractBlock {
type: 'IfBlock';
expression: Expression;
else: ElseBlock;
scope: TemplateScope;
const_tags: ConstTag[];
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.scope = scope.child();
this.expression = new Expression(component, this, scope, info.expression);
this.children = map_children(component, this, scope, info.children);
this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));
this.else = info.else
? new ElseBlock(component, this, scope, info.else)

@ -72,6 +72,8 @@ export type INode = Action
| Window;
export type INodeAllowConstTag =
| IfBlock
| ElseBlock
| EachBlock
| CatchBlock
| ThenBlock

@ -27,6 +27,7 @@ export class ElseBlockWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
add_const_tags_context(renderer, this.node.const_tags);
this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component),
@ -257,6 +258,18 @@ export default class EachBlockWrapper extends Wrapper {
}
if (this.else) {
let else_ctx = x`#ctx`;
if (this.else.node.const_tags.length > 0) {
const get_ctx_name = this.renderer.component.get_unique_name('get_else_ctx');
this.renderer.blocks.push(b`
function ${get_ctx_name}(#ctx) {
const child_ctx = #ctx.slice();
${add_const_tags(block, this.else.node.const_tags, 'child_ctx')}
return child_ctx;
}
`);
else_ctx = x`${get_ctx_name}(#ctx)`;
}
const each_block_else = component.get_unique_name(`${this.var.name}_else`);
block.chunks.init.push(b`let ${each_block_else} = null;`);
@ -264,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper {
// TODO neaten this up... will end up with an empty line in the block
block.chunks.init.push(b`
if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else} = ${this.else.block.name}(${else_ctx});
}
`);
@ -304,9 +317,9 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else.block.has_update_method) {
this.updates.push(b`
if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(#ctx, #dirty);
${each_block_else}.p(${else_ctx}, #dirty);
} else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else} = ${this.else.block.name}(${else_ctx});
${each_block_else}.c();
${has_transitions && b`@transition_in(${each_block_else}, 1);`}
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});
@ -321,7 +334,7 @@ export default class EachBlockWrapper extends Wrapper {
${destroy_block_else};
}
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else} = ${this.else.block.name}(${else_ctx});
${each_block_else}.c();
${has_transitions && b`@transition_in(${each_block_else}, 1);`}
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});

@ -2,7 +2,7 @@ import Renderer from '../../Renderer';
import Element from '../../../nodes/Element';
import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import { is_void } from '../../../../utils/names';
import { is_void } from '../../../../../shared/utils/names';
import FragmentWrapper from '../Fragment';
import { escape_html, string_literal } from '../../../utils/stringify';
import TextWrapper from '../Text';
@ -279,6 +279,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);
`);
@ -310,6 +311,7 @@ export default class ElementWrapper extends Wrapper {
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c();
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
@ -750,7 +752,7 @@ export default class ElementWrapper extends Wrapper {
}
});
if (this.node.attributes.some(attr => attr.is_spread)) {
if (this.node.attributes.some(attr => attr.is_spread) || this.node.is_dynamic_element) {
this.add_spread_attributes(block);
return;
}

@ -11,6 +11,7 @@ import { walk } from 'estree-walker';
import { is_head } from './shared/is_head';
import { Identifier, Node } from 'estree';
import { push_array } from '../../../utils/push_array';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
function is_else_if(node: ElseBlock) {
return (
@ -25,8 +26,10 @@ class IfBlockBranch extends Wrapper {
condition?: any;
snippet?: Node;
is_dynamic: boolean;
node: IfBlock | ElseBlock;
var = null;
get_ctx_name: Node | undefined;
constructor(
renderer: Renderer,
@ -63,6 +66,8 @@ class IfBlockBranch extends Wrapper {
}
}
add_const_tags_context(renderer, this.node.const_tags);
this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.get_unique_name(
@ -74,6 +79,10 @@ class IfBlockBranch extends Wrapper {
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
this.is_dynamic = this.block.dependencies.size > 0;
if (node.const_tags.length > 0) {
this.get_ctx_name = parent.renderer.component.get_unique_name(is_else ? 'get_else_ctx' : 'get_if_ctx');
}
}
}
@ -190,6 +199,18 @@ export default class IfBlockWrapper extends Wrapper {
const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = has_intros || has_outros;
this.branches.forEach(branch => {
if (branch.get_ctx_name) {
this.renderer.blocks.push(b`
function ${branch.get_ctx_name}(#ctx) {
const child_ctx = #ctx.slice();
${add_const_tags(block, branch.node.const_tags, 'child_ctx')}
return child_ctx;
}
`);
}
});
const vars = { name, anchor, if_exists_condition, has_else, has_transitions };
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
@ -260,9 +281,13 @@ export default class IfBlockWrapper extends Wrapper {
) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type = block.get_unique_name('current_block_type');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null;
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`;
const get_block = has_else
? x`${current_block_type}(#ctx)`
: x`${current_block_type} && ${current_block_type}(#ctx)`;
? x`${current_block_type}(${if_ctx})`
: x`${current_block_type} && ${current_block_type}(${if_ctx})`;
if (this.needs_update) {
block.chunks.init.push(b`
@ -288,6 +313,34 @@ export default class IfBlockWrapper extends Wrapper {
`);
}
if (need_select_block_ctx) {
// if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) {
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ condition, get_ctx_name, block }) => {
return condition
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)}
}
`);
} else {
// when not all branches need to create a new context,
// this code is simpler
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ get_ctx_name, block }) => {
return get_ctx_name
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: null;
}).filter(Boolean)}
return #ctx;
}
`);
}
}
block.chunks.init.push(b`
let ${current_block_type} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()});
let ${name} = ${get_block};
@ -322,7 +375,7 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.chunks.update.push(b`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #dirty)) && ${name}) {
${name}.p(#ctx, #dirty);
${name}.p(${if_ctx}, #dirty);
} else {
${change_block}
}
@ -336,9 +389,9 @@ export default class IfBlockWrapper extends Wrapper {
}
} else if (dynamic) {
if (if_exists_condition) {
block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(#ctx, #dirty);`);
block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(${if_ctx}, #dirty);`);
} else {
block.chunks.update.push(b`${name}.p(#ctx, #dirty);`);
block.chunks.update.push(b`${name}.p(${if_ctx}, #dirty);`);
}
}
@ -370,6 +423,9 @@ export default class IfBlockWrapper extends Wrapper {
const previous_block_index = block.get_unique_name('previous_block_index');
const if_block_creators = block.get_unique_name('if_block_creators');
const if_blocks = block.get_unique_name('if_blocks');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null;
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`;
const if_current_block_type_index = has_else
? nodes => nodes
@ -409,15 +465,43 @@ export default class IfBlockWrapper extends Wrapper {
`}
`);
if (need_select_block_ctx) {
// if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) {
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ condition, get_ctx_name }, i) => {
return condition
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)}
}
`);
} else {
// when not all branches need to create a new context,
// this code is simpler
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ get_ctx_name }, i) => {
return get_ctx_name
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: null;
}).filter(Boolean)}
return #ctx;
}
`);
}
}
if (has_else) {
block.chunks.init.push(b`
${current_block_type_index} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()});
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx});
`);
} else {
block.chunks.init.push(b`
if (~(${current_block_type_index} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()}))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx});
}
`);
}
@ -445,10 +529,10 @@ export default class IfBlockWrapper extends Wrapper {
const create_new_block = b`
${name} = ${if_blocks}[${current_block_type_index}];
if (!${name}) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx});
${name}.c();
} else {
${dynamic && b`${name}.p(#ctx, #dirty);`}
${dynamic && b`${name}.p(${if_ctx}, #dirty);`}
}
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
@ -480,7 +564,7 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.chunks.update.push(b`
if (${current_block_type_index} === ${previous_block_index}) {
${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#ctx, #dirty);`)}
${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(${if_ctx}, #dirty);`)}
} else {
${change_block}
}
@ -494,9 +578,9 @@ export default class IfBlockWrapper extends Wrapper {
}
} else if (dynamic) {
if (if_exists_condition) {
block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(#ctx, #dirty);`);
block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(${if_ctx}, #dirty);`);
} else {
block.chunks.update.push(b`${name}.p(#ctx, #dirty);`);
block.chunks.update.push(b`${name}.p(${if_ctx}, #dirty);`);
}
}
@ -514,11 +598,12 @@ export default class IfBlockWrapper extends Wrapper {
detaching
) {
const branch = this.branches[0];
const if_ctx = branch.get_ctx_name ? x`${branch.get_ctx_name}(#ctx)` : x`#ctx`;
if (branch.snippet) block.add_variable(branch.condition, branch.snippet);
block.chunks.init.push(b`
let ${name} = ${branch.condition} && ${branch.block.name}(#ctx);
let ${name} = ${branch.condition} && ${branch.block.name}(${if_ctx});
`);
const initial_mount_node = parent_node || '#target';
@ -533,15 +618,14 @@ export default class IfBlockWrapper extends Wrapper {
const enter = b`
if (${name}) {
${dynamic && b`${name}.p(#ctx, #dirty);`}
${
has_transitions &&
${dynamic && b`${name}.p(${if_ctx}, #dirty);`}
${has_transitions &&
b`if (${block.renderer.dirty(branch.dependencies)}) {
@transition_in(${name}, 1);
}`
}
} else {
${name} = ${branch.block.name}(#ctx);
${name} = ${branch.block.name}(${if_ctx});
${name}.c();
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
@ -578,7 +662,7 @@ export default class IfBlockWrapper extends Wrapper {
}
} else if (dynamic) {
block.chunks.update.push(b`
if (${branch.condition}) ${name}.p(#ctx, #dirty);
if (${branch.condition}) ${name}.p(${if_ctx}, #dirty);
`);
}

@ -2,6 +2,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import EachBlock from '../../nodes/EachBlock';
import { x } from 'code-red';
import { get_const_tags } from './shared/get_const_tags';
import { Node } from 'estree';
export default function(node: EachBlock, renderer: Renderer, options: RenderOptions) {
const args = [node.context_node];
@ -16,7 +17,8 @@ export default function(node: EachBlock, renderer: Renderer, options: RenderOpti
if (node.else) {
renderer.push();
renderer.render(node.else.children, options);
const alternate = renderer.pop();
let alternate: Node = renderer.pop();
if (node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${node.expression.node}.length ? ${consequent} : ${alternate}`);
} else {

@ -1,6 +1,6 @@
import { is_void } from '../../../utils/names';
import { is_void } from '../../../../shared/utils/names';
import { get_attribute_expression, get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { boolean_attributes } from './shared/boolean_attributes';
import { boolean_attributes } from '../../../../shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import { p, x } from 'code-red';
@ -9,7 +9,7 @@ import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns';
import { Expression as ESExpression } from 'estree';
import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) {
@ -24,6 +24,10 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
if (node.is_dynamic_element) {
renderer.push();
}
renderer.add_string('<');
add_tag_name();
@ -192,16 +196,34 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
renderer.add_string('\n');
}
}
if (node.is_dynamic_element) renderer.push();
renderer.render(children, options);
if (node.is_dynamic_element) {
const children = renderer.pop();
renderer.add_expression(x`@is_void(#tag) ? '' : ${children}`);
}
add_close_tag();
}
if (node.is_dynamic_element) {
let content: Node = renderer.pop();
if (options.dev && node.children.length > 0) content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`;
renderer.add_expression(x`((#tag) => {
${options.dev && x`@validate_dynamic_element(#tag)`}
return #tag ? ${content} : '';
})(${node.tag_expr.node})`);
}
function add_close_tag() {
if (!is_void(node.name)) {
if (node.tag_expr.node.type === 'Literal') {
if (!is_void(node.tag_expr.node.value as string)) {
renderer.add_string('</');
add_tag_name();
renderer.add_string('>');
}
return;
}
renderer.add_expression(x`@is_void(#tag) ? '' : \`</\${#tag}>\``);
}
function add_tag_name() {

@ -1,17 +1,21 @@
import IfBlock from '../../nodes/IfBlock';
import Renderer, { RenderOptions } from '../Renderer';
import { x } from 'code-red';
import { get_const_tags } from './shared/get_const_tags';
import { Node } from 'estree';
export default function (node: IfBlock, renderer: Renderer, options: RenderOptions) {
const condition = node.expression.node;
renderer.push();
renderer.render(node.children, options);
const consequent = renderer.pop();
let consequent: Node = renderer.pop();
if (node.const_tags.length > 0) consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`;
renderer.push();
if (node.else) renderer.render(node.else.children, options);
const alternate = renderer.pop();
let alternate: Node = renderer.pop();
if (node.else && node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${condition} ? ${consequent} : ${alternate}`);
}

@ -1,7 +1,7 @@
import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore';
import fuzzymatch from '../../utils/fuzzymatch';
import { is_void } from '../../utils/names';
import { is_void } from '../../../shared/utils/names';
import parser_errors from '../errors';
import { Parser } from '../index';
import read_expression from '../read/expression';

@ -117,12 +117,6 @@ export const reserved = new Set([
'yield'
]);
const void_element_names = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
export function is_void(name: string) {
return void_element_names.test(name) || name.toLowerCase() === '!doctype';
}
export function is_valid(str: string): boolean {
let i = 0;

@ -1,5 +1,6 @@
import { custom_event, append, append_hydration, insert, insert_hydration, detach, listen, attr } from './dom';
import { SvelteComponent } from './Component';
import { is_void } from '../../shared/utils/names';
export function dispatch_dev<T=any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, { bubbles: true }));
@ -108,11 +109,18 @@ export function validate_slots(name, slot, keys) {
}
export function validate_dynamic_element(tag: unknown) {
if (tag && typeof tag !== 'string') {
const is_string = typeof tag === 'string';
if (tag && !is_string) {
throw new Error('<svelte:element> expects "this" attribute to be a string.');
}
}
export function validate_void_dynamic_element(tag: undefined | string) {
if (tag && is_void(tag)) {
throw new Error(`<svelte:element this="${tag}"> is self-closing and cannot have content.`);
}
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;

@ -1,6 +1,7 @@
import { set_current_component, current_component } from './lifecycle';
import { run_all, blank_object } from './utils';
import { boolean_attributes } from '../../compiler/compile/render_ssr/handlers/shared/boolean_attributes';
import { boolean_attributes } from '../../shared/boolean_attributes';
export { is_void } from '../../shared/utils/names';
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2

@ -0,0 +1,5 @@
const void_element_names = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
export function is_void(name: string) {
return void_element_names.test(name) || name.toLowerCase() === '!doctype';
}

@ -0,0 +1 @@
dialog[open].svelte-xyz{display:grid}

@ -0,0 +1,7 @@
<dialog>Hello</dialog>
<style>
dialog[open] {
display: grid;
}
</style>

@ -0,0 +1,26 @@
export default {
html: `
<div>12 120 70, 30+4=34</div>
<div>35 350 120, 50+7=57</div>
<div>48 480 140, 60+8=68</div>
`,
async test({ component, target, assert }) {
component.boxes = [];
assert.htmlEqual(target.innerHTML, `
<div>10 * 2 = 20</div>
`);
component.constant = 35;
assert.htmlEqual(target.innerHTML, `
<div>35 * 2 = 70</div>
`);
component.boxes = [
{width: 3, height: 4}
];
assert.htmlEqual(target.innerHTML, `
<div>12 420 245, 105+4=109</div>
`);
}
};

@ -0,0 +1,22 @@
<script>
export let boxes = [
{width: 3, height: 4},
{width: 5, height: 7},
{width: 6, height: 8},
];
export let constant = 10;
function calculate(width, height, constant) {
return { area: width * height, volume: width * height * constant };
}
</script>
{#each boxes as box}
{@const {area, volume} = calculate(box.width, box.height, constant)}
{@const perimeter = (box.width + box.height) * constant}
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
{:else}
{@const double = constant + constant}
<div>{constant} * 2 = {double}</div>
{/each}

@ -0,0 +1,56 @@
export default {
html: `
<div>20 x 40</div>
<div>20 x 40</div>
`,
props: {
boxes: [{ width: 20, height: 40 }]
},
async test({ component, target, assert }) {
component.boxes = [{ width: 40, height: 70 }];
assert.htmlEqual(
target.innerHTML,
`
<div>40 x 70</div>
<div>40 x 70</div>
`
);
component.boxes = [];
assert.htmlEqual(target.innerHTML, '');
component.boxes = [
{ width: 20, height: 40 },
{ width: 30, height: 50 }
];
assert.htmlEqual(
target.innerHTML,
`
<div>20 x 40</div>
<div>30 x 50</div>
<div>20 x 40</div>
<div>30 x 50</div>
`
);
component.boxes = [
{ width: 80, height: 70 },
{ width: 90, height: 60 }
];
assert.htmlEqual(
target.innerHTML,
`
<div>80 x 70</div>
<div>90 x 60</div>
<div>80 x 70</div>
<div>90 x 60</div>
`
);
component.boxes = [];
assert.htmlEqual(target.innerHTML, '');
}
};

@ -0,0 +1,24 @@
<script>
export let boxes;
</script>
{#if boxes.length > 1}
{@const box1 = boxes[0]}
{@const box2 = boxes[1]}
{@const { width, height } = box1}
<div>{width} x {height}</div>
<div>{box2.width} x {box2.height}</div>
{:else if boxes.length > 0}
{@const box = boxes[0]}
{@const { width, height } = box}
<div>{width} x {height}</div>
{/if}
{#if boxes.length > 1}
<div>{boxes[0].width} x {boxes[0].height}</div>
<div>{boxes[1].width} x {boxes[1].height}</div>
{:else if boxes.length > 0}
{@const box = boxes[0]}
{@const { width, height } = box}
<div>{width} x {height}</div>
{/if}

@ -0,0 +1,57 @@
export default {
html: `
<div>20 x 40</div>
<div>20 x 40</div>
`,
props: {
boxes: [{ width: 20, height: 40 }]
},
async test({ component, target, assert, raf }) {
component.boxes = [{ width: 40, height: 70 }];
assert.htmlEqual(
target.innerHTML,
`
<div>40 x 70</div>
<div>40 x 70</div>
`
);
component.boxes = [];
raf.tick(0);
assert.htmlEqual(target.innerHTML, '');
component.boxes = [
{ width: 20, height: 40 },
{ width: 30, height: 50 }
];
assert.htmlEqual(
target.innerHTML,
`
<div>20 x 40</div>
<div>30 x 50</div>
<div>20 x 40</div>
<div>30 x 50</div>
`
);
component.boxes = [
{ width: 80, height: 70 },
{ width: 90, height: 60 }
];
assert.htmlEqual(
target.innerHTML,
`
<div>80 x 70</div>
<div>90 x 60</div>
<div>80 x 70</div>
<div>90 x 60</div>
`
);
component.boxes = [];
assert.htmlEqual(target.innerHTML, '');
}
};

@ -0,0 +1,29 @@
<script>
export let boxes;
function fade() {
return {
duration: 0,
}
}
</script>
{#if boxes.length > 1}
{@const box1 = boxes[0]}
{@const box2 = boxes[1]}
{@const { width, height } = box1}
<div>{width} x {height}</div>
<div>{box2.width} x {box2.height}</div>
{:else if boxes.length > 0}
{@const box = boxes[0]}
{@const { width, height } = box}
<div out:fade>{width} x {height}</div>
{/if}
{#if boxes.length > 1}
<div>{boxes[0].width} x {boxes[0].height}</div>
<div>{boxes[1].width} x {boxes[1].height}</div>
{:else if boxes.length > 0}
{@const box = boxes[0]}
{@const { width, height } = box}
<div out:fade>{width} x {height}</div>
{/if}

@ -0,0 +1,43 @@
export default {
html: '<div>20 x 40</div>',
props: {
boxes: [{ width: 20, height: 40 }]
},
async test({ component, target, assert }) {
component.boxes = [{ width: 30, height: 60 }];
assert.htmlEqual(target.innerHTML, `
<div>30 x 60</div>
`);
component.boxes = [
{ width: 20, height: 40 },
{ width: 30, height: 50 }
];
assert.htmlEqual(target.innerHTML, `
<div>20 x 40</div>
<div>30 x 50</div>
`);
component.boxes = [
{ width: 80, height: 70 },
{ width: 90, height: 60 }
];
assert.htmlEqual(target.innerHTML, `
<div>80 x 70</div>
<div>90 x 60</div>
`);
component.boxes = [
{ width: 20, height: 40 },
{ width: 30, height: 50 },
{ width: 30, height: 50 }
];
assert.htmlEqual(target.innerHTML, '<div>3</div>');
component.boxes = [];
assert.htmlEqual(target.innerHTML, '<div>0</div>');
}
};

@ -0,0 +1,18 @@
<script>
export let boxes;
</script>
{#if boxes.length === 2}
{@const box1 = boxes[0]}
{@const box2 = boxes[1]}
{@const { width, height } = box1}
<div>{width} x {height}</div>
<div>{box2.width} x {box2.height}</div>
{:else if boxes.length === 1}
{@const box = boxes[0]}
{@const { width, height } = box}
<div>{width} x {height}</div>
{:else}
{@const length = boxes.length}
<div>{length}</div>
{/if}

@ -0,0 +1,28 @@
export default {
html: '<div>10 x 34</div>',
props: {
boxes: [{ width: 10, height: 34 }]
},
async test({ component, target, assert }) {
component.boxes = [{ width: 20, height: 40 }];
assert.htmlEqual(
target.innerHTML,
`
<div>20 x 40</div>
`
);
component.boxes = [];
assert.htmlEqual(target.innerHTML, '');
component.boxes = [{ width: 18, height: 48 }];
assert.htmlEqual(
target.innerHTML,
`
<div>18 x 48</div>
`
);
}
};

@ -0,0 +1,9 @@
<script>
export let boxes;
</script>
{#if boxes.length > 0}
{@const box = boxes[0]}
{@const { width, height } = box}
<div>{width} x {height}</div>
{/if}

@ -0,0 +1,15 @@
export default {
props: {
disabled: false
},
html: '<button>Click me</button>',
test({ assert, component, target }) {
const button = target.querySelector('button');
assert.equal(button.disabled, false);
component.disabled = true;
assert.htmlEqual(target.innerHTML, '<button disabled>Click me</button>');
assert.equal(button.disabled, true);
}
};

@ -0,0 +1,5 @@
<script>
export let disabled = false;
</script>
<svelte:element {disabled} this="button">Click me</svelte:element>

@ -0,0 +1,23 @@
export default {
props: {
props: {
disabled: true,
type: 'button',
'data-named': 'foo'
}
},
html: '<button disabled type="button" data-named="foo">Click me</button>',
test({ assert, component, target }) {
const button = target.querySelector('button');
assert.equal(button.disabled, true);
assert.equal(button.type, 'button');
assert.equal(button.dataset.named, 'foo');
component.props = { type: 'submit' };
assert.htmlEqual(target.innerHTML, '<button type="submit">Click me</button>');
assert.equal(button.disabled, false);
assert.equal(button.type, 'submit');
assert.equal(button.dataset.named, undefined);
}
};

@ -0,0 +1,9 @@
<script>
export let props = {
disabled: false,
type: 'button',
'data-named': 'foo'
};
</script>
<svelte:element this="button" {...props}>Click me</svelte:element>

@ -0,0 +1,12 @@
export default {
props: {
propTag: 'hr'
},
html: '<h1></h1><col><img><hr><input><br>',
test({ assert, component, target }) {
assert.htmlEqual(target.innerHTML, '<h1></h1><col><img><hr><input><br>');
component.propTag = 'link';
assert.htmlEqual(target.innerHTML, '<h1></h1><col><img><link><input><br>');
}
};

@ -0,0 +1,12 @@
<script>
export let propTag;
const static_tag = 'input';
const func_tag = () => 'br';
</script>
<h1></h1>
<svelte:element this="{'col'}"></svelte:element>
<svelte:element this="img"></svelte:element>
<svelte:element this="{propTag}"></svelte:element>
<svelte:element this="{static_tag}"></svelte:element>
<svelte:element this="{func_tag()}"></svelte:element>

@ -0,0 +1,9 @@
export default {
compileOptions: {
dev: true
},
props: {
tag: 'br'
},
error: '<svelte:element this="br"> is self-closing and cannot have content.'
};

@ -0,0 +1,5 @@
<script>
export let tag;
</script>
<svelte:element this='{tag}'>foo</svelte:element>

@ -0,0 +1,9 @@
export default {
compileOptions: {
dev: true
},
props: {
tag: 'br'
},
error: '<svelte:element this="br"> is self-closing and cannot have content.'
};

@ -0,0 +1,5 @@
<script>
export let tag;
</script>
<svelte:element this='{tag}'><div>bar</div></svelte:element>

@ -0,0 +1,9 @@
export default {
compileOptions: {
dev: true
},
props: {
tag: 'br'
},
error: '<svelte:element this="br"> is self-closing and cannot have content.'
};

@ -0,0 +1,6 @@
<script>
import Nested from './Nested.svelte';
export let tag;
</script>
<svelte:element this='{tag}'><Nested/></svelte:element>

@ -0,0 +1,6 @@
export default {
props: {
tag: 'br'
},
html: '<br>'
};

@ -0,0 +1,6 @@
<script>
import Nested from './Nested.svelte';
export let tag;
</script>
<svelte:element this='{tag}'><Nested/></svelte:element>

@ -1,7 +1,7 @@
[
{
"code": "invalid-const-placement",
"message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>",
"message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>",
"start": { "line": 5, "column": 0, "character": 36 },
"end": { "line": 5, "column": 18, "character": 54 },
"pos": 36

@ -1,9 +1,9 @@
[
{
"code": "invalid-const-placement",
"message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>",
"start": { "line": 6, "column": 2, "character": 46 },
"end": { "line": 6, "column": 20, "character": 64 },
"pos": 46
"message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>",
"start": { "line": 7, "column": 4, "character": 63 },
"end": { "line": 7, "column": 18, "character": 77 },
"pos": 63
}
]

@ -2,6 +2,8 @@
export let a;
</script>
{#if a}
{@const b = a + 1}
{/if}
{#each a as i}
<div>
{@const b = i}
</div>
{/each}

@ -1,9 +0,0 @@
[
{
"code": "invalid-const-placement",
"message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>",
"start": { "line": 7, "column": 4, "character": 63 },
"end": { "line": 7, "column": 18, "character": 77 },
"pos": 63
}
]

@ -1,9 +0,0 @@
<script>
export let a;
</script>
{#each a as i}
<div>
{@const b = i}
</div>
{/each}
Loading…
Cancel
Save