Skip to content

Components

Okku edited this page Aug 6, 2021 · 4 revisions

Component extends HTMLElement.

You can define a template field on the component, which should contain a template from the xml/html template function.

It's recommended that state associated to the component uses ES #private fields to avoid namespace collisions with the parent class, as the native HTMLElement comes with quite a few. However, state doesn't have to be stored on the class: if you have global state that needn't be instantiated with the component, you can store it outside the class, or anywhere you'd like. Remember, the library doesn't use vDOM, so it doesn't matter where the state comes from, as long as it's a reactive value.

Example:

import { Component, reactive, xml } from "destiny-ui";

export class VisitorDemo extends Component {
  #who = reactive("visitor");
  #count = reactive(0);
  #timer = setInterval(() => {
    this.#count.value++;
  }, 1e3);

  disconnectedCallback (): void {
    clearInterval(this.#timer);
  }

  template = xml`
    <label>
      What's your name? <input type="text" value=${this.#who} />
    </label>
    <p>
      Hello, ${this.#who}! You arrived ${this.#count} seconds ago.
    </p>
  `;
}

Usage in templates

You can substitute the element name with a slotted raw Component class in templates to use them without registering explicitly them:

import { html } from "destiny-ui";
import { VisitorDemo } from "./VisitorDemo.js";

document.body.append(html`
  <h1> Visitor Demo </h1>
  <${VisitorDemo} />
`.content);

The VisitorDemo class gets automatically registered as a custom element using the Web Components API. It will receive the name visitor-demo-${uid}. A unique ID is appended to the name of automatically registered components to avoid namespace collisions. Note that if you use the same component in other templates, it won't be re-registered and will instead reuse the previously registered one.

register()

You can also register components manually. This is similar to the native customElements.define() method, except that it will automatically generate a kebab-cased name from a PascalCased class name that the class passed in uses.

Example:

import { html } from "destiny-ui";
import { VisitorDemo } from "./VisitorDemo.js";

register(VisitorDemo);

document.body.append(html`
  <h1> Visitor Demo </h1>
  <visitor-demo />
`.content);

Styling components

You can style your components using the css template tag and assigning it to a static property on the component called styles. Additionally, you can use the Component.prototype.attachCSSProperties() method to attach any CSS properties to any ReadonlyReactivePrimitive<string> value. If you want to attach it to a non-string value, you can easily use computed() to compute a string out of the state you have. When combined with custom CSS properties, this can create powerful, yet well performing style update routines that are fast enough to perform complex high frequency updates like animations.

The framework makes use of the Shadow Root, which means that CSS is always scoped to the component. The framework defaults to using Constructable Stylesheets in supported browsers but falls back to using <style> elements for unsupported ones, which can have a negative impact on style update performance in those browsers.

Example:

import { Component, reactive, html, css } from "destiny-ui";

class Foo extends Component {
  #color = reactive("pink");

  connectedCallback () {
    this.attachCSSProperties({
      color: this.#color,
    });
  }

  static styles = css`
    h1 {
      font-size: 2em;
    }
  `;

  template = html`
    <h1> Hello, World! </h1>
    What color would you like the title to be? <input type="text" prop:value=${this.#color} />
  `;
}

Passing props

[to be written]