diff --git a/README.md b/README.md index 98930f8..9b0040d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ The result is a reactive UI on property changes. [View on CodePen ↗](https://c 1. [`template` vs `render()`](#template-vs-render) 1. [Prop access](#prop-access) 1. [Alternatives](#alternatives) +1. [Styling](#styling) 1. [Just the Templating](#just-the-templating) 1. [Quick Start Example](#quick-start-example) 1. [Life-Cycle Hooks](#life-cycle-hooks) @@ -194,6 +195,47 @@ The current alternatives are using what `HTMLElement` provides out-of-the-box, w 1. `HTMLElement.dataset` for attributes prefixed with `data-*`. Read more about this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). 1. Methods for reading/writing attribute values: `setAttribute(...)` and `getAttribute(...)`; note that managing the attribute names as strings can be difficult as the code grows. +## Styling + +When using the built-in `html` function for tagged templates, a style object of type `Partial` can be passed to any element's `style` attribute. This allows for calculated and conditional styles. Read more on style objects [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration) + +```js +import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; + +class StyledElements extends WebComponent { + static props = { + condition: false, + type: "warn", + }; + + #typeStyles = { + warn: { + backgroundColor: "yellow", + border: "1px solid orange", + }, + error: { + backgroundColor: "orange", + border: "1px solid red", + }, + }; + + get template() { + return html` +
+

Wow!

+
+ `; + } +} + +customElements.define("styled-elements", StyledElements); +``` + ## Just the Templating You don't have to extend the whole base class just to use some features. All internals are exposed and usable separately so you can practically build the behavior on your own classes. diff --git a/assets/styling.png b/assets/styling.png new file mode 100644 index 0000000..f1dc5ea Binary files /dev/null and b/assets/styling.png differ diff --git a/examples/scoped-styles/index.html b/examples/scoped-styles/index.html new file mode 100644 index 0000000..4ef7b41 --- /dev/null +++ b/examples/scoped-styles/index.html @@ -0,0 +1,12 @@ + + + + + + WC demo + + + + + + diff --git a/examples/scoped-styles/index.js b/examples/scoped-styles/index.js new file mode 100644 index 0000000..58fe4dc --- /dev/null +++ b/examples/scoped-styles/index.js @@ -0,0 +1,39 @@ +// @ts-check +import { WebComponent, html } from "../../src/index.js"; + +class StyledElements extends WebComponent { + static props = { + condition: false, + type: "info", + }; + + #typeStyles = { + info: { + backgroundColor: "blue", + border: "1px solid green", + }, + warn: { + backgroundColor: "yellow", + border: "1px solid orange", + }, + error: { + backgroundColor: "orange", + border: "1px solid red", + }, + }; + + get template() { + return html` +
+

Wow!

+
+ `; + } +} + +customElements.define("styled-elements", StyledElements); diff --git a/src/WebComponent.js b/src/WebComponent.js index 5692174..d3d2299 100644 --- a/src/WebComponent.js +++ b/src/WebComponent.js @@ -31,7 +31,7 @@ export class WebComponent extends HTMLElement { /** * Read-only string property that represents how the component will be rendered - * @returns {string | Node | (string | Node)[]} + * @returns {string | any} * @see https://www.npmjs.com/package/web-component-base#template-vs-render */ get template() { diff --git a/src/utils/create-element.mjs b/src/utils/create-element.mjs index 6205123..a32c30b 100644 --- a/src/utils/create-element.mjs +++ b/src/utils/create-element.mjs @@ -1,5 +1,6 @@ +import { serialize } from "./serialize.mjs"; export function createElement(tree) { - if (tree.type === undefined) { + if (!tree.type) { if (Array.isArray(tree)) { const frag = document.createDocumentFragment(); frag.replaceChildren(...tree.map((leaf) => createElement(leaf))); @@ -8,16 +9,26 @@ export function createElement(tree) { return document.createTextNode(tree); } else { const el = document.createElement(tree.type); + /** + * handle props + */ if (tree.props) { - Object.keys(tree.props).forEach((prop) => { + Object.entries(tree.props).forEach(([prop, value]) => { const domProp = prop.toLowerCase(); - if (domProp in el) { - el[domProp] = tree.props[prop]; + if (domProp === "style" && typeof value === "object" && !!value) { + applyStyles(el, value); + } else if (prop in el) { + el[prop] = value; + } else if (domProp in el) { + el[domProp] = value; } else { - el.setAttribute(prop, tree.props[prop]); + el.setAttribute(prop, serialize(value)); } }); } + /** + * handle children + */ tree.children?.forEach((child) => { const childEl = createElement(child); if (childEl instanceof Node) { @@ -27,3 +38,9 @@ export function createElement(tree) { return el; } } + +function applyStyles(el, styleObj) { + Object.entries(styleObj).forEach(([rule, value]) => { + if (rule in el.style && value) el.style[rule] = value; + }); +}