atoms-element v5.0.0
git clone https://git.pyrossh.dev/atoms-element
A simple web component library for defining your custom elements. It works on both client and server.
examples/elements/app-counter.js
| e955350 | 1 | import { classMap, createElement, css, html } from '../../index.js'; |
| 24398b1 | 2 | |
| 582dce0 | 3 | // State lives only in the count attribute — no separate internal copy to |
| e955350 | 4 | // keep in sync. Counter.attrs below declares count as a Number: that folds |
| e955350 | 5 | // it into observedAttributes on its own (name is still destructured as a |
| e955350 | 6 | // plain param since it's only ever displayed, never written), and |
| 582dce0 | 7 | // createElement hands count to this function already wrapped as a |
| e955350 | 8 | // {value} getter/setter box, type-checked against the declared constructor. |
| 582dce0 | 9 | // count must be set in markup (no default) — reading count.value throws |
| 582dce0 | 10 | // otherwise. count.value += 1 reads the live attribute and writes straight |
| 582dce0 | 11 | // back via setAttribute. |
| e955350 | 12 | // |
| e955350 | 13 | // <output> is the element HTML actually defines for "the result of a |
| e955350 | 14 | // calculation" — a live-updating count is exactly that, which is what lets |
| e955350 | 15 | // the CSS below style it by tag name instead of an accessory class like the |
| e955350 | 16 | // old .count. |
| ba74771 | 17 | const Counter = ({ name, count }) => { |
| 582dce0 | 18 | const increment = () => { count.value += 1; }; |
| 582dce0 | 19 | const decrement = () => { count.value -= 1; }; |
| 24398b1 | 20 | |
| 24398b1 | 21 | return html` |
| e955350 | 22 | <p>Counter: ${name}</p> |
| e955350 | 23 | <div class="controls"> |
| e955350 | 24 | <button type="button" aria-label="Decrement" @click=${decrement}>-</button> |
| e955350 | 25 | <output class=${classMap({ warning: count.value > 10 })}>${count.value}</output> |
| e955350 | 26 | <button type="button" aria-label="Increment" @click=${increment}>+</button> |
| 24398b1 | 27 | </div> |
| 24398b1 | 28 | `; |
| 24398b1 | 29 | }; |
| 24398b1 | 30 | |
| e955350 | 31 | Counter.attrs = { count: Number }; |
| 582dce0 | 32 | |
| e264357 | 33 | Counter.styles = css` |
| e264357 | 34 | :scope { |
| e264357 | 35 | display: block; |
| e264357 | 36 | margin-top: 2.5rem; |
| e955350 | 37 | color: rgb(55 65 81); |
| e955350 | 38 | --color-danger: rgb(239 68 68); |
| e955350 | 39 | --color-button-bg: rgb(209 213 219); |
| e955350 | 40 | --color-button-bg-hover: rgb(229 231 235); |
| e264357 | 41 | } |
| e955350 | 42 | p { |
| e955350 | 43 | margin: 0 0 0.5rem; |
| e264357 | 44 | } |
| e264357 | 45 | .controls { |
| e264357 | 46 | display: flex; |
| e264357 | 47 | align-items: center; |
| e264357 | 48 | } |
| e955350 | 49 | output { |
| e264357 | 50 | margin: 0 5rem; |
| e264357 | 51 | font-size: 1.875rem; |
| e264357 | 52 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| e264357 | 53 | } |
| e955350 | 54 | output.warning { |
| e955350 | 55 | color: var(--color-danger); |
| e264357 | 56 | } |
| e264357 | 57 | button { |
| e955350 | 58 | background-color: var(--color-button-bg); |
| e955350 | 59 | color: inherit; |
| e264357 | 60 | border-radius: 0.25rem; |
| e264357 | 61 | padding: 0.5rem 1rem; |
| e264357 | 62 | font-size: 1.875rem; |
| e264357 | 63 | } |
| e264357 | 64 | button:hover { |
| e955350 | 65 | background-color: var(--color-button-bg-hover); |
| e264357 | 66 | } |
| e264357 | 67 | button:focus { |
| e264357 | 68 | outline: none; |
| e264357 | 69 | } |
| e264357 | 70 | `; |
| e264357 | 71 | |
| 24398b1 | 72 | export default createElement(import.meta, Counter); |