react-form-rewind 0.5.0 → 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/dist/index.d.mts +93 -1
- package/dist/index.d.ts +93 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface FieldRules {
|
|
5
|
+
required?: boolean | string;
|
|
6
|
+
pattern?: RegExp | {
|
|
7
|
+
value: RegExp;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
minLength?: number | {
|
|
11
|
+
value: number;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
maxLength?: number | {
|
|
15
|
+
value: number;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
min?: number | {
|
|
19
|
+
value: number;
|
|
20
|
+
message: string;
|
|
21
|
+
};
|
|
22
|
+
max?: number | {
|
|
23
|
+
value: number;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
validate?: (value: unknown) => string | null;
|
|
27
|
+
}
|
|
28
|
+
interface FieldError {
|
|
29
|
+
message: string;
|
|
30
|
+
type: string;
|
|
31
|
+
}
|
|
32
|
+
declare function validateField(value: unknown, rules: FieldRules): FieldError | null;
|
|
33
|
+
declare function validateAll(state: Record<string, unknown>, fieldRules: Record<string, FieldRules>): Record<string, FieldError>;
|
|
34
|
+
|
|
1
35
|
interface HistoryEntry<T> {
|
|
2
36
|
state: T;
|
|
3
37
|
timestamp: number;
|
|
@@ -30,7 +64,65 @@ interface UseFormHistoryReturn<T> {
|
|
|
30
64
|
past: HistoryEntry<T>[];
|
|
31
65
|
future: HistoryEntry<T>[];
|
|
32
66
|
}
|
|
67
|
+
interface FieldMeta {
|
|
68
|
+
name: string;
|
|
69
|
+
rules?: FieldRules;
|
|
70
|
+
touched: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface FormRewindContextValue {
|
|
73
|
+
state: Record<string, unknown>;
|
|
74
|
+
setState: (name: string, value: unknown) => void;
|
|
75
|
+
errors: Record<string, FieldError>;
|
|
76
|
+
setError: (name: string, error: FieldError) => void;
|
|
77
|
+
clearError: (name: string) => void;
|
|
78
|
+
touched: Record<string, boolean>;
|
|
79
|
+
touch: (name: string) => void;
|
|
80
|
+
fields: Record<string, FieldMeta>;
|
|
81
|
+
registerField: (name: string, rules?: FieldRules) => void;
|
|
82
|
+
unregisterField: (name: string) => void;
|
|
83
|
+
}
|
|
33
84
|
|
|
34
85
|
declare function useFormHistory<T>(initialState: T, options?: UseFormHistoryOptions<T>): UseFormHistoryReturn<T>;
|
|
35
86
|
|
|
36
|
-
|
|
87
|
+
declare function useFormRewindContext(): FormRewindContextValue;
|
|
88
|
+
interface FormRewindProps<T extends Record<string, unknown>> {
|
|
89
|
+
initialState: T;
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
onSubmit?: (state: T) => void;
|
|
92
|
+
maxHistory?: UseFormHistoryOptions<T>["maxHistory"];
|
|
93
|
+
debounceMs?: UseFormHistoryOptions<T>["debounceMs"];
|
|
94
|
+
persist?: UseFormHistoryOptions<T>["persist"];
|
|
95
|
+
keyboard?: UseFormHistoryOptions<T>["keyboard"];
|
|
96
|
+
}
|
|
97
|
+
declare function FormRewind<T extends Record<string, unknown>>({ initialState, children, onSubmit, maxHistory, debounceMs, persist, keyboard, }: FormRewindProps<T>): react.JSX.Element;
|
|
98
|
+
|
|
99
|
+
interface BaseFieldProps {
|
|
100
|
+
name: string;
|
|
101
|
+
label?: string;
|
|
102
|
+
rules?: FieldRules;
|
|
103
|
+
className?: string;
|
|
104
|
+
style?: React.CSSProperties;
|
|
105
|
+
}
|
|
106
|
+
interface TextFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "value" | "onChange"> {
|
|
107
|
+
}
|
|
108
|
+
declare function TextField({ name, label, rules, className, style, ...inputProps }: TextFieldProps): react.JSX.Element;
|
|
109
|
+
interface NumberFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "value" | "onChange" | "type"> {
|
|
110
|
+
}
|
|
111
|
+
declare function NumberField({ name, label, rules, className, style, ...inputProps }: NumberFieldProps): react.JSX.Element;
|
|
112
|
+
interface CheckboxFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "checked" | "onChange"> {
|
|
113
|
+
}
|
|
114
|
+
declare function CheckboxField({ name, label, rules, className, style, ...inputProps }: CheckboxFieldProps): react.JSX.Element;
|
|
115
|
+
interface SelectOption {
|
|
116
|
+
value: string;
|
|
117
|
+
label: string;
|
|
118
|
+
}
|
|
119
|
+
interface SelectFieldProps extends BaseFieldProps {
|
|
120
|
+
options: SelectOption[];
|
|
121
|
+
placeholder?: string;
|
|
122
|
+
}
|
|
123
|
+
declare function SelectField({ name, label, rules, options, placeholder, className, style }: SelectFieldProps): react.JSX.Element;
|
|
124
|
+
interface TextareaFieldProps extends BaseFieldProps, Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "name" | "value" | "onChange"> {
|
|
125
|
+
}
|
|
126
|
+
declare function TextareaField({ name, label, rules, className, style, ...textareaProps }: TextareaFieldProps): react.JSX.Element;
|
|
127
|
+
|
|
128
|
+
export { CheckboxField, type FieldError, type FieldMeta, type FieldRules, FormRewind, type FormRewindContextValue, type FormRewindProps, type HistoryEntry, NumberField, type PersistOptions, SelectField, TextField, TextareaField, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory, useFormRewindContext, validateAll, validateField };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface FieldRules {
|
|
5
|
+
required?: boolean | string;
|
|
6
|
+
pattern?: RegExp | {
|
|
7
|
+
value: RegExp;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
minLength?: number | {
|
|
11
|
+
value: number;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
maxLength?: number | {
|
|
15
|
+
value: number;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
min?: number | {
|
|
19
|
+
value: number;
|
|
20
|
+
message: string;
|
|
21
|
+
};
|
|
22
|
+
max?: number | {
|
|
23
|
+
value: number;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
validate?: (value: unknown) => string | null;
|
|
27
|
+
}
|
|
28
|
+
interface FieldError {
|
|
29
|
+
message: string;
|
|
30
|
+
type: string;
|
|
31
|
+
}
|
|
32
|
+
declare function validateField(value: unknown, rules: FieldRules): FieldError | null;
|
|
33
|
+
declare function validateAll(state: Record<string, unknown>, fieldRules: Record<string, FieldRules>): Record<string, FieldError>;
|
|
34
|
+
|
|
1
35
|
interface HistoryEntry<T> {
|
|
2
36
|
state: T;
|
|
3
37
|
timestamp: number;
|
|
@@ -30,7 +64,65 @@ interface UseFormHistoryReturn<T> {
|
|
|
30
64
|
past: HistoryEntry<T>[];
|
|
31
65
|
future: HistoryEntry<T>[];
|
|
32
66
|
}
|
|
67
|
+
interface FieldMeta {
|
|
68
|
+
name: string;
|
|
69
|
+
rules?: FieldRules;
|
|
70
|
+
touched: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface FormRewindContextValue {
|
|
73
|
+
state: Record<string, unknown>;
|
|
74
|
+
setState: (name: string, value: unknown) => void;
|
|
75
|
+
errors: Record<string, FieldError>;
|
|
76
|
+
setError: (name: string, error: FieldError) => void;
|
|
77
|
+
clearError: (name: string) => void;
|
|
78
|
+
touched: Record<string, boolean>;
|
|
79
|
+
touch: (name: string) => void;
|
|
80
|
+
fields: Record<string, FieldMeta>;
|
|
81
|
+
registerField: (name: string, rules?: FieldRules) => void;
|
|
82
|
+
unregisterField: (name: string) => void;
|
|
83
|
+
}
|
|
33
84
|
|
|
34
85
|
declare function useFormHistory<T>(initialState: T, options?: UseFormHistoryOptions<T>): UseFormHistoryReturn<T>;
|
|
35
86
|
|
|
36
|
-
|
|
87
|
+
declare function useFormRewindContext(): FormRewindContextValue;
|
|
88
|
+
interface FormRewindProps<T extends Record<string, unknown>> {
|
|
89
|
+
initialState: T;
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
onSubmit?: (state: T) => void;
|
|
92
|
+
maxHistory?: UseFormHistoryOptions<T>["maxHistory"];
|
|
93
|
+
debounceMs?: UseFormHistoryOptions<T>["debounceMs"];
|
|
94
|
+
persist?: UseFormHistoryOptions<T>["persist"];
|
|
95
|
+
keyboard?: UseFormHistoryOptions<T>["keyboard"];
|
|
96
|
+
}
|
|
97
|
+
declare function FormRewind<T extends Record<string, unknown>>({ initialState, children, onSubmit, maxHistory, debounceMs, persist, keyboard, }: FormRewindProps<T>): react.JSX.Element;
|
|
98
|
+
|
|
99
|
+
interface BaseFieldProps {
|
|
100
|
+
name: string;
|
|
101
|
+
label?: string;
|
|
102
|
+
rules?: FieldRules;
|
|
103
|
+
className?: string;
|
|
104
|
+
style?: React.CSSProperties;
|
|
105
|
+
}
|
|
106
|
+
interface TextFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "value" | "onChange"> {
|
|
107
|
+
}
|
|
108
|
+
declare function TextField({ name, label, rules, className, style, ...inputProps }: TextFieldProps): react.JSX.Element;
|
|
109
|
+
interface NumberFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "value" | "onChange" | "type"> {
|
|
110
|
+
}
|
|
111
|
+
declare function NumberField({ name, label, rules, className, style, ...inputProps }: NumberFieldProps): react.JSX.Element;
|
|
112
|
+
interface CheckboxFieldProps extends BaseFieldProps, Omit<React.InputHTMLAttributes<HTMLInputElement>, "name" | "checked" | "onChange"> {
|
|
113
|
+
}
|
|
114
|
+
declare function CheckboxField({ name, label, rules, className, style, ...inputProps }: CheckboxFieldProps): react.JSX.Element;
|
|
115
|
+
interface SelectOption {
|
|
116
|
+
value: string;
|
|
117
|
+
label: string;
|
|
118
|
+
}
|
|
119
|
+
interface SelectFieldProps extends BaseFieldProps {
|
|
120
|
+
options: SelectOption[];
|
|
121
|
+
placeholder?: string;
|
|
122
|
+
}
|
|
123
|
+
declare function SelectField({ name, label, rules, options, placeholder, className, style }: SelectFieldProps): react.JSX.Element;
|
|
124
|
+
interface TextareaFieldProps extends BaseFieldProps, Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "name" | "value" | "onChange"> {
|
|
125
|
+
}
|
|
126
|
+
declare function TextareaField({ name, label, rules, className, style, ...textareaProps }: TextareaFieldProps): react.JSX.Element;
|
|
127
|
+
|
|
128
|
+
export { CheckboxField, type FieldError, type FieldMeta, type FieldRules, FormRewind, type FormRewindContextValue, type FormRewindProps, type HistoryEntry, NumberField, type PersistOptions, SelectField, TextField, TextareaField, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory, useFormRewindContext, validateAll, validateField };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var react=require('react');var
|
|
1
|
+
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var ue=100,ce=300,ae=500,de=1;function pe(e){return !e||typeof e=="boolean"?null:{key:e.key,debounceMs:e.debounceMs??ae,version:e.version??de}}function K(){return typeof window<"u"&&!!window.localStorage}function me(e,t){try{if(!K())return null;let s=window.localStorage.getItem(e);if(!s)return null;let u=JSON.parse(s);return u.__v!==t?(window.localStorage.removeItem(e),null):u.__s}catch{return null}}function Y(e,t,s){try{if(!K())return;window.localStorage.setItem(e,JSON.stringify({__s:t,__v:s}));}catch{}}function ge(e){try{if(!K())return;window.localStorage.removeItem(e);}catch{}}function z(e,t={}){let{maxHistory:s=ue,debounceMs:u=ce,persist:i,keyboard:a=false,onUndo:r,onRedo:c,onSnapshot:T}=t,n=pe(i),[m,x]=react.useState(()=>{if(n){let l=me(n.key,n.version);if(l!==null)return l}return e}),d=react.useRef(m);d.current=m;let y=react.useRef([]),b=react.useRef([]),[U,S]=react.useState([]),[I,L]=react.useState([]),F=react.useRef(null),E=react.useRef(null),o=react.useRef(null),p=react.useRef(null),f=react.useCallback(()=>{b.current=[],L([]);},[]),R=react.useCallback(l=>{let g=[...y.current,l];g.length>s&&g.splice(0,g.length-s),y.current=g,S(g);},[s]);react.useEffect(()=>()=>{F.current!==null&&clearTimeout(F.current),p.current!==null&&clearTimeout(p.current);},[]);let M=react.useCallback(l=>{n&&(p.current!==null&&clearTimeout(p.current),p.current=setTimeout(()=>{Y(n.key,l,n.version),p.current=null;},n.debounceMs));},[n]),_=react.useCallback(l=>{n&&(p.current!==null&&(clearTimeout(p.current),p.current=null),Y(n.key,l,n.version));},[n]),se=react.useCallback((l,g)=>{let v=d.current,C=typeof l=="function"?l(v):l;if(!Object.is(v,C)){if(x(C),d.current=C,M(C),u<=0){let q={state:v,timestamp:Date.now(),label:g};R(q),f();return}E.current===null&&(o.current=v),E.current=C,F.current!==null&&clearTimeout(F.current),F.current=setTimeout(()=>{let q={state:o.current,timestamp:Date.now(),label:g};R(q),f(),F.current=null,E.current=null,o.current=null;},u);}},[u,R,M,f]),A=react.useCallback(()=>{if(F.current!==null&&(clearTimeout(F.current),F.current=null),E.current!==null&&o.current!==null){let C={state:o.current,timestamp:Date.now()};R(C),f(),E.current=null,o.current=null;}if(y.current.length===0)return;let l=y.current[y.current.length-1],g=y.current.slice(0,-1);y.current=g,S(g);let v={state:d.current,timestamp:Date.now()};b.current=[...b.current,v],L(b.current),x(l.state),d.current=l.state,_(l.state),r?.(l.state);},[R,r,_,f]),$=react.useCallback(()=>{if(b.current.length===0)return;let l=b.current[b.current.length-1],g=b.current.slice(0,-1);b.current=g,L(g);let v={state:d.current,timestamp:Date.now()};y.current=[...y.current,v],S(y.current),x(l.state),d.current=l.state,_(l.state),c?.(l.state);},[c,_]);react.useEffect(()=>{if(!a)return;let l=g=>{!(g.metaKey||g.ctrlKey)||g.key!=="z"||(g.preventDefault(),g.shiftKey?$():A());};return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[a,A,$]);let V=react.useCallback(()=>{F.current!==null&&(clearTimeout(F.current),F.current=null),E.current=null,o.current=null,y.current=[],b.current=[],S([]),L([]);},[]),ie=react.useCallback(l=>{let g=false;if(F.current!==null&&(clearTimeout(F.current),F.current=null),E.current!==null&&o.current!==null){let v={state:o.current,timestamp:Date.now()};R(v),f(),E.current=null,o.current=null,g=true;}if(g)l&&y.current.length>0&&(y.current[y.current.length-1].label=l,S([...y.current]));else {let v={state:d.current,timestamp:Date.now(),label:l};R(v),f(),T?.(v);}},[R,T,f]),le=react.useCallback(()=>{V(),n&&ge(n.key),x(e),d.current=e;},[V,e,n]);return {state:m,setState:se,undo:A,redo:$,canUndo:U.length>0,canRedo:I.length>0,clearHistory:V,clearDraft:le,snapshot:ie,past:U,future:I}}function N(e,t){return e&&typeof e=="object"&&"value"in e?e:{value:e,message:t}}function B(e,t){let u=typeof e=="number"?e:Number(e);if(t.required&&(typeof e=="string"?e.trim()==="":e==null||e===false))return {message:typeof t.required=="string"?t.required:"Required",type:"required"};if(t.pattern!=null){let{value:i,message:a}=N(t.pattern,"Invalid format");if(typeof e=="string"&&!i.test(e))return {message:a,type:"pattern"}}if(t.minLength!=null&&typeof e=="string"){let{value:i,message:a}=N(t.minLength,`Min ${t.minLength} characters`);if(e.length<i)return {message:a,type:"minLength"}}if(t.maxLength!=null&&typeof e=="string"){let{value:i,message:a}=N(t.maxLength,`Max ${t.maxLength} characters`);if(e.length>i)return {message:a,type:"maxLength"}}if(t.min!=null&&!isNaN(u)){let{value:i,message:a}=N(t.min,`Min ${t.min}`);if(u<i)return {message:a,type:"min"}}if(t.max!=null&&!isNaN(u)){let{value:i,message:a}=N(t.max,`Max ${t.max}`);if(u>i)return {message:a,type:"max"}}if(t.validate){let i=t.validate(e);if(i)return {message:i,type:"validate"}}return null}function J(e,t){let s={};for(let u in t){let i=B(e[u],t[u]);i&&(s[u]=i);}return s}var W=react.createContext(null);function H(){let e=react.useContext(W);if(!e)throw new Error("useFormRewindContext must be used within <FormRewind>");return e}function Z({initialState:e,children:t,onSubmit:s,maxHistory:u,debounceMs:i,persist:a,keyboard:r}){let c=z(e,{maxHistory:u,debounceMs:i,persist:a,keyboard:r}),[T,n]=react.useState({}),[m,x]=react.useState({}),d=react.useRef({}),y=react.useCallback((o,p)=>{d.current[o]=p??{};},[]),b=react.useCallback(o=>{delete d.current[o];},[]),U=react.useCallback((o,p)=>{n(f=>({...f,[o]:p}));},[]),S=react.useCallback(o=>{n(p=>{let f={...p};return delete f[o],f});},[]),I=react.useCallback(o=>{x(p=>({...p,[o]:true}));},[]),L=react.useCallback((o,p)=>{if(c.setState(f=>({...f,[o]:p}),o),m[o]&&d.current[o]){let f=B(p,d.current[o]);n(f?R=>({...R,[o]:f}):R=>{let M={...R};return delete M[o],M});}},[c.setState,m]),F=react.useCallback(o=>{o&&o.preventDefault();let p={};for(let R in d.current)p[R]=true;x(p);let f=J(c.state,d.current);return n(f),Object.keys(f).length===0&&s&&s(c.state),Object.keys(f).length===0},[c.state,s]),E={state:c.state,setState:L,errors:T,setError:U,clearError:S,touched:m,touch:I,fields:Object.fromEntries(Object.entries(d.current).map(([o,p])=>[o,{name:o,rules:p,touched:!!m[o]}])),registerField:y,unregisterField:b};return jsxRuntime.jsx(W.Provider,{value:E,children:jsxRuntime.jsx("form",{onSubmit:F,noValidate:true,children:t})})}function ee({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();react.useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{r.setState(e,x.target.value);},T=()=>{r.touch(e);},n=r.errors[e],m=String(r.state[e]??"");return jsxRuntime.jsxs("div",{className:u,style:i,children:[t&&jsxRuntime.jsx("label",{htmlFor:e,children:t}),jsxRuntime.jsx("input",{id:e,type:"text",value:m,onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsxRuntime.jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}function te({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();react.useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{let d=x.target.value===""?"":Number(x.target.value);r.setState(e,d);},T=()=>{r.touch(e);},n=r.errors[e],m=r.state[e];return jsxRuntime.jsxs("div",{className:u,style:i,children:[t&&jsxRuntime.jsx("label",{htmlFor:e,children:t}),jsxRuntime.jsx("input",{id:e,type:"number",value:m===""||m==null?"":Number(m),onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsxRuntime.jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}function re({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();react.useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=m=>{r.setState(e,m.target.checked);},T=r.errors[e],n=!!r.state[e];return jsxRuntime.jsxs("div",{className:u,style:i,children:[jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:n,onChange:c,"aria-invalid":!!T,...a}),t]}),T&&jsxRuntime.jsx("span",{role:"alert",children:T.message})]})}function ne({name:e,label:t,rules:s,options:u,placeholder:i,className:a,style:r}){let c=H();react.useEffect(()=>(c.registerField(e,s),()=>c.unregisterField(e)),[e,s,c.registerField,c.unregisterField]);let T=d=>{c.setState(e,d.target.value);},n=()=>{c.touch(e);},m=c.errors[e],x=String(c.state[e]??"");return jsxRuntime.jsxs("div",{className:a,style:r,children:[t&&jsxRuntime.jsx("label",{htmlFor:e,children:t}),jsxRuntime.jsxs("select",{id:e,value:x,onChange:T,onBlur:n,"aria-invalid":!!m,"aria-describedby":m?`${e}-error`:void 0,children:[i&&jsxRuntime.jsx("option",{value:"",disabled:true,children:i}),u.map(d=>jsxRuntime.jsx("option",{value:d.value,children:d.label},d.value))]}),m&&jsxRuntime.jsx("span",{id:`${e}-error`,role:"alert",children:m.message})]})}function oe({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();react.useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{r.setState(e,x.target.value);},T=()=>{r.touch(e);},n=r.errors[e],m=String(r.state[e]??"");return jsxRuntime.jsxs("div",{className:u,style:i,children:[t&&jsxRuntime.jsx("label",{htmlFor:e,children:t}),jsxRuntime.jsx("textarea",{id:e,value:m,onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsxRuntime.jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}exports.CheckboxField=re;exports.FormRewind=Z;exports.NumberField=te;exports.SelectField=ne;exports.TextField=ee;exports.TextareaField=oe;exports.useFormHistory=z;exports.useFormRewindContext=H;exports.validateAll=J;exports.validateField=B;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {useState,useRef,useCallback,useEffect}from'react';var
|
|
1
|
+
import {createContext,useState,useRef,useCallback,useEffect,useContext}from'react';import {jsx,jsxs}from'react/jsx-runtime';var ue=100,ce=300,ae=500,de=1;function pe(e){return !e||typeof e=="boolean"?null:{key:e.key,debounceMs:e.debounceMs??ae,version:e.version??de}}function K(){return typeof window<"u"&&!!window.localStorage}function me(e,t){try{if(!K())return null;let s=window.localStorage.getItem(e);if(!s)return null;let u=JSON.parse(s);return u.__v!==t?(window.localStorage.removeItem(e),null):u.__s}catch{return null}}function Y(e,t,s){try{if(!K())return;window.localStorage.setItem(e,JSON.stringify({__s:t,__v:s}));}catch{}}function ge(e){try{if(!K())return;window.localStorage.removeItem(e);}catch{}}function z(e,t={}){let{maxHistory:s=ue,debounceMs:u=ce,persist:i,keyboard:a=false,onUndo:r,onRedo:c,onSnapshot:T}=t,n=pe(i),[m,x]=useState(()=>{if(n){let l=me(n.key,n.version);if(l!==null)return l}return e}),d=useRef(m);d.current=m;let y=useRef([]),b=useRef([]),[U,S]=useState([]),[I,L]=useState([]),F=useRef(null),E=useRef(null),o=useRef(null),p=useRef(null),f=useCallback(()=>{b.current=[],L([]);},[]),R=useCallback(l=>{let g=[...y.current,l];g.length>s&&g.splice(0,g.length-s),y.current=g,S(g);},[s]);useEffect(()=>()=>{F.current!==null&&clearTimeout(F.current),p.current!==null&&clearTimeout(p.current);},[]);let M=useCallback(l=>{n&&(p.current!==null&&clearTimeout(p.current),p.current=setTimeout(()=>{Y(n.key,l,n.version),p.current=null;},n.debounceMs));},[n]),_=useCallback(l=>{n&&(p.current!==null&&(clearTimeout(p.current),p.current=null),Y(n.key,l,n.version));},[n]),se=useCallback((l,g)=>{let v=d.current,C=typeof l=="function"?l(v):l;if(!Object.is(v,C)){if(x(C),d.current=C,M(C),u<=0){let q={state:v,timestamp:Date.now(),label:g};R(q),f();return}E.current===null&&(o.current=v),E.current=C,F.current!==null&&clearTimeout(F.current),F.current=setTimeout(()=>{let q={state:o.current,timestamp:Date.now(),label:g};R(q),f(),F.current=null,E.current=null,o.current=null;},u);}},[u,R,M,f]),A=useCallback(()=>{if(F.current!==null&&(clearTimeout(F.current),F.current=null),E.current!==null&&o.current!==null){let C={state:o.current,timestamp:Date.now()};R(C),f(),E.current=null,o.current=null;}if(y.current.length===0)return;let l=y.current[y.current.length-1],g=y.current.slice(0,-1);y.current=g,S(g);let v={state:d.current,timestamp:Date.now()};b.current=[...b.current,v],L(b.current),x(l.state),d.current=l.state,_(l.state),r?.(l.state);},[R,r,_,f]),$=useCallback(()=>{if(b.current.length===0)return;let l=b.current[b.current.length-1],g=b.current.slice(0,-1);b.current=g,L(g);let v={state:d.current,timestamp:Date.now()};y.current=[...y.current,v],S(y.current),x(l.state),d.current=l.state,_(l.state),c?.(l.state);},[c,_]);useEffect(()=>{if(!a)return;let l=g=>{!(g.metaKey||g.ctrlKey)||g.key!=="z"||(g.preventDefault(),g.shiftKey?$():A());};return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[a,A,$]);let V=useCallback(()=>{F.current!==null&&(clearTimeout(F.current),F.current=null),E.current=null,o.current=null,y.current=[],b.current=[],S([]),L([]);},[]),ie=useCallback(l=>{let g=false;if(F.current!==null&&(clearTimeout(F.current),F.current=null),E.current!==null&&o.current!==null){let v={state:o.current,timestamp:Date.now()};R(v),f(),E.current=null,o.current=null,g=true;}if(g)l&&y.current.length>0&&(y.current[y.current.length-1].label=l,S([...y.current]));else {let v={state:d.current,timestamp:Date.now(),label:l};R(v),f(),T?.(v);}},[R,T,f]),le=useCallback(()=>{V(),n&&ge(n.key),x(e),d.current=e;},[V,e,n]);return {state:m,setState:se,undo:A,redo:$,canUndo:U.length>0,canRedo:I.length>0,clearHistory:V,clearDraft:le,snapshot:ie,past:U,future:I}}function N(e,t){return e&&typeof e=="object"&&"value"in e?e:{value:e,message:t}}function B(e,t){let u=typeof e=="number"?e:Number(e);if(t.required&&(typeof e=="string"?e.trim()==="":e==null||e===false))return {message:typeof t.required=="string"?t.required:"Required",type:"required"};if(t.pattern!=null){let{value:i,message:a}=N(t.pattern,"Invalid format");if(typeof e=="string"&&!i.test(e))return {message:a,type:"pattern"}}if(t.minLength!=null&&typeof e=="string"){let{value:i,message:a}=N(t.minLength,`Min ${t.minLength} characters`);if(e.length<i)return {message:a,type:"minLength"}}if(t.maxLength!=null&&typeof e=="string"){let{value:i,message:a}=N(t.maxLength,`Max ${t.maxLength} characters`);if(e.length>i)return {message:a,type:"maxLength"}}if(t.min!=null&&!isNaN(u)){let{value:i,message:a}=N(t.min,`Min ${t.min}`);if(u<i)return {message:a,type:"min"}}if(t.max!=null&&!isNaN(u)){let{value:i,message:a}=N(t.max,`Max ${t.max}`);if(u>i)return {message:a,type:"max"}}if(t.validate){let i=t.validate(e);if(i)return {message:i,type:"validate"}}return null}function J(e,t){let s={};for(let u in t){let i=B(e[u],t[u]);i&&(s[u]=i);}return s}var W=createContext(null);function H(){let e=useContext(W);if(!e)throw new Error("useFormRewindContext must be used within <FormRewind>");return e}function Z({initialState:e,children:t,onSubmit:s,maxHistory:u,debounceMs:i,persist:a,keyboard:r}){let c=z(e,{maxHistory:u,debounceMs:i,persist:a,keyboard:r}),[T,n]=useState({}),[m,x]=useState({}),d=useRef({}),y=useCallback((o,p)=>{d.current[o]=p??{};},[]),b=useCallback(o=>{delete d.current[o];},[]),U=useCallback((o,p)=>{n(f=>({...f,[o]:p}));},[]),S=useCallback(o=>{n(p=>{let f={...p};return delete f[o],f});},[]),I=useCallback(o=>{x(p=>({...p,[o]:true}));},[]),L=useCallback((o,p)=>{if(c.setState(f=>({...f,[o]:p}),o),m[o]&&d.current[o]){let f=B(p,d.current[o]);n(f?R=>({...R,[o]:f}):R=>{let M={...R};return delete M[o],M});}},[c.setState,m]),F=useCallback(o=>{o&&o.preventDefault();let p={};for(let R in d.current)p[R]=true;x(p);let f=J(c.state,d.current);return n(f),Object.keys(f).length===0&&s&&s(c.state),Object.keys(f).length===0},[c.state,s]),E={state:c.state,setState:L,errors:T,setError:U,clearError:S,touched:m,touch:I,fields:Object.fromEntries(Object.entries(d.current).map(([o,p])=>[o,{name:o,rules:p,touched:!!m[o]}])),registerField:y,unregisterField:b};return jsx(W.Provider,{value:E,children:jsx("form",{onSubmit:F,noValidate:true,children:t})})}function ee({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{r.setState(e,x.target.value);},T=()=>{r.touch(e);},n=r.errors[e],m=String(r.state[e]??"");return jsxs("div",{className:u,style:i,children:[t&&jsx("label",{htmlFor:e,children:t}),jsx("input",{id:e,type:"text",value:m,onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}function te({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{let d=x.target.value===""?"":Number(x.target.value);r.setState(e,d);},T=()=>{r.touch(e);},n=r.errors[e],m=r.state[e];return jsxs("div",{className:u,style:i,children:[t&&jsx("label",{htmlFor:e,children:t}),jsx("input",{id:e,type:"number",value:m===""||m==null?"":Number(m),onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}function re({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=m=>{r.setState(e,m.target.checked);},T=r.errors[e],n=!!r.state[e];return jsxs("div",{className:u,style:i,children:[jsxs("label",{children:[jsx("input",{type:"checkbox",checked:n,onChange:c,"aria-invalid":!!T,...a}),t]}),T&&jsx("span",{role:"alert",children:T.message})]})}function ne({name:e,label:t,rules:s,options:u,placeholder:i,className:a,style:r}){let c=H();useEffect(()=>(c.registerField(e,s),()=>c.unregisterField(e)),[e,s,c.registerField,c.unregisterField]);let T=d=>{c.setState(e,d.target.value);},n=()=>{c.touch(e);},m=c.errors[e],x=String(c.state[e]??"");return jsxs("div",{className:a,style:r,children:[t&&jsx("label",{htmlFor:e,children:t}),jsxs("select",{id:e,value:x,onChange:T,onBlur:n,"aria-invalid":!!m,"aria-describedby":m?`${e}-error`:void 0,children:[i&&jsx("option",{value:"",disabled:true,children:i}),u.map(d=>jsx("option",{value:d.value,children:d.label},d.value))]}),m&&jsx("span",{id:`${e}-error`,role:"alert",children:m.message})]})}function oe({name:e,label:t,rules:s,className:u,style:i,...a}){let r=H();useEffect(()=>(r.registerField(e,s),()=>r.unregisterField(e)),[e,s,r.registerField,r.unregisterField]);let c=x=>{r.setState(e,x.target.value);},T=()=>{r.touch(e);},n=r.errors[e],m=String(r.state[e]??"");return jsxs("div",{className:u,style:i,children:[t&&jsx("label",{htmlFor:e,children:t}),jsx("textarea",{id:e,value:m,onChange:c,onBlur:T,"aria-invalid":!!n,"aria-describedby":n?`${e}-error`:void 0,...a}),n&&jsx("span",{id:`${e}-error`,role:"alert",children:n.message})]})}export{re as CheckboxField,Z as FormRewind,te as NumberField,ne as SelectField,ee as TextField,oe as TextareaField,z as useFormHistory,H as useFormRewindContext,J as validateAll,B as validateField};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-form-rewind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Zero-dependency React state engine with auto-saved history stacks, time-traveling undo/redo, keyboard shortcuts, and draft persistence for forms.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|