ceProps -> customElement.props, tag -> customElement/customElement.tag, some fixes, types, remove tag warning, docs

pull/8457/head
Simon Holthausen 3 years ago
parent 1e8271839d
commit 5804b695e7

@ -1598,8 +1598,14 @@ export interface SvelteHTMLElements {
'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string };
'svelte:options': {
tag?: string | null | undefined;
ceProps?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }> | undefined,
customElement?: string | undefined | {
tag: string;
shadow?: 'open' | 'none' | undefined;
props?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }> | undefined;
};
immutable?: boolean | undefined;
accessors?: boolean | undefined;
namespace?: string | undefined;
[name: string]: any
};
'svelte:head': { [name: string]: any };

@ -1825,10 +1825,10 @@ The `<svelte:options>` element provides a place to specify per-component compile
* `accessors={true}` — adds getters and setters for the component's props
* `accessors={false}` — the default
* `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings
* `tag="..."` — the name to use when compiling this component as a custom element
* `customElement="..."` — the name to use when compiling this component as a custom element
```sv
<svelte:options tag="my-custom-element"/>
<svelte:options customElement="my-custom-element"/>
```
### `<svelte:fragment>`

@ -1118,7 +1118,7 @@ app.count += 1;
Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `<svelte:options>` [element](/docs#template-syntax-svelte-options).
```sv
<svelte:options tag="my-element" />
<svelte:options customElement="my-element" />
<script>
export let name = 'world';
@ -1130,12 +1130,12 @@ Svelte components can also be compiled to custom elements (aka web components) u
---
Alternatively, use `tag={null}` to indicate that the consumer of the custom element should name it.
You can leave out the tag name for any of your inner components which you don't want to expose and use them like regular Svelte components. Consumers of the component can still name it afterwards if needed, using the static `element` property which contains the custom element constructor and which is available when the `customElement` compiler option is `true`.
```js
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement);
customElements.define('my-element', MyElement.element);
```
---
@ -1166,15 +1166,42 @@ console.log(el.name);
el.name = 'everybody';
```
---
When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `<svelte:options>`. This object comprises a mandatory `tag` property for the custom element's name, an optional `shadow` property that can be set to `"none"` to forgo shadow root creation, and a `props` option, which offers the following settings:
- `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: "<desired name>"`.
- `reflect: boolean`: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set `reflect: true`.
- `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`: While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a `String` by default. This may not always be accurate. For instance, for a number type, define it using `type: "Number"`
```svelte
<svelte:options
customElement={{
tag: "custom-element",
shadow: "none",
props: {
name: { reflect: true, type: "Number", attribute: "element-index" },
},
}}
/>
<script>
export let elementIndex;
</script>
...
```
Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of:
* Styles are *encapsulated*, rather than merely *scoped*. This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier
* Styles are *encapsulated*, rather than merely *scoped* (unless you set `shadow: "none"`). This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier
* Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string
* Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads
* In Svelte, slotted content renders *lazily*. In the DOM, it renders *eagerly*. In other words, it will always be created even if the component's `<slot>` element is inside an `{#if ...}` block. Similarly, including a `<slot>` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times
* The `let:` directive has no effect
* The `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot
* Polyfills are required to support older browsers
When a custom element written with Svelte is created or updated, the shadow dom will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component.
### Server-side component API

@ -25,6 +25,6 @@ The options that can be set here are:
* `accessors={true}` — adds getters and setters for the component's props
* `accessors={false}` — the default
* `namespace="..."` — the namespace where this component will be used, most commonly `"svg"`
* `tag="..."` — the name to use when compiling this component as a custom element
* `customElement="..."` — the name to use when compiling this component as a custom element
Consult the [API reference](/docs) for more information on these options.

@ -16,7 +16,7 @@ import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports';
import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces';
import { Ast, CompileOptions, Var, Warning, CssResult, Attribute } from '../interfaces';
import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
@ -42,12 +42,14 @@ import Tag from './nodes/shared/Tag';
interface ComponentOptions {
namespace?: string;
tag?: string;
immutable?: boolean;
accessors?: boolean;
preserveWhitespace?: boolean;
ceProps?: Record<string, { reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object', attribute?: string }>;
shadowdom?: 'open' | 'none';
customElement?: {
tag: string | null;
shadow?: 'open' | 'none';
props?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }>;
};
}
const regex_leading_directory_separator = /^[/\\]/;
@ -169,16 +171,7 @@ export default class Component {
this.component_options.namespace;
if (compile_options.customElement) {
if (
this.component_options.tag === undefined &&
compile_options.tag === undefined
) {
const svelteOptions = ast.html.children.find(
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
this.warn(svelteOptions, compiler_warnings.custom_element_no_tag);
}
this.tag = this.component_options.tag || compile_options.tag;
this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name;
} else {
this.tag = this.name.name;
}
@ -1565,72 +1558,99 @@ function process_component_options(component: Component, nodes) {
if (attribute.type === 'Attribute') {
const { name } = attribute;
switch (name) {
case 'tag': {
const tag = get_value(attribute, compiler_errors.invalid_tag_attribute);
if (typeof tag !== 'string' && tag !== null) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
}
if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
}
if (tag && !component.compile_options.customElement) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
}
function parse_tag(attribute: Attribute, tag: string) {
if (typeof tag !== 'string' && tag !== null) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
}
component_options.tag = tag;
break;
if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
}
case 'shadowdom': {
const shadowdom = get_value(attribute, compiler_errors.invalid_shadowdom_attribute);
if (tag && !component.compile_options.customElement) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
}
if (shadowdom !== 'open' && shadowdom !== 'none') {
return component.error(attribute, compiler_errors.invalid_shadowdom_attribute);
}
component_options.customElement = component_options.customElement || {} as any;
component_options.customElement.tag = tag;
}
component_options.shadowdom = shadowdom;
switch (name) {
case 'tag': {
component.warn(attribute, compiler_warnings.tag_option_deprecated)
parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute));
break;
}
case 'ceProps': {
const error = () => component.error(attribute, compiler_errors.invalid_ceProps_attribute);
case 'customElement': {
component_options.customElement = component_options.customElement || {} as any;
const { value } = attribute;
const chunk = value[0];
component_options.ceProps = {};
if (!chunk) {
if (value[0].type === 'MustacheTag' && value[0].expression?.value === null) {
component_options.customElement.tag = null;
break;
} else if (value[0].type === 'Text') {
parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute));
break;
} else if (value[0].expression.type !== 'ObjectExpression') {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
}
if (value.length > 1 || chunk.expression?.type !== 'ObjectExpression') {
return error();
const tag = value[0].expression.properties.find(
(prop: any) => prop.key.name === 'tag'
);
if (tag) {
parse_tag(tag, tag.value?.value);
} else {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
}
const object = chunk.expression as ObjectExpression;
for (const property of object.properties) {
if (property.type !== 'Property' || property.computed || property.key.type !== 'Identifier' || property.value.type !== 'ObjectExpression') {
const props = value[0].expression.properties.find(
(prop: any) => prop.key.name === 'props'
);
if (props) {
const error = () => component.error(attribute, compiler_errors.invalid_props_attribute);
if (props.value?.type !== 'ObjectExpression') {
return error();
}
component_options.ceProps[property.key.name] = {};
for (const prop of property.value.properties) {
if (prop.type !== 'Property' || prop.computed || prop.key.type !== 'Identifier' || prop.value.type !== 'Literal') {
component_options.customElement.props = {};
for (const property of (props.value as ObjectExpression).properties) {
if (property.type !== 'Property' || property.computed || property.key.type !== 'Identifier' || property.value.type !== 'ObjectExpression') {
return error();
}
if (['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 ||
prop.key.name === 'type' && ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(prop.value.value as string) === -1 ||
prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean' ||
prop.key.name === 'attribute' && typeof prop.value.value !== 'string'
) {
return error();
component_options.customElement.props[property.key.name] = {};
for (const prop of property.value.properties) {
if (prop.type !== 'Property' || prop.computed || prop.key.type !== 'Identifier' || prop.value.type !== 'Literal') {
return error();
}
if (['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 ||
prop.key.name === 'type' && ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(prop.value.value as string) === -1 ||
prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean' ||
prop.key.name === 'attribute' && typeof prop.value.value !== 'string'
) {
return error();
}
component_options.customElement.props[property.key.name][prop.key.name] = prop.value.value;
}
component_options.ceProps[property.key.name][prop.key.name] = prop.value.value;
}
}
const shadow = value[0].expression.properties.find(
(prop: any) => prop.key.name === 'shadow'
);
if (shadow) {
const shadowdom = shadow.value?.value;
if (shadowdom !== 'open' && shadowdom !== 'none') {
return component.error(shadow, compiler_errors.invalid_shadow_attribute);
}
component_options.customElement.shadow = shadowdom;
}
break;
}
@ -1664,7 +1684,7 @@ function process_component_options(component: Component, nodes) {
}
default:
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown);
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown(name));
}
} else {
return component.error(attribute, compiler_errors.invalid_options_attribute);

@ -202,18 +202,23 @@ export default {
code: 'invalid-tag-property',
message: "tag name must be two or more words joined by the '-' character"
},
invalid_customElement_attribute: {
code: 'invalid-customElement-attribute',
message: "'customElement' must be a string literal defining a valid custom element name or an object of the form "+
"{ tag: string; shadow?: 'open' | 'none'; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }"
},
invalid_tag_attribute: {
code: 'invalid-tag-attribute',
message: "'tag' must be a string literal"
},
invalid_shadowdom_attribute: {
code: 'invalid-shadowdom-attribute',
message: "'shadowdom' must be either 'open' or 'none'"
invalid_shadow_attribute: {
code: 'invalid-shadow-attribute',
message: "'shadow' must be either 'open' or 'none'"
},
invalid_ceProps_attribute: {
code: 'invalid-ceProps-attribute',
message: "'ceProps' must be a statically analyzable object literal of the form " +
"'{ prop: { attribute?: string; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object', reflect?: boolean; } }'"
invalid_props_attribute: {
code: 'invalid-props-attribute',
message: "'props' must be a statically analyzable object literal of the form " +
"'{ [key: string]: { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }'"
},
invalid_namespace_property: (namespace: string, suggestion?: string) => ({
code: 'invalid-namespace-property',
@ -227,10 +232,10 @@ export default {
code: `invalid-${name}-value`,
message: `${name} attribute must be true or false`
}),
invalid_options_attribute_unknown: {
invalid_options_attribute_unknown: (name: string) => ({
code: 'invalid-options-attribute',
message: '<svelte:options> unknown attribute'
},
message: `<svelte:options> unknown attribute '${name}'`
}),
invalid_options_attribute: {
code: 'invalid-options-attribute',
message: "<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes"

@ -6,9 +6,9 @@ import { ARIAPropertyDefinition } from 'aria-query';
* @internal
*/
export default {
custom_element_no_tag: {
code: 'custom-element-no-tag',
message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>'
tag_option_deprecated: {
code: 'tag-option-deprecated',
message: "'tag' option is deprecated — use 'customElement' instead"
},
unused_export_let: (component: string, property: string) => ({
code: 'unused-export-let',

@ -544,7 +544,7 @@ export default function dom(
if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => {
def[prop.export_name] = component.component_options.ceProps?.[prop.export_name] || {};
def[prop.export_name] = component.component_options.customElement?.props?.[prop.export_name] || {};
if (prop.is_boolean && !def[prop.export_name].type) {
def[prop.export_name].type = 'Boolean';
}
@ -555,11 +555,11 @@ export default function dom(
.filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name))
.map(accessor => `"${accessor.key.name}"`)
.join(',');
const use_shadow_dom = component.component_options.shadowdom !== 'none' ? 'true' : 'false';
const use_shadow_dom = component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
if (component.tag != null) {
if (component.component_options.customElement?.tag) {
body.push(
b`@_customElements.define("${component.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`
b`@_customElements.define("${component.component_options.customElement.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`
);
} else {
body.push(b`@create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`);

@ -291,11 +291,12 @@ if (typeof HTMLElement === 'function') {
}
function get_custom_element_value(prop: string, value: any, props_definition: Record<string, CustomElementPropDefinition>, transform?: 'toAttribute' | 'toProp') {
value = props_definition[prop]?.type === 'Boolean' && typeof value !== 'boolean' ? value != null : value;
const type = props_definition[prop]?.type;
value = type === 'Boolean' && typeof value !== 'boolean' ? value != null : value;
if (!transform || !props_definition[prop]) {
return value;
} else if (transform === 'toAttribute') {
switch (props_definition[prop].type) {
switch (type) {
case 'Object':
case 'Array':
return value == null ? null : JSON.stringify(value);
@ -307,7 +308,7 @@ function get_custom_element_value(prop: string, value: any, props_definition: Re
return value;
}
} else {
switch (props_definition[prop].type) {
switch (type) {
case 'Object':
case 'Array':
return value && JSON.parse(value);
@ -322,9 +323,9 @@ function get_custom_element_value(prop: string, value: any, props_definition: Re
}
interface CustomElementPropDefinition {
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
attribute?: string;
}
/**

@ -284,11 +284,11 @@ export class SvelteComponentTyped<
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Component extends SvelteComponentDev = SvelteComponentDev> = new (
export type ComponentType<Component extends SvelteComponentDev = SvelteComponentDev> = (new (
options: ComponentConstructorOptions<
Component extends SvelteComponentDev<infer Props> ? Props : Record<string, any>
>
) => Component & {
) => Component) & {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement
};

@ -1,4 +1,4 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<script>
export let name;
@ -7,4 +7,3 @@
<p>name: {name}</p>
<p>$$props: {JSON.stringify($$props)}</p>
<p>$$restProps: {JSON.stringify($$restProps)}</p>

@ -1,4 +1,4 @@
<svelte:options tag={null} />
<svelte:options customElement={null} />
<script>
import "./my-widget.svelte";

@ -1,4 +1,4 @@
<svelte:options tag="my-widget" />
<svelte:options customElement="my-widget" />
<slot>fallback</slot>
<slot name="named"><p>named fallback</p></slot>

@ -1,8 +1,10 @@
<svelte:options customElement="custom-element" />
<script>
let data = '';
let data = "";
if ($$slots.b) {
data = 'foo';
data = "foo";
}
export function getData() {
@ -12,20 +14,18 @@
function toString(data) {
const result = {};
const sortedKeys = Object.keys(data).sort();
sortedKeys.forEach(key => result[key] = data[key]);
sortedKeys.forEach((key) => (result[key] = data[key]));
return JSON.stringify(result);
}
</script>
<svelte:options tag="custom-element"/>
<slot></slot>
<slot name="a"></slot>
<slot />
<slot name="a" />
<p>$$slots: {toString($$slots)}</p>
{#if $$slots.b}
<div>
<slot name="b"></slot>
<slot name="b" />
</div>
{:else}
<p>Slot b is not available</p>
{/if}
{/if}

@ -1,4 +1,4 @@
<svelte:options tag="custom-element" />
<svelte:options customElement="custom-element" />
<script>
export let name;

@ -1,9 +1,11 @@
<svelte:options
tag="custom-element"
ceProps={{
camelCase: { attribute: "camel-case" },
camelCase2: { reflect: true },
anArray: { attribute: "an-array", type: "Array", reflect: true },
customElement={{
tag: "custom-element",
props: {
camelCase: { attribute: "camel-case" },
camelCase2: { reflect: true },
anArray: { attribute: "an-array", type: "Array", reflect: true },
},
}}
/>

@ -1,7 +1,9 @@
<svelte:options
tag="custom-element"
ceProps={{
name: { reflect: false, type: "String", attribute: "name" },
customElement={{
tag: "custom-element",
props: {
name: { reflect: false, type: "String", attribute: "name" },
},
}}
/>

@ -1,4 +1,4 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<script>
export function updateFoo(value) {

@ -1,9 +1,9 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<span class='icon'></span>
<span class="icon" />
<style>
.icon::before {
content: '\ff'
content: "\ff";
}
</style>

@ -1,4 +1,4 @@
<svelte:options tag="custom-element" />
<svelte:options customElement="custom-element" />
<script>
import { createEventDispatcher } from "svelte";

@ -2,14 +2,14 @@ export default {
warnings: [{
code: 'avoid-is',
message: "The 'is' attribute is not supported cross-browser and should be avoided",
pos: 98,
pos: 109,
start: {
character: 98,
character: 109,
column: 8,
line: 7
},
end: {
character: 116,
character: 127,
column: 26,
line: 7
}

@ -1,7 +1,7 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<script>
import './custom-button.js';
import "./custom-button.js";
</script>
<button is="custom-button">click me</button>
<button is="custom-button">click me</button>

@ -1,11 +1,11 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<div>
<slot>
<p>default fallback content</p>
</slot>
<slot name='foo'>
<slot name="foo">
<p>foo fallback content</p>
</slot>
</div>

@ -1,4 +1,4 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<script>
export let name;

@ -1,4 +1,4 @@
<svelte:options tag="my-counter" />
<svelte:options customElement="my-counter" />
<script>
import { getContext } from "svelte";

@ -1,4 +1,4 @@
<svelte:options tag="my-app" />
<svelte:options customElement="my-app" />
<script>
import { setContext } from "svelte";

@ -1,4 +1,4 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<p>styled</p>

@ -1,4 +1,4 @@
<svelte:options tag="my-app"/>
<svelte:options customElement="my-app" />
<script>
export let foo;

@ -1,4 +1,4 @@
<svelte:options tag="custom-element" shadowdom="none" />
<svelte:options customElement={{ tag: "custom-element", shadow: "none" }} />
<script>
export let name;

@ -1,17 +0,0 @@
export default {
warnings: [{
code: 'custom-element-no-tag',
message: "No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag=\"my-thing\"/>. To hide this warning, use <svelte:options tag={null}/>",
pos: 0,
start: {
character: 0,
column: 0,
line: 1
},
end: {
character: 0,
column: 0,
line: 1
}
}]
};

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

@ -1,15 +0,0 @@
import * as assert from 'assert';
import { tick } from 'svelte';
import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal';
export default async function (target) {
customElements.define('no-tag', create_custom_element(CustomElement, {name: {}}, [], [], true));
target.innerHTML = '<no-tag name="world"></no-tag>';
await tick();
const el = target.querySelector('no-tag');
const h1 = el.shadowRoot.querySelector('h1');
assert.equal(h1.textContent, 'Hello world!');
}

@ -1,17 +0,0 @@
export default {
warnings: [{
code: 'custom-element-no-tag',
message: "No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag=\"my-thing\"/>. To hide this warning, use <svelte:options tag={null}/>",
pos: 0,
start: {
character: 0,
column: 0,
line: 1
},
end: {
character: 18,
column: 18,
line: 1
}
}]
};

@ -1,7 +0,0 @@
<svelte:options />
<script>
export let name;
</script>
<h1>Hello {name}!</h1>

@ -1,15 +0,0 @@
import * as assert from 'assert';
import { tick } from 'svelte';
import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal';
export default async function (target) {
customElements.define('no-tag', create_custom_element(CustomElement, { name: {}}, [], [], true));
target.innerHTML = '<no-tag name="world"></no-tag>';
await tick();
const el = target.querySelector('no-tag');
const h1 = el.shadowRoot.querySelector('h1');
assert.equal(h1.textContent, 'Hello world!');
}

@ -1,5 +1,3 @@
<svelte:options tag={null} />
<script>
export let name;
</script>

@ -1,4 +1,4 @@
<svelte:options tag="my-app" />
<svelte:options customElement="my-app" />
<script>
import { onMount } from "svelte";

@ -1,8 +1,8 @@
<svelte:options tag="my-app"/>
<svelte:options customElement="my-app" />
<script>
import { onMount, onDestroy } from 'svelte';
import { onMount, onDestroy } from "svelte";
let el;
let parentEl;
@ -11,12 +11,12 @@
return () => {
parentEl.dataset.onMountDestroyed = true;
}
};
});
onDestroy(() => {
parentEl.dataset.destroyed = true;
})
});
</script>
<div bind:this={el}></div>
<div bind:this={el} />

@ -1,9 +1,9 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<script>
import './my-widget.svelte';
import "./my-widget.svelte";
export let items = ['a', 'b', 'c'];
export let items = ["a", "b", "c"];
export let flagged = false;
</script>

@ -1,4 +1,4 @@
<svelte:options tag="my-widget"/>
<svelte:options customElement="my-widget" />
<script>
export let items = [];
@ -7,6 +7,6 @@
</script>
<p>{items.length} items</p>
<p>{items.join(', ')}</p>
<p>{flag1 ? 'flagged (dynamic attribute)' : 'not flagged'}</p>
<p>{flag2 ? 'flagged (static attribute)' : 'not flagged'}</p>
<p>{items.join(", ")}</p>
<p>{flag1 ? "flagged (dynamic attribute)" : "not flagged"}</p>
<p>{flag2 ? "flagged (static attribute)" : "not flagged"}</p>

@ -1,6 +1,8 @@
<svelte:options
tag="custom-element"
ceProps={{ red: { reflect: true, type: "Boolean" } }}
customElement={{
tag: "custom-element",
props: { red: { reflect: true, type: "Boolean" } },
}}
/>
<script>

@ -1,4 +1,9 @@
<svelte:options tag="my-widget" ceProps={{ red: { reflect: true } }} />
<svelte:options
customElement={{
tag: "my-widget",
props: { red: { reflect: true } },
}}
/>
<script>
export let red = false;

@ -350,8 +350,7 @@ export async function executeBrowserTest(browser, launchPuppeteer, additionalAss
const page = await browser.newPage();
page.on('console', (type) => {
// @ts-ignore -- TODO: Fix type
console[type._type](type._text);
console[type.type()](type.text());
});
page.on('error', error => {

@ -1,4 +1,4 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />
<div>fades in</div>
@ -8,7 +8,11 @@
}
@keyframes foo {
0% { opacity: 0; }
100% { opacity: 1; }
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>

@ -1 +1 @@
<svelte:options tag="custom-element" />
<svelte:options customElement="custom-element" />

@ -1 +1 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />

@ -1 +1 @@
<svelte:options tag="custom-element"/>
<svelte:options customElement="custom-element" />

@ -1 +1 @@
<svelte:options tag="invalid"/>
<svelte:options customElement="invalid" />

Loading…
Cancel
Save