diff --git a/CHANGELOG.md b/CHANGELOG.md index 9239ed8ca5..cfff198368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) ## 3.29.4 diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 6380843b87..024aafde14 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -474,7 +474,7 @@ export default function dom( ${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, { target: this.shadowRoot, props: @attribute_to_object(this.attributes) }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 5380443fbc..ad06d6ff08 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -360,3 +360,11 @@ export class HtmlTag { this.n.forEach(detach); } } + +export function attribute_to_object(attributes) { + const result = {}; + for (const attribute of attributes) { + result[attribute.name] = attribute.value; + } + return result; +} diff --git a/test/custom-elements/assert.js b/test/custom-elements/assert.js index b8add0dadc..4ee8d9dda0 100644 --- a/test/custom-elements/assert.js +++ b/test/custom-elements/assert.js @@ -31,3 +31,24 @@ export function equal(a, b, message) { export function ok(condition, message) { if (!condition) throw new Error(message || `Expected ${condition} to be truthy`); } + +export function htmlEqual(actual, expected, message) { + return deepEqual( + normalizeHtml(window, actual), + normalizeHtml(window, expected), + message + ); +} + +function normalizeHtml(window, html) { + try { + const node = window.document.createElement('div'); + node.innerHTML = html + .replace(//g, '') + .replace(/>[\s\r\n]+<') + .trim(); + return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); + } catch (err) { + throw new Error(`Failed to normalize HTML:\n${html}`); + } +} diff --git a/test/custom-elements/samples/$$props/main.svelte b/test/custom-elements/samples/$$props/main.svelte new file mode 100644 index 0000000000..68931e22db --- /dev/null +++ b/test/custom-elements/samples/$$props/main.svelte @@ -0,0 +1,10 @@ + + + + +

name: {name}

+

$$props: {JSON.stringify($$props)}

+

$$restProps: {JSON.stringify($$restProps)}

+ diff --git a/test/custom-elements/samples/$$props/test.js b/test/custom-elements/samples/$$props/test.js new file mode 100644 index 0000000000..94cad86577 --- /dev/null +++ b/test/custom-elements/samples/$$props/test.js @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.htmlEqual(el.shadowRoot.innerHTML, ` +

name: world

+

$$props: {"name":"world","answer":"42","test":"svelte"}

+

$$restProps: {"answer":"42","test":"svelte"}

+ `); +} diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe021..82a39e5924 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteElement, + attribute_to_object, detach, element, init, @@ -34,7 +35,18 @@ class Component extends SvelteElement { constructor(options) { super(); this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + + init( + this, + { + target: this.shadowRoot, + props: attribute_to_object(this.attributes) + }, + null, + create_fragment, + safe_not_equal, + {} + ); if (options) { if (options.target) {