funlit
TypeScript icon, indicating that this package has built-in type declarations

0.0.1-alpha.20 • Public • Published

🪵 funlit

Function Lit elements with reactive attributes, properties, and state. Light DOM by default for all your progressive-enhancement needs.

Live demo.

Install

Bundlers:

npm install funlit lit-html

Browsers:

<script type="importmap">
  {
    "imports": {
      "funlit": "https://unpkg.com/funlit",
      "lit-html": "https://unpkg.com/lit-html",
      "lit-html/": "https://unpkg.com/lit-html/"
    }
  }
</script>

Usage

<script type="module">
  import { define, attr, html } from 'funlit';

  function MyStepper(host) {
    const count = attr(host, 'count', 0, { parse: Number });

    function decrement() {
      count.value--;
    }

    function increment() {
      count.value++;
    }

    return () => html`
      <button @click=${decrement}>-</button>
      ${count}
      <button @click=${increment}>+</button>
    `;
  }

  define('my-stepper', MyStepper);
</script>

<my-stepper></my-stepper>

<my-stepper count="10"></my-stepper>

<script type="module">
  const stepper = document.createElement('my-stepper');
  stepper.count = 20;
  document.body.append(stepper);
</script>

API

html, svg, nothing

This package reexports html, svg, and nothing from lit-html as a convenience. Anything else you might need (such as directives) should be imported from lit-html itself.

define(tagName, init)

Alias: defineElement

  • tagName {string} Custom-element tag name to register.
  • init {(FunlitElement) => () => TemplateResult} An initialization function that receives a host element instance, implements core features, and optionally returns a renderer function.

Returns: {FunlitElementConstructor}

Defines a new custom element with the given tag name and init function. Returns the newly-created custom-element class constructor.

The init function is only called once per instance of the element (the host) the first time the host is connected. The function is passed a reference to the host which can be used to define attributes, properties, and state, as well as anything else you'd like. You can think of init like the constuctor, but it doesn't run until the host has been added to a document. May return a render function.

The render function will be called on every update-render cycle. May return a lit-html TemplateResult to be used to render an updated Light DOM subtree, or Shadow DOM subtree if host.attachShadow() has been called.

const MyCounterElement = define('my-counter', (host) => {
  const count = attr(host, 'count', 0, { parse: Number });

  function increment() {
    count.value++;
  }

  host.addEventListener('update', console.log);

  return () => html`
    ${count}
    <button @click=${increment}>+</button>
  `;
});

You may then use the element in HTML:

<my-counter count="10"></my-counter>

Elements may also be created programmatically via the constructor:

const counter = new MyCounterElement(); // does not fire `init`
conuter.count = 10;
document.body.append(counter); // fires `init`

Or, via document.createElement:

const counter = document.createElement('my-counter'); // does not fire `init`
conuter.count = 10;
document.body.append(counter); // fires `init`

attr(host, key[, value[, options]])

Alias: defineAttribute

  • host {FunlitElement} Host element.
  • key {string} Name of the property which will serve as the attribute's accessor.
  • value {any} (default: null) Optional initial value of the attribute if one is not provided in markup or imperatively.
  • options {object} (default: {})
    • attribute {string} (default: hyphenated key) Optionally specify the exact attribute name if you don't like the one autogenerated from the key.
    • boolean {boolean} (default: false) Whether the attribute is a boolean.
    • parse {(string) => value} Optional function to parse the attribute value to the property value.
    • stringify {(value) => string} Optional function to stringify the property value for rendering.

Returns: {{ value; toString: () => string }}

Defines a new public property on the host. Any change to the property will trigger an update-render cycle. The property is initialized with and will watch for changes to the related attribute's value. Changes to the property will not be reflected back to the DOM attribute (this is intentional for performance and security). Returns a mutable value ref.

define('my-counter', (host) => {
  const count = attr(host, 'count', 0, { parse: Number });

  function incrementByValue() {
    count.value++;
  }

  function incrementByProperty() {
    host.count++;
  }

  return () => html`
    ${count}
    <button @click=${incrementByValue}>+ value</button>
    <button @click=${incrementByProperty}>+ property</button>
  `;
});

You may set the value via markup.

<my-counter count="10"></my-counter>

Or, programmatically:

const counter = document.createElement('my-counter');
conuter.count = 10;
document.body.append(counter);

prop(host, key[, value[, options]])

