0.1.4 • Published 9 months ago

ts-nano-form v0.1.4

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

npm version install size npm bundle size npm downloads npm

Why

Need for a solution that works on different stacks.

Features

  • Form validation.
  • Mask input texts.

divider

Table of Contents

divider

Getting Started

Install

npm install ts-nano-form

Quickstart

For each form, create a component with the createForm method.

import createForm from "ts-nano-form";

type FormUserType = {
  name: string;
  document: string;
};

export const FormUserFields = {
  name: "",
  document: "",
};

export const FormUser = createForm<FormUserType>();

Values ​​and errors are accessed by get methods getValue and getError. To apply masks use setMasked or setMoneyMasked.

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getValue, getError, setValue, setMasked, setMoneyMasked } =
  field("name");

setValue("123456");
getValue();
//123456

setMasked("123456", "000-000");
getValue();
//123-456

setMoneyMasked("12346");
getValue();
//1.234,56

The submit method validates and returns errors.

import { FormUser, FormUserFields } from "./FormUser";

const { submit, field } = FormUser;
const { getError } = field("name");

submit((data) => {
  let errors = { ...FormUserFields };
  if (!data.name) errors.name = "name required";
  if (!data.document) errors.document = "document required";
  //check for errors
  if (JSON.stringify(errors) === JSON.stringify(FormUserFields))
    console.log("send data", data);

  return errors;
});

getError();
//'name required' if it is empty

divider

Store

Stores are used to store all values ​​and errors and then validated by the submit method. Each change can be watched with the subscribe method.

import createForm from "ts-nano-form";

type FormUserType = {
  document: string;
};

export const FormUserFields = {
  document: "12345",
};

export const FormUser = createForm<FormUserType>();
const { subscribeValue, setValue } = field("document");
subscribeValue((value: string, prevValue: string) =>
  console.log(value, prevValue)
);
setValue("67890");
//67890 12345

setValue, setError are to change store values. getValue, getError are to return the values.

import createForm from "ts-nano-form";

type FormUserType = {
  document: string;
};

export const FormUserFields = {
  document: "",
};

export const FormUser = createForm<FormUserType>();
const { setValue, setValueMasked, getValue } = field("document");
setValueMasked("12345");
getValue();
//123,45
setValue("67890");
getValue();
//67890

divider

Mask

There are some ready-to-use standard rules:

  • '0' = any digit
  • 'A' = any alphanumeric
  • 'S' = any letter
  • 'X' = any letter and transform to uppercase
  • 'x' = any letter and transform to lowercase
  • 'Z' = any alphanumeric and transform to uppercase
  • 'z' = any alphanumeric and transform to lowercase

To use the mask without selecting a field, use mask, unmask. These values ​​are not put in the store.

import { FormUser } from "./FormUser";

const { mask, unmask } = FormUser;
mask("123456789", "000-000-000");
//123-456-789

unmask("123-456-789");
//123456789

If is necessary to validate these values, put a masked value in a store, use the setMasked or setMoneyMasked methods.

There are also getMasked and getMoneyMasked which returns a masked value without changing the store.

Be careful when using the getUnmasked method, if the value is money use getMoneyUnmasked to add the decimal values.

import { FormUser } from "./FormUser";

const { field } = FormUser;
const {
  setValue,
  setMasked,
  setMoneyMasked,
  getMasked,
  getMoneyMasked,
  getMoneyMasked,
  getMoneyUnmasked,
} = field("document");

setValue("123456");
getMasked("000-000");
//123-456
getMoneyMasked();
//1.234,56
getValue();
//123456

setMasked("789012", "000-000");
getValue();
//789-012

setMoneyMasked("345678");
getValue();
//3.456,78

getMoneyUnmasked();
//3456.78
getUnmasked();
//345678

divider

TS Nano Form API

The API is separated into Mask API where the mask functions are located.

Form API methods, related to form, validation.

Store API methods, used to manipulate stores.

Form API

  • Submit store values

submit(validate: (values: T) => T)

import { FormUserFields, FormUser } from "./FormUser";

const { submit } = FormUser;

const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
  e.preventDefault();
  submit((data) => {
    const errors = { ...FormUserFields };
    if (!data.name) errors.name = "name required";
    //check for errors
    if (JSON.stringify(errors) === JSON.stringify(TsFormUserInitalValues))
      console.log("send data", data);

    return errors;
  });
};
  • Field store

field(name: string)

import { FormUserFields, FormUser } from "./FormUser";

const { field } = FormUser;
const { setMasked } = field("document");

