UNPKG

@formulier/core

Version:

Simple, performant form library

132 lines (131 loc) 4.6 kB
import { getPath, isEqual, removeKey, setKey, setPath } from './state-utils.js'; class Formulier { store; instances = {}; hasMounted = false; constructor({ initialValues }) { this.store = new Store({ values: initialValues, validators: {}, errors: {}, touched: {}, submitCount: 0, }); } setValues = (values) => { this.store.setState(state => ({ ...state, values })); }; setFieldErrors = (fieldErrors) => { this.store.batch(() => { this.store.setState(state => ({ ...state, errors: {} })); for (const [name, error] of Object.entries(fieldErrors)) { this.store.setState(state => ({ ...state, errors: setKey(state.errors, name, error) })); } }); }; validateFields = () => { const { validators, values } = this.store.getState(); const fieldErrors = Object.fromEntries(Object.entries(validators).map(([name, validate]) => { const value = getPath(values, name, null); const error = validate?.(value) || null; return [name, error]; })); this.setFieldErrors(fieldErrors); const noErrors = Object.values(this.store.getState().errors).every(value => value == null); return noErrors; }; validateField = (name) => { const { validators, values } = this.store.getState(); const validate = validators[name]; const value = getPath(values, name, null); const error = validate?.(value) || null; this.store.setState(state => ({ ...state, errors: setKey(state.errors, name, error) })); return !!validate && !error; }; registerField = (name, validate) => { this.store.setState(state => ({ ...state, values: setPath(state.values, name, getPath(state.values, name, null)), validators: setKey(state.validators, name, validate || null), errors: state.errors[name] === undefined ? setKey(state.errors, name, null) : state.errors, touched: state.touched[name] === undefined ? setKey(state.touched, name, false) : state.touched, })); return () => { this.store.setState(state => ({ ...state, values: setPath(state.values, name, undefined), validators: removeKey(state.validators, name), errors: removeKey(state.errors, name), touched: removeKey(state.touched, name), })); }; }; addInstance = (name, instanceId) => { this.hasMounted = true; this.instances[name] ||= new Set(); this.instances[name].add(instanceId); return () => { this.instances[name]?.delete(instanceId); }; }; hasInstance = (name) => { return !!this.instances[name]?.size; }; setFieldValue = (name, value) => { const { values } = this.store.getState(); if (isEqual(getPath(values, name), value)) return; this.store.setState(state => ({ ...state, values: setPath(state.values, name, value) })); }; touchField = (name, value = true) => { const { touched } = this.store.getState(); if (touched[name] === value) return; this.store.setState(state => ({ ...state, touched: setKey(state.touched, name, value) })); }; incrementSubmitCount = () => { this.store.setState(state => ({ ...state, submitCount: state.submitCount + 1 })); }; } class Store { batching = false; flushing = 0; listeners = new Set(); state; constructor(state) { this.state = state; } getState = () => { return this.state; }; setState = (updater) => { this.state = updater(this.state); this.flush(); }; subscribe = (listener) => { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }; batch = (callback) => { if (this.batching === true) return void callback(); this.batching = true; callback(); this.batching = false; this.flush(); }; flush = () => { if (this.batching === true) return; const state = this.getState(); const flushId = ++this.flushing; for (const listener of this.listeners) { if (this.flushing !== flushId) continue; listener(state); } }; } export { Formulier };