From c5b94ddd3c96e212eaa6912a5eef436313c42b69 Mon Sep 17 00:00:00 2001 From: Tobias Trumm Date: Mon, 27 Jun 2022 15:45:57 +0200 Subject: [PATCH] [feat] Support hyphenated attributes in custom elements --- src/compiler/compile/render_dom/index.ts | 2 +- src/runtime/internal/Component.ts | 3 ++- src/runtime/internal/dom.ts | 12 +++++++++--- test/custom-elements/samples/$$props/main.svelte | 2 ++ test/custom-elements/samples/$$props/test.js | 7 ++++--- test/custom-elements/samples/props/main.svelte | 3 ++- test/custom-elements/samples/props/my-widget.svelte | 4 ++++ test/custom-elements/samples/props/test.js | 6 +++++- 8 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 9d9699bdbf6c..8d1c1f46fdc6 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -558,7 +558,7 @@ export default function dom( computed: false, key: { type: 'Identifier', name: 'observedAttributes' }, value: x`function() { - return [${props.map(prop => x`"${prop.export_name}"`)}]; + return [${props.map(prop => x`"${prop.export_name.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`)}"`)}]; }` as FunctionExpression }); } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 3e4801054757..fe2e953f3b2b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -198,7 +198,8 @@ if (typeof HTMLElement === 'function') { } attributeChangedCallback(attr, _oldValue, newValue) { - this[attr] = newValue; + const camelCase = attr.replace(/-./g, c => c[1].toUpperCase()); + this[camelCase] = newValue; } disconnectedCallback() { diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index cadc1abbaafa..8519d3bbd620 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -315,9 +315,14 @@ export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attrib } } +function kebab_case_to_camel_case(str: string): string { + return str.replace(/-./g, c => c[1].toUpperCase()); +} + export function set_custom_element_data(node, prop, value) { - if (prop in node) { - node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value; + const camelCaseProp = kebab_case_to_camel_case(prop); + if (camelCaseProp in node) { + node[camelCaseProp] = typeof node[camelCaseProp] === 'boolean' && value === '' ? true : value; } else { attr(node, prop, value); } @@ -728,7 +733,8 @@ export class HtmlTagHydration extends HtmlTag { export function attribute_to_object(attributes: NamedNodeMap) { const result = {}; for (const attribute of attributes) { - result[attribute.name] = attribute.value; + const name = kebab_case_to_camel_case(attribute.name); + result[name] = attribute.value; } return result; } diff --git a/test/custom-elements/samples/$$props/main.svelte b/test/custom-elements/samples/$$props/main.svelte index 68931e22db79..e609aca296ee 100644 --- a/test/custom-elements/samples/$$props/main.svelte +++ b/test/custom-elements/samples/$$props/main.svelte @@ -2,9 +2,11 @@

name: {name}

+

hyphenated attribute: {hyphenatedAttr}

$$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 index 94cad865778c..a408e4f92eab 100644 --- a/test/custom-elements/samples/$$props/test.js +++ b/test/custom-elements/samples/$$props/test.js @@ -2,12 +2,13 @@ import * as assert from 'assert'; import './main.svelte'; export default function (target) { - target.innerHTML = ''; + 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"}

+

hyphenated attribute: galaxy

+

$$props: {"name":"world","answer":"42","test":"svelte","hyphenatedAttr":"galaxy","restAttr":"universe"}

+

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

`); } diff --git a/test/custom-elements/samples/props/main.svelte b/test/custom-elements/samples/props/main.svelte index cf47b436b590..75f9503e04ed 100644 --- a/test/custom-elements/samples/props/main.svelte +++ b/test/custom-elements/samples/props/main.svelte @@ -5,6 +5,7 @@ export let items = ['a', 'b', 'c']; export let flagged = false; + export let flaggedWithHyphen = false; - + diff --git a/test/custom-elements/samples/props/my-widget.svelte b/test/custom-elements/samples/props/my-widget.svelte index 970acf84b21b..04b5df170349 100644 --- a/test/custom-elements/samples/props/my-widget.svelte +++ b/test/custom-elements/samples/props/my-widget.svelte @@ -4,9 +4,13 @@ export let items = []; export let flag1 = false; export let flag2 = false; + export let flagWithHyphen1 = false; + export let flagWithHyphen2 = false;

{items.length} items

{items.join(', ')}

{flag1 ? 'flagged (dynamic attribute)' : 'not flagged'}

{flag2 ? 'flagged (static attribute)' : 'not flagged'}

+

{flagWithHyphen1 ? 'flagged with hyphen (dynamic attribute)' : 'not flagged'}

+

{flagWithHyphen2 ? 'flagged with hyphen (static attribute)' : 'not flagged'}

diff --git a/test/custom-elements/samples/props/test.js b/test/custom-elements/samples/props/test.js index 41ca77d29d02..df261cb9244f 100644 --- a/test/custom-elements/samples/props/test.js +++ b/test/custom-elements/samples/props/test.js @@ -11,17 +11,21 @@ export default function (target) { const el = target.querySelector('custom-element'); const widget = el.shadowRoot.querySelector('my-widget'); - const [p1, p2, p3, p4] = widget.shadowRoot.querySelectorAll('p'); + const [p1, p2, p3, p4, p5, p6] = widget.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); assert.equal(p2.textContent, 'a, b, c'); assert.equal(p3.textContent, 'not flagged'); assert.equal(p4.textContent, 'flagged (static attribute)'); + assert.equal(p5.textContent, 'not flagged'); + assert.equal(p6.textContent, 'flagged with hyphen (static attribute)'); el.items = ['d', 'e', 'f', 'g', 'h']; el.flagged = true; + el.flaggedWithHyphen = true; assert.equal(p1.textContent, '5 items'); assert.equal(p2.textContent, 'd, e, f, g, h'); assert.equal(p3.textContent, 'flagged (dynamic attribute)'); + assert.equal(p5.textContent, 'flagged with hyphen (dynamic attribute)'); }