@iwsio/forms
Advanced tools
Comparing version 2.2.2 to 3.0.0-alpha.1
@@ -5,2 +5,3 @@ import { PropsWithChildren } from 'react'; | ||
import { FieldValues } from './types'; | ||
import { ErrorMapping } from './useErrorMapping'; | ||
export type FieldManagerProps = PropsWithChildren & { | ||
@@ -16,2 +17,6 @@ /** | ||
/** | ||
* Error mapping to be used for validation. If not provided, default error messages are used. | ||
*/ | ||
errorMapping?: ErrorMapping; | ||
/** | ||
* Callback to be called when form is valid and submitted. Provides current field values. | ||
@@ -39,2 +44,6 @@ */ | ||
/** | ||
* Error mapping to be used for validation. If not provided, default error messages are used. | ||
*/ | ||
errorMapping?: ErrorMapping; | ||
/** | ||
* Callback to be called when form is valid and submitted. Provides current field values. | ||
@@ -41,0 +50,0 @@ */ |
@@ -27,4 +27,4 @@ "use strict"; | ||
exports.FieldManager = (0, react_1.forwardRef)((_a, ref) => { | ||
var { children, fields, defaultValues, onValidSubmit } = _a, props = __rest(_a, ["children", "fields", "defaultValues", "onValidSubmit"]); | ||
const fieldState = (0, useFieldState_1.useFieldState)(fields, defaultValues, onValidSubmit); | ||
var { children, fields, defaultValues, onValidSubmit, errorMapping } = _a, props = __rest(_a, ["children", "fields", "defaultValues", "onValidSubmit", "errorMapping"]); | ||
const fieldState = (0, useFieldState_1.useFieldState)(fields, defaultValues, onValidSubmit, errorMapping); | ||
return ((0, jsx_runtime_1.jsx)(FieldManagerContext_1.FieldManagerContext.Provider, { value: fieldState, children: (0, jsx_runtime_1.jsx)(FieldManagerForm_1.FieldManagerForm, Object.assign({ ref: ref }, props, { children: children })) })); | ||
@@ -31,0 +31,0 @@ }); |
@@ -11,1 +11,2 @@ export * from './Input'; | ||
export * from './ValidatedForm'; | ||
export * from './useErrorMapping'; |
@@ -27,1 +27,2 @@ "use strict"; | ||
__exportStar(require("./ValidatedForm"), exports); | ||
__exportStar(require("./useErrorMapping"), exports); |
@@ -24,3 +24,3 @@ "use strict"; | ||
if (onFieldError != null) | ||
onFieldError(name, message); | ||
onFieldError(name, localRef.current.validity, message); | ||
}; | ||
@@ -45,4 +45,9 @@ const localOnChange = (e) => { | ||
(0, react_1.useEffect)(() => { | ||
if (fieldError !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError || ''); | ||
var _a; | ||
if (fieldError == null) | ||
return localRef.current.setCustomValidity(''); | ||
if (fieldError.message == null) | ||
return localRef.current.setCustomValidity(''); | ||
if (((_a = fieldError.validity) === null || _a === void 0 ? void 0 : _a.customError) && fieldError.message !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError.message); | ||
} | ||
@@ -59,3 +64,3 @@ }, [fieldError]); | ||
const localRef = (0, useForwardRef_1.useForwardRef)(ref); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors } = (0, useFieldManager_1.useFieldManager)(); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors, mapError } = (0, useFieldManager_1.useFieldManager)(); | ||
const handleOnChange = (e) => { | ||
@@ -66,3 +71,7 @@ managerOnChange(e); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.Input, Object.assign({ ref: localRef, onChange: handleOnChange }, (/checkbox|radio/i.test(type) ? { value, checked: fields[name] === value } : { value: fields[name] }), { type: type, name: name, fieldError: fieldErrors[name], onFieldError: setFieldError }, other))); | ||
const handleFieldError = (key, validity, message) => { | ||
const mappedError = mapError(validity, message); | ||
setFieldError(key, mappedError, validity); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.Input, Object.assign({ ref: localRef, onChange: handleOnChange }, (/checkbox|radio/i.test(type) ? { value, checked: fields[name] === value } : { value: fields[name] }), { type: type, name: name, fieldError: fieldErrors[name], onFieldError: handleFieldError }, other))); | ||
}); | ||
@@ -69,0 +78,0 @@ exports.InputField.displayName = 'InputField'; |
@@ -24,3 +24,3 @@ "use strict"; | ||
if (onFieldError != null) | ||
onFieldError(name, message); | ||
onFieldError(name, localRef.current.validity, message); | ||
}; | ||
@@ -45,4 +45,7 @@ const localOnChange = (e) => { | ||
(0, react_1.useEffect)(() => { | ||
if (fieldError !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError || ''); | ||
var _a; | ||
if (fieldError == null) | ||
return; | ||
if (((_a = fieldError.validity) === null || _a === void 0 ? void 0 : _a.customError) && fieldError.message !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError.message || ''); | ||
} | ||
@@ -56,3 +59,3 @@ }, [fieldError]); | ||
const localRef = (0, useForwardRef_1.useForwardRef)(ref); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors } = (0, useFieldManager_1.useFieldManager)(); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors, mapError } = (0, useFieldManager_1.useFieldManager)(); | ||
const handleOnChange = (e) => { | ||
@@ -63,4 +66,8 @@ managerOnChange(e); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.Select, Object.assign({ ref: localRef, onChange: handleOnChange, onFieldError: setFieldError, name: name, fieldError: fieldErrors[name], value: fields[name] }, other))); | ||
const handleFieldError = (key, validity, message) => { | ||
const mappedError = mapError(validity, message); | ||
setFieldError(key, mappedError, validity); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.Select, Object.assign({ ref: localRef, onChange: handleOnChange, onFieldError: handleFieldError, name: name, fieldError: fieldErrors[name], value: fields[name] }, other))); | ||
}); | ||
exports.SelectField.displayName = 'SelectField'; |
@@ -24,3 +24,3 @@ "use strict"; | ||
if (onFieldError != null) | ||
onFieldError(name, message); | ||
onFieldError(name, localRef.current.validity, message); | ||
}; | ||
@@ -45,4 +45,7 @@ const localOnChange = (e) => { | ||
(0, react_1.useEffect)(() => { | ||
if (fieldError !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError || ''); | ||
var _a; | ||
if (fieldError == null) | ||
return; | ||
if (((_a = fieldError.validity) === null || _a === void 0 ? void 0 : _a.customError) && fieldError.message !== localRef.current.validationMessage) { | ||
localRef.current.setCustomValidity(fieldError.message || ''); | ||
} | ||
@@ -56,3 +59,3 @@ }, [fieldError]); | ||
const localRef = (0, useForwardRef_1.useForwardRef)(ref); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors } = (0, useFieldManager_1.useFieldManager)(); | ||
const { handleChange: managerOnChange, fields, setFieldError, fieldErrors, mapError } = (0, useFieldManager_1.useFieldManager)(); | ||
const handleOnChange = (e) => { | ||
@@ -63,4 +66,8 @@ managerOnChange(e); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.TextArea, Object.assign({ ref: localRef, onChange: handleOnChange, onFieldError: setFieldError, name: name, fieldError: fieldErrors[name], value: fields[name] }, other))); | ||
const handleFieldError = (key, validity, message) => { | ||
const mappedError = mapError(validity, message); | ||
setFieldError(key, mappedError, validity); | ||
}; | ||
return ((0, jsx_runtime_1.jsx)(exports.TextArea, Object.assign({ ref: localRef, onChange: handleOnChange, onFieldError: handleFieldError, name: name, fieldError: fieldErrors[name], value: fields[name] }, other))); | ||
}); | ||
exports.TextAreaField.displayName = 'TextAreaField'; |
import { ChangeEvent, Dispatch, SetStateAction } from 'react'; | ||
export type FieldError = { | ||
message: string | undefined; | ||
validity?: ValidityState | undefined; | ||
}; | ||
export type ValidationProps = { | ||
fieldError?: string; | ||
onFieldError?: (key: string, message?: string) => void; | ||
fieldError?: FieldError; | ||
onFieldError?: (key: string, validity: ValidityState, message?: string) => void; | ||
}; | ||
@@ -37,3 +41,3 @@ export type FieldValues = Record<string, string>; | ||
*/ | ||
fieldErrors: Record<string, string>; | ||
fieldErrors: Record<string, FieldError>; | ||
/** | ||
@@ -50,5 +54,5 @@ * Combines `reportValidation` and the `fieldErrors` state to determine if field requested should show an error. | ||
*/ | ||
setFieldError: (key: string, message?: string) => void; | ||
setFieldError: (key: string, message?: string, validity?: ValidityState | undefined) => void; | ||
/** Set ALL errors at once */ | ||
setFieldErrors: Dispatch<SetStateAction<Record<string, string>>>; | ||
setFieldErrors: Dispatch<SetStateAction<Record<string, FieldError>>>; | ||
/** | ||
@@ -76,2 +80,9 @@ * onChange handler to manage state and errors for input, select, and textarea changes. | ||
setDefaultValues: (values: FieldValues) => void; | ||
/** | ||
* Map the error message based on the field's validity state. | ||
* @param validity | ||
* @param message | ||
* @returns | ||
*/ | ||
mapError: (validity: ValidityState, message?: string) => string | undefined; | ||
}; |
import { FieldValues, UseFieldStateResult } from './types'; | ||
import { ErrorMapping } from './useErrorMapping'; | ||
/** | ||
@@ -8,2 +9,2 @@ * Manages field state via change handler, values and error state. | ||
*/ | ||
export declare function useFieldState(fields: FieldValues, defaultValues?: FieldValues, onValidSubmit?: (fields: FieldValues) => void): UseFieldStateResult; | ||
export declare function useFieldState(fields: FieldValues, defaultValues?: FieldValues, onValidSubmit?: (fields: FieldValues) => void, errorMapping?: ErrorMapping): UseFieldStateResult; |
@@ -7,2 +7,3 @@ "use strict"; | ||
const defaults_1 = require("./defaults"); | ||
const useErrorMapping_1 = require("./useErrorMapping"); | ||
/** | ||
@@ -14,3 +15,3 @@ * Manages field state via change handler, values and error state. | ||
*/ | ||
function useFieldState(fields, defaultValues, onValidSubmit) { | ||
function useFieldState(fields, defaultValues, onValidSubmit, errorMapping) { | ||
const initDefaultFieldValues = defaultValues != null ? (0, defaults_1.defaults)((0, omitBy_1.omitBy)(fields, (v) => v == null || v === ''), defaultValues) : fields; | ||
@@ -21,2 +22,3 @@ const [reportValidation, setReportValidation] = (0, react_1.useState)(false); | ||
const [defaultFieldValues, setDefaultFieldValues] = (0, react_1.useState)(initDefaultFieldValues); | ||
const mapError = (0, useErrorMapping_1.useErrorMapping)(errorMapping); | ||
const localOnValidSubmit = (0, react_1.useCallback)(onValidSubmit, []); | ||
@@ -46,10 +48,18 @@ const setField = (0, react_1.useCallback)((key, value) => { | ||
}, []); | ||
const setFieldError = (0, react_1.useCallback)((key, message) => { | ||
setFieldErrors((old) => (Object.assign(Object.assign({}, old), { [key]: message }))); | ||
}, []); | ||
const setFieldError = (0, react_1.useCallback)((key, message, validity) => { | ||
let _validity; | ||
const hasMessage = message != null && message.trim().length > 0; | ||
if (hasMessage) { | ||
_validity = validity !== null && validity !== void 0 ? validity : { valid: false, badInput: false, customError: true, patternMismatch: false, rangeOverflow: false, rangeUnderflow: false, stepMismatch: false, tooLong: false, tooShort: false, typeMismatch: false, valueMissing: false }; | ||
} | ||
setFieldErrors((old) => (Object.assign(Object.assign({}, old), { [key]: !hasMessage ? undefined : { message, validity: _validity } }))); | ||
}, [errorMapping]); | ||
const checkFieldError = (0, react_1.useCallback)((key) => { | ||
if (reportValidation && fieldErrors[key] != null && fieldErrors[key] !== '') | ||
return fieldErrors[key]; | ||
return undefined; | ||
}, [fieldErrors, reportValidation]); | ||
let fieldError = undefined; | ||
if (reportValidation && fieldErrors[key] != null && fieldErrors[key].message !== '') | ||
fieldError = fieldErrors[key]; | ||
if (fieldError == null) | ||
return undefined; | ||
return mapError(fieldError.validity, fieldError.message); | ||
}, [fieldErrors, reportValidation, mapError]); | ||
const reset = (0, react_1.useCallback)(() => { | ||
@@ -89,5 +99,6 @@ setFieldErrors((_old) => ({})); | ||
setReportValidation, | ||
setDefaultValues: setDefaultFieldValues | ||
setDefaultValues: setDefaultFieldValues, | ||
mapError | ||
}; | ||
} | ||
exports.useFieldState = useFieldState; |
{ | ||
"name": "@iwsio/forms", | ||
"version": "2.2.2", | ||
"version": "3.0.0-alpha.1", | ||
"description": "Simple library with useful React forms components and browser validation.", | ||
@@ -39,10 +39,10 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@testing-library/jest-dom": "^6.4.0", | ||
"@testing-library/react": "^14.1.2", | ||
"@testing-library/jest-dom": "^6.4.2", | ||
"@testing-library/react": "^14.2.1", | ||
"@testing-library/user-event": "^14.5.2", | ||
"@types/node": "^20.11.10", | ||
"@types/react": "^18.2.48", | ||
"@types/react-dom": "^18.2.18", | ||
"@typescript-eslint/eslint-plugin": "^6.20.0", | ||
"@typescript-eslint/parser": "^6.20.0", | ||
"@types/node": "^20.11.17", | ||
"@types/react": "^18.2.55", | ||
"@types/react-dom": "^18.2.19", | ||
"@typescript-eslint/eslint-plugin": "^7.0.1", | ||
"@typescript-eslint/parser": "^7.0.1", | ||
"eslint": "^8.56.0", | ||
@@ -61,3 +61,4 @@ "eslint-import-resolver-typescript": "^3.6.1", | ||
"typescript": "^5.3.3", | ||
"vitest": "^1.2.2" | ||
"vitest": "^1.2.2", | ||
"webpack-dev-server": "^5.0.1" | ||
}, | ||
@@ -64,0 +65,0 @@ "peerDependencies": { |
@@ -68,3 +68,3 @@ # @iwsio/forms | ||
`checkFieldError` | helper function to check error state in combination with `reportValidation`; returns true when fieldError exists and reportValidation is `true` | ||
`fieldErrors` | current field errors; `Record<string,string>` where keys match input names. | ||
`fieldErrors` | current field errors; `Record<string, FieldError>` where keys match input names. | ||
`handleChange` | `ChangeEventHandler<*>` used to control the input. | ||
@@ -197,2 +197,31 @@ `onChange` | alias to `handleChange` | ||
## Mapping Errors | ||
When disabling browser validation and customizing styling around error reporting, you might want to change the text for each error. Well, you can by providing an `errorMapping` prop to the `useFieldState` hook or to the `<FieldManager>` component. You can see [an example of customized error styling](https://forms.iws.io/invalid-feedback). | ||
The basic idea is to create a `ValidityState` map assigning each type of error to a message. | ||
```typescript | ||
// Note: These are intentionally vague for brevity. | ||
const mapping: ErrorMapping = { | ||
badInput: 'Invalid', | ||
customError: 'Invalid', | ||
patternMismatch: 'Invalid', | ||
rangeOverflow: 'Too high', | ||
rangeUnderflow: 'Too low', | ||
stepMismatch: 'Invalid', | ||
tooLong: 'Too long', | ||
tooShort: 'Too short', | ||
typeMismatch: 'Invalid', | ||
valueMissing: 'Required' | ||
} | ||
const { setFieldError, checkFieldError } = useFieldState(fields, defaultValues, onSubmit, mapping) | ||
``` | ||
Or with FieldManager: | ||
```jsx | ||
<FieldManager errorMapping={mapping} fields={fields}> | ||
``` | ||
## References: | ||
@@ -199,0 +228,0 @@ |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
56003
34
905
230
23
1