pull/8457/head
Simon Holthausen 3 years ago
parent d42ca041dd
commit 52f911cae4

@ -192,7 +192,7 @@ export default class Component {
this.pop_ignores(); this.pop_ignores();
this.elements.forEach(element => this.stylesheet.apply(element)); this.elements.forEach(element => this.stylesheet.apply(element));
if (!compile_options.customElement) this.stylesheet.reify(); this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this); this.stylesheet.warn_on_unused_selectors(this);
} }

@ -407,7 +407,7 @@ export default class Stylesheet {
}); });
} }
render(file: string, should_transform_selectors: boolean) { render(file: string) {
if (!this.has_styles) { if (!this.has_styles) {
return { code: null, map: null }; return { code: null, map: null };
} }
@ -421,12 +421,10 @@ export default class Stylesheet {
} }
}); });
if (should_transform_selectors) { const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased()));
const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); this.children.forEach((child: (Atrule | Rule)) => {
this.children.forEach((child: (Atrule | Rule)) => { child.transform(code, this.id, this.keyframes, max);
child.transform(code, this.id, this.keyframes, max); });
});
}
let c = 0; let c = 0;
this.children.forEach(child => { this.children.forEach(child => {

@ -6,7 +6,7 @@ import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic'; import { extract_names, Scope } from 'periscopic';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ImportDeclaration, ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; import { ImportDeclaration, ClassDeclaration, Node, Statement, ObjectExpression, Expression } from 'estree';
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code'; import { apply_preprocessor_sourcemap } from '../../utils/mapped_code';
import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { flatten } from '../../utils/flatten'; import { flatten } from '../../utils/flatten';
@ -25,9 +25,6 @@ export default function dom(
block.has_outro_method = true; block.has_outro_method = true;
// prevent fragment being created twice (#1063)
if (options.customElement) block.chunks.create.push(b`this.c = @noop;`);
const body = []; const body = [];
if (renderer.file_var) { if (renderer.file_var) {
@ -35,7 +32,7 @@ export default function dom(
body.push(b`const ${renderer.file_var} = ${file};`); body.push(b`const ${renderer.file_var} = ${file};`);
} }
const css = component.stylesheet.render(options.filename, !options.customElement); const css = component.stylesheet.render(options.filename);
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
@ -52,7 +49,6 @@ export default function dom(
const add_css = component.get_unique_name('add_css'); const add_css = component.get_unique_name('add_css');
const should_add_css = ( const should_add_css = (
!options.customElement &&
!!styles && !!styles &&
options.css === 'injected' options.css === 'injected'
); );
@ -519,8 +515,35 @@ export default function dom(
} }
} }
if (options.customElement) { const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
const optional_parameters = [];
if (should_add_css) {
optional_parameters.push(add_css);
} else if (dirty) {
optional_parameters.push(x`null`);
}
if (dirty) {
optional_parameters.push(dirty);
}
const declaration = b`
class ${name} extends ${superclass} {
constructor(options) {
super(${options.dev && 'options'});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
}
}
`[0] as ClassDeclaration;
push_array(declaration.body.body, accessors);
body.push(declaration);
if (options.customElement && component.tag != null) {
let init_props = x`@attribute_to_object(this.attributes)`; let init_props = x`@attribute_to_object(this.attributes)`;
if (uses_slots) { if (uses_slots) {
init_props = x`{ ...${init_props}, $$slots: @get_custom_elements_slots(this) }`; init_props = x`{ ...${init_props}, $$slots: @get_custom_elements_slots(this) }`;
@ -553,57 +576,15 @@ export default function dom(
} }
`[0] as ClassDeclaration; `[0] as ClassDeclaration;
if (props.length > 0) { const props_str = writable_props.map(prop => `"${prop.export_name}"`).join(',');
declaration.body.body.push({ const slots_str = [...component.slots.keys()].map(key => `"${key}"`).join(',');
type: 'MethodDefinition', const accessors_str = accessors
kind: 'get', .filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name))
static: true, .map(accessor => `"${accessor.key.name}"`)
computed: false, .join(',');
key: { type: 'Identifier', name: 'observedAttributes' }, body.push(
value: x`function() { b`@_customElements.define("${component.tag}", @create_custom_element(${name}, [${props_str}], [${slots_str}], [${accessors_str}]));`
return [${props.map(prop => x`"${prop.export_name}"`)}]; );
}` as FunctionExpression
});
}
push_array(declaration.body.body, accessors);
body.push(declaration);
if (component.tag != null) {
body.push(b`
@_customElements.define("${component.tag}", ${name});
`);
}
} else {
const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
const optional_parameters = [];
if (should_add_css) {
optional_parameters.push(add_css);
} else if (dirty) {
optional_parameters.push(x`null`);
}
if (dirty) {
optional_parameters.push(dirty);
}
const declaration = b`
class ${name} extends ${superclass} {
constructor(options) {
super(${options.dev && 'options'});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
}
}
`[0] as ClassDeclaration;
push_array(declaration.body.body, accessors);
body.push(declaration);
} }
return { js: flatten(body), css }; return { js: flatten(body), css };

@ -132,6 +132,10 @@ export default class SlotWrapper extends Wrapper {
const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn});
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
${has_fallback && this.renderer.options.customElement && this.renderer.options.tag
// This ensures that fallback content is rendered into the <slot> element given by the custom element wrapper
? b`if (${slot_or_fallback}.$$c_e) ${this.fallback.name}(#ctx);`
: null}
`); `);
block.chunks.create.push( block.chunks.create.push(

@ -33,7 +33,7 @@ export default function ssr(
// TODO concatenate CSS maps // TODO concatenate CSS maps
const css = options.customElement ? const css = options.customElement ?
{ code: null, map: null } : { code: null, map: null } :
component.stylesheet.render(options.filename, true); component.stylesheet.render(options.filename);
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const props = component.vars.filter(variable => !variable.module && variable.export_name); const props = component.vars.filter(variable => !variable.module && variable.export_name);

@ -115,7 +115,7 @@ export default function tag(parser: Parser) {
: (regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' : (regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: name === 'svelte:fragment' ? 'SlotTemplate' : name === 'svelte:fragment' ? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack) ? 'Title' : name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; : name === 'slot' ? 'Slot' : 'Element';
const element: TemplateNode = { const element: TemplateNode = {
start, start,

@ -1,9 +1,10 @@
import { add_render_callback, flush, flush_render_callbacks, schedule_update, dirty_components } from './scheduler'; import { add_render_callback, flush, flush_render_callbacks, schedule_update, dirty_components, tick } from './scheduler';
import { current_component, set_current_component } from './lifecycle'; import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils'; import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
import { children, detach, start_hydrating, end_hydrating } from './dom'; import { children, detach, start_hydrating, end_hydrating, set_custom_element_data, get_custom_elements_slots, insert } from './dom';
import { transition_in } from './transitions'; import { transition_in } from './transitions';
import { T$$ } from './types'; import { T$$ } from './types';
import { ComponentType } from './dev';
export function bind(component, name, callback) { export function bind(component, name, callback) {
const index = component.$$.props[name]; const index = component.$$.props[name];
@ -21,29 +22,27 @@ export function claim_component(block, parent_nodes) {
block && block.l(parent_nodes); block && block.l(parent_nodes);
} }
export function mount_component(component, target, anchor, customElement) { export function mount_component(component, target, anchor) {
const { fragment, after_update } = component.$$; const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor); fragment && fragment.m(target, anchor);
if (!customElement) { // onMount happens before the initial afterUpdate
// onMount happens before the initial afterUpdate add_render_callback(() => {
add_render_callback(() => {
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); // if the component was destroyed immediately
// if the component was destroyed immediately // it will update the `$$.on_destroy` reference to `null`.
// it will update the `$$.on_destroy` reference to `null`. // the destructured on_destroy may still reference to the old array
// the destructured on_destroy may still reference to the old array if (component.$$.on_destroy) {
if (component.$$.on_destroy) { component.$$.on_destroy.push(...new_on_destroy);
component.$$.on_destroy.push(...new_on_destroy); } else {
} else { // Edge case - component was destroyed immediately,
// Edge case - component was destroyed immediately, // most likely as a result of a binding initialising
// most likely as a result of a binding initialising run_all(new_on_destroy);
run_all(new_on_destroy); }
} component.$$.on_mount = [];
component.$$.on_mount = []; });
});
}
after_update.forEach(add_render_callback); after_update.forEach(add_render_callback);
} }
@ -137,7 +136,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
} }
if (options.intro) transition_in(component.$$.fragment); if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor, options.customElement); mount_component(component, options.target, options.anchor);
end_hydrating(); end_hydrating();
flush(); flush();
} }
@ -148,59 +147,202 @@ export function init(component, options, instance, create_fragment, not_equal, p
export let SvelteElement; export let SvelteElement;
if (typeof HTMLElement === 'function') { if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement { SvelteElement = class extends HTMLElement {
$$: T$$; private $$component?: SvelteComponent;
$$set?: ($$props: any) => void; private $$connected = false;
constructor() { private $$data = {};
private $$reflecting = false;
constructor(
private $$componentCtor: ComponentType,
private $$slots: string[],
) {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
} }
connectedCallback() { addEventListener(type: string, listener: any, options?: any): void {
const { on_mount } = this.$$; // We can't determine upfront if the event is a custom event or not, so we have to
this.$$.on_disconnect = on_mount.map(run).filter(is_function); // listen to both. If someone uses a custom event with the same name as a regular
// browser event, this fires twice - we can't avoid that.
this.$$component!.$on(type, listener);
super.addEventListener(type, listener, options);
}
// @ts-ignore todo: improve typings connectedCallback() {
for (const key in this.$$.slotted) { this.$$connected = true;
// @ts-ignore todo: improve typings if (!this.$$component) {
this.appendChild(this.$$.slotted[key]); for (const attribute of this.attributes) {
// this.$$data takes precedence over this.attributes
if (!(attribute.name in this.$$data)) {
this.$$data[attribute.name] = attribute.value;
}
}
function create_slot(name: string) {
return () => {
let node: HTMLSlotElement;
return {
c: function create() {
node = document.createElement('slot');
if (name !== 'default') {
node.setAttribute('name', name);
}
},
m: function mount(target: HTMLElement, anchor?: HTMLElement) {
insert(target, node, anchor);
},
d: function destroy(detaching: boolean) {
if (detaching) {
detach(node)
}
},
$$c_e: true
};
};
}
let $$slots: Record<string, any> = {};
const existing_slots = get_custom_elements_slots(this);
for (const name of this.$$slots) {
if (name in existing_slots) {
$$slots[name] = [create_slot(name)];
}
}
// Dilemma: We need to set the component props eagerly or they have the wrong value for actions/onMount etc.
// Boolean attributes are represented by the empty string, and we don't know if they represent boolean or string props.
this.$$component = new this.$$componentCtor({
target: this.shadowRoot!,
props: {
$$slots,
$$scope: {
ctx: []
}
}
});
// ensures that <foo-bar boolean-attribute /> works correctly
Object.keys(this.$$data).forEach(key => {
set_custom_element_data(this, key, this.$$data[key]);
this.$$data[key] = this[key]; // "" -> true for boolean attributes
});
} }
} }
attributeChangedCallback(attr, _oldValue, newValue) { // TODO we don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte
this[attr] = newValue; // and setting attributes through setAttribute etc, this is probably helpful
attributeChangedCallback(attr: string, _oldValue: any, newValue: any) {
if (this.$$reflecting) return;
set_custom_element_data(this.$$data, attr, newValue);
this.$$component![attr] = this.$$data;
} }
disconnectedCallback() { disconnectedCallback() {
run_all(this.$$.on_disconnect); this.$$connected = false;
// In a microtask, because this could be a move within the DOM
tick().then(() => {
if (!this.$$connected) {
this.$$component!.$destroy();
this.$$component = undefined;
}
});
} }
};
}
$destroy() { /**
destroy_component(this, 1); * Attribute value types that should be reflected to the DOM. Helpful
this.$destroy = noop; * for people relying on the custom element's attributes to be present,
} * for example when using a CSS selector which relies on an attribute.
*/
const should_reflect = ['string', 'number', 'boolean'];
$on(type, callback) { function camelToHyphen(str: string) {
// TODO should this delegate to addEventListener? return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
if (!is_function(callback)) { }
return noop;
/**
* Turn a Svelte component into a custom element.
* @param Component A Svelte component constructor
* @param props The props to observe
* @param slots The slots to create
* @param accessors Other accessors besides the ones for props the component has
* @param styles Additional styles to apply to the shadow root (not needed for Svelte components compiled with `customElement: true`)
* @returns A custom element class
*/
export function create_custom_element(
Component: ComponentType,
props: string[],
slots: string[],
accessors: string[],
styles?: string,
) {
const Class = class extends SvelteElement {
constructor() {
super(Component, slots);
if (styles) {
const style = document.createElement('style');
style.textContent = styles;
this.shadowRoot!.appendChild(style);
} }
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); }
callbacks.push(callback);
return () => { static get observedAttributes() {
const index = callbacks.indexOf(callback); return props;
if (index !== -1) callbacks.splice(index, 1);
};
} }
};
$set($$props) { function createProperty(name: string, prop: string) {
if (this.$$set && !is_empty($$props)) { Object.defineProperty(Class.prototype, name, {
this.$$.skip_bound = true; get() {
this.$$set($$props); return this.$$component && prop in this.$$component
this.$$.skip_bound = false; ? this.$$component[prop]
: this.$$data[prop];
},
set(value) {
this.$$data[prop] = value;
if (this.$$component) {
if(should_reflect.indexOf(typeof value) !== -1) {
this.$$reflecting = true;
if (value === false || value == null) {
this.removeAttribute(prop);
} else {
this.setAttribute(prop, value);
}
this.$$reflecting = false;
}
this.$$component[prop] = value;
}
} }
})
}
props.forEach((prop) => {
createProperty(prop, prop);
// <c-e camelCase="foo" /> will be ce.camcelcase = "foo"
const lower = prop.toLowerCase();
if (lower !== prop) {
createProperty(lower, prop);
} }
}; // also support hyphenated version where <c-e camel-case="foo" /> will be ce['camel-case'] = "foo"
const hyphen = camelToHyphen(prop);
if (hyphen !== lower) {
createProperty(hyphen, prop)
}
});
accessors.forEach(accessor => {
Object.defineProperty(Class.prototype, accessor, {
get() {
return this.$$component?.[accessor];
},
})
});
return Class;
} }
/** /**

@ -0,0 +1,20 @@
<svelte:options tag="custom-element" />
<script>
export let name;
export let events = [];
function action(_node, name) {
events.push(name);
return {
update(name) {
events.push(name);
},
destroy() {
events.push("destroy");
},
};
}
</script>
<div use:action={name}>action</div>

@ -0,0 +1,14 @@
import * as assert from 'assert';
import './main.svelte';
export default function (target) {
target.innerHTML = '<custom-element name="foo"></custom-element>';
const el = target.querySelector('custom-element');
assert.deepEqual(el.events, ['foo']);
el.name = 'bar';
assert.deepEqual(el.events, ['foo', 'bar']);
target.innerHTML = '';
assert.deepEqual(el.events, ['foo', 'bar', 'destroy']);
}

@ -1,11 +1,8 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import './main.svelte';
export default function (target) { export default function (target) {
new CustomElement({ target.innerHTML = '<custom-element></custom-element>';
target
});
const icon = target.querySelector('custom-element').shadowRoot.querySelector('.icon'); const icon = target.querySelector('custom-element').shadowRoot.querySelector('.icon');
const before = getComputedStyle(icon, '::before'); const before = getComputedStyle(icon, '::before');

@ -0,0 +1,9 @@
<svelte:options tag="custom-element" />
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch("custom", "foo")}>bubble click</button>

@ -0,0 +1,18 @@
import * as assert from 'assert';
import './main.svelte';
export default function (target) {
target.innerHTML = '<custom-element></custom-element>';
const el = target.querySelector('custom-element');
const events = [];
el.addEventListener('custom', e => {
events.push(e.detail);
});
el.addEventListener('click', () => {
events.push('click');
});
el.shadowRoot.querySelector('button').click();
assert.deepEqual(events, ['foo', 'click']);
}

@ -1,11 +1,8 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import './main.svelte';
export default function (target) { export default function (target) {
new CustomElement({ target.innerHTML = '<custom-element></custom-element>';
target
});
assert.equal(target.innerHTML, '<custom-element></custom-element>'); assert.equal(target.innerHTML, '<custom-element></custom-element>');
const el = target.querySelector('custom-element'); const el = target.querySelector('custom-element');

@ -13,5 +13,5 @@ export default function (target) {
const [slot0, slot1] = div.children; const [slot0, slot1] = div.children;
assert.equal(slot0.assignedNodes()[1], target.querySelector('strong')); assert.equal(slot0.assignedNodes()[1], target.querySelector('strong'));
assert.equal(slot1.assignedNodes().length, 0); assert.equal(slot1.innerHTML, 'foo fallback content');
} }

@ -1,7 +0,0 @@
<svelte:options tag="my-counter"/>
<script>
export let count = 0;
</script>
<button on:click='{() => count += 1}'>count: {count}</button>

@ -1,10 +0,0 @@
<svelte:options tag="my-app"/>
<script>
import Counter from './Counter.svelte';
export let count;
</script>
<Counter bind:count/>
<p>clicked {count} times</p>

@ -1,17 +0,0 @@
import * as assert from 'assert';
import './main.svelte';
export default async function (target) {
target.innerHTML = '<my-app/>';
const el = target.querySelector('my-app');
const counter = el.shadowRoot.querySelector('my-counter');
const button = counter.shadowRoot.querySelector('button');
assert.equal(counter.count, 0);
assert.equal(counter.shadowRoot.innerHTML, '<button>count: 0</button>');
await button.dispatchEvent(new MouseEvent('click'));
assert.equal(counter.count, 1);
assert.equal(counter.shadowRoot.innerHTML, '<button>count: 1</button>');
}

@ -0,0 +1,14 @@
<svelte:options tag="my-counter" />
<script>
export let count = 0;
</script>
<slot />
<button on:click={() => (count += 1)}>count: {count}</button>
<style>
button {
color: red;
}
</style>

@ -0,0 +1,13 @@
<svelte:options tag="my-app" />
<script>
import Counter from "./Counter.svelte";
export let count;
export let counter;
</script>
<Counter bind:count bind:this={counter}>
<span>slot {count}</span>
</Counter>
<p>clicked {count} times</p>

@ -0,0 +1,20 @@
import * as assert from 'assert';
import './main.svelte';
export default async function (target) {
target.innerHTML = '<my-app/>';
const el = target.querySelector('my-app');
const button = el.shadowRoot.querySelector('button');
const span = el.shadowRoot.querySelector('span');
assert.equal(el.counter.count, 0);
assert.equal(button.innerHTML, 'count: 0');
assert.equal(span.innerHTML, 'slot 0');
assert.equal(getComputedStyle(button).color, 'rgb(255, 0, 0)');
await button.dispatchEvent(new MouseEvent('click'));
assert.equal(el.counter.count, 1);
assert.equal(button.innerHTML, 'count: 1');
assert.equal(span.innerHTML, 'slot 1');
}

@ -1,12 +1,9 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import './main.svelte';
export default function (target) { export default function (target) {
target.innerHTML = '<p>unstyled</p>'; target.innerHTML = '<p>unstyled</p>';
target.appendChild(document.createElement('custom-element'));
new CustomElement({
target
});
const unstyled = target.querySelector('p'); const unstyled = target.querySelector('p');
const styled = target.querySelector('custom-element').shadowRoot.querySelector('p'); const styled = target.querySelector('custom-element').shadowRoot.querySelector('p');

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

@ -1,18 +0,0 @@
import * as assert from 'assert';
import CustomElement from './main.svelte';
export default function (target) {
new CustomElement({
target,
props: {
name: 'world'
}
});
assert.equal(target.innerHTML, '<custom-element></custom-element>');
const el = target.querySelector('custom-element');
const h1 = el.shadowRoot.querySelector('h1');
assert.equal(h1.textContent, 'Hello world!');
}

@ -1,8 +1,9 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal';
export default function (target) { export default function (target) {
customElements.define('no-tag', CustomElement); customElements.define('no-tag', create_custom_element(CustomElement, ['name'], [], []));
target.innerHTML = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
const el = target.querySelector('no-tag'); const el = target.querySelector('no-tag');

@ -1,8 +1,9 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal';
export default function (target) { export default function (target) {
customElements.define('no-tag', CustomElement); customElements.define('no-tag', create_custom_element(CustomElement, ['name'], [], []));
target.innerHTML = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
const el = target.querySelector('no-tag'); const el = target.querySelector('no-tag');

@ -1,8 +1,9 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal';
export default function (target) { export default function (target) {
customElements.define('no-tag', CustomElement); customElements.define('no-tag', create_custom_element(CustomElement, ['name'], [], []));
target.innerHTML = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
const el = target.querySelector('no-tag'); const el = target.querySelector('no-tag');

@ -1,14 +1,14 @@
<svelte:options tag="my-app"/> <svelte:options tag="my-app" />
<script> <script>
import { onMount } from 'svelte'; import { onMount } from "svelte";
export let prop = false; export let prop = false;
export let propsInitialized; export let propsInitialized;
export let wasCreated; export let wasCreated;
onMount(() => { onMount(() => {
propsInitialized = prop !== false; propsInitialized = prop !== false;
wasCreated = true; wasCreated = true;
}); });
</script> </script>

@ -1,10 +1,13 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { tick } from 'svelte';
import './main.svelte'; import './main.svelte';
export default function (target) { export default async function (target) {
target.innerHTML = '<my-app prop/>'; target.innerHTML = '<my-app prop/>';
const el = target.querySelector('my-app'); const el = target.querySelector('my-app');
await tick();
assert.ok(el.wasCreated); assert.ok(el.wasCreated);
assert.ok(el.propsInitialized); assert.ok(el.propsInitialized);
} }

@ -1,11 +1,14 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { tick } from 'svelte';
import './main.svelte'; import './main.svelte';
export default function (target) { export default async function (target) {
target.innerHTML = '<my-app/>'; target.innerHTML = '<my-app/>';
const el = target.querySelector('my-app'); const el = target.querySelector('my-app');
target.removeChild(el); target.removeChild(el);
await tick();
assert.ok(target.dataset.onMountDestroyed); assert.ok(target.dataset.onMountDestroyed);
assert.equal(target.dataset.destroyed, undefined); assert.ok(target.dataset.destroyed);
} }

@ -1,10 +1,8 @@
import * as assert from 'assert'; import * as assert from 'assert';
import CustomElement from './main.svelte'; import './main.svelte';
export default function (target) { export default function (target) {
new CustomElement({ target.innerHTML = '<custom-element></custom-element>';
target
});
assert.equal(target.innerHTML, '<custom-element></custom-element>'); assert.equal(target.innerHTML, '<custom-element></custom-element>');

@ -0,0 +1,20 @@
<svelte:options tag="custom-element" />
<script>
import "./my-widget.svelte";
export let red;
red;
</script>
<div>hi</div>
<p>hi</p>
<my-widget red white />
<style>
:host([red]) div {
color: red;
}
:host([white]) p {
color: white;
}
</style>

@ -0,0 +1,18 @@
<svelte:options tag="my-widget" />
<script>
export let red;
red;
</script>
<div>hi</div>
<p>hi</p>
<style>
:host([red]) div {
color: red;
}
:host([white]) p {
color: white;
}
</style>

@ -0,0 +1,19 @@
import * as assert from 'assert';
import './main.svelte';
export default function (target) {
target.innerHTML = '<custom-element red white></custom-element>';
const ceRoot = target.querySelector('custom-element').shadowRoot;
const div = ceRoot.querySelector('div');
const p = ceRoot.querySelector('p');
assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)');
const innerRoot = ceRoot.querySelector('my-widget').shadowRoot;
const innerDiv = innerRoot.querySelector('div');
const innerP = innerRoot.querySelector('p');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(innerP).color, 'rgb(255, 255, 255)');
}

@ -1,6 +1,7 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"include": ["."], "include": ["."],
"exclude": ["./**/_output/**/*"],
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,

Loading…
Cancel
Save