From bdd8716951d6ba64fe7b27e78ab769a8c3fa4908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Luba=C5=84ski?= Date: Tue, 16 Apr 2024 15:26:04 +0200 Subject: [PATCH] fix(html): add fallback for camel-cased property name --- docs/component-model/templates.md | 25 +++---------------- src/template/resolvers/property.js | 40 ++++++++++++++++++++++-------- test/spec/html.js | 16 ++++++++++++ 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/docs/component-model/templates.md b/docs/component-model/templates.md index 532e7d23..41570756 100644 --- a/docs/component-model/templates.md +++ b/docs/component-model/templates.md @@ -49,32 +49,15 @@ const update = ({ radius }) => html` ## Properties & Attributes ```javascript -html`
`; +// el.propertyName = value, el.otherProperty = value +html``; ``` -Expression in the attribute set corresponding property of an element instance. Even though attributes are not case-sensitive, the template engine uses the exact name defined in the template. - -### Attribute Fallback - -If the property is not found in the prototype of an element, it fallbacks to the attribute value (the attribute name is not translated from camel-case to dash or in any other way). If your template contains a custom element, which only supports attributes, you can use the original name: - -```javascript -html`` -``` - -Custom elements defined with the library support both camel-case property and dashed attribute. However, the best option is to use the original property name, if you want to pass dynamic data to the element. On another hand, if you have a fully static value, you can set dashed attribute in the template content: - -```javascript -// Attribute: static value -html``; - -// Property: the only way to create dynamically changing value -html``; -``` +Expression as the element's attribute content set corresponding existing case-sensitive property, translated camel-cased property or fallbacks to the attribute value if property is not found. This behavior maximizes compatibility with custom elements created outside of the library. ### Mixed Values -If the attribute value contains additional characters or multiple expressions, the engine fallbacks to the attribute value with concatenated characters. It has precedence even over the special cases described below. +If the attribute value contains additional characters or multiple expressions, the attribute is always used with concatenated string value. It has precedence even over the special cases described below. ```javascript html`
` diff --git a/src/template/resolvers/property.js b/src/template/resolvers/property.js index 4c48e625..2cb3e44e 100644 --- a/src/template/resolvers/property.js +++ b/src/template/resolvers/property.js @@ -2,6 +2,15 @@ import resolveEventListener from "./event.js"; import resolveClassList from "./class.js"; import resolveStyleList from "./style.js"; +function updateAttr(target, attrName, value) { + if (value === false || value === undefined || value === null) { + target.removeAttribute(attrName); + } else { + const attrValue = value === true ? "" : String(value); + target.setAttribute(attrName, attrValue); + } +} + export default function resolveProperty(attrName, propertyName, isSVG) { if (propertyName.substr(0, 2) === "on") { const eventType = propertyName.substr(2); @@ -14,20 +23,31 @@ export default function resolveProperty(attrName, propertyName, isSVG) { case "style": return resolveStyleList; default: { - let isProp = false; + if (isSVG) { + return (host, target, value) => { + updateAttr(target, attrName, value); + }; + } + + let isProp = undefined; return (host, target, value) => { - isProp = - isProp || - (!isSVG && - !(target instanceof globalThis.SVGElement) && - propertyName in target); + if (isProp === undefined) { + isProp = target.tagName !== "svg"; + if (isProp) { + isProp = propertyName in target; + if (!isProp) { + propertyName = attrName.replace(/-./g, (match) => + match[1].toUpperCase(), + ); + isProp = propertyName in target; + } + } + } + if (isProp) { target[propertyName] = value; - } else if (value === false || value === undefined || value === null) { - target.removeAttribute(attrName); } else { - const attrValue = value === true ? "" : String(value); - target.setAttribute(attrName, attrValue); + updateAttr(target, attrName, value); } }; } diff --git a/test/spec/html.js b/test/spec/html.js index 0defa01f..f843cfb4 100644 --- a/test/spec/html.js +++ b/test/spec/html.js @@ -215,6 +215,22 @@ describe("html:", () => { expect(fragment.children[0].customProperty).toBe(1); }); + + it("sets property using dashed name", () => { + define({ + tag: "test-html-property", + customProperty: 0, + }); + + const render = html` + + + `; + + render(fragment); + + expect(fragment.children[0].customProperty).toBe(1); + }); }); describe("class expression attribute", () => {