feat: scoped styles (#31)

This commit is contained in:
Ayo Ayco 2023-12-18 16:57:36 +01:00 committed by GitHub
parent 9eae4e5b8a
commit aee0ec74aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 6 deletions

View file

@ -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<CSSStyleDeclaration>` 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`
<div
style=${{
...this.#typeStyles[this.props.type],
padding: "1em",
}}
>
<p style=${{ fontStyle: this.props.condition && "italic" }}>Wow!</p>
</div>
`;
}
}
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.

BIN
assets/styling.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WC demo</title>
<script type="module" src="./index.js"></script>
</head>
<body>
<styled-elements type="warn" condition></styled-elements>
</body>
</html>

View file

@ -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`
<div
style=${{
...this.#typeStyles[this.props.type],
padding: "1em",
}}
>
<p style=${{ fontStyle: this.props.condition && "italic" }}>Wow!</p>
</div>
`;
}
}
customElements.define("styled-elements", StyledElements);

View file

@ -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() {

View file

@ -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;
});
}