UNPKG

rvx

Version:

A signal based rendering library

252 lines (200 loc) 5.64 kB
# Web Components Rvx supports using web components just like any other native element. === "JSX" ```jsx <some-web-component /> ``` === "No Build" ```jsx 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: === "JSX" ```jsx import { RvxElement } from "rvx/element"; class ExampleComponent extends RvxElement { render() { return <h1>Hello World!</h1>; } } customElements.define("example-component", ExampleComponent); ``` === "No Build" ```jsx import { e } from "./rvx.js"; import { RvxElement } from "./rvx.element.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. === "JSX" ```jsx 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); ``` === "No Build" ```jsx import { e } from "./rvx.js"; import { RvxElement } from "./rvx.element.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: ```jsx 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: === "JSX" ```jsx const elem = <example-element /> as ExampleElement; // Start rendering the component: elem.start(); // Dispose the component: elem.dispose(); ``` === "No Build" ```jsx 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: ```jsx 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: === "JSX" ```jsx 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; } } ``` === "No Build" ```jsx import { mount, capture, teardown, e } from "./rvx.js"; class ExampleComponent extends HTMLElement { /** @type {import("./rvx.js").TeardownHook} */ #dispose; 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; } } ```