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 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
- export { type HistoryEntry, type PersistOptions, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory };
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
- export { type HistoryEntry, type PersistOptions, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory };
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 j=100,V=300,X=500,Y=1;function q(e){return !e||typeof e=="boolean"?null:{key:e.key,debounceMs:e.debounceMs??X,version:e.version??Y}}function U(){return typeof window<"u"&&!!window.localStorage}function G(e,g){try{if(!U())return null;let f=window.localStorage.getItem(e);if(!f)return null;let E=JSON.parse(f);return E.__v!==g?(window.localStorage.removeItem(e),null):E.__s}catch{return null}}function K(e,g,f){try{if(!U())return;window.localStorage.setItem(e,JSON.stringify({__s:g,__v:f}));}catch{}}function Q(e){try{if(!U())return;window.localStorage.removeItem(e);}catch{}}function W(e,g={}){let{maxHistory:f=j,debounceMs:E=V,persist:C,keyboard:P=false,onUndo:k,onRedo:I,onSnapshot:M}=g,n=q(C),[R,h]=react.useState(()=>{if(n){let t=G(n.key,n.version);if(t!==null)return t}return e}),a=react.useRef(R);a.current=R;let u=react.useRef([]),c=react.useRef([]),[x,H]=react.useState([]),[L,S]=react.useState([]),o=react.useRef(null),m=react.useRef(null),i=react.useRef(null),y=react.useRef(null),T=react.useCallback(()=>{c.current=[],S([]);},[]),p=react.useCallback(t=>{let r=[...u.current,t];r.length>f&&r.splice(0,r.length-f),u.current=r,H(r);},[f]);react.useEffect(()=>()=>{o.current!==null&&clearTimeout(o.current),y.current!==null&&clearTimeout(y.current);},[]);let N=react.useCallback(t=>{n&&(y.current!==null&&clearTimeout(y.current),y.current=setTimeout(()=>{K(n.key,t,n.version),y.current=null;},n.debounceMs));},[n]),v=react.useCallback(t=>{n&&(y.current!==null&&(clearTimeout(y.current),y.current=null),K(n.key,t,n.version));},[n]),z=react.useCallback((t,r)=>{let s=a.current,d=typeof t=="function"?t(s):t;if(!Object.is(s,d)){if(h(d),a.current=d,N(d),E<=0){let F={state:s,timestamp:Date.now(),label:r};p(F),T();return}m.current===null&&(i.current=s),m.current=d,o.current!==null&&clearTimeout(o.current),o.current=setTimeout(()=>{let F={state:i.current,timestamp:Date.now(),label:r};p(F),T(),o.current=null,m.current=null,i.current=null;},E);}},[E,p,N,T]),D=react.useCallback(()=>{if(o.current!==null&&(clearTimeout(o.current),o.current=null),m.current!==null&&i.current!==null){let d={state:i.current,timestamp:Date.now()};p(d),T(),m.current=null,i.current=null;}if(u.current.length===0)return;let t=u.current[u.current.length-1],r=u.current.slice(0,-1);u.current=r,H(r);let s={state:a.current,timestamp:Date.now()};c.current=[...c.current,s],S(c.current),h(t.state),a.current=t.state,v(t.state),k?.(t.state);},[p,k,v,T]),_=react.useCallback(()=>{if(c.current.length===0)return;let t=c.current[c.current.length-1],r=c.current.slice(0,-1);c.current=r,S(r);let s={state:a.current,timestamp:Date.now()};u.current=[...u.current,s],H(u.current),h(t.state),a.current=t.state,v(t.state),I?.(t.state);},[I,v]);react.useEffect(()=>{if(!P)return;let t=r=>{!(r.metaKey||r.ctrlKey)||r.key!=="z"||(r.preventDefault(),r.shiftKey?_():D());};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[P,D,_]);let b=react.useCallback(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null),m.current=null,i.current=null,u.current=[],c.current=[],H([]),S([]);},[]),B=react.useCallback(t=>{let r=false;if(o.current!==null&&(clearTimeout(o.current),o.current=null),m.current!==null&&i.current!==null){let s={state:i.current,timestamp:Date.now()};p(s),T(),m.current=null,i.current=null,r=true;}if(r)t&&u.current.length>0&&(u.current[u.current.length-1].label=t,H([...u.current]));else {let s={state:a.current,timestamp:Date.now(),label:t};p(s),T(),M?.(s);}},[p,M,T]),J=react.useCallback(()=>{b(),n&&Q(n.key),h(e),a.current=e;},[b,e,n]);return {state:R,setState:z,undo:D,redo:_,canUndo:x.length>0,canRedo:L.length>0,clearHistory:b,clearDraft:J,snapshot:B,past:x,future:L}}exports.useFormHistory=W;
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 j=100,V=300,X=500,Y=1;function q(e){return !e||typeof e=="boolean"?null:{key:e.key,debounceMs:e.debounceMs??X,version:e.version??Y}}function U(){return typeof window<"u"&&!!window.localStorage}function G(e,g){try{if(!U())return null;let f=window.localStorage.getItem(e);if(!f)return null;let E=JSON.parse(f);return E.__v!==g?(window.localStorage.removeItem(e),null):E.__s}catch{return null}}function K(e,g,f){try{if(!U())return;window.localStorage.setItem(e,JSON.stringify({__s:g,__v:f}));}catch{}}function Q(e){try{if(!U())return;window.localStorage.removeItem(e);}catch{}}function W(e,g={}){let{maxHistory:f=j,debounceMs:E=V,persist:C,keyboard:P=false,onUndo:k,onRedo:I,onSnapshot:M}=g,n=q(C),[R,h]=useState(()=>{if(n){let t=G(n.key,n.version);if(t!==null)return t}return e}),a=useRef(R);a.current=R;let u=useRef([]),c=useRef([]),[x,H]=useState([]),[L,S]=useState([]),o=useRef(null),m=useRef(null),i=useRef(null),y=useRef(null),T=useCallback(()=>{c.current=[],S([]);},[]),p=useCallback(t=>{let r=[...u.current,t];r.length>f&&r.splice(0,r.length-f),u.current=r,H(r);},[f]);useEffect(()=>()=>{o.current!==null&&clearTimeout(o.current),y.current!==null&&clearTimeout(y.current);},[]);let N=useCallback(t=>{n&&(y.current!==null&&clearTimeout(y.current),y.current=setTimeout(()=>{K(n.key,t,n.version),y.current=null;},n.debounceMs));},[n]),v=useCallback(t=>{n&&(y.current!==null&&(clearTimeout(y.current),y.current=null),K(n.key,t,n.version));},[n]),z=useCallback((t,r)=>{let s=a.current,d=typeof t=="function"?t(s):t;if(!Object.is(s,d)){if(h(d),a.current=d,N(d),E<=0){let F={state:s,timestamp:Date.now(),label:r};p(F),T();return}m.current===null&&(i.current=s),m.current=d,o.current!==null&&clearTimeout(o.current),o.current=setTimeout(()=>{let F={state:i.current,timestamp:Date.now(),label:r};p(F),T(),o.current=null,m.current=null,i.current=null;},E);}},[E,p,N,T]),D=useCallback(()=>{if(o.current!==null&&(clearTimeout(o.current),o.current=null),m.current!==null&&i.current!==null){let d={state:i.current,timestamp:Date.now()};p(d),T(),m.current=null,i.current=null;}if(u.current.length===0)return;let t=u.current[u.current.length-1],r=u.current.slice(0,-1);u.current=r,H(r);let s={state:a.current,timestamp:Date.now()};c.current=[...c.current,s],S(c.current),h(t.state),a.current=t.state,v(t.state),k?.(t.state);},[p,k,v,T]),_=useCallback(()=>{if(c.current.length===0)return;let t=c.current[c.current.length-1],r=c.current.slice(0,-1);c.current=r,S(r);let s={state:a.current,timestamp:Date.now()};u.current=[...u.current,s],H(u.current),h(t.state),a.current=t.state,v(t.state),I?.(t.state);},[I,v]);useEffect(()=>{if(!P)return;let t=r=>{!(r.metaKey||r.ctrlKey)||r.key!=="z"||(r.preventDefault(),r.shiftKey?_():D());};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[P,D,_]);let b=useCallback(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null),m.current=null,i.current=null,u.current=[],c.current=[],H([]),S([]);},[]),B=useCallback(t=>{let r=false;if(o.current!==null&&(clearTimeout(o.current),o.current=null),m.current!==null&&i.current!==null){let s={state:i.current,timestamp:Date.now()};p(s),T(),m.current=null,i.current=null,r=true;}if(r)t&&u.current.length>0&&(u.current[u.current.length-1].label=t,H([...u.current]));else {let s={state:a.current,timestamp:Date.now(),label:t};p(s),T(),M?.(s);}},[p,M,T]),J=useCallback(()=>{b(),n&&Q(n.key),h(e),a.current=e;},[b,e,n]);return {state:R,setState:z,undo:D,redo:_,canUndo:x.length>0,canRedo:L.length>0,clearHistory:b,clearDraft:J,snapshot:B,past:x,future:L}}export{W as useFormHistory};
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.5.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",