setMasked("123456", "000-000");
  • Change mask rules

setRulesMask(rules: MaskOptions)

import { FormUser } from "./FormUser";

const maskOptions = {
  map: new Map<string, MapOptions>([["9", { pattern: /\d/ }]]),
};

const { setRulesMask } = FormUser;
setRulesMask(maskOptions);
  • Change money rules

setRulesMoney(rules: MoneyOptions)

import { FormUser } from "./FormUser";

const moneyOptions = {
  thousands: " ",
  decimal: ".",
  precision: 3,
  prefix: "R$",
};

const { setRulesMask } = FormUser;
setRulesMoney(moneyOptions);
  • Get current rules

getRules()

import { FormUser } from "./FormUser";

const { getRules } = FormUser;
getRules();

Store API

  • Get the store value

getValue(): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getValue } = field("name");
getValue();
  • Get masked value without changing the store

getMasked(maskRule: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getMasked } = field("name");
getMasked("000-000");
  • Get unmasked value without changing the store

getUnmasked(maskRule: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getUnmasked } = field("name");
getUnmasked();
  • Get masked money without changing the store

getMoneyMasked(maskRule: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getMoneyMasked } = field("name");
getMoneyMasked();
  • Get unmasked money without changing the store

getMoneyUnmasked(): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getMoneyUnmasked } = field("name");
getMoneyUnmasked();
  • Get the store error

getError(): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { getError } = field("name");
getError();
  • Get all store values

getValues(): T

import { FormUser } from "./FormUser";

const { getValues } = FormUser;

getValues();
  • Get all store errors

getErrors(): T

import { FormUser } from "./FormUser";

const { getErrors } = FormUser;

getErrors();
  • Set the store value

