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

lite-form

v1.1.1

Published

Form HOC for Web Components. It's also works for LitElement.

Downloads

62

Readme

Lite Form

Form HOC for Web Components. It's also works for LitElement.

Examples

Lite Form implements a Formik-like API, so just try using it. See API Reference for more information.

Native Inputs

import { LitElement, html } from 'lit-element'
import { withForm } from 'lite-form'

class MyForm extends LitElement {
  static get properties() {
    return {
      values: { type: Object },
      errors: { type: Object },
      touched: { type: Object }
    }
  }

  render() {
    return html`
      <form method="POST" @submit=${this.handleSubmit}>
        <input
          id="login"
          @input=${this.handleChange}
          @change=${this.handleChange}
          @blur=${this.handleBlur}
          .value=${this.values.login}
        />
        ${this.errors.login && this.touched.login
          ? this.errors.login
          : ''}
        <input
          id="password"
          type="password"
          @input=${this.handleChange}
          @change=${this.handleChange}
          @blur=${this.handleBlur}
          .value=${this.values.password}
        />

        ${this.errors.password && this.touched.password
          ? this.errors.password
          : ''}

        <button type="submit">Submit</button>
      </form>
    `
  }
}

const enhance = withForm({
  initialValues: { login: '', password: '' },
  onSubmit: values => console.log(values),
  validationSchema: {
    login: value => {
      if (!value) return 'Required'
    },
    password: value => {
      if (value.length < 5) return 'Must be more than 5 letters'
    }
  }
})

customElements.define('native-inputs-form', enhance(MyForm))

Custom Inputs

It will become more concise if you create your own <custom-input> using withField and <error-message> using withError:

import { LitElement, html } from 'lit-element'
import { withForm } from 'lite-form'

class MyForm extends LitElement {
  render() {
    return html`
      <form method="POST" @submit=${this.handleSubmit}>
        <custom-input name="login"></custom-input>
        <error-message name="login"></error-message>

        <custom-input name="password" type="password"></custom-input>
        <error-message name="password"></error-message>

        <button type="submit">Submit</button>
      </form>
    `
  }
}

const enhance = withForm({
  initialValues: { login: '', password: '' },
  onSubmit: values => console.log(values),
  validationSchema: {
    login: value => {
      if (!value) return 'Required'
    },
    password: value => {
      if (value.length < 5) return 'Must be more than 5 letters'
    }
  }
})

customElements.define('wrapped-inputs-form', enhance(MyForm))

Here are the components:

// custom-input
import { LitElement, html } from 'lit-element'
import { withField } from 'lite-form'

export default class ExwcInput extends LitElement {
  static get properties() {
    return {
      type: { type: String },
      name: { type: String },
      value: { type: String }
    }
  }

  constructor() {
    super()
    this.type = 'text'
    this.value = ''
  }

  render() {
    return html`
      <input
        autocomplete="off"
        type=${this.type}
        name=${this.name}
        .value=${this.value}
        @input=${this.handleChange}
      />
    `
  }
}

customElements.define('custom-input', withField(ExwcInput))
// error-message
import { LitElement, html } from 'lit-element'
import { withError } from 'lite-form'

export default class ExwcInput extends LitElement {
  static get properties() {
    return {
      name: { type: String },
      error: { type: String },
      touched: { type: Boolean }
    }
  }

  render() {
    return html`
      ${this.error && this.touched
        ? this.error
        : ''}
    `
  }
}

customElements.define('error-message', withError(ExwcInput))

Form Base Class

You can also create your own base form class. For example:

// Form Base Class
import { LitElement, html } from 'lit-element'
import { withForm } from 'lite-form'

class LiteForm extends LitElement {
  render() {
    return html`<form @submit=${this.handleSubmit} method=${this.method}>
      ${this.formRender(this)}
    </form>`
  }
}

customElements.define('lite-form', withForm(LiteForm))

And use it in HTML:

// Using Base Form Class
import { html, render } from 'lit-html'


const formRender = ({ values, handleBlur, handleChange }) =>
  html` 
    <custom-input name="login"></custom-input>
    <error-message name="login"></error-message>

    <custom-input name="password" type="password"></custom-input>
    <error-message name="password"></error-message>

    <button type="submit">Submit</button>
  `

