npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

funlit

v0.0.1-alpha.20

Published

Function Lit elements.

Downloads

28

Readme

🪵 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