<CustomElement/>Base class for shadow-DOM web components in pota. Extend it for a
component with its own shadow root, adopted stylesheets, a this.html
setter that accepts a string or a JSX-yielding component, and a
this.emit() helper for dispatching custom events. Register the class
with customElement.
Anything beyond those conveniences — querying shadow children,
toggling hidden, inspecting slots — uses the standard DOM APIs
directly. For how attributes differ from properties in the renderer,
see Attributes and Properties.
The CustomElement class provides an API for the things most commonly
needed on custom elements. It was developed to be used as a base class
for a pota components library.
On construct it attaches an open shadowRoot and adopts the
stylesheets listed in the two static class fields — baseStyleSheets
(intended for a shared reset / design-system layer) then styleSheets
(per-element). Both accept CSSStyleSheet instances or raw CSS
strings.
Adopting extra stylesheets at runtime isn't a method on the instance —
import addStyleSheets from pota/use/css and call it with the
shadow root. URLs and inline CSS strings are both accepted; URL
fetches are cached so siblings share the same CSSStyleSheet.
| name | kind | argument | description |
|---|---|---|---|
baseStyleSheets |
Static field | (CSSStyleSheet | string)[] |
Stylesheets adopted into the shadow root before styleSheets. Intended for a shared reset / design-system layer. |
styleSheets |
Static field | (CSSStyleSheet | string)[] |
Per-element stylesheets, adopted after baseStyleSheets. |
html |
Setter | string | Component |
Assign a string to write shadowRoot.innerHTML; assign a Component (or pass a falsy value to fall back to <slot/>) to replace the shadow root's children with the rendered tree. |
emit |
Method | eventName, [init] |
Dispatches a CustomEvent from the element. The second argument is the standard CustomEventInit bag ({ detail, bubbles, composed, cancelable }). |
For anything else — querying shadow children, toggling attributes,
observing slot changes — use the standard DOM APIs directly:
this.shadowRoot.querySelector(...),
this.toggleAttribute('hidden', flag),
this.shadowRoot.addEventListener('slotchange', fn).
Extend CustomElement, render the shadow tree from
connectedCallback by assigning a JSX-yielding function to
this.html, and dispatch a custom event with this.emit that the
host listens for with on:*.
import { render, signal } from 'pota'
import { CustomElement, customElement } from 'pota/components'
class CounterEl extends CustomElement {
static styleSheets = [`button { font-weight: bold }`]
count = signal(0)
connectedCallback() {
this.html = () => (
<button on:click={() => this.bump()}>
count: <span class="n">{this.count.read}</span>
</button>
)
}
bump() {
this.count.update(n => n + 1)
this.emit('changed', { detail: this.count.read() })
}
}
customElement('my-counter', CounterEl)
declare global {
namespace JSX {
interface IntrinsicElements {
'my-counter': JSX.HTMLAttributes<HTMLElement>
}
}
}
const last = signal(0)
render(
<>
<my-counter on:changed={e => last.write(e.detail)} />
<span> last 'changed' detail: {last.read}</span>
</>,
)
Assigning a string to this.html writes the shadow HTML; reassigning
a JSX component later swaps the shadow tree to a reactive render. The
css tagged template supplies the adopted stylesheet.
import { render } from 'pota'
import { CustomElement, customElement } from 'pota/components'
import { css } from 'pota/use/css'
class MyElement extends CustomElement {
static styleSheets = [
css`
:host {
color: aqua;
}
`,
]
constructor() {
super()
this.html = 'hello <b><slot/></b>!'
setTimeout(() => {
this.html = () => <b>JSX Component takes over!</b>
}, 2000)
}
}
customElement('hello-world', MyElement)
declare global {
namespace JSX {
interface IntrinsicElements {
'hello-world': JSX.HTMLAttributes<HTMLElement>
}
}
}
render(<hello-world>World</hello-world>)