@weser/form 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/createForm.d.ts +254 -0
- package/dist/createForm.d.ts.map +1 -0
- package/dist/createForm.js +20 -0
- package/dist/createForm.js.map +1 -0
- package/dist/defaultFormatErrorMessage.d.ts +3 -0
- package/dist/defaultFormatErrorMessage.d.ts.map +1 -0
- package/dist/defaultFormatErrorMessage.js +4 -0
- package/dist/defaultFormatErrorMessage.js.map +1 -0
- package/dist/defaultIsEmpty.d.ts +1 -0
- package/dist/defaultIsEmpty.js +6 -0
- package/dist/defaultParseValue.d.ts +2 -0
- package/dist/defaultParseValue.js +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/useClickAway.d.ts +2 -0
- package/dist/useClickAway.js +19 -0
- package/dist/useClientOnly.d.ts +1 -0
- package/dist/useClientOnly.js +6 -0
- package/dist/useDisclosure.d.ts +16 -0
- package/dist/useDisclosure.js +25 -0
- package/dist/useField.d.ts +61 -0
- package/dist/useField.d.ts.map +1 -0
- package/dist/useField.js +123 -0
- package/dist/useField.js.map +1 -0
- package/dist/useFieldArray.d.ts +1 -0
- package/dist/useFieldArray.js +2 -0
- package/dist/useFocusTrap.d.ts +7 -0
- package/dist/useFocusTrap.js +77 -0
- package/dist/useForm.d.ts +74 -0
- package/dist/useForm.d.ts.map +1 -0
- package/dist/useForm.js +102 -0
- package/dist/useForm.js.map +1 -0
- package/dist/useForm.jsx +112 -0
- package/dist/useKeyDown.d.ts +7 -0
- package/dist/useKeyDown.js +22 -0
- package/dist/useRouteChange.d.ts +1 -0
- package/dist/useRouteChange.js +19 -0
- package/dist/useScrollBlocking.d.ts +1 -0
- package/dist/useScrollBlocking.js +28 -0
- package/dist/useTrigger.d.ts +7 -0
- package/dist/useTrigger.js +24 -0
- package/package.json +62 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ChangeEvent } from 'react';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
import { $ZodIssue } from 'zod/v4/core';
|
|
4
|
+
import { type T_Field, type Options } from './types';
|
|
5
|
+
export default function useField<T = string, C = ChangeEvent<HTMLInputElement>>(schema: ZodType, { name, value, disabled, touched, showValidationOn, parseEvent, formatValue, formatErrorMessage, _onInit, _onUpdate, _storedField, }?: Options<T, C>): {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
update: (data: Partial<T_Field<T>>) => void;
|
|
8
|
+
reset: () => void;
|
|
9
|
+
validate: () => import("zod").ZodSafeParseResult<unknown>;
|
|
10
|
+
errorMessage: string | undefined;
|
|
11
|
+
inputProps: {
|
|
12
|
+
onFocus: () => void;
|
|
13
|
+
onBlur: () => void;
|
|
14
|
+
value: any;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
name: string | undefined;
|
|
17
|
+
'data-valid': boolean;
|
|
18
|
+
onChange: (e: C) => void;
|
|
19
|
+
} | {
|
|
20
|
+
onFocus: () => void;
|
|
21
|
+
onBlur?: undefined;
|
|
22
|
+
value: any;
|
|
23
|
+
disabled: boolean;
|
|
24
|
+
name: string | undefined;
|
|
25
|
+
'data-valid': boolean;
|
|
26
|
+
onChange: (e: C) => void;
|
|
27
|
+
};
|
|
28
|
+
props: {
|
|
29
|
+
onFocus: () => void;
|
|
30
|
+
onBlur: () => void;
|
|
31
|
+
value: any;
|
|
32
|
+
disabled: boolean;
|
|
33
|
+
name: string | undefined;
|
|
34
|
+
valid: boolean;
|
|
35
|
+
errorMessage: string | undefined;
|
|
36
|
+
onChange: (e: C) => void;
|
|
37
|
+
} | {
|
|
38
|
+
onFocus: () => void;
|
|
39
|
+
onBlur?: undefined;
|
|
40
|
+
value: any;
|
|
41
|
+
disabled: boolean;
|
|
42
|
+
name: string | undefined;
|
|
43
|
+
valid: boolean;
|
|
44
|
+
errorMessage: string | undefined;
|
|
45
|
+
onChange: (e: C) => void;
|
|
46
|
+
};
|
|
47
|
+
_initial: {
|
|
48
|
+
value: T;
|
|
49
|
+
disabled: boolean;
|
|
50
|
+
touched: boolean;
|
|
51
|
+
dirty: boolean;
|
|
52
|
+
valid: boolean;
|
|
53
|
+
errorMessage: string | undefined;
|
|
54
|
+
};
|
|
55
|
+
_applyError: (issue: $ZodIssue) => void;
|
|
56
|
+
value: T;
|
|
57
|
+
disabled: boolean;
|
|
58
|
+
touched: boolean;
|
|
59
|
+
dirty: boolean;
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=useField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useField.d.ts","sourceRoot":"","sources":["../src/useField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAuB,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AAOpD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,WAAW,CAAC,gBAAgB,CAAC,EAC5E,MAAM,EAAE,OAAO,EACf,EACE,IAAI,EACJ,KAAe,EACf,QAAgB,EAChB,OAAe,EACf,gBAA2B,EAC3B,UAAoC,EACpC,WAAmC,EACnC,kBAA8C,EAC9C,OAAO,EACP,SAAS,EACT,YAAY,GACb,GAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAM;;mBA6BC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;sBA6CpB,CAAC;;;;;;;;sBAAD,CAAC;;;;;;;;;;sBAAD,CAAC;;;;;;;;;sBAAD,CAAC;;;;;;;;;;yBAyBM,SAAS;;;;;EAyCtC"}
|
package/dist/useField.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import defaultFormatErrorMessage from './defaultFormatErrorMessage';
|
|
3
|
+
const defaultParseEvent = (e) => e.target.value;
|
|
4
|
+
const defaultFormatValue = (value) => value;
|
|
5
|
+
export default function useField(schema, { name, value = '', disabled = false, touched = false, showValidationOn = 'submit', parseEvent = (defaultParseEvent), formatValue = (defaultFormatValue), formatErrorMessage = defaultFormatErrorMessage, _onInit, _onUpdate, _storedField, } = {}) {
|
|
6
|
+
function _validate(value) {
|
|
7
|
+
const { success, error } = schema.safeParse(value);
|
|
8
|
+
if (!success && error.issues.length > 0) {
|
|
9
|
+
return formatErrorMessage(error.issues[0], value, name);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const message = _validate(value);
|
|
13
|
+
const initialField = {
|
|
14
|
+
value,
|
|
15
|
+
disabled,
|
|
16
|
+
touched,
|
|
17
|
+
dirty: false,
|
|
18
|
+
valid: !message,
|
|
19
|
+
errorMessage: message,
|
|
20
|
+
};
|
|
21
|
+
const [field, setField] = useState(_storedField ?? initialField);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (_onInit && !_storedField) {
|
|
24
|
+
_onInit(field);
|
|
25
|
+
}
|
|
26
|
+
}, [_onInit, _storedField]);
|
|
27
|
+
function update(data) {
|
|
28
|
+
if (data.value !== undefined) {
|
|
29
|
+
const dirty = data.value !== initialField.value;
|
|
30
|
+
const errorMessage = _validate(data.value);
|
|
31
|
+
const _data = {
|
|
32
|
+
touched: showValidationOn === 'change' ? dirty : field.touched,
|
|
33
|
+
dirty,
|
|
34
|
+
...data,
|
|
35
|
+
errorMessage,
|
|
36
|
+
valid: !errorMessage,
|
|
37
|
+
};
|
|
38
|
+
if (_onUpdate) {
|
|
39
|
+
_onUpdate(_data);
|
|
40
|
+
}
|
|
41
|
+
setField((field) => ({
|
|
42
|
+
...field,
|
|
43
|
+
..._data,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (_onUpdate) {
|
|
48
|
+
_onUpdate(data);
|
|
49
|
+
}
|
|
50
|
+
setField((field) => ({
|
|
51
|
+
...field,
|
|
52
|
+
...data,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function reset() {
|
|
57
|
+
if (_onUpdate) {
|
|
58
|
+
_onUpdate(initialField);
|
|
59
|
+
}
|
|
60
|
+
setField(initialField);
|
|
61
|
+
}
|
|
62
|
+
function validate() {
|
|
63
|
+
return schema.safeParse(field.value);
|
|
64
|
+
}
|
|
65
|
+
function onChange(e) {
|
|
66
|
+
update({ value: parseEvent(e) });
|
|
67
|
+
}
|
|
68
|
+
// Only show validation error when is touched
|
|
69
|
+
const valid = !field.touched ? true : !field.errorMessage;
|
|
70
|
+
// Only show errrorMessage and validation styles if the field is touched according to the config
|
|
71
|
+
const errorMessage = field.touched ? field.errorMessage : undefined;
|
|
72
|
+
const touch = () => update({ touched: true });
|
|
73
|
+
const untouch = () => update({ touched: false });
|
|
74
|
+
function getListeners() {
|
|
75
|
+
if (showValidationOn === 'blur') {
|
|
76
|
+
return {
|
|
77
|
+
onFocus: untouch,
|
|
78
|
+
onBlur: touch,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
onFocus: untouch,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function _applyError(issue) {
|
|
86
|
+
const errorMessage = formatErrorMessage(issue, field.value, name);
|
|
87
|
+
setField((field) => ({
|
|
88
|
+
...field,
|
|
89
|
+
errorMessage,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
const inputValue = formatValue(field.value);
|
|
93
|
+
const inputProps = {
|
|
94
|
+
value: inputValue,
|
|
95
|
+
disabled: field.disabled,
|
|
96
|
+
name,
|
|
97
|
+
'data-valid': valid,
|
|
98
|
+
onChange,
|
|
99
|
+
...getListeners(),
|
|
100
|
+
};
|
|
101
|
+
const props = {
|
|
102
|
+
value: inputValue,
|
|
103
|
+
disabled: field.disabled,
|
|
104
|
+
name,
|
|
105
|
+
valid,
|
|
106
|
+
errorMessage,
|
|
107
|
+
onChange,
|
|
108
|
+
...getListeners(),
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
...field,
|
|
112
|
+
valid,
|
|
113
|
+
update,
|
|
114
|
+
reset,
|
|
115
|
+
validate,
|
|
116
|
+
errorMessage,
|
|
117
|
+
inputProps,
|
|
118
|
+
props,
|
|
119
|
+
_initial: initialField,
|
|
120
|
+
_applyError,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=useField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useField.js","sourceRoot":"","sources":["../src/useField.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useFieldArray(): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import useKeyDown from './useKeyDown.js';
|
|
3
|
+
const focusableSelector = `:is(
|
|
4
|
+
a[href],
|
|
5
|
+
area[href],
|
|
6
|
+
input:not([disabled]),
|
|
7
|
+
select:not([disabled]),
|
|
8
|
+
textarea:not([disabled]),
|
|
9
|
+
button:not([disabled]),
|
|
10
|
+
[tabindex]:not([tabindex="-1"]),
|
|
11
|
+
[contenteditable]
|
|
12
|
+
)`;
|
|
13
|
+
export default function useFocusTrap(ref, active, config = {}) {
|
|
14
|
+
const { visible, autoFocus = true } = config;
|
|
15
|
+
useKeyDown('Tab', (e) => {
|
|
16
|
+
const element = ref.current;
|
|
17
|
+
if (!active || !element) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const focusables = [
|
|
21
|
+
...element.querySelectorAll(focusableSelector),
|
|
22
|
+
].filter((el) => el.hasAttribute('tabindex')
|
|
23
|
+
? el.getAttribute('tabindex') !== '-1'
|
|
24
|
+
: true);
|
|
25
|
+
const firstFocusable = focusables[0];
|
|
26
|
+
const lastFocusable = focusables[focusables.length - 1];
|
|
27
|
+
const activeElement = document.activeElement;
|
|
28
|
+
let nextElement;
|
|
29
|
+
if (activeElement && focusables.includes(activeElement)) {
|
|
30
|
+
const index = focusables.indexOf(activeElement);
|
|
31
|
+
if (e.shiftKey) {
|
|
32
|
+
if (index === 0) {
|
|
33
|
+
nextElement = lastFocusable;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (index === focusables.length - 1) {
|
|
38
|
+
nextElement = firstFocusable;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (e.shiftKey) {
|
|
44
|
+
nextElement = lastFocusable;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
nextElement = firstFocusable;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (nextElement) {
|
|
51
|
+
;
|
|
52
|
+
nextElement.focus();
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
}
|
|
55
|
+
}, {
|
|
56
|
+
active,
|
|
57
|
+
});
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const element = ref.current;
|
|
60
|
+
if (!autoFocus || !visible || !element) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const autoFocusElement = element.querySelector('[data-autofocus="true"]' + focusableSelector);
|
|
64
|
+
if (autoFocusElement) {
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
autoFocusElement.focus();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const nodeList = element.querySelectorAll(focusableSelector);
|
|
70
|
+
const elements = Array.from(nodeList);
|
|
71
|
+
const focusableElements = elements.filter((element) => !element.hasAttribute('tabindex') ||
|
|
72
|
+
element.getAttribute('tabindex') === '0');
|
|
73
|
+
// 1. focus the first focusable
|
|
74
|
+
// @ts-ignore, TODO: fix typing
|
|
75
|
+
focusableElements[0]?.focus();
|
|
76
|
+
}, [visible, ref, autoFocus]);
|
|
77
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { FormEvent, ChangeEvent } from 'react';
|
|
2
|
+
import { z, ZodObject, ZodRawShape } from 'zod';
|
|
3
|
+
import { $ZodIssue } from 'zod/v4/core';
|
|
4
|
+
import { type Options } from './types';
|
|
5
|
+
import useField from './useField';
|
|
6
|
+
type FieldsMap = Record<string, ReturnType<typeof useField<any, any>>>;
|
|
7
|
+
type Config = {
|
|
8
|
+
formatErrorMessage?: (error: $ZodIssue, value: any, name?: string) => string;
|
|
9
|
+
_onUpdate?: (fields: FieldsMap) => void;
|
|
10
|
+
};
|
|
11
|
+
export default function useForm<S extends ZodRawShape>(schema: ZodObject<S>, { formatErrorMessage, _onUpdate }?: Config): {
|
|
12
|
+
isValidating: boolean;
|
|
13
|
+
useFormField: <T = string, C = ChangeEvent<HTMLInputElement>>(_name: keyof S, options?: Omit<Options<T, C>, "formatErrorMessage" | "name" | "_onInit" | "_onUpdate" | "_storedField">) => {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
update: (data: Partial<import("./types").T_Field<T>>) => void;
|
|
16
|
+
reset: () => void;
|
|
17
|
+
validate: () => z.ZodSafeParseResult<unknown>;
|
|
18
|
+
errorMessage: string | undefined;
|
|
19
|
+
inputProps: {
|
|
20
|
+
onFocus: () => void;
|
|
21
|
+
onBlur: () => void;
|
|
22
|
+
value: any;
|
|
23
|
+
disabled: boolean;
|
|
24
|
+
name: string | undefined;
|
|
25
|
+
'data-valid': boolean;
|
|
26
|
+
onChange: (e: C) => void;
|
|
27
|
+
} | {
|
|
28
|
+
onFocus: () => void;
|
|
29
|
+
onBlur?: undefined;
|
|
30
|
+
value: any;
|
|
31
|
+
disabled: boolean;
|
|
32
|
+
name: string | undefined;
|
|
33
|
+
'data-valid': boolean;
|
|
34
|
+
onChange: (e: C) => void;
|
|
35
|
+
};
|
|
36
|
+
props: {
|
|
37
|
+
onFocus: () => void;
|
|
38
|
+
onBlur: () => void;
|
|
39
|
+
value: any;
|
|
40
|
+
disabled: boolean;
|
|
41
|
+
name: string | undefined;
|
|
42
|
+
valid: boolean;
|
|
43
|
+
errorMessage: string | undefined;
|
|
44
|
+
onChange: (e: C) => void;
|
|
45
|
+
} | {
|
|
46
|
+
onFocus: () => void;
|
|
47
|
+
onBlur?: undefined;
|
|
48
|
+
value: any;
|
|
49
|
+
disabled: boolean;
|
|
50
|
+
name: string | undefined;
|
|
51
|
+
valid: boolean;
|
|
52
|
+
errorMessage: string | undefined;
|
|
53
|
+
onChange: (e: C) => void;
|
|
54
|
+
};
|
|
55
|
+
_initial: {
|
|
56
|
+
value: T;
|
|
57
|
+
disabled: boolean;
|
|
58
|
+
touched: boolean;
|
|
59
|
+
dirty: boolean;
|
|
60
|
+
valid: boolean;
|
|
61
|
+
errorMessage: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
_applyError: (issue: $ZodIssue) => void;
|
|
64
|
+
value: T;
|
|
65
|
+
disabled: boolean;
|
|
66
|
+
touched: boolean;
|
|
67
|
+
dirty: boolean;
|
|
68
|
+
};
|
|
69
|
+
handleSubmit: (onSubmit: (data: z.infer<typeof schema>) => void, onError?: (issues: Array<$ZodIssue>) => void) => (e: FormEvent<HTMLFormElement>) => Promise<void>;
|
|
70
|
+
checkDirty: () => boolean;
|
|
71
|
+
reset: () => void;
|
|
72
|
+
};
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=useForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../src/useForm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAY,WAAW,EAAW,MAAM,KAAK,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC,OAAO,QAAQ,MAAM,YAAY,CAAA;AAEjC,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;AAEtE,KAAK,MAAM,GAAG;IACZ,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5E,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACxC,CAAA;AACD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,CAAC,SAAS,WAAW,EACnD,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EACpB,EAAE,kBAA8C,EAAE,SAAS,EAAE,GAAE,MAAW;;mBAKpD,CAAC,WAAW,CAAC,yCAC1B,MAAM,CAAC,YACL,IAAI,CACX,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACb,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,CACzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA4DS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,KAAK,IAAI,YACtC,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,IAAI,MAE9B,GAAG,SAAS,CAAC,eAAe,CAAC;;;EA2C9C"}
|
package/dist/useForm.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import defaultFormatErrorMessage from './defaultFormatErrorMessage';
|
|
3
|
+
import useField from './useField';
|
|
4
|
+
export default function useForm(schema, { formatErrorMessage = defaultFormatErrorMessage, _onUpdate } = {}) {
|
|
5
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
6
|
+
const fields = useRef({});
|
|
7
|
+
function useFormField(_name, options = {}) {
|
|
8
|
+
const name = String(_name);
|
|
9
|
+
const shape = schema.shape[_name];
|
|
10
|
+
const stored = fields.current[name];
|
|
11
|
+
const field = useField(shape, {
|
|
12
|
+
...options,
|
|
13
|
+
name,
|
|
14
|
+
formatErrorMessage,
|
|
15
|
+
// internals
|
|
16
|
+
_storedField: stored,
|
|
17
|
+
_onInit: (data) => {
|
|
18
|
+
fields.current[name] = {
|
|
19
|
+
...field,
|
|
20
|
+
...data,
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
_onUpdate: (data) => {
|
|
24
|
+
fields.current[name] = {
|
|
25
|
+
...fields.current[name],
|
|
26
|
+
...data,
|
|
27
|
+
};
|
|
28
|
+
if (_onUpdate) {
|
|
29
|
+
_onUpdate(fields.current);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return field;
|
|
34
|
+
}
|
|
35
|
+
function touchFields() {
|
|
36
|
+
for (const name in fields.current) {
|
|
37
|
+
fields.current[name].update({
|
|
38
|
+
touched: true,
|
|
39
|
+
// force revalidate
|
|
40
|
+
value: fields.current[name].value,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function reset() {
|
|
45
|
+
for (const name in fields.current) {
|
|
46
|
+
fields.current[name].reset();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function checkDirty() {
|
|
50
|
+
for (const name in fields.current) {
|
|
51
|
+
if (fields.current[name].dirty) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function handleSubmit(onSubmit, onError) {
|
|
58
|
+
return async (e) => {
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
touchFields();
|
|
62
|
+
const data = mapFieldsToData(fields.current);
|
|
63
|
+
setIsValidating(true);
|
|
64
|
+
const parsed = await schema.safeParseAsync(data);
|
|
65
|
+
setIsValidating(false);
|
|
66
|
+
if (parsed.success) {
|
|
67
|
+
onSubmit(parsed.data);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (parsed.error.issues.length > 0) {
|
|
71
|
+
_applyErrors(parsed.error.issues);
|
|
72
|
+
}
|
|
73
|
+
if (onError) {
|
|
74
|
+
onError(parsed.error.issues);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function _applyErrors(issues) {
|
|
80
|
+
for (const issue of issues) {
|
|
81
|
+
const field = fields.current[issue.path[0]];
|
|
82
|
+
if (field) {
|
|
83
|
+
field._applyError(issue);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
isValidating,
|
|
89
|
+
useFormField,
|
|
90
|
+
handleSubmit,
|
|
91
|
+
checkDirty,
|
|
92
|
+
reset,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function mapFieldsToData(fields) {
|
|
96
|
+
const obj = {};
|
|
97
|
+
for (const name in fields) {
|
|
98
|
+
obj[name] = fields[name].value;
|
|
99
|
+
}
|
|
100
|
+
return obj;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=useForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useForm.js","sourceRoot":"","sources":["../src/useForm.ts"],"names":[],"mappings":"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"}
|
package/dist/useForm.jsx
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useRef, useState, } from 'react';
|
|
2
|
+
import { createContext } from '@weser/context';
|
|
3
|
+
import defaultFormatErrorMessage from './defaultFormatErrorMessage.js';
|
|
4
|
+
import useField from './useField.js';
|
|
5
|
+
export function createForm(schema) {
|
|
6
|
+
function _useForm() {
|
|
7
|
+
return useForm(schema);
|
|
8
|
+
}
|
|
9
|
+
const [useFormContext, FormProvider] = createContext(null);
|
|
10
|
+
return {
|
|
11
|
+
useForm: _useForm,
|
|
12
|
+
useFormContext,
|
|
13
|
+
FormProvider,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function mapFieldsToData(fields) {
|
|
17
|
+
const obj = {};
|
|
18
|
+
for (const name in fields) {
|
|
19
|
+
obj[name] = fields[name].value;
|
|
20
|
+
}
|
|
21
|
+
return obj;
|
|
22
|
+
}
|
|
23
|
+
export default function useForm(schema, formatErrorMessage = defaultFormatErrorMessage) {
|
|
24
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
25
|
+
const fields = useRef({});
|
|
26
|
+
function useFormField(_name, options = {}) {
|
|
27
|
+
const name = String(_name);
|
|
28
|
+
const shape = schema.shape[_name];
|
|
29
|
+
const stored = fields.current[name];
|
|
30
|
+
const field = useField(shape, {
|
|
31
|
+
...options,
|
|
32
|
+
name,
|
|
33
|
+
formatErrorMessage,
|
|
34
|
+
// internals
|
|
35
|
+
_storedField: stored,
|
|
36
|
+
_onInit: (data) => {
|
|
37
|
+
fields.current[name] = {
|
|
38
|
+
...field,
|
|
39
|
+
...data,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
_onUpdate: (data) => {
|
|
43
|
+
fields.current[name] = {
|
|
44
|
+
...fields.current[name],
|
|
45
|
+
...data,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return field;
|
|
50
|
+
}
|
|
51
|
+
function touchFields() {
|
|
52
|
+
for (const name in fields.current) {
|
|
53
|
+
fields.current[name].update({
|
|
54
|
+
touched: true,
|
|
55
|
+
// force revalidate
|
|
56
|
+
value: fields.current[name].value,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function reset() {
|
|
61
|
+
for (const name in fields.current) {
|
|
62
|
+
fields.current[name].reset();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function checkDirty() {
|
|
66
|
+
for (const name in fields.current) {
|
|
67
|
+
if (fields.current[name].dirty) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function handleSubmit(onSubmit, onError) {
|
|
74
|
+
return async (e) => {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
touchFields();
|
|
78
|
+
// TODO: abort if fields are invalid already
|
|
79
|
+
// collect validation for each field
|
|
80
|
+
const data = mapFieldsToData(fields.current);
|
|
81
|
+
setIsValidating(true);
|
|
82
|
+
const parsed = await schema.safeParseAsync(data);
|
|
83
|
+
setIsValidating(false);
|
|
84
|
+
if (parsed.success) {
|
|
85
|
+
onSubmit(parsed.data);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (parsed.error.issues.length > 0) {
|
|
89
|
+
_applyErrors(parsed.error.issues);
|
|
90
|
+
}
|
|
91
|
+
if (onError) {
|
|
92
|
+
onError(parsed.error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function _applyErrors(issues) {
|
|
98
|
+
for (const issue of issues) {
|
|
99
|
+
const field = fields.current[issue.path[0]];
|
|
100
|
+
if (field) {
|
|
101
|
+
field._applyError(issue);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
useFormField,
|
|
107
|
+
handleSubmit,
|
|
108
|
+
checkDirty,
|
|
109
|
+
isValidating,
|
|
110
|
+
reset,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
export default function useKeyDown(keyCode, callback, options = {}) {
|
|
3
|
+
const { active = true, target } = options;
|
|
4
|
+
const keyCodes = [].concat(keyCode);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!active || target === null || target?.current === null) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const hasRef = target && target.current;
|
|
10
|
+
const element = hasRef ? target.current : document;
|
|
11
|
+
if (element) {
|
|
12
|
+
const handleKeyDown = (e) => {
|
|
13
|
+
if (keyCodes.includes(e.code) &&
|
|
14
|
+
(!hasRef || (hasRef && document.activeElement === element))) {
|
|
15
|
+
callback(e);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
element.addEventListener('keydown', handleKeyDown);
|
|
19
|
+
return () => element.removeEventListener('keydown', handleKeyDown);
|
|
20
|
+
}
|
|
21
|
+
}, [target, callback, active, keyCode]);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useRouteChange(onRouteChange: (path: string) => void, pathname?: string): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
export default function useRouteChange(onRouteChange, pathname) {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
if (pathname) {
|
|
5
|
+
onRouteChange(pathname);
|
|
6
|
+
}
|
|
7
|
+
}, [pathname]);
|
|
8
|
+
// track clicks on links with the current path
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const onClick = (e) => {
|
|
11
|
+
const target = e.target;
|
|
12
|
+
if (target.tagName === 'A' && target.href === pathname) {
|
|
13
|
+
onRouteChange(pathname);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
window.addEventListener('click', onClick);
|
|
17
|
+
return () => window.removeEventListener('click', onClick);
|
|
18
|
+
}, []);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useScrollBlocking(active: boolean): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
let scrollTop;
|
|
3
|
+
function blockScrolling(scrollElement) {
|
|
4
|
+
scrollTop = window.scrollY;
|
|
5
|
+
scrollElement.style.overflow = 'hidden';
|
|
6
|
+
scrollElement.style.position = 'fixed';
|
|
7
|
+
scrollElement.style.width = '100%';
|
|
8
|
+
scrollElement.style.top = -scrollTop + 'px';
|
|
9
|
+
}
|
|
10
|
+
function enableScrolling(scrollElement) {
|
|
11
|
+
scrollElement.style.removeProperty('position');
|
|
12
|
+
scrollElement.style.removeProperty('overflow');
|
|
13
|
+
scrollElement.style.removeProperty('top');
|
|
14
|
+
scrollElement.style.removeProperty('width');
|
|
15
|
+
window.scrollTo(0, scrollTop);
|
|
16
|
+
}
|
|
17
|
+
function toggleScrolling(isBlocked) {
|
|
18
|
+
const scrollElement = document.scrollingElement;
|
|
19
|
+
if (isBlocked) {
|
|
20
|
+
blockScrolling(scrollElement);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
enableScrolling(scrollElement);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export default function useScrollBlocking(active) {
|
|
27
|
+
useEffect(() => toggleScrolling(active), [active]);
|
|
28
|
+
}
|