From 07e2dde83d5ea46f29fb57049c57fcd5c6e4ba64 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Thu, 9 Jul 2020 11:45:30 +0930 Subject: [PATCH] Initialize custom elements in connected callback --- src/compiler/compile/render_dom/index.ts | 17 ++++++--- src/runtime/internal/Component.ts | 37 ++++++++++++++++++- .../samples/props-after-create/main.svelte | 8 ++++ .../samples/props-after-create/test.js | 23 ++++++++++++ test/custom-elements/samples/props/test.js | 11 +++++- 5 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 test/custom-elements/samples/props-after-create/main.svelte create mode 100644 test/custom-elements/samples/props-after-create/test.js diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3b5001d483..5b4b47895e 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -90,8 +90,8 @@ export default function dom( ${uses_rest && !uses_props && x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`} ${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)} ${writable_props.map(prop => - b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` - )} + b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` + )} ${component.slots.size > 0 && b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} } @@ -190,8 +190,8 @@ export default function dom( ${$$props} => { ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${injectable_vars.map( - v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` - )} + v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` + )} } `; @@ -462,10 +462,15 @@ export default function dom( class ${name} extends @SvelteElement { constructor(options) { super(); - + if (options) { + this.$$runSetup(options); + } + } + $$setup(options) { + console.log("$$setup"); ${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} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index c0f6facdd2..f1334c791d 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -171,7 +171,32 @@ if (typeof HTMLElement === 'function') { this.attachShadow({ mode: 'open' }); } + $$initialProps: Record | null = {}; + $$options: Record | null = {}; + $$setup(_options) { + // overridden by instance + } + + $$runSetup(options?: Record) { + console.log("$$runSetup"); + const {$$initialProps} = this; + if ($$initialProps) { + const opts = (options || this.$$options); + this.$$initialProps = null; + this.$$options = null; + this.$$setup({ + ...opts, + props: { + ...opts.props, + ...$$initialProps + } + }); + } + } + connectedCallback() { + console.log("connectedCallback"); + this.$$runSetup(); // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings @@ -179,7 +204,12 @@ if (typeof HTMLElement === 'function') { } } + // initial implementation of method, will be overridden on setup attributeChangedCallback(attr, _oldValue, newValue) { + console.log(`attributeChangedCallback: ${attr}, ${newValue}`); + if (this.$$initialProps) { + this.$$initialProps[attr] = newValue; + } this[attr] = newValue; } @@ -199,7 +229,12 @@ if (typeof HTMLElement === 'function') { }; } - $set() { + $set(obj) { + if (this.$$initialProps && obj) { + for (const attr of Object.getOwnPropertyNames(obj)) { + this.$$initialProps[attr] = obj[attr]; + } + } // overridden by instance, if it has 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 0000000000..22c91751d0 --- /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 0000000000..a9e08c168e --- /dev/null +++ b/test/custom-elements/samples/props-after-create/test.js @@ -0,0 +1,23 @@ +import * as assert from 'assert'; +import CustomElement from './main.svelte'; + +export default async function (target) { + const el = new CustomElement(); + + assert.equal(el.outerHTML, ''); + + // const el = target.querySelector('custom-element'); + + assert.equal(el.shadowRoot, undefined); + + el.items = ['a', 'b', 'c']; + 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/custom-elements/samples/props/test.js b/test/custom-elements/samples/props/test.js index 9c7e44c3a3..f5e12e0a55 100644 --- a/test/custom-elements/samples/props/test.js +++ b/test/custom-elements/samples/props/test.js @@ -1,7 +1,7 @@ import * as assert from 'assert'; import CustomElement from './main.svelte'; -export default function (target) { +export default async function (target) { new CustomElement({ target }); @@ -9,8 +9,15 @@ export default function (target) { assert.equal(target.innerHTML, ''); const el = target.querySelector('custom-element'); + + // await new Promise((resolve) => setTimeout(resolve, 100)); + // await new Promise((resolve) => setTimeout(resolve, 100)); const widget = el.shadowRoot.querySelector('my-widget'); + console.log(widget); + + // await new Promise((resolve) => setTimeout(resolve, 100)); + const [p1, p2] = widget.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); @@ -20,4 +27,4 @@ export default function (target) { assert.equal(p1.textContent, '5 items'); assert.equal(p2.textContent, 'd, e, f, g, h'); -} \ No newline at end of file +}