Alias: defineProperty

  • host {FunlitElement} Host element.
  • key {string} Name of the property.
  • value {any} (default: null) Optional initial value of the property, if one is not provided imperatively.
  • options {object} (default: {})
    • stringify {(value) => string} Optional function to stringify the property value for rendering.

Returns: {{ value; toString: () => string }}

Defines a new public property on the host. Any change to the property will trigger an update-render cycle. Returns a mutable value ref.

define('my-counter', (host) => {
  const count = prop(host, 'count', 0);

  function incrementByValue() {
    count.value++;
  }

  function incrementByProperty() {
    host.count++;
  }

  return () => html`
    ${count}
    <button @click=${incrementByValue}>+ value</button>
    <button @click=${incrementByProperty}>+ property</button>
  `;
});

You may set the value programmatically:

const counter = document.createElement('my-counter');
conuter.count = 10;
document.body.append(counter);

val(host, value[, options])

Alias: defineValue

  • host {FunlitElement} Host element.
  • value {any} (default: undefined) Optional initial value of the property.
  • options {object} (default: {})
    • stringify {(value) => string} Optional function to stringify the value for rendering.

Returns: {{ value; toString: () => string }}

Defines a new private state value. Any change to the value will trigger an update-render cycle. Returns a mutable value ref.

define('my-counter', (host) => {
  const count = val(host, 0);

  function increment() {
    count.value++;
  }

  return () => html`
    ${count}
    <button @click=${increment}>+</button>
  `;
});

Host

Lifecycle callbacks

Native lifecycle callbacks are emitted as non-bubbling adopt, connect, and disconnect events. There is no attributechange event emitted as attribute changes are handled with attr() and cannot be defined using the observedAttributes property.

define('my-element', (host) => {
  host.addEventListener('adopt', () => { /* ... */ });
  host.addEventListener('connect', () => { /* ... */ });
  host.addEventListener('disconnect', () => { /* ... */ });
  host.addEventListener('update', () => { /* ... */ });
});

host.update(), host.updateComplete

The .update() method is automatically called any time the host is connected or a defined attribute, property, or value changes, but may also be called directly. Updates are batched so it's safe to trigger any number of updates at a time without causing unnecessary rerenders. Will trigger a non-bubbling update event. Returns a Promise that resolves after the resulting rerender happens.

The connect and update event handlers may make use of host.updateComplete to run code before or after a render.

define('my-element', (host) => {
  async function refresh() {
    // before render
    await host.update();
    // after render
  }

  host.addEventListener('connect', async () => {
    // before render
    await host.updateCompleted;
    // after render
  });

  host.addEventListener('update', async () => {
    // before render
    await host.updateCompleted;
    // after render
  });

  return () => html`
    ${new Date()}
    <button @click=${refresh}>Refresh</button>
  `;
});

TypeScript

You can define elements using TypeScript, if you're into that sort of thing.

import { define, attr, prop, val, html } from 'funlit';

export const FunTypesElement = define<{
  foo: number;
  bar: string;
  doSomething: () => void;
}>('fun-types', (host) => {
  // must be a number
  const foo = attr(host, 'foo', 123, { parse: Number });

  // must be a string
  const bar = prop(host, 'bar', 'abc');

  // infers a boolean
  const baz = val(host, true);

  // could be a bigint later
  const big = val<bigint | undefined>(host);

  host.doSomething = () => { /* ... */ };

  console.log(foo.value); // number
  console.log(bar.value); // string
  console.log(baz.value); // boolean
  console.log(big.value); // bigint | undefined

  return () => html`
    <div>foo: ${foo}</div>
    <div>bar: ${bar}</div>
    <div>baz: ${baz}</div>
    <div>big: ${big}</div>
  `;
});

declare global {
  interface HTMLElementTagNameMap {
    'fun-types': InstanceType<typeof FunTypesElement>;
  }
}

const a = new FunTypesElement();

console.log(a.foo); // number
console.log(a.bar); // string
console.log(a.doSomething); // function
console.log(a.update); // function

const b = document.createElement('fun-types');

console.log(b.foo); // number
console.log(b.bar); // string
console.log(a.doSomething); // function
console.log(b.update); // function

MIT © Shannon Moeller

Readme

Keywords

none

Package Sidebar

Install

npm i funlit

Weekly Downloads

2

Version

0.0.1-alpha.20

License

MIT

Unpacked Size

27.6 kB

Total Files

7

Last publish

Collaborators

  • shannonmoeller