setValue(value: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { setValue } = field("name");
setValue("John Doe");
  • Set the store with the masked value

setMasked(value: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { setMasked } = field("name");
setMasked("123456", "000-000");
  • Set the store with the masked money

setMoneyMasked(value: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { setMoneyMasked } = field("name");
setMoneyMasked("123456");
  • Set the store error

setError(value: string): string

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { setError } = field("name");
setError("name required");
  • Watch changes in the store value

subscribeValue(listener: (value: string, prevValue: string) => void): () => void

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { subscribeValue } = field("name");

subscribeValue((value: string, prevValue: string) =>
  console.log(value, prevValue)
);
  • Watch changes in the store error

subscribeError(listener: (value: string, prevValue: string) => void): () => void

import { FormUser } from "./FormUser";

const { field } = FormUser;
const { subscribeError } = field("name");

subscribeError((value: string, prevValue: string) =>
  console.log(value, prevValue)
);
  • Watch changes in all store values

subscribeAllValues(listener: (value: string, prevValue: string) => void): () => void

import { FormUser } from "./FormUser";

const { subscribeAllValues } = FormUser;

subscribeAllValues((value: string, prevValue: string) =>
  console.log(value, prevValue)
);
  • Watch changes in all store errors

subscribeAllErrors(listener: (value: string, prevValue: string) => void): () => void

import { FormUser } from "./FormUser";

const { subscribeAllErrors } = FormUser;

subscribeAllErrors((value: string, prevValue: string) =>
  console.log(value, prevValue)
);

Mask API

  • Mask text

mask(value: string, maskRule: string)

import { FormUser } from "./FormUser";

const { mask } = FormUser;
mask("ABC1A23", "SSS-0A00");
//ABC-1A23
  • Unmask text

unmask(value: string)

import { FormUser } from "./FormUser";

const { unmask } = FormUser;
const unmasked = unmask("ABC-1A23");
//ABC1A23
  • Mask money

maskMoney(value: string)

import { FormUser } from "./FormUser";

const { maskMoney } = FormUser;
const masked = maskMoney("123456");
//1.234,56
  • Unmask money

unmaskMoney(value: string)

import { FormUser } from "./FormUser";

const { unmaskMoney } = FormUser;
const umasked = unmaskMoney("1.234,56");
//123456
  • Get placeholder

getPlaceholder(maskRule: string)

import { FormUser } from "./FormUser";

const { getPlaceholder } = FormUser;
const placeholder = getPlaceholder("SSS-0A00");
//___-____

divider

Options

  • Default options
const DEFAULT_MONEY_OPTIONS = {
  thousands: ".",
  decimal: ",",
  precision: 2,
};

const DEFAULT_MASK_OPTIONS = {
  map: new Map<string, MapOptions>([
    ["0", { pattern: /\d/ }],
    ["A", { pattern: /[a-zA-Z0-9]/ }],
    ["S", { pattern: /[A-Za-z]/ }],
    [
      "X",
      {
        pattern: /[A-Za-z]/,
        transform: (prevValue, newChar) => ({
          prevValue,
          newChar: newChar.toLocaleUpperCase(),
        }),
      },
    ],
    [
      "x",
      {
        pattern: /[A-Za-z]/,
        transform: (prevValue, newChar) => ({
          prevValue,
          newChar: newChar.toLocaleLowerCase(),
        }),
      },
    ],
    [
      "Z",
      {
        pattern: /[a-zA-Z0-9]/,
        transform: (prevValue, newChar) => ({
          prevValue,
          newChar: newChar.toLocaleUpperCase(),
        }),
      },
    ],
    [
      "z",
      {
        pattern: /[a-zA-Z0-9]/,
        transform: (prevValue, newChar) => ({
          prevValue,
          newChar: newChar.toLocaleLowerCase(),
        }),
      },
    ],
  ]),
};
  • Custom options
import createForm, { MapOptions } from "ts-nano-form";

type FormUserType = {
  document: string;
};

const moneyOptions = {
  thousands: " ",
  decimal: ".",
  precision: 3,
  prefix: "R$",
};

const maskOptions = {
  map: new Map<string, MapOptions>([
    [
      "#",
      {
        pattern: /[A-Za-z]/,
        transform: (prevValue, newChar) => ({
          prevValue,
          newChar: newChar.toLocaleUpperCase(),
        }),
      },
    ],
    ["9", { pattern: /\d/ }],
  ]),
};

export const FormUserFields = {
  document: "",
};

export const FormUser = createForm<FormUserType>({
  initialValues: FormUserFields,
  options: {
    maskOptions,
    moneyOptions,
  },
});

const { mask, maskMoney, setRulesMask, setRulesMoney } = FormUser;

mask("abcd", "####");
//return ABCD

maskMoney("123456789");
//return R$12 345.689

setRulesMask(maskOptions);
setRulesMoney(moneyOptions);
//change the mask rules
  • Before Mask, After Mask
import createForm, { MapOptions } from "ts-nano-form";

type FormUserType = {
  document: string;
};

const moneyOptions = {
  thousands: ".",
  decimal: ",",
  precision: 2,
  beforeMask: (value) => (value === "1000" ? "1001" : value),
  afterMask: (value) => "$" + value,
};

const maskOptions = {
  map: new Map<string, MapOptions>([["#", { pattern: /[A-Za-z]/ }]]),
  beforeMask: (value) => (value === "hello" ? "helloworld" : value),
  afterMask: (value) => (value.length > 10 ? value.slice(0, -1) : value),
};

export const FormUserFields = {
  document: "",
};

const FormUser = createForm<FormUserType>({
  initialValues: FormUserFields,
  options: {
    maskOptions,
    moneyOptions,
  },
});

const { mask, maskMoney } = FormUser;

mask("hello", "###########");
//return helloworld

maskMoney("1000");
//return $10,00

divider

Examples

Practical use examples

Vanilla JS

<form class="form">
  <input type="text" class="name" />
  <input type="submit" value="submit" />
</form>
import createForm from "ts-nano-form";

type FormUserType = {
  name: string;
};

const FormUserFields = {
  name: "",
};

const FormUser = createForm<FormUserType>();

const { field, submit } = FormUser;

const nameInput = document.querySelector<HTMLInputElement>(".name");
if (nameInput)
  nameInput.addEventListener("input", function (e) {
    if (e.target instanceof HTMLInputElement)
      nameInput.value = field("name").setValue(e.target.value);
  });

const form = document.querySelector<HTMLFormElement>(".form");
if (form)
  form.addEventListener("submit", function (e) {
    e.preventDefault();
    submit((data) => {
      let errors = { ...FormUserFields };
      if (!data.name) errors.name = "name required";
      //check for errors
      if (JSON.stringify(errors) === JSON.stringify(FormUserFields))
        console.log("send data", data);

      return errors;
    });
  });

React

Create a component and use the useSyncExternalStore hook to watch value and error changes.

import { useSyncExternalStore } from "react";
import FormUser from "./createFormUser";

interface InputTextProps {
  field: string;
}

const InputText = ({ field }: InputTextProps) => {
  const { subscribeValue, getValue, subscribeError, getError, setValue } =
    FormUser.field(field);

  const value = useSyncExternalStore(subscribeValue, getValue);
  const error = useSyncExternalStore(subscribeError, getError);

  return (
    <>
      <label>{field}</label>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>{error}</p>
    </>
  );
};

export default InputText;

Validate the fields with the submit method.

import InputText from "./InputText";
import FormUser, { FormUserFields } from "./createFormUser";

function Form() {
  const { submit } = FormUser;

  const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
    e.preventDefault();
    submit((data) => {
      const errors = { ...FormUserFields };

      if (!data.name) errors.name = "name required";
      if (!data.document) errors.document = "document required";
      //check for errors
      if (JSON.stringify(errors) === JSON.stringify(TsFormUserInitalValues))
        console.log("send data", data);

      return errors;
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <InputText field="name" />
      <InputText field="document" />
      <p>
        <input type="submit" value="Enviar" />
      </p>
    </form>
  );
}

export default Form;

Angular

Input html

<div>
  <div>
    <label>Name</label>
    <input type="text" name="name" (input)="changeName($event)" />
  </div>
  <div>{{ error }}</div>
  <button (click)="submitData()">Send</button>
</div>

Input component

import { Component, OnInit } from "@angular/core";
import createForm from "ts-nano-form";
import FormUser from "./createFormUser";

const { field } = FormUser;

@Component({
  selector: "app-form",
  templateUrl: "./form.component.html",
  styleUrls: ["./form.component.scss"],
})
export class FormComponent implements OnInit {
  @Input() field: string = "";
  public value: string = "";
  public error: string = "";
  public fieldName = field("name");

  ngOnInit() {
    field(this.field).subscribeValue((value) => (this.value = value));
    field(this.field).subscribeError((value) => (this.error = value));
  }

  changeName(e: any) {
    field(this.field).setValue(e.target.value);
  }

  submitData() {
    submit((data) => {
      let errors = { ...FormUserFields };
      if (!data.name) errors.name = "name required";
      //check for errors
      if (JSON.stringify(errors) === JSON.stringify(FormUserFields))
        console.log("send data", data);

      return errors;
    });
  }
}

Submit html

<div>
  <app-form field="name" />
  <button (click)="submitData()">Submit</button>
</div>

Submit component

import { Component } from "@angular/core";
import { FormUser, FormUserFields } from "../formUser";

const { submit } = FormUser;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"],
})
export class AppComponent {
  constructor() {}

  submitData() {
    submit((data) => {
      let errors = { ...FormUserFields };
      if (!data.name) errors.name = "name required";
      //check for errors
      if (JSON.stringify(errors) === JSON.stringify(FormUserFields))
        console.log("send data", data);

      return errors;
    });
  }
}

Validators

Examples of using form validators

Yup

import { AnyObject, ObjectSchema, ValidationError } from "yup";
import TsFormUser, { userSchema } from "./createFormUser";

const validateYup = <T>(data: T, schema: ObjectSchema<AnyObject>) => {
  let errors = { ...data };
  try {
    schema.validateSync(data, { abortEarly: false });
  } catch (e) {
    if (e instanceof ValidationError) {
      errors = e.inner.reduce((acc: any, error) => {
        acc[error.path!] = error.message;
        return acc;
      }, {} as T);
    }

    return errors;
  }
};

const { submit } = TsFormUser;
submit((data) => validateYup(data, userSchema));

Zod

import { z } from "zod";
import TsFormUser, { userSchema } from "./createFormUser";

const validateZod = <T>(data: T, schema: z.ZodType<T>) => {
  let errors = { ...data };
  try {
    schema.parse(data);
  } catch (e) {
    if (e instanceof z.ZodError) {
      errors = e.issues.reduce((acc: any, error) => {
        acc[error.path.join(".")] = error.message;
        return acc;
      }, {} as T);
    }
    return errors;
  }
};

const { submit } = TsFormUser;
submit((data) => validateZod(data, userSchema));

divider

License

MIT

0.0.20

10 months ago

0.0.21

10 months ago

0.0.22

10 months ago

0.0.23

9 months ago

0.0.15

11 months ago

0.0.16

10 months ago

0.0.17

10 months ago

0.0.18

10 months ago

0.0.19

10 months ago

0.0.14

11 months ago

0.1.0

9 months ago

0.1.2

9 months ago

0.1.1

9 months ago

0.1.4

9 months ago

0.1.3

9 months ago

0.0.13

11 months ago

0.0.12

11 months ago

0.0.11

11 months ago

0.0.9

11 months ago

0.0.8

11 months ago

0.0.7

11 months ago

0.0.6

11 months ago

0.0.5

11 months ago

0.0.4

11 months ago

0.0.3

11 months ago

0.0.2

11 months ago

0.0.1

11 months ago