diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index b2c2fff248..c90272620c 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -544,6 +544,9 @@ export default class Component {
extract_names(declarator.id).forEach(name => {
const variable = this.var_lookup.get(name);
variable.export_name = name;
+ if (declarator.init?.type === 'Literal' && typeof declarator.init.value === 'boolean') {
+ variable.is_boolean = true;
+ }
if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name));
}
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index 058c132281..200dbdb2ba 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -576,14 +576,14 @@ export default function dom(
}
`[0] as ClassDeclaration;
- const props_str = writable_props.map(prop => `"${prop.export_name}"`).join(',');
+ const props_str = JSON.stringify(writable_props.map(prop => prop.is_boolean ? { name: prop.export_name, type: 'boolean' } : prop.export_name));
const slots_str = [...component.slots.keys()].map(key => `"${key}"`).join(',');
const accessors_str = accessors
.filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name))
.map(accessor => `"${accessor.key.name}"`)
.join(',');
body.push(
- b`@_customElements.define("${component.tag}", @create_custom_element(${name}, [${props_str}], [${slots_str}], [${accessors_str}]));`
+ b`@_customElements.define("${component.tag}", @create_custom_element(${name}, ${props_str}, [${slots_str}], [${accessors_str}]));`
);
}
diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts
index 4ae53594bb..5767f1523d 100644
--- a/src/compiler/interfaces.ts
+++ b/src/compiler/interfaces.ts
@@ -206,7 +206,10 @@ export interface AppendTarget {
export interface Var {
name: string;
- export_name?: string; // the `bar` in `export { foo as bar }`
+ /** the `bar` in `export { foo as bar }` or `export let bar` */
+ export_name?: string;
+ /** true if assigned a boolean default value (`export let foo = true`) */
+ is_boolean?: boolean;
injected?: boolean;
module?: boolean;
mutated?: boolean;
diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts
index 2a1655a896..b370bfbff0 100644
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -1,7 +1,7 @@
import { add_render_callback, flush, flush_render_callbacks, schedule_update, dirty_components, tick } from './scheduler';
import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
-import { children, detach, start_hydrating, end_hydrating, set_custom_element_data, get_custom_elements_slots, insert } from './dom';
+import { children, detach, start_hydrating, end_hydrating, get_custom_elements_slots, insert } from './dom';
import { transition_in } from './transitions';
import { T$$ } from './types';
import { ComponentType } from './dev';
@@ -151,6 +151,7 @@ if (typeof HTMLElement === 'function') {
private $$connected = false;
private $$data = {};
private $$reflecting = false;
+ private $$boolean_props: string[] = [];
constructor(
private $$componentCtor: ComponentType,
@@ -171,13 +172,6 @@ if (typeof HTMLElement === 'function') {
connectedCallback() {
this.$$connected = true;
if (!this.$$component) {
- 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;
@@ -209,22 +203,25 @@ if (typeof HTMLElement === 'function') {
}
}
+ for (const attribute of this.attributes) {
+ // this.$$data takes precedence over this.attributes
+ if (!(attribute.name in this.$$data)) {
+ this.$$data[attribute.name] = get_custom_element_value(attribute.name, attribute.value, this.$$boolean_props);
+ }
+ }
+
// 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: {
+ ...this.$$data,
$$slots,
$$scope: {
ctx: []
}
}
});
- // ensures that 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
- });
}
}
@@ -233,8 +230,8 @@ if (typeof HTMLElement === 'function') {
attributeChangedCallback(attr: string, _oldValue: any, newValue: any) {
if (this.$$reflecting) return;
- set_custom_element_data(this.$$data, attr, newValue);
- this.$$component![attr] = this.$$data;
+ this.$$data[attr] = get_custom_element_value(attr, newValue, this.$$boolean_props);
+ this.$$component![attr] = this.$$data[attr];
}
disconnectedCallback() {
@@ -261,6 +258,10 @@ function camelToHyphen(str: string) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
+function get_custom_element_value(prop, value, boolean_attrs) {
+ return value === '' && boolean_attrs.indexOf(prop) !== -1 ? true : value;
+}
+
/**
* Turn a Svelte component into a custom element.
* @param Component A Svelte component constructor
@@ -272,14 +273,18 @@ function camelToHyphen(str: string) {
*/
export function create_custom_element(
Component: ComponentType,
- props: string[],
+ props: (string | { name: string; type: 'boolean' })[],
slots: string[],
accessors: string[],
styles?: string,
) {
+ const prop_names = props.map((prop) => (typeof prop === 'string' ? prop : prop.name));
+ const boolean_props = props.filter((prop) => typeof prop !== 'string').map((prop) => (prop as { name:string }).name);
+
const Class = class extends SvelteElement {
constructor() {
super(Component, slots);
+ this.$$boolean_props = boolean_props;
if (styles) {
const style = document.createElement('style');
style.textContent = styles;
@@ -288,7 +293,7 @@ export function create_custom_element(
}
static get observedAttributes() {
- return props;
+ return prop_names;
}
};
@@ -301,26 +306,26 @@ export function create_custom_element(
},
set(value) {
- this.$$data[prop] = value;
+ this.$$data[prop] = get_custom_element_value(prop, value, boolean_props);
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;
}
+
+ if(should_reflect.indexOf(typeof value) !== -1 || value == null) {
+ this.$$reflecting = true;
+ if (value === false || value == null) {
+ this.removeAttribute(prop);
+ } else {
+ this.setAttribute(prop, value);
+ }
+ this.$$reflecting = false;
+ }
}
})
}
- props.forEach((prop) => {
+ prop_names.forEach((prop) => {
createProperty(prop, prop);
// will be ce.camcelcase = "foo"
const lower = prop.toLowerCase();
diff --git a/test/custom-elements/samples/action/test.js b/test/custom-elements/samples/action/test.js
index 6bf1d3850a..e3fa0a808f 100644
--- a/test/custom-elements/samples/action/test.js
+++ b/test/custom-elements/samples/action/test.js
@@ -1,14 +1,17 @@
+import { tick } from 'svelte';
import * as assert from 'assert';
import './main.svelte';
-export default function (target) {
+export default async function (target) {
target.innerHTML = '';
const el = target.querySelector('custom-element');
- assert.deepEqual(el.events, ['foo']);
+ const events = el.events; // need to get the array reference, else it's gone when destroyed
+ assert.deepEqual(events, ['foo']);
el.name = 'bar';
- assert.deepEqual(el.events, ['foo', 'bar']);
+ assert.deepEqual(events, ['foo', 'bar']);
target.innerHTML = '';
- assert.deepEqual(el.events, ['foo', 'bar', 'destroy']);
+ await tick();
+ assert.deepEqual(events, ['foo', 'bar', 'destroy']);
}
diff --git a/test/custom-elements/samples/reflect-attributes/main.svelte b/test/custom-elements/samples/reflect-attributes/main.svelte
index 4aadb8bc06..726b0838c9 100644
--- a/test/custom-elements/samples/reflect-attributes/main.svelte
+++ b/test/custom-elements/samples/reflect-attributes/main.svelte
@@ -2,7 +2,7 @@
diff --git a/test/custom-elements/samples/reflect-attributes/my-widget.svelte b/test/custom-elements/samples/reflect-attributes/my-widget.svelte
index ef6d071d2c..02685c8cb3 100644
--- a/test/custom-elements/samples/reflect-attributes/my-widget.svelte
+++ b/test/custom-elements/samples/reflect-attributes/my-widget.svelte
@@ -1,7 +1,7 @@