Web Components
Rvx supports using web components just like any other native element.
<some-web-component />
e("some-web-component");
To implement a web component, you can extend the RvxElement
class which takes care of creating a shadow root and renders content when the element is connected to the document:
import { RvxElement } from "rvx/element";
class ExampleComponent extends RvxElement {
render() {
return <h1>Hello World!</h1>;
}
}
customElements.define("example-component", ExampleComponent);
import { RvxElement, e } from "./rvx.js";
class ExampleComponent extends RvxElement {
render() {
return e("h1").append("Hello World!");
}
}
customElements.define("example-component", ExampleComponent);
Reflecting Attributes
The reflect
function can be used to get a signal that reflects an attribute value.
import { RvxElement } from "rvx/element";
class ExampleCounter extends RvxElement {
// Allow this component to detect changes to the "count" attribute:
static observedAttributes = ["count"];
// Create a signal that reflects the "count" attribute:
#count = this.reflect("count");
render() {
return <button on:click={() => {
const newCount = Number(this.#count) + 1;
// Updating the signal will also update the "count" attribute:
this.#count.value = newCount;
// Dispatch an event to notify users of your web component:
this.dispatchEvent(new CustomEvent("count-changed", { detail: newCount }));
}}>
Clicked {this.#count} times!
</button>;
}
// Optionally, you can implement property accessors:
get count() {
return Number(this.#count.value);
}
set count(value: number) {
this.#count.value = String(value);
}
}
customElements.define("example-counter", ExampleCounter);
import { RvxElement, e } from "./rvx.js";
class ExampleCounter extends RvxElement {
// Allow this component to detect changes to the "count" attribute:
static observedAttributes = ["count"];
// Create a signal that reflects the "count" attribute:
#count = this.reflect("count");
render() {
return e("button")
.on("click", () => {
const newCount = Number(this.#count) + 1;
// Updating the signal will also update the "count" attribute:
this.#count.value = newCount;
// Dispatch an event to notify users of your web component:
this.dispatchEvent(new CustomEvent("count-changed", { detail: newCount }));
})
.append(
"Clicked ", this.#count, " times!"
);
}
// Optionally, you can implement property accessors:
get count() {
return Number(this.#count.value);
}
set count(value) {
this.#count.value = String(value);
}
}
customElements.define("example-counter", ExampleCounter);
Lifecycle
By default, the content is rendered when the component is connected to the DOM and disposed when it's disconnected.
This default behavior can be disabled:
class ExampleElement extends RvxElement {
constructor() {
super({
// Disable automatic rendering when connected:
start: "manual",
// Disable automatic disposal when disconnected:
dispose: "manual",
});
}
}
You can always start or dispose the component manually:
const elem = <example-element /> as ExampleElement;
// Start rendering the component:
elem.start();
// Dispose the component:
elem.dispose();
const elem = e("example-element");
// Start rendering the component:
elem.start();
// Dispose the component:
elem.dispose();
Note, that components can be started and disposed multiple times.
Shadow DOM
By default, content returned from the render
function is attached to an open shadow root.
This behavior can be changed with the following options:
class ExampleElement extends RvxElement {
constructor() {
super({
// Don't create a shadow root and attach content to the element directly:
shadow: false,
// Specify options for creating the shadow root:
shadow: {
mode: "open",
},
});
}
}
Manual Implementation
Due to it's simple lifecycle system, you can also implement web components manually:
import { mount, capture, teardown, TeardownHook } from "rvx";
class ExampleComponent extends HTMLElement {
#dispose?: TeardownHook;
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.#dispose = capture(() => {
// Create and append content to the shadow root until disposed:
mount(
this.shadowRoot,
<h1>Hello World!</h1>,
);
});
}
disconnectedCallback() {
// Run teardown hooks:
this.#dispose?.();
this.#dispose = undefined;
}
}
import { mount, capture, teardown, TeardownHook, e } from "./rvx.js";
class ExampleComponent extends HTMLElement {
#dispose?: TeardownHook;
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.#dispose = capture(() => {
// Create and append content to the shadow root:
const view = mount(
this.shadowRoot,
e("h1").append("Hello World!"),
);
// Remove content from the shadow root when disposed:
teardown(() => view.detach());
});
}
disconnectedCallback() {
// Run teardown hooks:
this.#dispose?.();
this.#dispose = undefined;
}
}