feat: scoped styles (#31)
This commit is contained in:
parent
9eae4e5b8a
commit
aee0ec74aa
6 changed files with 116 additions and 6 deletions
42
README.md
42
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. [`template` vs `render()`](#template-vs-render)
|
||||||
1. [Prop access](#prop-access)
|
1. [Prop access](#prop-access)
|
||||||
1. [Alternatives](#alternatives)
|
1. [Alternatives](#alternatives)
|
||||||
|
1. [Styling](#styling)
|
||||||
1. [Just the Templating](#just-the-templating)
|
1. [Just the Templating](#just-the-templating)
|
||||||
1. [Quick Start Example](#quick-start-example)
|
1. [Quick Start Example](#quick-start-example)
|
||||||
1. [Life-Cycle Hooks](#life-cycle-hooks)
|
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. `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.
|
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
|
## 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.
|
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
BIN
assets/styling.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 319 KiB |
12
examples/scoped-styles/index.html
Normal file
12
examples/scoped-styles/index.html
Normal 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>
|
39
examples/scoped-styles/index.js
Normal file
39
examples/scoped-styles/index.js
Normal 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);
|
|
@ -31,7 +31,7 @@ export class WebComponent extends HTMLElement {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read-only string property that represents how the component will be rendered
|
* 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
|
* @see https://www.npmjs.com/package/web-component-base#template-vs-render
|
||||||
*/
|
*/
|
||||||
get template() {
|
get template() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { serialize } from "./serialize.mjs";
|
||||||
export function createElement(tree) {
|
export function createElement(tree) {
|
||||||
if (tree.type === undefined) {
|
if (!tree.type) {
|
||||||
if (Array.isArray(tree)) {
|
if (Array.isArray(tree)) {
|
||||||
const frag = document.createDocumentFragment();
|
const frag = document.createDocumentFragment();
|
||||||
frag.replaceChildren(...tree.map((leaf) => createElement(leaf)));
|
frag.replaceChildren(...tree.map((leaf) => createElement(leaf)));
|
||||||
|
@ -8,16 +9,26 @@ export function createElement(tree) {
|
||||||
return document.createTextNode(tree);
|
return document.createTextNode(tree);
|
||||||
} else {
|
} else {
|
||||||
const el = document.createElement(tree.type);
|
const el = document.createElement(tree.type);
|
||||||
|
/**
|
||||||
|
* handle props
|
||||||
|
*/
|
||||||
if (tree.props) {
|
if (tree.props) {
|
||||||
Object.keys(tree.props).forEach((prop) => {
|
Object.entries(tree.props).forEach(([prop, value]) => {
|
||||||
const domProp = prop.toLowerCase();
|
const domProp = prop.toLowerCase();
|
||||||
if (domProp in el) {
|
if (domProp === "style" && typeof value === "object" && !!value) {
|
||||||
el[domProp] = tree.props[prop];
|
applyStyles(el, value);
|
||||||
|
} else if (prop in el) {
|
||||||
|
el[prop] = value;
|
||||||
|
} else if (domProp in el) {
|
||||||
|
el[domProp] = value;
|
||||||
} else {
|
} else {
|
||||||
el.setAttribute(prop, tree.props[prop]);
|
el.setAttribute(prop, serialize(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* handle children
|
||||||
|
*/
|
||||||
tree.children?.forEach((child) => {
|
tree.children?.forEach((child) => {
|
||||||
const childEl = createElement(child);
|
const childEl = createElement(child);
|
||||||
if (childEl instanceof Node) {
|
if (childEl instanceof Node) {
|
||||||
|
@ -27,3 +38,9 @@ export function createElement(tree) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyStyles(el, styleObj) {
|
||||||
|
Object.entries(styleObj).forEach(([rule, value]) => {
|
||||||
|
if (rule in el.style && value) el.style[rule] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue