diff --git a/README.md b/README.md index cba16b9..b5f59f1 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ An input state management for React. It comes with useful common validations if [MIT][license-url] -[size-shield]: https://img.shields.io/bundlephobia/minzip/aio-inputs/3.1.2?style=for-the-badge +[size-shield]: https://img.shields.io/bundlephobia/minzip/aio-inputs/3.1.3?style=for-the-badge [license-shield]: https://img.shields.io/github/license/klm-lab/inputs?style=for-the-badge diff --git a/package.json b/package.json index e0c8742..020a5ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aio-inputs", - "version": "3.1.2", + "version": "3.1.3", "description": "An opinionated inputs state manager for react", "scripts": { "build": "npm ci && npm run build:noci", diff --git a/src/index.ts b/src/index.ts index e33e8aa..5aaf197 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,7 @@ export { useInputs, trackInputs } from "./inputs/hook"; export { required } from "./inputs/validations/required"; -export { number, min, max } from "./inputs/validations/number"; -export { - minLength, - maxLength, - minLengthWithoutSpace, - maxLengthWithoutSpace -} from "./inputs/validations/length"; +export { number, min, max } from "./inputs/validations/length"; export { email } from "./inputs/validations/email"; -export { regex } from "./inputs/validations/regex"; -export { startsWith, endsWith } from "./inputs/validations/string"; -export { copy } from "./inputs/validations/copy"; +//export { copy } from "./inputs/validations/copy"; export { match } from "./inputs/validations/match"; export { asyncCustom } from "./inputs/validations/asyncCustom"; diff --git a/src/inputs/handlers/changes.ts b/src/inputs/handlers/changes.ts index f3c27ab..610c81e 100644 --- a/src/inputs/handlers/changes.ts +++ b/src/inputs/handlers/changes.ts @@ -1,4 +1,5 @@ import { + GetFile, Input, InputStore, InternalInput, @@ -7,7 +8,7 @@ import { } from "../../types"; import { validate, validateState } from "../validations"; import { extractValues, setValue } from "./values"; -import { retrieveFile } from "./files"; +import { parseFile } from "./files"; import { CHECKBOX, FILE, RADIO } from "../../util/helper"; import { setCRValues } from "./checkboxAndRadio"; @@ -15,18 +16,24 @@ export const initValue = ( objKey: string, value: Unknown, store: InputStore, - type: string + type: string, + getFile?: GetFile ) => { // Clone inputs const input = store.get(`i.${objKey}`); - if (type === FILE) { [value].flat().forEach((v: Unknown, index: number) => { - retrieveFile(v, store, objKey, index); + input.files[index] = parseFile(objKey, store, v, !!getFile, {} as File); + getFile && + getFile(v).then((r: Unknown) => { + store.set((ref) => { + const f = ref.i[objKey].files[index]; + f.fetching = false; + f.file = r as File; + }); + }); }); - return; - } - if (type === RADIO) { + } else if (type === RADIO) { // Check right radio input setValue(input, input.value === value); } else if (type === CHECKBOX) { @@ -65,10 +72,11 @@ export const nextChange = ( } else { setValue(input, value, false); } + entry[objKey].validationFailed = false; // we sync handlers syncChanges( store, - setValidAndEm(entry, objKey, validate(store, entry, objKey, value)) + setTouchedEm(entry, objKey, validate(store, entry, objKey, value)) ); // run after changes const r = (input as InternalInput).afterChange; @@ -79,14 +87,13 @@ export const nextChange = ( }); }; -// Set touched, valid and error message -export const setValidAndEm = ( +// Set touched, and error message and return the entry (inputs) +export const setTouchedEm = ( entry: ObjectInputs, objKey: string, em: Unknown ) => { entry[objKey].touched = true; - entry[objKey].valid = !em; entry[objKey].errorMessage = em; return entry; }; @@ -98,6 +105,6 @@ export const syncChanges = (store: InputStore, data: ObjectInputs) => { // isTouched ref.t = true; // new valid state - ref.iv = validateState(data).iv; + ref.iv = validateState(data); }); }; diff --git a/src/inputs/handlers/checkboxAndRadio.ts b/src/inputs/handlers/checkboxAndRadio.ts index 6f4bc31..f42da35 100644 --- a/src/inputs/handlers/checkboxAndRadio.ts +++ b/src/inputs/handlers/checkboxAndRadio.ts @@ -7,9 +7,8 @@ export const setCRValues = ( fn?: (I: Input) => Unknown ) => { store.ev[name].o.forEach((c: string) => { - const inp = entry[c]; - fn && fn(inp); - inp.valid = true; - inp.errorMessage = null; + fn && fn(entry[c]); + entry[c].valid = true; + entry[c].errorMessage = ""; }); }; diff --git a/src/inputs/handlers/files.ts b/src/inputs/handlers/files.ts index 5b651a6..9911289 100644 --- a/src/inputs/handlers/files.ts +++ b/src/inputs/handlers/files.ts @@ -20,6 +20,25 @@ export const createFiles = ( return parsed; }; +// export const createFiles = ( +// value: Unknown, +// store: InputStore, +// objKey: string, +// input: Input +// ) => { +// const parsed: Unknown = input.merge ? { ...input.files } : {}; +// if (!input.merge) { +// // revoke preview file url +// keys(input.files).forEach(R); +// } +// value.forEach((f: Unknown) => { +// // parse and add new file +// const url = C(f); +// parsed[url] = parseFile(objKey, store, url, false, f); +// }); +// return parsed; +// }; + // return result in r and files in f const filterOrFindIndex = ( ref: IPS, @@ -38,25 +57,25 @@ export const parseFile = ( url: string, fetching: boolean, file: File -): ParsedFile => { +) => { const key = newKey(); return { fetching, file, key, url, - update: null, - loaded: false, + // update: null, + // loaded: false, onLoad: () => { !store.get("c").pid && R(url); store.set((ref) => { // const index = ref.i[objKey].files.findIndex((f) => f.key === key); - const { r } = filterOrFindIndex( + const index = filterOrFindIndex( ref, objKey, (f: ParsedFile) => f.key === key - ); - ref.i[objKey].files[r].loaded = true; + ).r; + ref.i[objKey].files[index].loaded = true; }); }, selfUpdate: (data: Unknown) => { @@ -90,40 +109,11 @@ export const parseFile = ( input.valid = !em; input.errorMessage = em; // Validate form - ref.iv = validateState(ref.i).iv; + ref.iv = validateState(ref.i); }); } - }; + } as ParsedFile; }; - -export const retrieveFile = ( - value: Unknown, - store: InputStore, - id: string, - index: number -) => { - const fileConfig = store.fc; - store.set((ref) => { - ref.i[id].files[index] = parseFile( - id, - store, - value, - !!fileConfig.getBlob, // true is getBlob is present - {} as File - ); - ref.i[id].valid = true; - }); - if (fileConfig.getBlob) { - Promise.resolve(fileConfig.getBlob(value)).then((r) => { - store.set((ref) => { - const f = ref.i[id].files[index]; - f.fetching = false; - f.file = r as File; - }); - }); - } -}; - // Remove useless tools for db export const cleanFiles = (files: ParsedFile[]) => { // Set type to any to break the contract type diff --git a/src/inputs/handlers/values.ts b/src/inputs/handlers/values.ts index d708651..e4ad7c2 100644 --- a/src/inputs/handlers/values.ts +++ b/src/inputs/handlers/values.ts @@ -1,4 +1,5 @@ import { type GetValue, Input, ObjectInputs, type Unknown } from "../../types"; +import { FILE } from "../../util/helper"; export const extractValues = (state: ObjectInputs) => { const result = {} as { [k in string]: Unknown }; @@ -13,7 +14,7 @@ export const setValue = (input: Input, value: Unknown, cr: boolean = true) => { if (cr) { input.checked = value; input.props.checked = value; - } else { + } else if (input.type !== FILE) { input.value = value; input.props.value = value; } diff --git a/src/inputs/index.ts b/src/inputs/index.ts index f03751b..519602f 100644 --- a/src/inputs/index.ts +++ b/src/inputs/index.ts @@ -6,7 +6,7 @@ import { Unknown } from "../types"; import { finalizeInputs, touchInput, transformToArray } from "../util"; -import { persist } from "../util/helper"; +import { newSet, persist } from "../util/helper"; import { extractValues } from "./handlers/values"; export const createInputs = (initialState: Unknown, config: InputConfig) => { @@ -45,8 +45,9 @@ export const createInputs = (initialState: Unknown, config: InputConfig) => { const reset = () => { // clear possibly checkbox value // st.n => contains all inputs names + // st.i => contains all initial selected value // st.ev => contains all inputs extra data and validation like total of inputs, selected inputs etc... - st.n.forEach((n: string) => st.ev[n].s.clear()); + st.n.forEach((n: string) => (st.ev[n].s = newSet(st.ev[n].i))); // reset with initial value st.set((ref) => { // ref.i => is the inputs diff --git a/src/inputs/validations/asyncCustom.ts b/src/inputs/validations/asyncCustom.ts index ca59f5f..19b2a2c 100644 --- a/src/inputs/validations/asyncCustom.ts +++ b/src/inputs/validations/asyncCustom.ts @@ -1,55 +1,49 @@ import { AsyncValidateInput, - AsyncValidationParams, - CustomAsyncValidationType + CustomAsyncValidationType, + Unknown } from "../../types"; import { syncChanges } from "../handlers/changes"; -const asyncCallback = ({ em, ok, st, f }: AsyncValidationParams) => { - // Clone inputs - const entry = st.get("i"); - const input = entry[ok]; - // Finish calling server - entry[ok].validating = false; - entry[ok].validationFailed = !!f; - - if (f) { - syncChanges(st, entry); - return; - } - // Add server validation only actual data is valid - entry[ok].valid = input.errorMessage ? false : !em; - // Add server error message only actual data is valid else keep actual error Message - entry[ok].errorMessage = input.errorMessage ?? em; - - // Sync handlers - syncChanges(st, entry); -}; - export const asyncCustom = ( callback: CustomAsyncValidationType ): AsyncValidateInput => { - return ({ va, ip, ok, st }) => { + return ({ i, va, ip, ok, st }) => { const timeoutKeys = st.a; + // clear previous task clearTimeout(timeoutKeys[ip.key]); + // new task with the input key timeoutKeys[ip.key] = setTimeout( () => { - // Save the time + // Save the time because, changes can happen before response const ST = timeoutKeys[ip.key]; - Promise.resolve(callback(va)) - .then((em) => { - /* we check if time match the request id time - * If not, that means, another request has been sent. - * So we wait for that response - * */ - if (ST === timeoutKeys[ip.key]) { - asyncCallback({ em, ok, st }); - } - }) - .catch((error) => { - console.error(error); - asyncCallback({ f: true, ok, st }); - }); + const onError = () => { + i[ok].validating = false; + i[ok].validationFailed = true; + syncChanges(st, i); + }; + + const onSuccess = (errorMessage: Unknown) => { + /* we check if time match the request id time + * If not, that means, another request has been sent. + * So we wait for that response + * */ + if (ST === timeoutKeys[ip.key]) { + // sync with latest inputs state + i = st.get(`i`); + // Get latest input errorMessage + const em = i[ok].errorMessage; + // Add server validation because it is always false before calling server + i[ok].valid = !errorMessage; + // Add server error message only if actual data is valid else keep actual error Message + // '' ?? 'not empty' is a js bug and will return '', so we go the other way + //i[ok].errorMessage = em ?? errorMessage; + i[ok].errorMessage = !em ? errorMessage : em; + i[ok].validating = false; + syncChanges(st, i); + } + }; + callback(va, onSuccess, onError); }, st.get("c.asyncDelay") ?? 800 ); diff --git a/src/inputs/validations/copy.ts b/src/inputs/validations/copy.ts index 8138a91..00680c7 100644 --- a/src/inputs/validations/copy.ts +++ b/src/inputs/validations/copy.ts @@ -1,10 +1,10 @@ -import { ValidateInput, ValidationStateType } from "../../types"; -import { validate } from "./index"; - -export const copy = ( - name: string, - omittedRules?: (keyof ValidationStateType)[] -): ValidateInput => { - return ({ i, ok, st, va, omr }) => - validate(st, i!, st.ev[name].k, va, omittedRules ?? omr, ok); -}; +// import { ValidateInput, ValidationStateType } from "../../types"; +// import { validate } from "./index"; +// +// export const copy = ( +// name: string, +// omittedRules?: (keyof ValidationStateType)[] +// ): ValidateInput => { +// return ({ i, ok, st, va, omr }) => +// validate(st, i, st.ev[name].k, va, omittedRules ?? omr, ok); +// }; diff --git a/src/inputs/validations/email.ts b/src/inputs/validations/email.ts index 973eda4..7002df2 100644 --- a/src/inputs/validations/email.ts +++ b/src/inputs/validations/email.ts @@ -1,10 +1,10 @@ import { Unknown, ValidateInput } from "../../types"; -export const email = (em?: Unknown): ValidateInput => { +export const email = (errorMessage: Unknown): ValidateInput => { return ({ va }) => /^(([^<>()\]\\.,;:\s@"]+(\.[^<>()\]\\.,;:\s@"]+)*)|(".+"))@(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( va?.toLowerCase() ) - ? null - : em; + ? "" + : errorMessage; }; diff --git a/src/inputs/validations/index.ts b/src/inputs/validations/index.ts index 9a21a3e..b256cb0 100644 --- a/src/inputs/validations/index.ts +++ b/src/inputs/validations/index.ts @@ -4,7 +4,6 @@ import { ObjectInputs, Unknown, ValidateInputParams, - ValidateState, ValidationResult } from "../../types"; import { keys, newSet } from "../../util/helper"; @@ -12,18 +11,22 @@ import { keys, newSet } from "../../util/helper"; const validate = ( st: InputStore, i: ObjectInputs, - ok: string, + // objectKey to find the input name and validation + defaultObjKey: string, + // value va: Unknown, + // omitted rules omr = ["asyncCustom"], - o = ok + // objectKey to write the valid status + realObjKey = defaultObjKey ): ValidationResult => { - const ip: Input = i[ok]; + const ip: Input = i[defaultObjKey]; const rules = st.ev[ip.name].v; - let em: Unknown = null; + let em: Unknown = ""; if (!rules) { return em; } - const params = { i, st, ip, ok: o, va, omr } as ValidateInputParams; + const params = { i, st, ip, ok: realObjKey, va, omr } as ValidateInputParams; newSet(keys(rules)).forEach((r: Unknown) => { if (em) { @@ -34,9 +37,13 @@ const validate = ( em = r === "custom" ? f(va) : f(params); } }); + // set the valid status + i[realObjKey].valid = !em; if (!em && rules.asyncCustom) { - i[ok].validating = true; + // make it invalid because an async validation is present + i[realObjKey].valid = false; + i[realObjKey].validating = true; rules.asyncCustom(params); } @@ -44,17 +51,15 @@ const validate = ( }; // Validate the state -const validateState = (data: ObjectInputs): ValidateState => { - let iv = true; - let ik = null; +const validateState = (data: ObjectInputs) => { + let valid = true; for (const formKey in data) { - if (!iv) { + if (!valid) { break; } - iv = data[formKey].valid; - ik = formKey; + valid = data[formKey].valid; } - return { iv, ik }; + return valid; }; export { validate, validateState }; diff --git a/src/inputs/validations/length.ts b/src/inputs/validations/length.ts index d6a0e15..ef70acc 100644 --- a/src/inputs/validations/length.ts +++ b/src/inputs/validations/length.ts @@ -1,29 +1,23 @@ import { Unknown, ValidateInput } from "../../types"; -export const minLength = (minLength: number, em?: Unknown): ValidateInput => { - return ({ va }) => (va?.length >= minLength ? null : em); +export const number = (errorMessage: Unknown): ValidateInput => { + return ({ va }) => (!!va && !isNaN(va) ? "" : errorMessage); }; -export const maxLength = (maxLength: number, em?: Unknown): ValidateInput => { - return ({ va }) => (va?.length <= maxLength ? null : em); -}; - -export const minLengthWithoutSpace = ( - minLengthWithoutSpace: number, - em?: Unknown +export const min = ( + min: number, + errorMessage: Unknown, + number?: boolean ): ValidateInput => { return ({ va }) => - va?.indexOf(" ") === -1 && va?.trim().length >= minLengthWithoutSpace - ? null - : em; + (number ? Number(va) : va.length) >= min ? "" : errorMessage; }; -export const maxLengthWithoutSpace = ( - maxLengthWithoutSpace: number, - em?: Unknown +export const max = ( + max: number, + errorMessage: Unknown, + number?: boolean ): ValidateInput => { return ({ va }) => - va?.indexOf(" ") === -1 && va?.trim().length <= maxLengthWithoutSpace - ? null - : em; + (number ? Number(va) : va.length) <= max ? "" : errorMessage; }; diff --git a/src/inputs/validations/match.ts b/src/inputs/validations/match.ts index db9a114..eb29929 100644 --- a/src/inputs/validations/match.ts +++ b/src/inputs/validations/match.ts @@ -1,12 +1,12 @@ import { Unknown, ValidateInput } from "../../types"; import { validate } from "./index"; -export const match = (name: string, em?: Unknown): ValidateInput => { +export const match = (name: string, errorMessage: Unknown): ValidateInput => { return ({ i, ok, st, va }) => { - const key = st.ev[name].k; + const objKey = st.ev[name].k; - if (!key) { - return null; + if (!objKey) { + return ""; } // if (!entry![inputId].validation.match) { // entry![inputId].validation.match = ({ @@ -19,11 +19,11 @@ export const match = (name: string, em?: Unknown): ValidateInput => { // }; // } - const error = validate(st, i!, key, va, ["match"], ok); - const message = !error ? em : error; - const valid = !error && va === i![key].value; - i![key].valid = valid; - i![key].errorMessage = valid ? null : i![key].errorMessage; - return valid ? null : message; + const error = validate(st, i, objKey, va, ["match"], ok); + const message = !error ? errorMessage : error; + const valid = !error && va === i[objKey].value; + i[objKey].valid = valid; + i[objKey].errorMessage = valid ? "" : i[objKey].errorMessage; + return valid ? "" : message; }; }; diff --git a/src/inputs/validations/number.ts b/src/inputs/validations/number.ts deleted file mode 100644 index 200560b..0000000 --- a/src/inputs/validations/number.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Unknown, ValidateInput } from "../../types"; - -export const number = (em?: Unknown): ValidateInput => { - return ({ va }) => (!!va && !isNaN(va) ? null : em); -}; - -export const min = (min: number, em?: Unknown): ValidateInput => { - return ({ va }) => (Number(va) >= min ? null : em); -}; - -export const max = (max: number, em?: Unknown): ValidateInput => { - return ({ va }) => (Number(va) <= max ? null : em); -}; diff --git a/src/inputs/validations/regex.ts b/src/inputs/validations/regex.ts deleted file mode 100644 index ac1c680..0000000 --- a/src/inputs/validations/regex.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Unknown, ValidateInput } from "../../types"; - -export const regex = (regex: RegExp & Unknown, em?: Unknown): ValidateInput => { - return ({ va }) => (regex?.test(va) ? null : em); -}; diff --git a/src/inputs/validations/required.ts b/src/inputs/validations/required.ts index ddbd945..72404fd 100644 --- a/src/inputs/validations/required.ts +++ b/src/inputs/validations/required.ts @@ -1,6 +1,6 @@ import { Unknown, ValidateInput } from "../../types"; -export const required = (em?: Unknown): ValidateInput => { +export const required = (errorMessage: Unknown): ValidateInput => { return ({ va }) => - !!va && va.indexOf(" ") === -1 && va.length > 0 ? null : em; + !!va && (va.trim ? va.trim() !== "" : va.length > 0) ? "" : errorMessage; }; diff --git a/src/inputs/validations/string.ts b/src/inputs/validations/string.ts deleted file mode 100644 index 72feb3f..0000000 --- a/src/inputs/validations/string.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Unknown, ValidateInput } from "../../types"; - -export const startsWith = ( - startsWith: Unknown, - em?: Unknown -): ValidateInput => { - return ({ va }) => (va.startsWith(startsWith) ? null : em); -}; - -export const endsWith = (endsWith: Unknown, em?: Unknown): ValidateInput => { - return ({ va }) => (va.endsWith(endsWith) ? null : em); -}; diff --git a/src/types/index.ts b/src/types/index.ts index d50612b..d1df1bd 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -27,12 +27,12 @@ type HTMLInputTypeAttribute = type Unknown = any; interface CustomAsyncValidationType { - (value: Unknown): Promise; + (value: Unknown, onSuccess: (em: Unknown) => void, onError: () => void): void; } type ValidateInputParams = { // entry of inputs - i?: ObjectInputs; + i: ObjectInputs; // input ip: Input; // objkey @@ -54,15 +54,8 @@ interface ValidationStateType { number?: ValidateInput; min?: ValidateInput; max?: ValidateInput; - minLength?: ValidateInput; - minLengthWithoutSpace?: ValidateInput; - maxLength?: ValidateInput; - maxLengthWithoutSpace?: ValidateInput; match?: ValidateInput; - startsWith?: ValidateInput; - endsWith?: ValidateInput; - regex?: ValidateInput; - copy?: ValidateInput; + // copy?: ValidateInput; custom?: (value: Unknown) => Unknown; asyncCustom?: ValidateInput; } @@ -82,6 +75,7 @@ interface InternalInput { placeholder?: Unknown; validation?: ValidationStateType; data?: Unknown; + [k: string]: Unknown; afterChange?(params: { value: Unknown; input: Input }): void; } @@ -101,20 +95,11 @@ interface ParsedFile { selfUpdate(data: Unknown): void; } -type ValidateState = { - // invalid - iv: boolean; - // invalid key - ik: string | null; -}; - // FOr some reason, Build-in Required doesn't work interface InputProps { id: string; accept: string; name: string; - min: number | string; - max: number | string; type: HTMLInputTypeAttribute; value: Unknown; checked: boolean; @@ -122,6 +107,7 @@ interface InputProps { placeholder: Unknown; onChange(event: Unknown): void; + [k: string]: Unknown; } interface Input extends InputProps { @@ -139,15 +125,15 @@ interface Input extends InputProps { set