const MyForm = html`
  <lite-form
    method="post"
    .formRender=${formRender}
    .onSubmit=${values => console.log(values)}
    .initialValues=${{
      login: '',
      password: ''
    }}
    .validationSchema=${{
      login: value => {
        if (!value) return 'Required'
      },
      password: value => {
        if (value.length < 5) return 'Must be more than 5 letters'
      }
    }}
  ></lite-form>
`

render(html`${MyForm}`, document.getElementById('root'))

Don't use a slot instead of providing a form template if you need from events - the form will not trigger its events on elements inside the slot.

Builtin Element Extends

You can also extends builtin form element using Lite Form:

import { withForm } from 'lite-form'

class LiteForm extends HTMLFormElement {
  connectedCallback() {
    this.addEventListener('submit', this.handleSubmit)
    this.addEventListener('reset', this.handleReset)
  }

  disconnectedCallback() {
    this.removeEventListener('submit', this.handleSubmit)
    this.removeEventListener('reset', this.handleReset)
  }
}

customElements.define('native-form', withForm(LiteForm), { extends: 'form' })

And use it in HTML:

// Using form element
import { html } from 'lit-element'

const MyForm = html`
  <form
    is="native-form"
    .onSubmit=${values => console.log(values)}
    .initialValues=${{
      login: '',
      password: ''
    }}
    .validationSchema=${{
      login: value => {
        if (!value) return 'Required'
      },
      password: value => {
        if (value.length < 5) return 'Must be more than 5 letters'
      }
    }}
  >
    <custom-input name="login"></custom-input>
    <error-message name="login"></error-message>

    <custom-input name="password" type="password"></custom-input>
    <error-message name="password"></error-message>

    <button type="submit">Submit</button>
  </form>
`

render(html`${MyForm}`, document.getElementById('root'))

Customising builtin elements does not work in Safari and iOS (13) without polyfill.

API Reference

withForm

Usage

withForm(config)(Component) or withForm(ComponentWithConfig)

Instead of using config in the HOC, you can put it in the Component class. This will allow you to create your own base form class.

Config
  • onSubmit: (values, props)=>{}
  • initialValues: { propName: value } || props => ({ propName: value })
  • validationSchema: { propName: (value, props) => 'error'||{} }
  • validateOnBlur: boolean. Default: true
  • validateOnChang: boolean. Default: true
Props
  • values: object
  • errors: object
  • touched: object
  • isValid: boolean
  • handleSubmit: function
  • handleChange: (path || event, value) => {}. event: Event: {target: {name || id, value}} || CustomEvent: {detail: {name || id, value}}
  • handleBlur: (path || event) => {}. event: Event: {target: {name || id}} || CustomEvent: {detail: {name || id}}
  • handleValidate: function
  • handleReset: function
Events
  • values_change: e=>console.log(e.detail.values)
  • errors_touched_change: e=>console.log(e.detail.errors, e.detail.touched)

withField

Usage

withField(Component) or withField(config)(Component)

Component must have name (means path) or id attribute.

Config (optional)
  • captureBlur boolean. Default: true
  • listenChange boolean. Default: false

captureBlur is usefull if you don't use Shadow DOM in your Component or if you use it with slots. If you use Shadow DOM without slots the event will be bubbling (composed) by default and you don't need to use capture for it.

listenChange is usefull if you don't using Shadow DOM in your Component or if your Component dispatch @change event (with options {bubbles: true, composed: true}) instead of using handleChange method. It is useless if you use Shadow DOM in your Component and not dispatch @change event, because js will replace event.target from your internal input to your Component, and it will lose target.value

Props
  • value: any
  • handleChange: (value || event) => {}. event: Event: {target: {value}} || CustomEvent: {detail: {value}}
  • handleBlur: function
Listening Events
  • blur: Event or CustomEvent. If captureBlur==true it will be listening on capturing phase.
  • change: Event: {target: {value}} || CustomEvent: {details: {value}}. Listening only if listenChange==true

withError

Usage

withError(Component)

Component must have name (means path) or id attribute.

Props
  • error: object
  • touched: object

withValue

Usage

withValue(Component)

Component must have name (means path) or id attribute.

Props
  • value: any

withFormClass

Usage

withFormClass(Component)

Props
  • _formClass: your withForm(Component) class. It's usefull to build your own HOCs.

Remarks

  • Don't use slots with input elements inside <form> tag if you need to catch form's events from them!
  • Don't use Shadow DOM if you need autofill to your form (e.g. login & password)!
  • Customising builtin elements does not work in Safari and iOS (13) without polyfill.