From 55ea55480bf8658861fa6c34a32e3f0a2c11e534 Mon Sep 17 00:00:00 2001 From: klm <58208740+klm-lab@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:55:34 +0000 Subject: [PATCH 1/3] Add show error method --- README.md | 2 +- examples/README.md | 2 +- package.json | 2 +- src/inputs/handlers/changes.ts | 6 +++--- src/inputs/handlers/files.ts | 10 +++++----- src/inputs/index.ts | 36 ++++++++++++++++++++++------------ src/types/index.ts | 6 ++++-- src/util/index.ts | 5 +---- 8 files changed, 39 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 9034e21..02e6ee3 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,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/2.1.12?style=for-the-badge +[size-shield]: https://img.shields.io/bundlephobia/minzip/aio-inputs/2.1.13?style=for-the-badge [license-shield]: https://img.shields.io/github/license/klm-lab/inputs?style=for-the-badge diff --git a/examples/README.md b/examples/README.md index 2f6ee49..7907a31 100644 --- a/examples/README.md +++ b/examples/README.md @@ -38,7 +38,7 @@ Tracking inputs [HERE][no-tracking-link] [MIT][license-url] -[size-shield]: https://img.shields.io/bundlephobia/minzip/aio-inputs/2.1.12?style=for-the-badge +[size-shield]: https://img.shields.io/bundlephobia/minzip/aio-inputs/2.1.13?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 66e0a61..729f3d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aio-inputs", - "version": "2.1.12", + "version": "2.1.13", "description": "An opinionated inputs state manager for react", "scripts": { "build": "npm ci && npm run build:noci", diff --git a/src/inputs/handlers/changes.ts b/src/inputs/handlers/changes.ts index f7e779c..cf8b243 100644 --- a/src/inputs/handlers/changes.ts +++ b/src/inputs/handlers/changes.ts @@ -1,5 +1,5 @@ import type { - Config, + InputConfig, Helper, InputStore, ParsedFile, @@ -48,7 +48,7 @@ const onChange = ( input: Input, element: HTMLInputElement | HTMLSelectElement, store: InputStore, - config: Config, + config: InputConfig, isEvent: boolean, helper: Helper ) => { @@ -132,7 +132,7 @@ export const inputChange = ( key: string, entry: ObjectInput, store: InputStore, - config: Config, + config: InputConfig, helper: Helper ) => { const isEvent = diff --git a/src/inputs/handlers/files.ts b/src/inputs/handlers/files.ts index 6ab84f9..12a4ad3 100644 --- a/src/inputs/handlers/files.ts +++ b/src/inputs/handlers/files.ts @@ -1,5 +1,5 @@ import type { - Config, + InputConfig, Helper, InitFileConfig, InputStore, @@ -14,7 +14,7 @@ export const createFiles = ( clone: ObjectInput, ID: string, store: InputStore, - config: Config, + config: InputConfig, helper: Helper ) => { const entry = clone[ID]; @@ -47,7 +47,7 @@ export const parseFile = ( clone: ObjectInput, ID: string, store: InputStore, - config: Config, + config: InputConfig, url: string, gettingFile: boolean, file: File, @@ -106,7 +106,7 @@ export const blobStringJob = ( store: InputStore, clone: ObjectInput, ID: string, - config: Config, + config: InputConfig, fileConfig: InitFileConfig, index: number, valid: boolean, @@ -147,7 +147,7 @@ export const retrieveBlob = ( store: InputStore, clone: ObjectInput, ID: string, - config: Config, + config: InputConfig, fileConfig: InitFileConfig, valid: boolean, helper: Helper diff --git a/src/inputs/index.ts b/src/inputs/index.ts index 2d5815a..6dd2955 100644 --- a/src/inputs/index.ts +++ b/src/inputs/index.ts @@ -1,7 +1,7 @@ import type { ArrayStateOutput, ComputeOnceOut, - Config, + InputConfig, CreateArrayInput, CreateObjectInput, ForEachCallback, @@ -39,7 +39,7 @@ const init = ( input: Input, value: unknown, store: InputStore, - config: Config, + config: InputConfig, fileConfig: InitFileConfig, helper: Helper ) => { @@ -77,7 +77,7 @@ const init = ( }); }; -const populate = (state: any, type: StateType, config: Config) => { +const populate = (state: any, type: StateType, config: InputConfig) => { const final = {} as CreateObjectInput; const helper = He(); for (const stateKey in state) { @@ -106,7 +106,7 @@ const populate = (state: any, type: StateType, config: Config) => { const computeOnce = ( initialState: unknown, type: StateType, - config: Config + config: InputConfig ) => { if (config.persistID && persist[config.persistID]) { return persist[config.persistID]; @@ -132,17 +132,23 @@ const computeOnce = ( const initialForm = store.get("entry"); const getValues = (name?: string) => { - if (config.lockValuesOnError && !touchInput(store, helper)) { + if ( + config.lockValuesOnError && + !validateState(store.get("entry")).isValid + ) { return null; } const values = extractValues(store.get("entry")); return name ? values[name] : values; }; + const showError = () => { + touchInput(store, helper); + }; const onSubmit = (event: SyntheticEvent) => { event.preventDefault(); event.stopPropagation(); - touchInput(store, helper); + showError(); }; const loop = (callback: ForEachCallback | MapCallback, method: Method) => { @@ -192,7 +198,8 @@ const computeOnce = ( forEach, map, length, - onSubmit + onSubmit, + showError }; if (config.trackID && config.trackID.ID) { @@ -220,7 +227,7 @@ const computeOnce = ( const parsedInputs = ( initialState: unknown, type: StateType, - config: Config, + config: InputConfig, selective?: string ) => { const { store, ...rest } = useMemo( @@ -242,19 +249,22 @@ const parsedInputs = ( function useInputs( initialState: CreateObjectInput | S, - config?: Config + config?: InputConfig ): ObjStateOutput; function useInputs( initialState: CreateArrayInput, - config?: Config + config?: InputConfig ): ArrayStateOutput; function useInputs( initialState: (string | InternalInput)[], - config?: Config + config?: InputConfig ): ArrayStateOutput; -function useInputs(initialState: string, config?: Config): StringStateOutput; +function useInputs( + initialState: string, + config?: InputConfig +): StringStateOutput; -function useInputs(initialState: unknown, config: Config = {}): unknown { +function useInputs(initialState: unknown, config: InputConfig = {}): unknown { if (initialState instanceof Array) { return parsedInputs( initialState.map((entry, i) => diff --git a/src/types/index.ts b/src/types/index.ts index bd514e1..5e2ce0b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -207,7 +207,7 @@ type ArrayStateOutput = [ArrayInput & IsValid, Form]; type StateType = "object" | "array"; -type Config = { +type InputConfig = { asyncDelay?: number; persistID?: string; trackID?: IDTrackUtil; @@ -246,6 +246,7 @@ interface Form extends CommonForm { getValues(name?: string): Unknown; onSubmit(event: SyntheticEvent): void; + showError(): void; } interface IDTrackUtil extends CommonForm { @@ -295,6 +296,7 @@ interface ComputeOnceOut extends CommonForm { getValues(name?: string): Unknown; onSubmit(event: SyntheticEvent): void; + showError(): void; } interface Helper { @@ -325,7 +327,7 @@ export type { CopyKeyObjType, MergeType, CopyType, - Config, + InputConfig, Input, IDTrackUtil, InputStore, diff --git a/src/util/index.ts b/src/util/index.ts index bff0362..da98052 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,8 +1,8 @@ import type { CreateObjectInput, - InputProps, Helper, Input, + InputProps, InputStore, InternalInput, MatchResultType, @@ -342,7 +342,6 @@ const touchInput = (store: InputStore, helper: Helper) => { const { isValid, invalidKey } = validateState(data); if (invalidKey) { const input = data[invalidKey]; - const value = input.type === "file" ? input.files @@ -358,8 +357,6 @@ const touchInput = (store: InputStore, helper: Helper) => { invalidKey, input.type === "radio" ? (radioValid ? value : null) : value ); - data[invalidKey].touched = true; - data[invalidKey].errorMessage = em; store.set((ref) => { ref.entry[invalidKey].touched = true; From 1778cf795ca57e797164e066455f5b140d855470 Mon Sep 17 00:00:00 2001 From: klm <58208740+klm-lab@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:36:55 +0000 Subject: [PATCH 2/3] Add extraData and setExtraData and enhance types --- package-lock.json | 15 ++++--- package.json | 2 +- src/inputs/handlers/changes.ts | 20 +++++---- src/inputs/handlers/checkbox.ts | 4 +- src/inputs/handlers/files.ts | 40 ++++++----------- src/inputs/handlers/radio.ts | 4 +- src/inputs/handlers/select.ts | 12 +++-- src/inputs/index.ts | 79 ++++++++++++++++++++------------- src/tracking/index.ts | 3 +- src/types/index.ts | 54 +++++++++++----------- src/util/helper.ts | 4 +- src/util/index.ts | 23 +++++----- src/util/validation.ts | 10 ++--- 13 files changed, 146 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd98b13..6a368be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "aio-inputs", - "version": "2.0.9", + "version": "2.1.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aio-inputs", - "version": "2.0.9", + "version": "2.1.13", "license": "MIT", "dependencies": { - "aio-store": "^2.4.3" + "aio-store": "^2.4.41" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.5", @@ -727,9 +727,12 @@ } }, "node_modules/aio-store": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/aio-store/-/aio-store-2.4.3.tgz", - "integrity": "sha512-6H7Q2ad4E1i9SBlBvRbJILnKgkMhnf1lojNRvpstlW9FEukYgr+LAC5xqMGdQElIFuVUNceLRnqtHAVxZdPGSw==" + "version": "2.4.41", + "resolved": "https://registry.npmjs.org/aio-store/-/aio-store-2.4.41.tgz", + "integrity": "sha512-+jgaF0XIudHQqZkZQY4+ouK0j8B0MONogqCOmTE8oTopftA1EQM550kES0Smu16hU5OrIasDFP/J+KFr1Mm3Dw==", + "peerDependencies": { + "react": "^18.2.0" + } }, "node_modules/ajv": { "version": "6.12.6", diff --git a/package.json b/package.json index 729f3d2..4f21118 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "state" ], "dependencies": { - "aio-store": "^2.4.3" + "aio-store": "^2.4.41" }, "peerDependencies": { "react": "^18.2.0" diff --git a/src/inputs/handlers/changes.ts b/src/inputs/handlers/changes.ts index cf8b243..a60b38b 100644 --- a/src/inputs/handlers/changes.ts +++ b/src/inputs/handlers/changes.ts @@ -1,10 +1,10 @@ import type { - InputConfig, Helper, - InputStore, - ParsedFile, Input, - ObjectInput + InputConfig, + InputStore, + ObjectInputs, + ParsedFile } from "../../types"; import { AsyncValidationParams } from "../../types"; import { asyncValidation, validate } from "../../util/validation"; @@ -66,8 +66,12 @@ const onChange = ( config, helper ) - : input.type === "select" && input.multiple - ? createSelectFiles(isEvent, element as HTMLSelectElement, clone, ID) + : input.type === "select" + ? input.multiple + ? createSelectFiles(isEvent, element as HTMLSelectElement, clone, ID) + : element.value !== "" && element.value !== clone[ID].placeholder + ? element.value + : "" : element.value; const toValidate = @@ -120,7 +124,7 @@ const onChange = ( syncChanges(store, clone); }; -const syncChanges = (store: InputStore, data: ObjectInput) => { +const syncChanges = (store: InputStore, data: ObjectInputs) => { store.set((ref) => { ref.entry = data; ref.isValid = validateState(data).isValid; @@ -130,7 +134,7 @@ const syncChanges = (store: InputStore, data: ObjectInput) => { export const inputChange = ( value: any, key: string, - entry: ObjectInput, + entry: ObjectInputs, store: InputStore, config: InputConfig, helper: Helper diff --git a/src/inputs/handlers/checkbox.ts b/src/inputs/handlers/checkbox.ts index aa69461..3058483 100644 --- a/src/inputs/handlers/checkbox.ts +++ b/src/inputs/handlers/checkbox.ts @@ -1,7 +1,7 @@ -import type { ObjectInput } from "../../types"; +import type { ObjectInputs } from "../../types"; export const createCheckboxValue = ( - clone: ObjectInput, + clone: ObjectInputs, ID: string, userChange = true ) => { diff --git a/src/inputs/handlers/files.ts b/src/inputs/handlers/files.ts index 12a4ad3..c9fafaf 100644 --- a/src/inputs/handlers/files.ts +++ b/src/inputs/handlers/files.ts @@ -4,14 +4,14 @@ import type { InitFileConfig, InputStore, ParsedFile, - ObjectInput + ObjectInputs } from "../../types"; import { validate } from "../../util/validation"; import { validateState } from "../../util"; export const createFiles = ( files: FileList | null, - clone: ObjectInput, + clone: ObjectInputs, ID: string, store: InputStore, config: InputConfig, @@ -44,7 +44,7 @@ export const createFiles = ( }; export const parseFile = ( - clone: ObjectInput, + clone: ObjectInputs, ID: string, store: InputStore, config: InputConfig, @@ -87,24 +87,17 @@ export const parseFile = ( }; }; -// export const getFile = async (url: string, fileConfig: InitFileConfig) => { -// const URL = fileConfig.useDefaultProxyUrl -// ? "cors-anywhere.herokuapp.com/" + url -// : fileConfig.proxyUrl -// ? fileConfig.proxyUrl + "/" + url -// : url; -// const blob = await fetch(URL).then((r) => r.blob()); -// const fileName = url.match(/([a-z0-9_-]+\.\w+)(?!.*\/)/gi); -// const name = fileName ? fileName[0] : ""; -// return new File([blob], name, { -// type: blob.type -// }); -// }; +const getFile = (url: string, blob: Blob) => { + const fileName = url.match(/([a-z0-9_-]+\.\w+)(?!.*\/)/gi); + return new File([blob], fileName ? fileName[0] : "", { + type: blob.type + }); +}; export const blobStringJob = ( value: any, store: InputStore, - clone: ObjectInput, + clone: ObjectInputs, ID: string, config: InputConfig, fileConfig: InitFileConfig, @@ -126,26 +119,19 @@ export const blobStringJob = ( ref.entry[ID].valid = valid; }); if (fileConfig.getBlob) { - Promise.resolve(fileConfig.getBlob(value)).then((file) => { + Promise.resolve(fileConfig.getBlob(value)).then((blob) => { store.set((ref) => { ref.entry[ID].files[index].gettingFile = false; - ref.entry[ID].files[index].file = file; + ref.entry[ID].files[index].file = getFile(value, blob); }); }); } - - // getFile(value, fileConfig).then((file) => { - // store.set((ref) => { - // ref.entry[ID].files[index].gettingFile = false; - // ref.entry[ID].files[index].file = file; - // }); - // }); }; export const retrieveBlob = ( value: any, store: InputStore, - clone: ObjectInput, + clone: ObjectInputs, ID: string, config: InputConfig, fileConfig: InitFileConfig, diff --git a/src/inputs/handlers/radio.ts b/src/inputs/handlers/radio.ts index 2ca2a72..6b5d8e2 100644 --- a/src/inputs/handlers/radio.ts +++ b/src/inputs/handlers/radio.ts @@ -1,6 +1,6 @@ -import type { ObjectInput } from "../../types"; +import type { ObjectInputs } from "../../types"; -export const radioIsChecked = (clone: ObjectInput, ID: string) => { +export const radioIsChecked = (clone: ObjectInputs, ID: string) => { let isSelected = false; for (const key in clone) { if ( diff --git a/src/inputs/handlers/select.ts b/src/inputs/handlers/select.ts index 73a113e..6aae19c 100644 --- a/src/inputs/handlers/select.ts +++ b/src/inputs/handlers/select.ts @@ -1,16 +1,18 @@ -import type { ObjectInput } from "../../types"; +import type { ObjectInputs } from "../../types"; export const createSelectFiles = ( isEvent: boolean, element: HTMLSelectElement, - clone: ObjectInput, + clone: ObjectInputs, ID: string ) => { let selected = [] as string[]; if (isEvent) { const els = element.selectedOptions; for (let i = 0; i < els.length; i++) { - selected.push(els[i].value); + els[i].value !== "" && + els[i].value !== clone[ID].placeholder && + selected.push(els[i].value); } } else { // Not need to clone, we keep the same reference safely @@ -18,7 +20,9 @@ export const createSelectFiles = ( if (selected.includes(element.value)) { selected = selected.filter((v: any) => v !== element.value); } else { - selected.push(element.value); + element.value !== "" && + element.value !== clone[ID].placeholder && + selected.push(element.value); } } return selected; diff --git a/src/inputs/index.ts b/src/inputs/index.ts index 6dd2955..6f051b9 100644 --- a/src/inputs/index.ts +++ b/src/inputs/index.ts @@ -1,28 +1,29 @@ import type { ArrayStateOutput, ComputeOnceOut, - InputConfig, - CreateArrayInput, - CreateObjectInput, + CreateArrayInputs, + CreateObjectInputs, ForEachCallback, Helper, InitFileConfig, Input, + InputConfig, InputStore, - InternalInput, IsValid, MapCallback, Method, - ObjectInput, + ObjectInputs, ObjStateOutput, StateType, - StringStateOutput + StringStateOutput, + Unknown } from "../types"; import { commonProps, extractValues, lockProps, matchRules, + O, parseValue, touchInput, transformToArray, @@ -35,9 +36,9 @@ import { retrieveBlob } from "./handlers/files"; import { inputChange } from "./handlers/changes"; import { validate } from "../util/validation"; -const init = ( +const initValue = ( input: Input, - value: unknown, + value: Unknown, store: InputStore, config: InputConfig, fileConfig: InitFileConfig, @@ -62,7 +63,7 @@ const init = ( clone[ID].props.checked = clone[ID].value === value; } else if (input.type === "checkbox") { // Toggle the checkbox input - const cbV = (value as unknown[]).includes(clone[ID].value); + const cbV = (value as Unknown[]).includes(clone[ID].value); clone[ID].checked = cbV; clone[ID].props.checked = cbV; } else { @@ -78,7 +79,7 @@ const init = ( }; const populate = (state: any, type: StateType, config: InputConfig) => { - const final = {} as CreateObjectInput; + const final = {} as CreateObjectInputs; const helper = He(); for (const stateKey in state) { const parseKey = type === "object" ? stateKey : state[stateKey].id; @@ -92,7 +93,7 @@ const populate = (state: any, type: StateType, config: InputConfig) => { final[parseKey] = v; helper.s[parseKey] = { ...v }; } - const entry = helper.clean(matchRules(final, helper)) as ObjectInput; + const entry = helper.clean(matchRules(final, helper)) as ObjectInputs; const isValid = validateState(entry).isValid; return { entry, @@ -104,7 +105,7 @@ const populate = (state: any, type: StateType, config: InputConfig) => { }; const computeOnce = ( - initialState: unknown, + initialState: Unknown, type: StateType, config: InputConfig ) => { @@ -119,12 +120,22 @@ const computeOnce = ( store.set((ref) => { const entry = ref.entry; for (const key in entry) { + // onChange ref.entry[key].onChange = (value) => inputChange(value, key, entry, store, config, helper); + // Props onChange ref.entry[key].props.onChange = (value) => inputChange(value, key, entry, store, config, helper); - ref.entry[key].init = (value, fileConfig: InitFileConfig = {}) => - init(entry[key], value, store, config, fileConfig, helper); + // initValue + ref.entry[key].initValue = (value, fileConfig: InitFileConfig = {}) => + initValue(entry[key], value, store, config, fileConfig, helper); + // setExtraData + ref.entry[key].setExtraData = (data) => { + store.set((ref) => { + ref.entry[key].extraData = data; + }); + }; + // files ref.entry[key].files = []; } }); @@ -156,7 +167,7 @@ const computeOnce = ( const v = { i: 0, ar: transformToArray(entry), - mapR: [] as unknown[] + mapR: [] as Unknown[] }; for (const key in entry) { v.mapR.push(callback(entry[key], v.i, v.ar)); @@ -168,15 +179,15 @@ const computeOnce = ( }; const forEach = (callback: ForEachCallback) => loop(callback, "forEach"); - const map = (callback: MapCallback) => loop(callback, "map") as unknown[]; + const map = (callback: MapCallback) => loop(callback, "map") as Unknown[]; const toArray = (): Input[] & IsValid => { const r = transformToArray(store.get("entry")) as Input[] & IsValid; r.isValid = store.get("isValid"); return r; }; - const toObject = (): ObjectInput & IsValid => { - const r = store.get("entry") as ObjectInput & IsValid; + const toObject = (): ObjectInputs & IsValid => { + const r = store.get("entry") as ObjectInputs & IsValid; r.isValid = store.get("isValid"); return r; }; @@ -187,7 +198,7 @@ const computeOnce = ( }); }; - const length = Object.keys(initialForm).length; + const length = O.keys(initialForm).length; const result: ComputeOnceOut = { reset, @@ -225,7 +236,7 @@ const computeOnce = ( }; const parsedInputs = ( - initialState: unknown, + initialState: Unknown, type: StateType, config: InputConfig, selective?: string @@ -242,29 +253,37 @@ const parsedInputs = ( const form = useMemo(() => rest, []); const parsedInputs = - type === "object" ? inputs : transformToArray(inputs as ObjectInput); + type === "object" + ? inputs + : transformToArray(inputs as ObjectInputs); (parsedInputs as typeof parsedInputs & IsValid).isValid = isValid; return [parsedInputs, form]; }; -function useInputs( - initialState: CreateObjectInput | S, +// External declaration support (Dynamic infer) +function useInputs( + initialState: I extends Array + ? CreateArrayInputs | I + : CreateObjectInputs | I, config?: InputConfig -): ObjStateOutput; -function useInputs( - initialState: CreateArrayInput, +): I extends Array ? ArrayStateOutput : ObjStateOutput; +// Internal declaration object +function useInputs>( + initialState: CreateObjectInputs | I, config?: InputConfig -): ArrayStateOutput; -function useInputs( - initialState: (string | InternalInput)[], +): ObjStateOutput; +// Internal declaration Array +function useInputs( + initialState: CreateArrayInputs | I, config?: InputConfig ): ArrayStateOutput; +// string function useInputs( initialState: string, config?: InputConfig ): StringStateOutput; -function useInputs(initialState: unknown, config: InputConfig = {}): unknown { +function useInputs(initialState: Unknown, config: InputConfig = {}): Unknown { if (initialState instanceof Array) { return parsedInputs( initialState.map((entry, i) => diff --git a/src/tracking/index.ts b/src/tracking/index.ts index 2e11e1d..5bf5968 100644 --- a/src/tracking/index.ts +++ b/src/tracking/index.ts @@ -4,8 +4,9 @@ import type { MapCallback, TrackUtil } from "../types"; +import { O } from "../util"; -const TRACKING_KEYS = Object.freeze([ +const TRACKING_KEYS = O.freeze([ "getValues", "reset", "isValid", diff --git a/src/types/index.ts b/src/types/index.ts index 5e2ce0b..08f23a6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -119,6 +119,7 @@ type ErrorMessageType = Unknown; interface InternalInput { id?: string; + accept?: string; name?: string; type?: HTMLInputTypeAttribute; label?: Unknown; @@ -131,6 +132,7 @@ interface InternalInput { placeholder?: Unknown; errorMessage?: Unknown; validation?: ValidationStateType; + extraData?: Unknown; } interface ParsedFile { @@ -155,9 +157,9 @@ type ValidateState = { // FOr some reason, Build-in Required doesn't work interface InputProps { id: string; + accept: string; name: string; type: HTMLInputTypeAttribute; - "aria-label": Unknown; value: Unknown; checked: boolean; multiple: boolean; @@ -179,31 +181,33 @@ interface Input extends InputProps { valid: boolean; touched: boolean; - init(value: Unknown, initFileConfig?: InitFileConfig): void; + initValue(value: Unknown, initFileConfig?: InitFileConfig): void; + setExtraData(data: Unknown): void; props: InputProps; + extraData: Unknown; } interface InitFileConfig { // entryFormat?: "url" | "url[]"; // proxyUrl?: string; // useDefaultProxyUrl?: boolean; - getBlob?(url: string): File; + getBlob?(url: string): Blob | Promise; } -type CreateObjectInput = { - [key in string]: InternalInput; +type CreateObjectInputs = { + [key in K & string]: InternalInput; }; -type ObjectInput = { - [key in string]: Input; +type ObjectInputs = { + [key in K & string]: Input; }; -type ArrayInput = Input[]; -type CreateArrayInput = InternalInput[]; +type ArrayInputs = Input[]; +type CreateArrayInputs = (string | InternalInput)[]; -type ObjStateOutput = [{ [k in Key & string]: Input } & IsValid, Form]; +type ObjStateOutput = [{ [k in keyof I & string]: Input } & IsValid, Form]; type StringStateOutput = [Input & IsValid, Form]; -type ArrayStateOutput = [ArrayInput & IsValid, Form]; +type ArrayStateOutput = [ArrayInputs & IsValid, Form]; type StateType = "object" | "array"; @@ -219,25 +223,25 @@ interface IsValid { } interface CommonForm { - toObject(): ObjectInput & IsValid; + toObject(): ObjectInputs & IsValid; - toArray(): ArrayInput & IsValid; + toArray(): ArrayInputs & IsValid; reset(): void; forEach(callback: ForEachCallback): void; - map(callback: MapCallback): unknown[]; + map(callback: MapCallback): Unknown[]; } type Method = "forEach" | "map"; interface ForEachCallback { - (input: Input, index: number, array: ArrayInput): void; + (input: Input, index: number, array: ArrayInputs): void; } interface MapCallback { - (input: Input, index: number, array: ArrayInput): unknown; + (input: Input, index: number, array: ArrayInputs): Unknown; } interface Form extends CommonForm { @@ -249,8 +253,8 @@ interface Form extends CommonForm { showError(): void; } -interface IDTrackUtil extends CommonForm { - ID: S; +interface IDTrackUtil extends CommonForm { + ID: I; length: number; isValid(): boolean; @@ -273,7 +277,7 @@ interface TrackUtil extends CommonForm { } type InputStore = StoreType<{ - entry: ObjectInput; + entry: ObjectInputs; isValid: boolean; helper: Helper; initialValid: boolean; @@ -301,12 +305,12 @@ interface ComputeOnceOut extends CommonForm { interface Helper { ok: { [k in string]: Set }; - s: CreateObjectInput; + s: CreateObjectInputs; em: { [k in string]: ErrorMessageType | undefined }; tm: { [k in string]: string[] }; a: { [k in string]: Unknown }; - clean(s: CreateObjectInput): CreateObjectInput; + clean(s: CreateObjectInputs): CreateObjectInputs; } export type { @@ -333,7 +337,7 @@ export type { InputStore, AsyncCallback, AsyncValidationParams, - ObjectInput, + ObjectInputs, ComputeOnceOut, ParsedFile, InitFileConfig, @@ -341,9 +345,9 @@ export type { MapCallback, Method, IsValid, - CreateObjectInput, - ArrayInput, - CreateArrayInput, + CreateObjectInputs, + ArrayInputs, + CreateArrayInputs, InputProps, ValidateState }; diff --git a/src/util/helper.ts b/src/util/helper.ts index 7269a45..ae0b5c7 100644 --- a/src/util/helper.ts +++ b/src/util/helper.ts @@ -1,4 +1,4 @@ -import type { ComputeOnceOut, Helper, CreateObjectInput } from "../types"; +import type { ComputeOnceOut, Helper, CreateObjectInputs } from "../types"; const He = (): Helper => { // omitted keys @@ -12,7 +12,7 @@ const He = (): Helper => { // async delay const a = {}; - const clean = (s: CreateObjectInput) => { + const clean = (s: CreateObjectInputs) => { for (const sKey in s) { delete s[sKey].validation?.copy; delete s[sKey].validation?.match; diff --git a/src/util/index.ts b/src/util/index.ts index da98052..61e4bf1 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,5 +1,5 @@ import type { - CreateObjectInput, + CreateObjectInputs, Helper, Input, InputProps, @@ -7,7 +7,7 @@ import type { InternalInput, MatchResultType, MergeType, - ObjectInput, + ObjectInputs, ParsedFile, Unknown, ValidateState, @@ -16,6 +16,7 @@ import type { import { deepMatch, parseCopy, validate } from "./validation"; import { createCheckboxValue } from "../inputs/handlers/checkbox"; import { radioIsChecked } from "../inputs/handlers/radio"; +export const O = Object; const parseValue = (input: Input, value: any) => input.type === "number" || input.validation?.number @@ -26,7 +27,7 @@ const parseValue = (input: Input, value: any) => const initValid = (entry: InternalInput) => { const validation = entry.validation || {}; - const isValid = !Object.keys(validation).length; + const isValid = !O.keys(validation).length; // return !["", 0, null, undefined].includes(entry.value); // return isValid ? !!entry.checked : false; return entry.checked ? true : isValid; @@ -37,7 +38,6 @@ const lockProps = (entry: Input) => { id: entry.id, name: entry.name, type: entry.type, - "aria-label": entry.label, value: entry.value, checked: entry.checked, multiple: entry.multiple, @@ -64,7 +64,8 @@ const commonProps = (entry: InternalInput, id: string) => { touched: false, placeholder: entry.placeholder ?? entry.name ?? defaultID, errorMessage: undefined, - validating: false + validating: false, + extraData: null }; }; @@ -148,7 +149,7 @@ const merge = ( // Match and copy input validation const mcv = ( helper: Helper, - state: CreateObjectInput, + state: CreateObjectInputs, stateKey: string, matchOrCopyKey: string, keyPath: keyof ValidationStateType @@ -255,7 +256,7 @@ const mcv = ( * }. * */ -const matchRules = (state: CreateObjectInput, helper: Helper) => { +const matchRules = (state: CreateObjectInputs, helper: Helper) => { const patch = { checkbox: { tab: [] @@ -312,7 +313,7 @@ const matchRules = (state: CreateObjectInput, helper: Helper) => { } } - Object.keys(patch).forEach((o) => { + O.keys(patch).forEach((o) => { if (patch[o].fv) { patch[o].tab.forEach((id: string) => { // we get the name @@ -369,7 +370,7 @@ const touchInput = (store: InputStore, helper: Helper) => { // Validate the state // Set form is valid -const validateState = (data: ObjectInput): ValidateState => { +const validateState = (data: ObjectInputs): ValidateState => { let isValid = true; let invalidKey = null; for (const formKey in data) { @@ -382,7 +383,7 @@ const validateState = (data: ObjectInput): ValidateState => { return { isValid, invalidKey }; }; // T transform array to object and vice versa -const transformToArray = (state: ObjectInput) => { +const transformToArray = (state: ObjectInputs) => { const result: Input[] = []; for (const key in state) { result.push(state[key]); @@ -401,7 +402,7 @@ const cleanFiles = (files: ParsedFile[]) => { }; // E extract values from state -const extractValues = (state: ObjectInput) => { +const extractValues = (state: ObjectInputs) => { const result = {} as { [k in string]: any }; for (const key in state) { const K = state[key].name; diff --git a/src/util/validation.ts b/src/util/validation.ts index e37de0e..5375f16 100644 --- a/src/util/validation.ts +++ b/src/util/validation.ts @@ -5,9 +5,9 @@ import type { ErrorMessageType, InputStore, MatchResultType, - CreateObjectInput, + CreateObjectInputs, Input, - ObjectInput, + ObjectInputs, ValidationStateType, Unknown, Helper @@ -45,7 +45,7 @@ const parseCopy = ( // Deep match const deepMatch = ( helper: Helper, - state: CreateObjectInput, + state: CreateObjectInputs, stateKey: string, matchKey: string, keyPath: keyof ValidationStateType @@ -121,7 +121,7 @@ const getValue = (rule: any) => { // V is validate const validate = ( helper: Helper, - state: ObjectInput, + state: ObjectInputs, target: string, value: Unknown ) => { @@ -342,7 +342,7 @@ const validate = ( const asyncValidation = ( store: InputStore, helper: Helper, - state: ObjectInput, + state: ObjectInputs, target: string, value: unknown, callback: AsyncCallback From b44c71388f1aeaa83a4f108e58233bc663173483 Mon Sep 17 00:00:00 2001 From: klm <58208740+klm-lab@users.noreply.github.com> Date: Sat, 25 Nov 2023 01:59:05 +0000 Subject: [PATCH 3/3] Add getInputById and ByName --- src/inputs/index.ts | 30 +++++++++++++++++++++++++++-- src/tracking/index.ts | 45 ++++++++++++++++++++++++++++++++++++------- src/types/index.ts | 6 +++++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/inputs/index.ts b/src/inputs/index.ts index 6f051b9..daa35f5 100644 --- a/src/inputs/index.ts +++ b/src/inputs/index.ts @@ -193,11 +193,32 @@ const computeOnce = ( }; const reset = () => { store.set((ref) => { - ref.entry = initialForm; + const entry = ref.entry; + const rForm: ObjectInputs = {}; + O.keys(initialForm).forEach((k) => { + rForm[k] = { + ...initialForm[k], + extraData: entry[k].extraData + }; + }); + ref.entry = rForm; ref.isValid = store.get("initialValid"); }); }; + const getInputById = (id: string) => { + return store.get(`entry.${id}`); + }; + + const getInputsByName = (name: string) => { + const entry = store.get("entry"); + const r: Input[] = []; + O.keys(entry).forEach((k) => { + entry[k].name === name && r.push(entry[k]); + }); + return r; + }; + const length = O.keys(initialForm).length; const result: ComputeOnceOut = { @@ -210,11 +231,16 @@ const computeOnce = ( map, length, onSubmit, - showError + showError, + getInputById, + getInputsByName }; if (config.trackID && config.trackID.ID) { config.trackID.getValues = getValues; + config.trackID.showError = showError; + config.trackID.getInputById = getInputById; + config.trackID.getInputsByName = getInputsByName; config.trackID.toArray = toArray; config.trackID.toObject = toObject; config.trackID.forEach = forEach; diff --git a/src/tracking/index.ts b/src/tracking/index.ts index 5bf5968..d8cccf6 100644 --- a/src/tracking/index.ts +++ b/src/tracking/index.ts @@ -1,6 +1,7 @@ import type { ForEachCallback, IDTrackUtil, + Input, MapCallback, TrackUtil } from "../types"; @@ -15,7 +16,10 @@ const TRACKING_KEYS = O.freeze([ "forEach", "map", "length", - "useValues" + "useValues", + "showError", + "getInputById", + "getInputsByName" ]); export const trackInputs = (trackingID: S[]) => { @@ -61,6 +65,31 @@ export const trackInputs = (trackingID: S[]) => { }; }); + track.getInputById = (id: string) => { + let i = undefined; + for (const t in track) { + if ( + !TRACKING_KEYS.includes(t) && + track[t] && + track[t].getInputById && + i === undefined + ) { + i = track[t].getInputById(id); + } + } + return i; + }; + + track.getInputsByName = (name: string) => { + const i: Input[] = []; + for (const t in track) { + if (!TRACKING_KEYS.includes(t) && track[t] && track[t].getInputById) { + i.push(...track[t].getInputsByName(name)); + } + } + return i; + }; + ["forEach", "map"].forEach((func) => { track[func] = (callback: ForEachCallback | MapCallback) => { for (const t in track) { @@ -71,13 +100,15 @@ export const trackInputs = (trackingID: S[]) => { }; }); - track.reset = () => { - for (const t in track) { - if (!TRACKING_KEYS.includes(t) && track[t] && track[t].reset) { - track[t].reset(); + ["reset", "showError"].forEach((func) => { + track[func] = () => { + for (const t in track) { + if (!TRACKING_KEYS.includes(t) && track[t] && track[t][func]) { + track[t][func](); + } } - } - }; + }; + }); track.isValid = () => { let isValid = true; diff --git a/src/types/index.ts b/src/types/index.ts index 08f23a6..a013a75 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -232,6 +232,11 @@ interface CommonForm { forEach(callback: ForEachCallback): void; map(callback: MapCallback): Unknown[]; + + showError(): void; + + getInputById(id: string): Input; + getInputsByName(name: string): Input[]; } type Method = "forEach" | "map"; @@ -250,7 +255,6 @@ interface Form extends CommonForm { getValues(name?: string): Unknown; onSubmit(event: SyntheticEvent): void; - showError(): void; } interface IDTrackUtil extends CommonForm {