diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index 7d0dce831536..799821c29c04 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -462,10 +462,14 @@ export default function dom(
class ${name} extends @SvelteElement {
constructor(options) {
super();
-
+ if (options) {
+ this.$$setup(options);
+ }
+ }
+ $$setup(options) {
${css.code && b`this.shadowRoot.innerHTML = \`\`;`}
- @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
+ @init(this, { props: options ? options.props : null, target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}
@@ -517,7 +521,7 @@ export default function dom(
constructor(options) {
super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
- @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
+ @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts
index 459a78031a04..489623ac0d8f 100644
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -174,7 +174,23 @@ if (typeof HTMLElement === 'function') {
this.attachShadow({ mode: 'open' });
}
+ // placeholder object to allow props to be set pre-$$setup
+ $$initialProps: Record | null = {};
+ $$setup: (options) => void;
+
connectedCallback() {
+ if (!this.$$) {
+ // wasn't set up from constructor as options were not ready
+ const options = is_empty(this.$$initialProps) ?
+ null:
+ {
+ props: this.$$initialProps
+ };
+
+ this.$$setup(options);
+ // clean up, prevent reuse of $$initialProps
+ this.$$initialProps = null;
+ }
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
@@ -182,7 +198,11 @@ if (typeof HTMLElement === 'function') {
}
}
+ // initial implementation of method, will be overridden on setup
attributeChangedCallback(attr, _oldValue, newValue) {
+ if (this.$$initialProps) {
+ this.$$initialProps[attr] = newValue;
+ }
this[attr] = newValue;
}
@@ -203,6 +223,11 @@ if (typeof HTMLElement === 'function') {
}
$set($$props) {
+ if (this.$$initialProps && $$props) {
+ for (const attr of Object.getOwnPropertyNames($$props)) {
+ this.$$initialProps[attr] = $$props[attr];
+ }
+ }
if (this.$$set && !is_empty($$props)) {
this.$$.skip_bound = true;
this.$$set($$props);
diff --git a/test/custom-elements/samples/props-after-create/main.svelte b/test/custom-elements/samples/props-after-create/main.svelte
new file mode 100644
index 000000000000..22c91751d099
--- /dev/null
+++ b/test/custom-elements/samples/props-after-create/main.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+{items.length} items
+{items.join(', ')}
diff --git a/test/custom-elements/samples/props-after-create/test.js b/test/custom-elements/samples/props-after-create/test.js
new file mode 100644
index 000000000000..c4dc6b31d0a8
--- /dev/null
+++ b/test/custom-elements/samples/props-after-create/test.js
@@ -0,0 +1,28 @@
+import * as assert from 'assert';
+import CustomElement from './main.svelte';
+
+export default function (target) {
+
+ // initialize without options to simulate instantiation within HTML
+ const el = new CustomElement();
+
+ assert.equal(el.outerHTML, '');
+
+ el.items = ['a', 'b', 'c'];
+ const p0 = el.shadowRoot.querySelector('p');
+
+ // shouldn't be instantitated yet
+ assert.equal(p0, undefined);
+
+ // add to to DOM to trigger setup
+ target.appendChild(el);
+
+ const [p1, p2] = el.shadowRoot.querySelectorAll('p');
+ assert.equal(p1.textContent, '3 items');
+ assert.equal(p2.textContent, 'a, b, c');
+
+ el.items = ['d', 'e', 'f', 'g', 'h'];
+
+ assert.equal(p1.textContent, '5 items');
+ assert.equal(p2.textContent, 'd, e, f, g, h');
+}
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js
index a0a0ebe0211b..91ef177ec63f 100644
--- a/test/js/samples/css-shadow-dom-keyframes/expected.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected.js
@@ -33,8 +33,26 @@ function create_fragment(ctx) {
class Component extends SvelteElement {
constructor(options) {
super();
+
+ if (options) {
+ this.$$setup(options);
+ }
+ }
+
+ $$setup(options) {
this.shadowRoot.innerHTML = ``;
- init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {});
+
+ init(
+ this,
+ {
+ props: options ? options.props : null,
+ target: this.shadowRoot
+ },
+ null,
+ create_fragment,
+ safe_not_equal,
+ {}
+ );
if (options) {
if (options.target) {
@@ -45,4 +63,4 @@ class Component extends SvelteElement {
}
customElements.define("custom-element", Component);
-export default Component;
\ No newline at end of file
+export default Component;