-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit moving project from bitbucket
- Loading branch information
Showing
14 changed files
with
1,956 additions
and
2 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"name": "zustand-forms", | ||
"version": "0.3.1", | ||
"description": "**fast typesafe form states as zustand stores**", | ||
"main": "./lib/index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"build": "tsc", | ||
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", | ||
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx", | ||
"prepare": "npm run build" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+ssh://git@bitbucket.org/conduct/react-helpers-forms.git" | ||
}, | ||
"keywords": ["form", "forms", "zustand", "react", "typescript", "validation"], | ||
"author": "Hugo McPhee", | ||
"license": "MIT", | ||
"homepage": "https://bitbucket.org/conduct/react-helpers-forms#readme", | ||
"typings": "./lib/index.d.ts", | ||
"devDependencies": { | ||
"@typescript-eslint/eslint-plugin": "^3.7.1", | ||
"@typescript-eslint/parser": "^3.7.1", | ||
"eslint": "^7.5.0", | ||
"eslint-config-react-app": "^5.2.1", | ||
"eslint-plugin-import": "^2.22.0", | ||
"eslint-plugin-react": "^7.20.5", | ||
"eslint-plugin-react-hooks": "^4.0.8", | ||
"eslint-plugin-flowtype": "^5.2.0", | ||
"eslint-plugin-jsx-a11y": "^6.3.1", | ||
"babel-eslint": "^10.1.0", | ||
"prettier": "^2.0.5", | ||
"typescript": "^3.9.7", | ||
"react": "^16.13.1", | ||
"@types/react": "^16.9.43" | ||
}, | ||
"files": [ | ||
"lib/**/*" | ||
], | ||
"prettier": { | ||
"trailingComma": "es5", | ||
"arrowParens": "always" | ||
}, | ||
"dependencies": { | ||
"immer": "^7.0.7", | ||
"zustand": "^2.2.3" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.13.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { StoreApi } from "zustand"; | ||
|
||
export { default as makeMakeFormStore } from "./makeFormStore"; | ||
export { | ||
makeValidatorFunction as makeValidator, | ||
makeValidatorUtils, | ||
getTypedMakeValidator, | ||
} from "./makeFormStore/validatorFunctionUtils"; | ||
export { makeFormHooks } from "./makeFormHooks"; | ||
|
||
// @ts-ignore: expects "export type {..." but that only works in the module, not if this is in a local folder | ||
export { MakeFormStoresHelperTypes } from "./utils/typeHelpers"; | ||
|
||
export type InputIdFromFormApi< | ||
T_FormApi extends StoreApi<any> | ||
> = keyof ReturnType<T_FormApi["getState"]>["inputStates"]; | ||
|
||
// export type { | ||
// InputValue, | ||
// InputValueFromOptions, | ||
// MakeInputsOptions, | ||
// } from "./makeFormStore/types"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import useLatestDefined from "../utils/useLatestDefined"; | ||
import makeMakeFormStore from "../makeFormStore"; | ||
import { InputValueFromOptions } from "../makeFormStore/types"; | ||
import { MakeFormStoresHelperTypes } from "../utils/typeHelpers"; | ||
import shallow from "zustand/shallow"; | ||
|
||
type AllFormStoresObject = Record< | ||
string, | ||
{ | ||
hook: ReturnType<ReturnType<typeof makeMakeFormStore>>[0]; | ||
api: ReturnType<ReturnType<typeof makeMakeFormStore>>[1]; | ||
} | ||
>; | ||
|
||
export function makeFormHooks<T_AllFormStores extends AllFormStoresObject>( | ||
allFormStores: T_AllFormStores | ||
) { | ||
type FormStores = typeof allFormStores; | ||
// type FormName = keyof FormStores; // FormStoresHelperTypes["FormStores"] so it works with the helper types | ||
|
||
type FormStoresHelperTypes = MakeFormStoresHelperTypes<typeof allFormStores>; | ||
type FormName = FormStoresHelperTypes["FormName"]; | ||
type InputIdByFormName = FormStoresHelperTypes["InputIdByFormName"]; | ||
|
||
type AnyFormStoreHookUnion = { | ||
[K_FormName in FormName]: FormStores[K_FormName]["hook"]; | ||
}[FormName]; | ||
|
||
type AnyFormStoreApiUnion = { | ||
[K_FormName in FormName]: FormStores[K_FormName]["api"]; | ||
}[FormName]; | ||
|
||
type AnyFormStoreHook = UnionToIntersection<AnyFormStoreHookUnion>; | ||
type AnyFormStoreApi = UnionToIntersection<AnyFormStoreApiUnion>; | ||
|
||
// using FormStoresHelperTypes["InputIdByFormName"] so it works with the helper types | ||
type FormStoreHookFromName< | ||
T_FormName extends FormName | ||
> = FormStores[T_FormName]["hook"]; | ||
|
||
type InputIdsFromFormName< | ||
T_FormName extends keyof FormStores | ||
> = keyof ReturnType< | ||
FormStores[T_FormName]["api"]["getState"] | ||
>["inputStates"]; | ||
|
||
type UnionToIntersection<U> = (U extends any | ||
? (k: U) => void | ||
: never) extends (k: infer I) => void | ||
? I | ||
: never; | ||
|
||
function useFormInput< | ||
T_FormName extends FormName, | ||
T_InputId extends InputIdByFormName[T_FormName] | ||
>({ formName, inputId }: { inputId: T_InputId; formName: T_FormName }) { | ||
type T_FormStore = ReturnType< | ||
typeof allFormStores[typeof formName]["api"]["getState"] | ||
>; | ||
|
||
const useFormStore = allFormStores[formName].hook; | ||
|
||
const [inputState, updateInput, toggleFocus] = useFormStore( | ||
(state: T_FormStore) => [ | ||
state.inputStates[inputId], | ||
state.updateInput, | ||
state.toggleFocus, | ||
], | ||
shallow | ||
); | ||
|
||
const { | ||
isValid, | ||
isValidServer, | ||
hasBeenUnfocused, | ||
localErrorTypes, | ||
localErrorTextByErrorType, | ||
serverErrorTexts, | ||
isFocused, | ||
isEdited, | ||
timeUpdated, | ||
value, | ||
validatorsOptions, | ||
} = inputState; | ||
|
||
const hasHadServerErrors = serverErrorTexts.length > 0; | ||
const canShowServerErrors = !isValidServer; | ||
const canShowLocalErrors = | ||
(hasBeenUnfocused && localErrorTypes.length > 0) || hasHadServerErrors; | ||
const canShowErrors = canShowLocalErrors || canShowServerErrors; | ||
|
||
const valueIsEmpty = (typeof value === "string" && value === "") || false; | ||
const shouldShowPlaceholder = valueIsEmpty && !isFocused; | ||
|
||
const inlineErrorTexts = [ | ||
...localErrorTypes.map( | ||
(errorType: string) => localErrorTextByErrorType[errorType] | ||
), | ||
// adds server error texts after local errors | ||
...(canShowServerErrors ? serverErrorTexts : []), | ||
]; | ||
|
||
const latestVisibleInlineErrorTexts = useLatestDefined({ | ||
value: inlineErrorTexts, | ||
isDefined: inlineErrorTexts.length > 0, | ||
}); | ||
|
||
// TODO fix the as any types (T_InputId isn't working there) | ||
return { | ||
value: inputState.value, | ||
onChange: (newValue: InputValueFromOptions<any, any, any, any, any>) => | ||
updateInput({ inputId, newValue } as any), | ||
onFocus: () => toggleFocus({ inputId, isFocused: true } as any), | ||
onBlur: () => toggleFocus({ inputId, isFocused: false } as any), | ||
isFocused, | ||
isEdited, | ||
isValid, | ||
canShowErrors, | ||
hasVisibleErrors: canShowErrors && !isValid, | ||
inlineErrorTexts, | ||
latestVisibleInlineErrorTexts, | ||
localErrorTypes, | ||
localErrorTextByErrorType, | ||
serverErrorTexts, | ||
shouldShowPlaceholder, | ||
validatorsOptions, | ||
valueIsEmpty, | ||
}; | ||
} | ||
|
||
return { useFormInput }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// only a partial InputState object for properties with basic types | ||
export const defaultInputState = { | ||
localErrorTypes: [] as string[], | ||
localErrorTextByErrorType: {} as Record<string, string>, | ||
serverErrorTexts: [] as string[], | ||
// times | ||
timeUpdated: 0, | ||
timeBecameCheckable: 0, // whenever the input becomes visible | ||
timeBecameUncheckable: 0, // whenever the input becomes hidden | ||
timeFocused: 0, | ||
timeUnfocused: 0, | ||
// booleans | ||
isValid: true, | ||
isEdited: false, | ||
hasBeenUnfocused: false, | ||
isFocused: false, | ||
isCheckable: true, | ||
}; |
Oops, something went wrong.