( prop: P, value: Input[P], - fileConfig?: FileConfig + getFile?: GetFile ): void; props: InputProps; data: Unknown; } -interface FileConfig { - getBlob?(url: string): Unknown; +interface GetFile { + (url: string): Unknown; } type CreateObjectInputs = { @@ -204,7 +190,7 @@ type IPS = { c: InputConfig; }; type InputStore = StoreType & { - fc: FileConfig; + fc: GetFile; // async Delay key a: { [k in string]: Unknown }; // extra variables, validation, counter, objKey and checkbox values @@ -212,26 +198,21 @@ type InputStore = StoreType & { [k in string]: { v: ValidationStateType; c: number; - // a set of value + // Initial selected values + i: Set; + // A set of selected value s: Set; // objkey bind to name o: Unknown[]; // common objKey k: string; + // parsed Value + p?: Unknown; }; }; // all inputs name n: Unknown[]; }; -type AsyncValidationParams = { - em?: Unknown; - // objkey - ok: string; - //store - st: InputStore; - // failed - f?: boolean; -}; interface CompForm extends CommonForm { getValues(): Unknown; @@ -319,18 +300,16 @@ export type { InputConfig, Input, InputStore, - AsyncValidationParams, ObjectInputs, Computed, ParsedFile, - FileConfig, + GetFile, EachCallback, IsValid, CreateObjectInputs, ArrayInputs, CreateArrayInputs, InputProps, - ValidateState, ValidateInput, AsyncValidateInput, ValidateInputParams, diff --git a/src/util/helper.ts b/src/util/helper.ts index 05e7c48..0182919 100644 --- a/src/util/helper.ts +++ b/src/util/helper.ts @@ -10,6 +10,18 @@ export const newSet = (p?: Unknown): Set => new Set(p); export const persist = {} as { [k in string]: Computed }; export const keys = Object.keys; +export const RESERVED = newSet([ + "props", + "label", + "merge", + "valid", + "touched", + "validation", + "data", + "afterChange", + "g", + "set" +]); export const CHECKBOX = "checkbox"; export const RADIO = "radio"; export const STRING = "string"; diff --git a/src/util/index.ts b/src/util/index.ts index 0f5e915..1b832cf 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -2,12 +2,11 @@ import type { GetValue, Input, InputConfig, - InputProps, InputStore, ObjectInputs, Unknown } from "../types"; -import { validate, validateState } from "../inputs/validations"; +import { validate } from "../inputs/validations"; import { CHECKBOX, FILE, @@ -16,6 +15,7 @@ import { newKey, newSet, RADIO, + RESERVED, SELECT, STRING } from "./helper"; @@ -23,7 +23,7 @@ import { createStore } from "aio-store/react"; import { initValue, nextChange, - setValidAndEm + setTouchedEm } from "../inputs/handlers/changes"; import { cleanFiles, createFiles } from "../inputs/handlers/files"; import { createSelectFiles } from "../inputs/handlers/select"; @@ -37,16 +37,16 @@ const createInput = ( ) => { const key = newKey(); const isString = matchType(inp, STRING); - const { id, multiple, type, checked, validation = {} } = inp; + const { id, multiple, type, checked } = inp; const fIp = { id: id ?? key, name: isString ? inp : key, value: type === SELECT && multiple ? [] : type === RADIO ? objKey : "", files: [], checked: false, - valid: checked ? true : !keys(validation).length, ...(isString ? {} : inp), - key + key, + props: {} } as Input & GetValue; fIp.g = function (oldValue, touching) { @@ -74,20 +74,24 @@ const createInput = ( const entry = store.get("i"); const input = entry[objKey]; const { type, placeholder, multiple } = input; - const targetValue = isEvent + let values = isEvent ? value.target.value || value.nativeEvent.text || "" - : value; + : value !== placeholder + ? value + : ""; if (type === FILE) { - entry[objKey].files = createFiles( + const files = createFiles( isEvent ? newSet(value.target.files) : value, store, objKey, input ); + entry[objKey].files = files; + values = files; } - const values = + values = type === SELECT ? multiple ? createSelectFiles( @@ -95,20 +99,17 @@ const createInput = ( input, isEvent ) - : targetValue !== "" && targetValue !== placeholder - ? targetValue - : "" - : targetValue; + : values + : values; nextChange(values, store, entry, input, objKey); }; // Let user set value, type and data - fIp.set = (prop, value, fileConfig = {}) => { + fIp.set = (prop, value, getFile) => { store.set((ref) => { const input = ref.i[objKey]; if (prop === "value") { - store.fc = fileConfig; - initValue(objKey, value, store, input.type); + initValue(objKey, value, store, input.type, getFile); } if (prop === "type") { input.type = value; @@ -121,39 +122,50 @@ const createInput = ( } }); }; - fIp.props = { - id: fIp.id, - accept: fIp.accept, - name: fIp.name, - type: fIp.type, - value: fIp.value, - checked: fIp.checked, - multiple: fIp.multiple, - placeholder: fIp.placeholder, - onChange: fIp.onChange - } as InputProps; + + keys(fIp).forEach((k) => { + if (!RESERVED.has(k)) { + fIp.props[k] = fIp[k]; + } + }); const { name } = fIp; // we save the validation const ev = store.ev[name] || {}; + const initialSelection = checked + ? ev.s + ? ev.s.add(fIp.value) + : newSet().add(fIp.value) + : ev.s ?? newSet(); store.ev[name] = { // save validations v: ev.v ?? fIp.validation, // count inputs name c: ev.c ? ev.c + 1 : 1, // track selected value - s: newSet(), + s: initialSelection, + i: newSet(initialSelection), // save object keys for form.get, checkbox and radio value changes o: [...(ev.o ?? []), objKey], // save common objKey for copy and match validation k: objKey }; + // save all name for reset function store.n = [...(store.n ?? []), name]; // assign the final input entry[objKey] = fIp; + // validate input + fIp.valid = checked + ? true + : !validate( + store, + entry, + objKey, + [RADIO, CHECKBOX].includes(type) ? [...store.ev[name].s] : fIp.value + ); // Reset errorMessage - entry[objKey].errorMessage = null; + entry[objKey].errorMessage = ""; return entry[objKey].valid; }; @@ -191,17 +203,22 @@ export const finalizeInputs = (initialState: Unknown, config: InputConfig) => { const touchInput = (store: InputStore) => { const data = store.get("i"); - // invalid key - const ik = validateState(data).ik; - if (ik) { - const input = data[ik] as Input & GetValue; - store.set((ref) => - setValidAndEm( - ref.i, - ik, - validate(store, data, ik, input.g(input.value, true)) - ) - ); + for (const key in data) { + if (!data[key].valid) { + store.set((ref) => + setTouchedEm( + ref.i, + key, + validate( + store, + data, + key, + (data[key] as Input & GetValue).g(data[key].value, true) + ) + ) + ); + break; + } } };