JSPM

element-vir

0.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1382
  • Score
    100M100P100Q98651F
  • License MIT

Package Exports

  • element-vir

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (element-vir) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

element-vir

Heroic, reactive, functional, type safe, custom web components.

No need for an extra build step,
no need for weird file extensions,
no need for extra static analysis tools,
no need for a dedicated funky syntax.
It's just TypeScript.

Built using the power of native JavaScript custom web elements, native JavaScript template literals, native JavaScript functions*, native HTML, and lit-element.

This is basically a lit-element wrapper that adds type-safe I/O and functional-programming style component definition.

Works in every major browser except Internet Explorer. (Heaven help you if you still need to support IE.)

*okay I hope it's obvious that functions are native

Install

Published on npm:

npm i element-vir

Usage

Most usage of this package is done through the defineFunctionalElement function. See the FunctionalElementInit type for that function's inputs. These inputs are also described below with examples.

All of lit's syntax and functionality is also available for use.

Simple element definition

Use defineFunctionalElement to define your element. This is apparent if you inspect the types but it must be given an object with at least tagName and renderCallback properties. This is a bare-minimum example custom element:

import {defineFunctionalElement, html} from 'element-vir';

export const MySimpleElement = defineFunctionalElement({
    tagName: 'my-simple-element',
    renderCallback: () => html`
        <span>Hello there!</span>
    `,
});

Make sure to export your element definition as that will be used to instantiate it in your HTML templates.

Using in other elements

To use already defined functional elements (like my-simple-element above), they must be interpolated into HTML templates like so:

import {defineFunctionalElement, html} from 'element-vir';
import {MySimpleElement} from './my-simple.element';

export const MyAppElement = defineFunctionalElement({
    tagName: 'my-app-element',
    renderCallback: () => html`
        <h1>My App</h1>
        <${MySimpleElement}></${MySimpleElement}>
    `,
});

This requirement ensures that the element is properly imported and registered with the browser. (Compare to pure lit where you must remember to import each element file as a side effect, or without actually referencing any of its exports in your code.)

If you wish to bypass this interpolation requirement, instead import html directly from lit.

Adding styles

Styles are added through styles when defining a functional element (similar to how they are defined in lit):

import {css} from 'lit';
import {defineFunctionalElement, html} from 'element-vir';

export const MySimpleWithStylesElement = defineFunctionalElement({
    tagName: 'my-simple-with-styles-element',
    styles: css`
        :host {
            display: flex;
            flex-direction: column;
            font-family: sans-serif;
        }

        span + span {
            margin-top: 16px;
        }
    `,
    renderCallback: () => html`
        <span>Hello there!</span>
        <span>How are you doing?</span>
    `,
});

Defining and using properties (inputs)

Define properties with props when defining a functional element. Each property must be given a default value. If you wish to leave the property's default value as undefined, give it a type as well (shown below with as string | undefined) so you can assign a defined value of that type to it later.

To use a custom element's properties, grab props from renderCallback's parameters and interpolate it into your HTML template:

import {defineFunctionalElement, html} from 'element-vir';

export const MySimpleWithPropsElement = defineFunctionalElement({
    tagName: 'my-simple-element-with-props',
    props: {
        currentUsername: 'dev',
        currentEmail: undefined as string | undefined,
    },
    renderCallback: ({props}) => html`
        <span>Hello there ${props.currentUsername}!</span>
    `,
});

Assigning to properties (inputs)

Use the assign directive to assign properties to child custom elements:

import {assign, defineFunctionalElement, html} from 'element-vir';
import {MySimpleWithPropsElement} from './my-simple-with-props.element';

export const MyAppWithPropsElement = defineFunctionalElement({
    tagName: 'my-app-with-props-element',
    renderCallback: () => html`
        <h1>My App</h1>
        <${MySimpleWithPropsElement}
            ${assign(MySimpleWithPropsElement.props.currentUsername, 'user')}
            ${assign(MySimpleWithPropsElement.props.currentEmail, 'user@example.com')}
        >
        </${MySimpleWithPropsElement}>
    `,
});

Defining and dispatching custom events (outputs)

Define events with events when defining a functional element. Each event must be initialized with eventInit and a type parameter. eventInit accepts no inputs as it doesn't make sense for events to have a default value.

To dispatch an event, grab dispatchEvent from renderCallback's parameters.

import {defineFunctionalElement, ElementEvent, eventInit, html} from 'element-vir';

export const MySimpleWithEventsElement = defineFunctionalElement({
    tagName: 'my-simple-element-with-events',
    events: {
        logoutClick: eventInit<void>(),
        randomNumber: eventInit<number>(),
    },
    renderCallback: ({props, dispatchEvent, events}) => html`
        <!-- normal DOM events must be listened to with the "@" keyword from lit. -->
        <button @click=${() => dispatchEvent(new ElementEvent(events.logoutClick, undefined))}>
            log out
        </button>
        <button @click=${() => dispatchEvent(new ElementEvent(events.randomNumber, Math.random()))}>
            generate random number
        </button>
    `,
});

Listening to custom events (outputs)

Use the listen directive to listen to custom events emitted by your custom functional elements:

import {defineFunctionalElement, html, listen} from 'element-vir';
import {MySimpleWithEventsElement} from './my-simple-with-events.element';

export const MyAppWithEventsElement = defineFunctionalElement({
    tagName: 'my-app-with-events-element',
    props: {
        myNumber: -1,
    },
    renderCallback: ({props}) => html`
        <h1>My App</h1>
        <${MySimpleWithEventsElement}
            ${listen(MySimpleWithEventsElement.events.logoutClick, () => {
                console.log('logout triggered!');
            })}
            ${listen(MySimpleWithEventsElement.events.randomNumber, (event) => {
                props.myNumber = event.detail;
            })}
        >
        </${MySimpleWithEventsElement}>
        <span>${props.myNumber}</span>
    `,
});

Directives

Some custom lit directives are also contained within this package.

onDomCreate

This directive should be used instead of trying to use querySelector directly on the custom element.

This triggers only once when the element it's contained within is created in the DOM. If it's containing element changes, the callback will be triggered again.

import {defineFunctionalElement, html, onDomCreated} from 'element-vir';

export const MySimpleWithOnDomCreatedElement = defineFunctionalElement({
    tagName: 'my-simple-with-on-dom-created-element',
    renderCallback: () => html`
        <span
            ${onDomCreated((element) => {
                // logs a span element
                console.log(element);
            })}
        >
            Hello there!
        </span>
    `,
});

onResize

This directive fulfills a common use case of triggering callbacks when something resizes. Instead of just tracking the globally resizing window though, this allows you to track resizes of an individual element. The callback here is given a portion of the ResizeObserverEntry (since not all properties are supported well in browsers).

import {defineFunctionalElement, html, onResize} from 'element-vir';

export const MySimpleWithOnResizeElement = defineFunctionalElement({
    tagName: 'my-simple-with-on-dom-created-element',
    renderCallback: () => html`
        <span
            ${onResize((entry) => {
                // this will track resizing of this span
                // the entry parameter contains target and contentRect properties
                console.log(entry);
            })}
        >
            Hello there!
        </span>
    `,
});