cross-state 0.13.0 → 0.15.1
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/cjs/react/index.cjs +126 -103
- package/dist/cjs/react/index.cjs.map +1 -1
- package/dist/es/react/index.mjs +127 -104
- package/dist/es/react/index.mjs.map +1 -1
- package/dist/types/react/form/form.d.ts +32 -12
- package/package.json +2 -2
package/dist/cjs/react/index.cjs
CHANGED
|
@@ -6,6 +6,33 @@ const store = require("../store.cjs");
|
|
|
6
6
|
const scope = require("../scope.cjs");
|
|
7
7
|
const jsxRuntime = require("react/jsx-runtime");
|
|
8
8
|
const urlStore = require("../urlStore.cjs");
|
|
9
|
+
function wildcardMatch(s, w) {
|
|
10
|
+
if (typeof s === "string") {
|
|
11
|
+
s = store.castArrayPath(s);
|
|
12
|
+
}
|
|
13
|
+
if (typeof w === "string") {
|
|
14
|
+
w = store.castArrayPath(w);
|
|
15
|
+
}
|
|
16
|
+
return s.length === w.length && s.every((s2, i) => w[i] === "*" || s2 === w[i]);
|
|
17
|
+
}
|
|
18
|
+
function getWildCardMatches(object, path) {
|
|
19
|
+
const matches = {};
|
|
20
|
+
const [first, ...rest] = store.castArrayPath(path);
|
|
21
|
+
if (first === void 0) {
|
|
22
|
+
return object;
|
|
23
|
+
}
|
|
24
|
+
if (!(object instanceof Object)) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
if (first === "*") {
|
|
28
|
+
for (const [key, value] of Object.entries(object)) {
|
|
29
|
+
matches[key] = getWildCardMatches(value, rest);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
matches[first] = getWildCardMatches(object[first], rest);
|
|
33
|
+
}
|
|
34
|
+
return matches;
|
|
35
|
+
}
|
|
9
36
|
function FormError({ name }) {
|
|
10
37
|
const { errors, isDirty } = this.useField(name);
|
|
11
38
|
return isDirty ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: errors.join(", ") }) : null;
|
|
@@ -22,9 +49,16 @@ function FormField({
|
|
|
22
49
|
}) {
|
|
23
50
|
const id = "";
|
|
24
51
|
const component = ("component" in restProps ? restProps.component : "children" in restProps ? restProps.children : void 0) ?? "input";
|
|
52
|
+
const form = this.useForm();
|
|
25
53
|
const state = useCache.useScope(this.state);
|
|
26
54
|
const { value, setValue, errors } = this.useField(name);
|
|
27
|
-
const errorString = require$$0.useMemo(
|
|
55
|
+
const errorString = require$$0.useMemo(
|
|
56
|
+
() => errors.map((error) => {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
return ((_b = (_a = form.options).localizeError) == null ? void 0 : _b.call(_a, error)) ?? error;
|
|
59
|
+
}).join("\n"),
|
|
60
|
+
[errors, form.options.localizeError]
|
|
61
|
+
);
|
|
28
62
|
const [localValue, setLocalValue] = require$$0.useState();
|
|
29
63
|
const _id = require$$0.useMemo(
|
|
30
64
|
() => `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,
|
|
@@ -89,33 +123,6 @@ function FormField({
|
|
|
89
123
|
};
|
|
90
124
|
return require$$0.createElement(component, props);
|
|
91
125
|
}
|
|
92
|
-
function wildcardMatch(s, w) {
|
|
93
|
-
if (typeof s === "string") {
|
|
94
|
-
s = store.castArrayPath(s);
|
|
95
|
-
}
|
|
96
|
-
if (typeof w === "string") {
|
|
97
|
-
w = store.castArrayPath(w);
|
|
98
|
-
}
|
|
99
|
-
return s.length === w.length && s.every((s2, i) => w[i] === "*" || s2 === w[i]);
|
|
100
|
-
}
|
|
101
|
-
function getWildCardMatches(object, path) {
|
|
102
|
-
const matches = {};
|
|
103
|
-
const [first, ...rest] = store.castArrayPath(path);
|
|
104
|
-
if (first === void 0) {
|
|
105
|
-
return object;
|
|
106
|
-
}
|
|
107
|
-
if (!(object instanceof Object)) {
|
|
108
|
-
return {};
|
|
109
|
-
}
|
|
110
|
-
if (first === "*") {
|
|
111
|
-
for (const [key, value] of Object.entries(object)) {
|
|
112
|
-
matches[key] = getWildCardMatches(value, rest);
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
matches[first] = getWildCardMatches(object[first], rest);
|
|
116
|
-
}
|
|
117
|
-
return matches;
|
|
118
|
-
}
|
|
119
126
|
function FormContainer({
|
|
120
127
|
form,
|
|
121
128
|
...formProps
|
|
@@ -134,6 +141,81 @@ function FormContainer({
|
|
|
134
141
|
}
|
|
135
142
|
);
|
|
136
143
|
}
|
|
144
|
+
function getFormInstance(original, options, state) {
|
|
145
|
+
const instance = {
|
|
146
|
+
original,
|
|
147
|
+
draft: state.map(
|
|
148
|
+
(state2) => state2.draft ?? original ?? options.defaultValue,
|
|
149
|
+
(draft) => (state2) => ({ ...state2, draft })
|
|
150
|
+
),
|
|
151
|
+
options,
|
|
152
|
+
getField: (path) => {
|
|
153
|
+
const { draft } = instance;
|
|
154
|
+
return {
|
|
155
|
+
get originalValue() {
|
|
156
|
+
return original !== void 0 ? store.get(original, path) : void 0;
|
|
157
|
+
},
|
|
158
|
+
get value() {
|
|
159
|
+
return store.get(draft.get(), path);
|
|
160
|
+
},
|
|
161
|
+
setValue(update) {
|
|
162
|
+
draft.set(path, update);
|
|
163
|
+
},
|
|
164
|
+
get isDirty() {
|
|
165
|
+
const comparisonValue = this.originalValue ?? store.get(options.defaultValue, path);
|
|
166
|
+
return state.get().hasTriggeredValidations || !store.deepEqual(comparisonValue, this.value);
|
|
167
|
+
},
|
|
168
|
+
get errors() {
|
|
169
|
+
const blocks = Object.entries(
|
|
170
|
+
options.validations ?? {}
|
|
171
|
+
).filter(([key]) => wildcardMatch(path, key)).map(([, value2]) => value2);
|
|
172
|
+
const value = this.value;
|
|
173
|
+
const draftValue = draft.get();
|
|
174
|
+
const errors = [];
|
|
175
|
+
for (const block of blocks ?? []) {
|
|
176
|
+
for (const [validationName, validate] of Object.entries(block)) {
|
|
177
|
+
if (!validate(value, { draft: draftValue, original, field: path })) {
|
|
178
|
+
errors.push(validationName);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return errors;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
get hasChanges() {
|
|
187
|
+
const { draft } = state.get();
|
|
188
|
+
return !!draft && !store.deepEqual(draft, original ?? options.defaultValue);
|
|
189
|
+
},
|
|
190
|
+
get errors() {
|
|
191
|
+
const draft = instance.draft.get();
|
|
192
|
+
const errors = /* @__PURE__ */ new Set();
|
|
193
|
+
for (const [path, block] of Object.entries(options.validations ?? {})) {
|
|
194
|
+
for (const [validationName, validate] of Object.entries(
|
|
195
|
+
block
|
|
196
|
+
)) {
|
|
197
|
+
for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
|
|
198
|
+
if (!validate(value, { draft, original, field })) {
|
|
199
|
+
errors.add(`${field}.${validationName}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return [...errors];
|
|
205
|
+
},
|
|
206
|
+
get isValid() {
|
|
207
|
+
return instance.errors.length === 0;
|
|
208
|
+
},
|
|
209
|
+
validate: () => {
|
|
210
|
+
state.set("hasTriggeredValidations", true);
|
|
211
|
+
return instance.isValid;
|
|
212
|
+
},
|
|
213
|
+
reset() {
|
|
214
|
+
state.set("draft", void 0);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return instance;
|
|
218
|
+
}
|
|
137
219
|
class Form {
|
|
138
220
|
constructor(options) {
|
|
139
221
|
this.options = options;
|
|
@@ -150,80 +232,12 @@ class Form {
|
|
|
150
232
|
useForm() {
|
|
151
233
|
const { original, options } = require$$0.useContext(this.context);
|
|
152
234
|
const state = useCache.useScope(this.state);
|
|
153
|
-
return require$$0.useMemo(() =>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
),
|
|
160
|
-
getField: (path) => {
|
|
161
|
-
const { draft } = instance;
|
|
162
|
-
return {
|
|
163
|
-
get originalValue() {
|
|
164
|
-
return original !== void 0 ? store.get(original, path) : void 0;
|
|
165
|
-
},
|
|
166
|
-
get value() {
|
|
167
|
-
return store.get(draft.get(), path);
|
|
168
|
-
},
|
|
169
|
-
setValue(update) {
|
|
170
|
-
draft.set(path, update);
|
|
171
|
-
},
|
|
172
|
-
get isDirty() {
|
|
173
|
-
const comparisonValue = this.originalValue ?? store.get(options.defaultValue, path);
|
|
174
|
-
return state.get().hasTriggeredValidations || !store.deepEqual(comparisonValue, this.value);
|
|
175
|
-
},
|
|
176
|
-
get errors() {
|
|
177
|
-
const blocks = Object.entries(
|
|
178
|
-
options.validations ?? {}
|
|
179
|
-
).filter(([key]) => wildcardMatch(path, key)).map(([, value2]) => value2);
|
|
180
|
-
const value = this.value;
|
|
181
|
-
const draftValue = draft.get();
|
|
182
|
-
const errors = [];
|
|
183
|
-
for (const block of blocks ?? []) {
|
|
184
|
-
for (const [validationName, validate] of Object.entries(block)) {
|
|
185
|
-
if (!validate(value, { draft: draftValue, original, field: path })) {
|
|
186
|
-
errors.push(validationName);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return errors;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
},
|
|
194
|
-
get hasChanges() {
|
|
195
|
-
const { draft } = state.get();
|
|
196
|
-
return !!draft && !store.deepEqual(draft, original ?? options.defaultValue);
|
|
197
|
-
},
|
|
198
|
-
get errors() {
|
|
199
|
-
const draft = instance.draft.get();
|
|
200
|
-
const errors = /* @__PURE__ */ new Set();
|
|
201
|
-
for (const [path, block] of Object.entries(options.validations ?? {})) {
|
|
202
|
-
for (const [validationName, validate] of Object.entries(
|
|
203
|
-
block
|
|
204
|
-
)) {
|
|
205
|
-
for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
|
|
206
|
-
if (!validate(value, { draft, original, field })) {
|
|
207
|
-
errors.add(`${field}.${validationName}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return [...errors];
|
|
213
|
-
},
|
|
214
|
-
get isValid() {
|
|
215
|
-
return instance.errors.length === 0;
|
|
216
|
-
},
|
|
217
|
-
validate: () => {
|
|
218
|
-
state.set("hasTriggeredValidations", true);
|
|
219
|
-
return instance.isValid;
|
|
220
|
-
},
|
|
221
|
-
reset() {
|
|
222
|
-
state.set("draft", void 0);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
return instance;
|
|
226
|
-
}, [original, options, state]);
|
|
235
|
+
return require$$0.useMemo(() => getFormInstance(original, options, state), [original, options, state]);
|
|
236
|
+
}
|
|
237
|
+
useFormState(selector) {
|
|
238
|
+
const { original, options } = require$$0.useContext(this.context);
|
|
239
|
+
const state = useCache.useScope(this.state);
|
|
240
|
+
return useCache.useStore(state.map(() => selector(getFormInstance(original, options, state))));
|
|
227
241
|
}
|
|
228
242
|
useField(path, useStoreOptions) {
|
|
229
243
|
const form = this.useForm();
|
|
@@ -253,6 +267,7 @@ class Form {
|
|
|
253
267
|
original,
|
|
254
268
|
defaultValue,
|
|
255
269
|
validations,
|
|
270
|
+
localizeError,
|
|
256
271
|
urlState,
|
|
257
272
|
...formProps
|
|
258
273
|
}) {
|
|
@@ -261,7 +276,8 @@ class Form {
|
|
|
261
276
|
original,
|
|
262
277
|
options: {
|
|
263
278
|
defaultValue: { ...this.options.defaultValue, ...defaultValue },
|
|
264
|
-
validations: { ...this.options.validations, ...validations }
|
|
279
|
+
validations: { ...this.options.validations, ...validations },
|
|
280
|
+
localizeError: localizeError ?? this.options.localizeError
|
|
265
281
|
}
|
|
266
282
|
}),
|
|
267
283
|
[original, defaultValue, validations]
|
|
@@ -280,6 +296,13 @@ class Form {
|
|
|
280
296
|
}, [store$1, scope.hash(urlState)]);
|
|
281
297
|
return /* @__PURE__ */ jsxRuntime.jsx(this.context.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(useCache.ScopeProvider, { scope: this.state, store: store$1, children: /* @__PURE__ */ jsxRuntime.jsx(FormContainer, { ...formProps, form: this }) }) });
|
|
282
298
|
}
|
|
299
|
+
Subscribe({
|
|
300
|
+
selector,
|
|
301
|
+
children
|
|
302
|
+
}) {
|
|
303
|
+
const selectedState = this.useFormState(selector);
|
|
304
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(selectedState) });
|
|
305
|
+
}
|
|
283
306
|
Field(props) {
|
|
284
307
|
return Reflect.apply(FormField, this, [props]);
|
|
285
308
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../src/react/form/formError.tsx","../../../src/react/form/formField.tsx","../../../src/lib/wildcardMatch.ts","../../../src/react/form/form.tsx","../../../src/react/read.ts","../../../src/react/useDecoupledState.ts","../../../src/lib/castArray.ts","../../../src/react/useUrlParamScope.ts"],"sourcesContent":["import { type Form } from './form';\nimport { type PathAsString } from '@lib/path';\n\nexport type FormErrorProps<TDraft, TPath extends PathAsString<TDraft>> = {\n name: TPath;\n};\n\nexport function FormError<TDraft, TPath extends PathAsString<TDraft>>(\n this: Form<TDraft, any>,\n { name }: FormErrorProps<TDraft, TPath>,\n) {\n const { errors, isDirty } = this.useField(name);\n\n return isDirty ? <>{errors.join(', ')}</> : null;\n}\n","import {\n createElement,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ElementType,\n type HTMLProps,\n} from 'react';\nimport { type Form } from './form';\nimport { type PathAsString } from '@index';\nimport { type Value } from '@lib/path';\nimport { useScope } from '@react/scope';\n\nexport type FormFieldComponent<T> = ElementType<{\n id: string;\n value: T;\n onChange: (event: { target: { value: T } } | T | undefined, ...args: any[]) => void;\n onFocus: (...args: any[]) => void;\n onBlur: (...args: any[]) => void;\n}>;\n\ntype FieldValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<T & 'input'> extends {\n value: infer U;\n}\n ? U\n : ComponentPropsWithoutRef<T & 'input'> extends {\n value?: infer U;\n }\n ? U | undefined\n : never;\n\ntype FieldChangeValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<\n T & 'input'\n> extends {\n onChange?: (update: infer U) => void;\n}\n ? U extends { target: { value: infer V } }\n ? V\n : U\n : never;\n\nexport type FormFieldProps<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n> = {\n name: TPath;\n commitOnBlur?: boolean;\n commitDebounce?: number;\n inputFilter?: (value: FieldChangeValue<TComponent>) => boolean;\n onChange?: ComponentPropsWithoutRef<TComponent>['onChange'];\n onBlur?: ComponentPropsWithoutRef<TComponent>['onBlur'];\n} & (TComponent extends 'input' | ((props: HTMLProps<HTMLInputElement>) => JSX.Element)\n ? { component?: TComponent } | { children?: TComponent }\n : { component: TComponent } | { children: TComponent }) &\n Omit<\n ComponentPropsWithoutRef<TComponent>,\n | 'form'\n | 'name'\n | 'component'\n | 'commitOnBlur'\n | 'commitDebounce'\n | 'value'\n | 'onChange'\n | 'onBlur'\n | 'children'\n > &\n (Value<TDraft, TPath> extends FieldValue<TComponent>\n ? { serialize?: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }\n : { serialize: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }) &\n (FieldChangeValue<TComponent> extends Value<TDraft, TPath>\n ? { deserialize?: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> }\n : { deserialize: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> });\n\nexport function FormField<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n>(\n this: Form<TDraft, any>,\n {\n // id,\n name,\n commitOnBlur,\n commitDebounce,\n inputFilter,\n serialize = (x) => x as FieldValue<TComponent>,\n deserialize = (x) => x as Value<TDraft, TPath>,\n ...restProps\n }: FormFieldProps<TDraft, TPath, TComponent>,\n): JSX.Element {\n type T = FieldChangeValue<TComponent>;\n const id = '';\n const component = (('component' in restProps\n ? restProps.component\n : 'children' in restProps\n ? restProps.children\n : undefined) ?? 'input') as TComponent;\n\n const state = useScope(this.state);\n const { value, setValue, errors } = this.useField(name);\n const errorString = useMemo(() => errors.join('\\n'), [errors]);\n const [localValue, setLocalValue] = useState<T>();\n const _id = useMemo(\n () =>\n id || `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,\n\n [id],\n );\n\n useEffect(() => {\n if (localValue === undefined || !commitDebounce) {\n return;\n }\n\n const timeout = setTimeout(() => {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }, commitDebounce);\n\n return () => clearTimeout(timeout);\n }, [localValue, commitDebounce]);\n\n useEffect(() => {\n const element = document.querySelector(\n [`#${_id} input`, `#${_id} select`, `#${_id} textarea`, `#${_id}`].join(','),\n );\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n )\n ) {\n return;\n }\n\n element.setCustomValidity(errorString);\n }, [_id, errorString]);\n\n const props = {\n ...restProps,\n component: undefined,\n children: undefined,\n id: _id,\n name,\n value: localValue ?? serialize(value),\n onChange: (event: { target: { value: T } } | T, ...moreArgs: any[]) => {\n const value =\n typeof event === 'object' && event !== null && 'target' in event\n ? event.target.value\n : event;\n\n if (inputFilter && !inputFilter(value)) {\n return;\n }\n\n if (commitOnBlur || commitDebounce) {\n setLocalValue(value);\n } else {\n setValue(deserialize(value));\n }\n\n restProps.onChange?.(event, ...moreArgs);\n },\n onFocus(...args: any[]) {\n state.set('touched', (touched) => {\n touched = new Set(touched);\n touched.add(_id);\n return touched;\n });\n\n restProps.onFocus?.apply(null, args);\n },\n onBlur(...args: any[]) {\n if (localValue !== undefined) {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }\n\n restProps.onBlur?.apply(null, args);\n },\n };\n\n return createElement(component, props);\n}\n","import { type KeyType } from './path';\nimport { castArrayPath } from './propAccess';\n\nexport function wildcardMatch(s: KeyType[] | string, w: KeyType[] | string): boolean {\n if (typeof s === 'string') {\n s = castArrayPath(s);\n }\n\n if (typeof w === 'string') {\n w = castArrayPath(w);\n }\n\n return s.length === w.length && s.every((s, i) => w[i] === '*' || s === w[i]);\n}\n\nexport function getWildCardMatches(object: any, path: KeyType[] | string): Record<KeyType, any> {\n const matches: Record<KeyType, any> = {};\n const [first, ...rest] = castArrayPath(path);\n\n if (first === undefined) {\n return object;\n }\n\n if (!(object instanceof Object)) {\n return {};\n }\n\n if (first === '*') {\n for (const [key, value] of Object.entries(object)) {\n matches[key] = getWildCardMatches(value, rest);\n }\n } else {\n matches[first] = getWildCardMatches(object[first], rest);\n }\n\n return matches;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ComponentPropsWithoutRef,\n type HTMLProps,\n} from 'react';\nimport { ScopeProvider, useScope } from '../scope';\nimport { useStore, type UseStoreOptions } from '../useStore';\nimport { FormError, type FormErrorProps } from './formError';\nimport { FormField, type FormFieldComponent, type FormFieldProps } from './formField';\nimport { Scope, connectUrl, createStore, type UrlStoreOptions } from '@core';\nimport { autobind } from '@lib/autobind';\nimport { deepEqual } from '@lib/equals';\nimport { hash } from '@lib/hash';\nimport {\n type PathAsString,\n type Value,\n type WildcardPathAsString,\n type WildcardValue,\n} from '@lib/path';\nimport { get } from '@lib/propAccess';\nimport { getWildCardMatches, wildcardMatch } from '@lib/wildcardMatch';\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Form types\n/// /////////////////////////////////////////////////////////////////////////////\n\nexport interface FormOptions<TDraft, TOriginal> {\n defaultValue: TDraft;\n validations?: Validations<TDraft, TOriginal>;\n urlState?: boolean | UrlStoreOptions<TDraft>;\n}\n\nexport type Validations<TDraft, TOriginal> = {\n [P in WildcardPathAsString<TDraft>]?: Record<\n string,\n Validation<WildcardValue<TDraft, P>, TDraft, TOriginal>\n >;\n};\n\nexport type Validation<TValue, TDraft, TOriginal> = (\n value: TValue,\n context: { draft: TDraft; original: TOriginal; field: PathAsString<TDraft> },\n) => boolean;\n\nexport interface Field<TDraft, TOriginal, TPath extends PathAsString<TDraft>> {\n originalValue: Value<TOriginal, TPath> | undefined;\n value: Value<TDraft, TPath>;\n setValue: (\n value: Value<TDraft, TPath> | ((value: Value<TDraft, TPath>) => Value<TDraft, TPath>),\n ) => void;\n isDirty: boolean;\n errors: string[];\n}\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Implementation\n/// /////////////////////////////////////////////////////////////////////////////\n\nfunction FormContainer({\n form,\n ...formProps\n}: { form: Form<any, any> } & Omit<HTMLProps<HTMLFormElement>, 'form'>) {\n const { validate } = form.useForm();\n\n return (\n <form\n {...formProps}\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n\n validate();\n event.currentTarget.reportValidity();\n }}\n />\n );\n}\n\nexport class Form<TDraft, TOriginal extends TDraft = TDraft> {\n context = createContext({\n original: undefined as TOriginal | undefined,\n options: this.options,\n });\n\n state = new Scope<{\n draft?: TDraft;\n touched: Set<string>;\n errors: Map<string, string[]>;\n hasTriggeredValidations?: boolean;\n }>({\n touched: new Set(),\n errors: new Map(),\n });\n\n constructor(public readonly options: FormOptions<TDraft, TOriginal>) {\n autobind(Form);\n }\n\n useForm() {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useMemo(() => {\n const instance = {\n original,\n\n draft: state.map(\n (state) => state.draft ?? original ?? options.defaultValue,\n (draft) => (state) => ({ ...state, draft }),\n ),\n\n getField: <TPath extends PathAsString<TDraft>>(\n path: TPath,\n ): Field<TDraft, TOriginal, TPath> => {\n const { draft } = instance;\n\n return {\n get originalValue() {\n return original !== undefined ? get(original as any, path as any) : undefined;\n },\n\n get value() {\n return get(draft.get(), path);\n },\n\n setValue(update) {\n draft.set(path, update);\n },\n\n get isDirty() {\n const comparisonValue = this.originalValue ?? get(options.defaultValue, path);\n\n return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);\n },\n\n get errors() {\n const blocks: Record<string, Validation<any, any, any>>[] = Object.entries(\n options.validations ?? {},\n )\n .filter(([key]) => wildcardMatch(path, key))\n .map(([, value]) => value);\n\n const value = this.value;\n const draftValue = draft.get();\n const errors: string[] = [];\n\n for (const block of blocks ?? []) {\n for (const [validationName, validate] of Object.entries(block)) {\n if (!validate(value, { draft: draftValue, original, field: path })) {\n errors.push(validationName);\n }\n }\n }\n\n return errors;\n },\n };\n },\n\n get hasChanges() {\n const { draft } = state.get();\n return !!draft && !deepEqual(draft, original ?? options.defaultValue);\n },\n\n get errors(): string[] {\n const draft = instance.draft.get();\n const errors = new Set<string>();\n\n for (const [path, block] of Object.entries(options.validations ?? {})) {\n for (const [validationName, validate] of Object.entries(\n block as Record<string, Validation<any, any, any>>,\n )) {\n for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {\n if (!validate(value, { draft, original, field })) {\n errors.add(`${field}.${validationName}`);\n }\n }\n }\n }\n\n return [...errors] as any;\n },\n\n get isValid() {\n return instance.errors.length === 0;\n },\n\n validate: () => {\n state.set('hasTriggeredValidations', true);\n return instance.isValid;\n },\n\n reset() {\n state.set('draft', undefined);\n },\n };\n\n return instance;\n }, [original, options, state]);\n }\n\n useField<TPath extends PathAsString<TDraft>>(path: TPath, useStoreOptions?: UseStoreOptions) {\n const form = this.useForm();\n const state = useScope(this.state);\n\n useStore(\n form.draft.map((draft) => get(draft, path)),\n useStoreOptions,\n );\n\n useStore(\n state.map((state) => state.hasTriggeredValidations),\n useStoreOptions,\n );\n\n return form.getField(path);\n }\n\n useHasChanges() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.hasChanges));\n }\n\n useIsValid() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.isValid));\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // React Components\n // ///////////////////////////////////////////////////////////////////////////\n\n Form({\n original,\n defaultValue,\n validations,\n urlState,\n ...formProps\n }: {\n original?: TOriginal;\n } & Partial<FormOptions<TDraft, TOriginal>> &\n Omit<HTMLProps<HTMLFormElement>, 'defaultValue'>) {\n const value = useMemo(\n () => ({\n original,\n options: {\n defaultValue: { ...this.options.defaultValue, ...defaultValue },\n validations: { ...this.options.validations, ...validations } as Validations<\n TDraft,\n TOriginal\n >,\n },\n }),\n [original, defaultValue, validations],\n );\n\n const store = useMemo(() => {\n return createStore(this.state.defaultValue);\n }, []);\n\n useEffect(() => {\n if (urlState) {\n return connectUrl(\n store.map('draft'),\n typeof urlState === 'object' ? urlState : { key: 'form' },\n );\n }\n\n return undefined;\n }, [store, hash(urlState)]);\n\n return (\n <this.context.Provider value={value}>\n <ScopeProvider scope={this.state} store={store}>\n <FormContainer {...formProps} form={this} />\n </ScopeProvider>\n </this.context.Provider>\n );\n }\n\n Field<\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any> = (\n props: ComponentPropsWithoutRef<'input'>,\n ) => JSX.Element,\n >(props: FormFieldProps<TDraft, TPath, TComponent>): JSX.Element {\n return Reflect.apply(FormField, this, [props]);\n }\n\n Error<TPath extends PathAsString<TDraft>>({ name }: FormErrorProps<TDraft, TPath>) {\n return Reflect.apply(FormError, this, [{ name }]);\n }\n}\n\nexport function createForm<TDraft, TOriginal extends TDraft = TDraft>(\n options: FormOptions<TDraft, TOriginal>,\n) {\n return new Form(options);\n}\n","import { useCache } from './useCache';\nimport type { UseStoreOptions } from './useStore';\nimport type { Cache } from '@core';\n\nexport function read<T>(cache: Cache<T>, options?: UseStoreOptions): T {\n const { status, value, error } = useCache(cache, options);\n\n if (status === 'value') {\n return value;\n }\n\n if (status === 'error') {\n throw error;\n }\n\n throw cache.state.once((state) => state.status !== 'pending');\n}\n","import { startTransition, useEffect, useMemo, useRef, useState } from 'react';\nimport { type Duration } from '@core';\nimport { debounce } from '@lib/debounce';\nimport { hash } from '@lib/hash';\nimport { throttle } from '@lib/throttle';\n\nexport interface UseDecoupledStateOptions<T> {\n debounce?: Duration;\n throttle?: Duration;\n onCommit?: (value: T) => void;\n}\n\nexport function useDecoupledState<T>(\n value: T,\n onChange: (value: T) => void,\n options: UseDecoupledStateOptions<T> = {},\n): [state: T, setState: (value: T) => void] {\n const [dirty, setDirty] = useState<{ v: T }>();\n const ref = useRef({ onChange, onCommit: options.onCommit });\n\n useEffect(() => {\n ref.current = { onChange, onCommit: options.onCommit };\n }, [onChange]);\n\n const update = useMemo(() => {\n const { onChange, onCommit } = ref.current;\n\n const update = (value: T) => {\n onChange(value);\n setDirty(undefined);\n onCommit?.(value);\n };\n\n let delayedUpdate: (value: T) => void;\n\n if (options.debounce) {\n delayedUpdate = debounce(update, options.debounce);\n } else if (options.throttle) {\n delayedUpdate = throttle(update, options.throttle);\n } else {\n delayedUpdate = (value) => startTransition(() => update(value));\n }\n\n return (value: T) => {\n setDirty({ v: value });\n delayedUpdate(value);\n };\n }, [hash([options.debounce, options.throttle])]);\n\n return [dirty ? dirty.v : value, update];\n}\n","export function castArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n","import { type ReactNode, useEffect } from 'react';\nimport { castArray } from '@lib/castArray';\nimport { hash } from '@lib/hash';\n\nexport function useUrlParamScope({\n key,\n type = 'search',\n}: {\n key: string | string[];\n type?: 'search' | 'hash';\n}) {\n useEffect(\n () => () => {\n const url = new URL(window.location.href);\n const parameters = new URLSearchParams(url[type].slice(1));\n\n for (const _key of castArray(key)) {\n parameters.delete(_key);\n }\n\n url[type] = parameters.toString();\n window.history.replaceState(null, '', url.toString());\n },\n [hash(key), type],\n );\n}\n"],"names":["jsx","Fragment","useScope","useMemo","useState","useEffect","value","createElement","castArrayPath","s","createContext","Scope","autobind","useContext","state","get","deepEqual","useStore","store","createStore","connectUrl","hash","ScopeProvider","useCache","useRef","onChange","update","debounce","throttle","startTransition"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../src/lib/wildcardMatch.ts","../../../src/react/form/formError.tsx","../../../src/react/form/formField.tsx","../../../src/react/form/form.tsx","../../../src/react/read.ts","../../../src/react/useDecoupledState.ts","../../../src/lib/castArray.ts","../../../src/react/useUrlParamScope.ts"],"sourcesContent":["import { type KeyType } from './path';\nimport { castArrayPath } from './propAccess';\n\nexport function wildcardMatch(s: KeyType[] | string, w: KeyType[] | string): boolean {\n if (typeof s === 'string') {\n s = castArrayPath(s);\n }\n\n if (typeof w === 'string') {\n w = castArrayPath(w);\n }\n\n return s.length === w.length && s.every((s, i) => w[i] === '*' || s === w[i]);\n}\n\nexport function getWildCardMatches(object: any, path: KeyType[] | string): Record<KeyType, any> {\n const matches: Record<KeyType, any> = {};\n const [first, ...rest] = castArrayPath(path);\n\n if (first === undefined) {\n return object;\n }\n\n if (!(object instanceof Object)) {\n return {};\n }\n\n if (first === '*') {\n for (const [key, value] of Object.entries(object)) {\n matches[key] = getWildCardMatches(value, rest);\n }\n } else {\n matches[first] = getWildCardMatches(object[first], rest);\n }\n\n return matches;\n}\n","import { type Form } from './form';\nimport { type PathAsString } from '@lib/path';\n\nexport type FormErrorProps<TDraft, TPath extends PathAsString<TDraft>> = {\n name: TPath;\n};\n\nexport function FormError<TDraft, TPath extends PathAsString<TDraft>>(\n this: Form<TDraft, any>,\n { name }: FormErrorProps<TDraft, TPath>,\n) {\n const { errors, isDirty } = this.useField(name);\n\n return isDirty ? <>{errors.join(', ')}</> : null;\n}\n","import {\n createElement,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ElementType,\n type HTMLProps,\n} from 'react';\nimport { type Form } from './form';\nimport { type PathAsString } from '@index';\nimport { type Value } from '@lib/path';\nimport { useScope } from '@react/scope';\n\nexport type FormFieldComponent<T> = ElementType<{\n id: string;\n value: T;\n onChange: (event: { target: { value: T } } | T | undefined, ...args: any[]) => void;\n onFocus: (...args: any[]) => void;\n onBlur: (...args: any[]) => void;\n}>;\n\ntype FieldValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<T & 'input'> extends {\n value: infer U;\n}\n ? U\n : ComponentPropsWithoutRef<T & 'input'> extends {\n value?: infer U;\n }\n ? U | undefined\n : never;\n\ntype FieldChangeValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<\n T & 'input'\n> extends {\n onChange?: (update: infer U) => void;\n}\n ? U extends { target: { value: infer V } }\n ? V\n : U\n : never;\n\nexport type FormFieldProps<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n> = {\n name: TPath;\n commitOnBlur?: boolean;\n commitDebounce?: number;\n inputFilter?: (value: FieldChangeValue<TComponent>) => boolean;\n onChange?: ComponentPropsWithoutRef<TComponent>['onChange'];\n onBlur?: ComponentPropsWithoutRef<TComponent>['onBlur'];\n} & (TComponent extends 'input' | ((props: HTMLProps<HTMLInputElement>) => JSX.Element)\n ? { component?: TComponent } | { children?: TComponent }\n : { component: TComponent } | { children: TComponent }) &\n Omit<\n ComponentPropsWithoutRef<TComponent>,\n | 'form'\n | 'name'\n | 'component'\n | 'commitOnBlur'\n | 'commitDebounce'\n | 'value'\n | 'onChange'\n | 'onBlur'\n | 'children'\n > &\n (Value<TDraft, TPath> extends FieldValue<TComponent>\n ? { serialize?: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }\n : { serialize: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }) &\n (FieldChangeValue<TComponent> extends Value<TDraft, TPath>\n ? { deserialize?: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> }\n : { deserialize: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> });\n\nexport function FormField<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n>(\n this: Form<TDraft, any>,\n {\n // id,\n name,\n commitOnBlur,\n commitDebounce,\n inputFilter,\n serialize = (x) => x as FieldValue<TComponent>,\n deserialize = (x) => x as Value<TDraft, TPath>,\n ...restProps\n }: FormFieldProps<TDraft, TPath, TComponent>,\n): JSX.Element {\n type T = FieldChangeValue<TComponent>;\n const id = '';\n const component = (('component' in restProps\n ? restProps.component\n : 'children' in restProps\n ? restProps.children\n : undefined) ?? 'input') as TComponent;\n\n const form = this.useForm();\n const state = useScope(this.state);\n const { value, setValue, errors } = this.useField(name);\n\n const errorString = useMemo(\n () => errors.map((error) => form.options.localizeError?.(error) ?? error).join('\\n'),\n [errors, form.options.localizeError],\n );\n const [localValue, setLocalValue] = useState<T>();\n const _id = useMemo(\n () =>\n id || `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,\n\n [id],\n );\n\n useEffect(() => {\n if (localValue === undefined || !commitDebounce) {\n return;\n }\n\n const timeout = setTimeout(() => {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }, commitDebounce);\n\n return () => clearTimeout(timeout);\n }, [localValue, commitDebounce]);\n\n useEffect(() => {\n const element = document.querySelector(\n [`#${_id} input`, `#${_id} select`, `#${_id} textarea`, `#${_id}`].join(','),\n );\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n )\n ) {\n return;\n }\n\n element.setCustomValidity(errorString);\n }, [_id, errorString]);\n\n const props = {\n ...restProps,\n component: undefined,\n children: undefined,\n id: _id,\n name,\n value: localValue ?? serialize(value),\n onChange: (event: { target: { value: T } } | T, ...moreArgs: any[]) => {\n const value =\n typeof event === 'object' && event !== null && 'target' in event\n ? event.target.value\n : event;\n\n if (inputFilter && !inputFilter(value)) {\n return;\n }\n\n if (commitOnBlur || commitDebounce) {\n setLocalValue(value);\n } else {\n setValue(deserialize(value));\n }\n\n restProps.onChange?.(event, ...moreArgs);\n },\n onFocus(...args: any[]) {\n state.set('touched', (touched) => {\n touched = new Set(touched);\n touched.add(_id);\n return touched;\n });\n\n restProps.onFocus?.apply(null, args);\n },\n onBlur(...args: any[]) {\n if (localValue !== undefined) {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }\n\n restProps.onBlur?.apply(null, args);\n },\n };\n\n return createElement(component, props);\n}\n","import { Scope, type Store, connectUrl, createStore, type UrlStoreOptions } from '@core';\nimport { autobind } from '@lib/autobind';\nimport { deepEqual } from '@lib/equals';\nimport { hash } from '@lib/hash';\nimport {\n type PathAsString,\n type Value,\n type WildcardPathAsString,\n type WildcardValue,\n} from '@lib/path';\nimport { get } from '@lib/propAccess';\nimport { getWildCardMatches, wildcardMatch } from '@lib/wildcardMatch';\nimport {\n type ReactNode,\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ComponentPropsWithoutRef,\n type HTMLProps,\n} from 'react';\nimport { ScopeProvider, useScope } from '../scope';\nimport { useStore, type UseStoreOptions } from '../useStore';\nimport { FormError, type FormErrorProps } from './formError';\nimport { FormField, type FormFieldComponent, type FormFieldProps } from './formField';\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Form types\n/// /////////////////////////////////////////////////////////////////////////////\n\nexport interface FormOptions<TDraft, TOriginal> {\n defaultValue: TDraft;\n validations?: Validations<TDraft, TOriginal>;\n localizeError?: (error: string) => string | undefined;\n urlState?: boolean | UrlStoreOptions<TDraft>;\n}\n\nexport type Validations<TDraft, TOriginal> = {\n [P in WildcardPathAsString<TDraft>]?: Record<\n string,\n Validation<WildcardValue<TDraft, P>, TDraft, TOriginal>\n >;\n};\n\nexport type Validation<TValue, TDraft, TOriginal> = (\n value: TValue,\n context: { draft: TDraft; original: TOriginal; field: PathAsString<TDraft> },\n) => boolean;\n\nexport interface Field<TDraft, TOriginal, TPath extends PathAsString<TDraft>> {\n originalValue: Value<TOriginal, TPath> | undefined;\n value: Value<TDraft, TPath>;\n setValue: (\n value: Value<TDraft, TPath> | ((value: Value<TDraft, TPath>) => Value<TDraft, TPath>),\n ) => void;\n isDirty: boolean;\n errors: string[];\n}\n\ninterface FormState<TDraft> {\n draft?: TDraft;\n touched: Set<string>;\n errors: Map<string, string[]>;\n hasTriggeredValidations?: boolean;\n}\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Implementation\n/// /////////////////////////////////////////////////////////////////////////////\n\nfunction FormContainer({\n form,\n ...formProps\n}: { form: Form<any, any> } & Omit<HTMLProps<HTMLFormElement>, 'form'>) {\n const { validate } = form.useForm();\n\n return (\n <form\n {...formProps}\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n\n validate();\n event.currentTarget.reportValidity();\n }}\n />\n );\n}\n\nfunction getFormInstance<TDraft, TOriginal extends TDraft>(\n original: TOriginal | undefined,\n options: FormOptions<TDraft, TOriginal>,\n state: Store<FormState<TDraft>>,\n) {\n const instance = {\n original,\n\n draft: state.map(\n (state) => state.draft ?? original ?? options.defaultValue,\n (draft) => (state) => ({ ...state, draft }),\n ),\n\n options,\n\n getField: <TPath extends PathAsString<TDraft>>(\n path: TPath,\n ): Field<TDraft, TOriginal, TPath> => {\n const { draft } = instance;\n\n return {\n get originalValue() {\n return original !== undefined ? get(original as any, path as any) : undefined;\n },\n\n get value() {\n return get(draft.get(), path);\n },\n\n setValue(update) {\n draft.set(path, update);\n },\n\n get isDirty() {\n const comparisonValue = this.originalValue ?? get(options.defaultValue, path);\n\n return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);\n },\n\n get errors() {\n const blocks: Record<string, Validation<any, any, any>>[] = Object.entries(\n options.validations ?? {},\n )\n .filter(([key]) => wildcardMatch(path, key))\n .map(([, value]) => value);\n\n const value = this.value;\n const draftValue = draft.get();\n const errors: string[] = [];\n\n for (const block of blocks ?? []) {\n for (const [validationName, validate] of Object.entries(block)) {\n if (!validate(value, { draft: draftValue, original, field: path })) {\n errors.push(validationName);\n }\n }\n }\n\n return errors;\n },\n };\n },\n\n get hasChanges() {\n const { draft } = state.get();\n return !!draft && !deepEqual(draft, original ?? options.defaultValue);\n },\n\n get errors(): string[] {\n const draft = instance.draft.get();\n const errors = new Set<string>();\n\n for (const [path, block] of Object.entries(options.validations ?? {})) {\n for (const [validationName, validate] of Object.entries(\n block as Record<string, Validation<any, any, any>>,\n )) {\n for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {\n if (!validate(value, { draft, original, field })) {\n errors.add(`${field}.${validationName}`);\n }\n }\n }\n }\n\n return [...errors];\n },\n\n get isValid() {\n return instance.errors.length === 0;\n },\n\n validate: () => {\n state.set('hasTriggeredValidations', true);\n return instance.isValid;\n },\n\n reset() {\n state.set('draft', undefined);\n },\n };\n\n return instance;\n}\n\nexport class Form<TDraft, TOriginal extends TDraft = TDraft> {\n context = createContext({\n original: undefined as TOriginal | undefined,\n options: this.options,\n });\n\n state = new Scope<FormState<TDraft>>({\n touched: new Set(),\n errors: new Map(),\n });\n\n constructor(public readonly options: FormOptions<TDraft, TOriginal>) {\n autobind(Form);\n }\n\n useForm() {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useMemo(() => getFormInstance(original, options, state), [original, options, state]);\n }\n\n useFormState<S>(selector: (state: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S) {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useStore(state.map(() => selector(getFormInstance(original, options, state))));\n }\n\n useField<TPath extends PathAsString<TDraft>>(path: TPath, useStoreOptions?: UseStoreOptions) {\n const form = this.useForm();\n const state = useScope(this.state);\n\n useStore(\n form.draft.map((draft) => get(draft, path)),\n useStoreOptions,\n );\n\n useStore(\n state.map((state) => state.hasTriggeredValidations),\n useStoreOptions,\n );\n\n return form.getField(path);\n }\n\n useHasChanges() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.hasChanges));\n }\n\n useIsValid() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.isValid));\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // React Components\n // ///////////////////////////////////////////////////////////////////////////\n\n Form({\n original,\n defaultValue,\n validations,\n localizeError,\n urlState,\n ...formProps\n }: {\n original?: TOriginal;\n } & Partial<FormOptions<TDraft, TOriginal>> &\n Omit<HTMLProps<HTMLFormElement>, 'defaultValue'>) {\n const value = useMemo(\n () => ({\n original,\n options: {\n defaultValue: { ...this.options.defaultValue, ...defaultValue },\n validations: { ...this.options.validations, ...validations } as Validations<\n TDraft,\n TOriginal\n >,\n localizeError: localizeError ?? this.options.localizeError,\n },\n }),\n [original, defaultValue, validations],\n );\n\n const store = useMemo(() => {\n return createStore(this.state.defaultValue);\n }, []);\n\n useEffect(() => {\n if (urlState) {\n return connectUrl(\n store.map('draft'),\n typeof urlState === 'object' ? urlState : { key: 'form' },\n );\n }\n\n return undefined;\n }, [store, hash(urlState)]);\n\n return (\n <this.context.Provider value={value}>\n <ScopeProvider scope={this.state} store={store}>\n <FormContainer {...formProps} form={this} />\n </ScopeProvider>\n </this.context.Provider>\n );\n }\n\n Subscribe<S>({\n selector,\n children,\n }: {\n selector: (form: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S;\n children: (selectedState: S) => ReactNode;\n }) {\n const selectedState = this.useFormState(selector);\n return <>{children(selectedState)}</>;\n }\n\n Field<\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any> = (\n props: ComponentPropsWithoutRef<'input'>,\n ) => JSX.Element,\n >(props: FormFieldProps<TDraft, TPath, TComponent>): JSX.Element {\n return Reflect.apply(FormField, this, [props]);\n }\n\n Error<TPath extends PathAsString<TDraft>>({ name }: FormErrorProps<TDraft, TPath>) {\n return Reflect.apply(FormError, this, [{ name }]);\n }\n}\n\nexport function createForm<TDraft, TOriginal extends TDraft = TDraft>(\n options: FormOptions<TDraft, TOriginal>,\n) {\n return new Form(options);\n}\n","import { useCache } from './useCache';\nimport type { UseStoreOptions } from './useStore';\nimport type { Cache } from '@core';\n\nexport function read<T>(cache: Cache<T>, options?: UseStoreOptions): T {\n const { status, value, error } = useCache(cache, options);\n\n if (status === 'value') {\n return value;\n }\n\n if (status === 'error') {\n throw error;\n }\n\n throw cache.state.once((state) => state.status !== 'pending');\n}\n","import { startTransition, useEffect, useMemo, useRef, useState } from 'react';\nimport { type Duration } from '@core';\nimport { debounce } from '@lib/debounce';\nimport { hash } from '@lib/hash';\nimport { throttle } from '@lib/throttle';\n\nexport interface UseDecoupledStateOptions<T> {\n debounce?: Duration;\n throttle?: Duration;\n onCommit?: (value: T) => void;\n}\n\nexport function useDecoupledState<T>(\n value: T,\n onChange: (value: T) => void,\n options: UseDecoupledStateOptions<T> = {},\n): [state: T, setState: (value: T) => void] {\n const [dirty, setDirty] = useState<{ v: T }>();\n const ref = useRef({ onChange, onCommit: options.onCommit });\n\n useEffect(() => {\n ref.current = { onChange, onCommit: options.onCommit };\n }, [onChange]);\n\n const update = useMemo(() => {\n const { onChange, onCommit } = ref.current;\n\n const update = (value: T) => {\n onChange(value);\n setDirty(undefined);\n onCommit?.(value);\n };\n\n let delayedUpdate: (value: T) => void;\n\n if (options.debounce) {\n delayedUpdate = debounce(update, options.debounce);\n } else if (options.throttle) {\n delayedUpdate = throttle(update, options.throttle);\n } else {\n delayedUpdate = (value) => startTransition(() => update(value));\n }\n\n return (value: T) => {\n setDirty({ v: value });\n delayedUpdate(value);\n };\n }, [hash([options.debounce, options.throttle])]);\n\n return [dirty ? dirty.v : value, update];\n}\n","export function castArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n","import { type ReactNode, useEffect } from 'react';\nimport { castArray } from '@lib/castArray';\nimport { hash } from '@lib/hash';\n\nexport function useUrlParamScope({\n key,\n type = 'search',\n}: {\n key: string | string[];\n type?: 'search' | 'hash';\n}) {\n useEffect(\n () => () => {\n const url = new URL(window.location.href);\n const parameters = new URLSearchParams(url[type].slice(1));\n\n for (const _key of castArray(key)) {\n parameters.delete(_key);\n }\n\n url[type] = parameters.toString();\n window.history.replaceState(null, '', url.toString());\n },\n [hash(key), type],\n );\n}\n"],"names":["castArrayPath","s","jsx","Fragment","useScope","useMemo","useState","useEffect","value","createElement","state","get","deepEqual","createContext","Scope","autobind","useContext","useStore","store","createStore","connectUrl","hash","ScopeProvider","useCache","useRef","onChange","update","debounce","throttle","startTransition"],"mappings":"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"}
|
package/dist/es/react/index.mjs
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import { a as useScope, d as useStore, S as ScopeProvider, u as useCache } from "../useCache.mjs";
|
|
2
2
|
import { r, e } from "../useCache.mjs";
|
|
3
3
|
import { useMemo, useState, useEffect, createElement, createContext, useContext, useRef, startTransition } from "react";
|
|
4
|
-
import { d as castArrayPath, a as autobind, g as get,
|
|
4
|
+
import { d as castArrayPath, a as autobind, g as get, c as createStore, e as deepEqual, f as debounce, t as throttle } from "../store.mjs";
|
|
5
5
|
import { S as Scope, h as hash } from "../scope.mjs";
|
|
6
6
|
import { jsx, Fragment } from "react/jsx-runtime";
|
|
7
7
|
import { c as connectUrl } from "../urlStore.mjs";
|
|
8
|
+
function wildcardMatch(s, w) {
|
|
9
|
+
if (typeof s === "string") {
|
|
10
|
+
s = castArrayPath(s);
|
|
11
|
+
}
|
|
12
|
+
if (typeof w === "string") {
|
|
13
|
+
w = castArrayPath(w);
|
|
14
|
+
}
|
|
15
|
+
return s.length === w.length && s.every((s2, i) => w[i] === "*" || s2 === w[i]);
|
|
16
|
+
}
|
|
17
|
+
function getWildCardMatches(object, path) {
|
|
18
|
+
const matches = {};
|
|
19
|
+
const [first, ...rest] = castArrayPath(path);
|
|
20
|
+
if (first === void 0) {
|
|
21
|
+
return object;
|
|
22
|
+
}
|
|
23
|
+
if (!(object instanceof Object)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
if (first === "*") {
|
|
27
|
+
for (const [key, value] of Object.entries(object)) {
|
|
28
|
+
matches[key] = getWildCardMatches(value, rest);
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
matches[first] = getWildCardMatches(object[first], rest);
|
|
32
|
+
}
|
|
33
|
+
return matches;
|
|
34
|
+
}
|
|
8
35
|
function FormError({ name }) {
|
|
9
36
|
const { errors, isDirty } = this.useField(name);
|
|
10
37
|
return isDirty ? /* @__PURE__ */ jsx(Fragment, { children: errors.join(", ") }) : null;
|
|
@@ -21,9 +48,16 @@ function FormField({
|
|
|
21
48
|
}) {
|
|
22
49
|
const id = "";
|
|
23
50
|
const component = ("component" in restProps ? restProps.component : "children" in restProps ? restProps.children : void 0) ?? "input";
|
|
51
|
+
const form = this.useForm();
|
|
24
52
|
const state = useScope(this.state);
|
|
25
53
|
const { value, setValue, errors } = this.useField(name);
|
|
26
|
-
const errorString = useMemo(
|
|
54
|
+
const errorString = useMemo(
|
|
55
|
+
() => errors.map((error) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
return ((_b = (_a = form.options).localizeError) == null ? void 0 : _b.call(_a, error)) ?? error;
|
|
58
|
+
}).join("\n"),
|
|
59
|
+
[errors, form.options.localizeError]
|
|
60
|
+
);
|
|
27
61
|
const [localValue, setLocalValue] = useState();
|
|
28
62
|
const _id = useMemo(
|
|
29
63
|
() => `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,
|
|
@@ -88,33 +122,6 @@ function FormField({
|
|
|
88
122
|
};
|
|
89
123
|
return createElement(component, props);
|
|
90
124
|
}
|
|
91
|
-
function wildcardMatch(s, w) {
|
|
92
|
-
if (typeof s === "string") {
|
|
93
|
-
s = castArrayPath(s);
|
|
94
|
-
}
|
|
95
|
-
if (typeof w === "string") {
|
|
96
|
-
w = castArrayPath(w);
|
|
97
|
-
}
|
|
98
|
-
return s.length === w.length && s.every((s2, i) => w[i] === "*" || s2 === w[i]);
|
|
99
|
-
}
|
|
100
|
-
function getWildCardMatches(object, path) {
|
|
101
|
-
const matches = {};
|
|
102
|
-
const [first, ...rest] = castArrayPath(path);
|
|
103
|
-
if (first === void 0) {
|
|
104
|
-
return object;
|
|
105
|
-
}
|
|
106
|
-
if (!(object instanceof Object)) {
|
|
107
|
-
return {};
|
|
108
|
-
}
|
|
109
|
-
if (first === "*") {
|
|
110
|
-
for (const [key, value] of Object.entries(object)) {
|
|
111
|
-
matches[key] = getWildCardMatches(value, rest);
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
114
|
-
matches[first] = getWildCardMatches(object[first], rest);
|
|
115
|
-
}
|
|
116
|
-
return matches;
|
|
117
|
-
}
|
|
118
125
|
function FormContainer({
|
|
119
126
|
form,
|
|
120
127
|
...formProps
|
|
@@ -133,6 +140,81 @@ function FormContainer({
|
|
|
133
140
|
}
|
|
134
141
|
);
|
|
135
142
|
}
|
|
143
|
+
function getFormInstance(original, options, state) {
|
|
144
|
+
const instance = {
|
|
145
|
+
original,
|
|
146
|
+
draft: state.map(
|
|
147
|
+
(state2) => state2.draft ?? original ?? options.defaultValue,
|
|
148
|
+
(draft) => (state2) => ({ ...state2, draft })
|
|
149
|
+
),
|
|
150
|
+
options,
|
|
151
|
+
getField: (path) => {
|
|
152
|
+
const { draft } = instance;
|
|
153
|
+
return {
|
|
154
|
+
get originalValue() {
|
|
155
|
+
return original !== void 0 ? get(original, path) : void 0;
|
|
156
|
+
},
|
|
157
|
+
get value() {
|
|
158
|
+
return get(draft.get(), path);
|
|
159
|
+
},
|
|
160
|
+
setValue(update) {
|
|
161
|
+
draft.set(path, update);
|
|
162
|
+
},
|
|
163
|
+
get isDirty() {
|
|
164
|
+
const comparisonValue = this.originalValue ?? get(options.defaultValue, path);
|
|
165
|
+
return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);
|
|
166
|
+
},
|
|
167
|
+
get errors() {
|
|
168
|
+
const blocks = Object.entries(
|
|
169
|
+
options.validations ?? {}
|
|
170
|
+
).filter(([key]) => wildcardMatch(path, key)).map(([, value2]) => value2);
|
|
171
|
+
const value = this.value;
|
|
172
|
+
const draftValue = draft.get();
|
|
173
|
+
const errors = [];
|
|
174
|
+
for (const block of blocks ?? []) {
|
|
175
|
+
for (const [validationName, validate] of Object.entries(block)) {
|
|
176
|
+
if (!validate(value, { draft: draftValue, original, field: path })) {
|
|
177
|
+
errors.push(validationName);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return errors;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
get hasChanges() {
|
|
186
|
+
const { draft } = state.get();
|
|
187
|
+
return !!draft && !deepEqual(draft, original ?? options.defaultValue);
|
|
188
|
+
},
|
|
189
|
+
get errors() {
|
|
190
|
+
const draft = instance.draft.get();
|
|
191
|
+
const errors = /* @__PURE__ */ new Set();
|
|
192
|
+
for (const [path, block] of Object.entries(options.validations ?? {})) {
|
|
193
|
+
for (const [validationName, validate] of Object.entries(
|
|
194
|
+
block
|
|
195
|
+
)) {
|
|
196
|
+
for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
|
|
197
|
+
if (!validate(value, { draft, original, field })) {
|
|
198
|
+
errors.add(`${field}.${validationName}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return [...errors];
|
|
204
|
+
},
|
|
205
|
+
get isValid() {
|
|
206
|
+
return instance.errors.length === 0;
|
|
207
|
+
},
|
|
208
|
+
validate: () => {
|
|
209
|
+
state.set("hasTriggeredValidations", true);
|
|
210
|
+
return instance.isValid;
|
|
211
|
+
},
|
|
212
|
+
reset() {
|
|
213
|
+
state.set("draft", void 0);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
return instance;
|
|
217
|
+
}
|
|
136
218
|
class Form {
|
|
137
219
|
constructor(options) {
|
|
138
220
|
this.options = options;
|
|
@@ -149,80 +231,12 @@ class Form {
|
|
|
149
231
|
useForm() {
|
|
150
232
|
const { original, options } = useContext(this.context);
|
|
151
233
|
const state = useScope(this.state);
|
|
152
|
-
return useMemo(() =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
),
|
|
159
|
-
getField: (path) => {
|
|
160
|
-
const { draft } = instance;
|
|
161
|
-
return {
|
|
162
|
-
get originalValue() {
|
|
163
|
-
return original !== void 0 ? get(original, path) : void 0;
|
|
164
|
-
},
|
|
165
|
-
get value() {
|
|
166
|
-
return get(draft.get(), path);
|
|
167
|
-
},
|
|
168
|
-
setValue(update) {
|
|
169
|
-
draft.set(path, update);
|
|
170
|
-
},
|
|
171
|
-
get isDirty() {
|
|
172
|
-
const comparisonValue = this.originalValue ?? get(options.defaultValue, path);
|
|
173
|
-
return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);
|
|
174
|
-
},
|
|
175
|
-
get errors() {
|
|
176
|
-
const blocks = Object.entries(
|
|
177
|
-
options.validations ?? {}
|
|
178
|
-
).filter(([key]) => wildcardMatch(path, key)).map(([, value2]) => value2);
|
|
179
|
-
const value = this.value;
|
|
180
|
-
const draftValue = draft.get();
|
|
181
|
-
const errors = [];
|
|
182
|
-
for (const block of blocks ?? []) {
|
|
183
|
-
for (const [validationName, validate] of Object.entries(block)) {
|
|
184
|
-
if (!validate(value, { draft: draftValue, original, field: path })) {
|
|
185
|
-
errors.push(validationName);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return errors;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
},
|
|
193
|
-
get hasChanges() {
|
|
194
|
-
const { draft } = state.get();
|
|
195
|
-
return !!draft && !deepEqual(draft, original ?? options.defaultValue);
|
|
196
|
-
},
|
|
197
|
-
get errors() {
|
|
198
|
-
const draft = instance.draft.get();
|
|
199
|
-
const errors = /* @__PURE__ */ new Set();
|
|
200
|
-
for (const [path, block] of Object.entries(options.validations ?? {})) {
|
|
201
|
-
for (const [validationName, validate] of Object.entries(
|
|
202
|
-
block
|
|
203
|
-
)) {
|
|
204
|
-
for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
|
|
205
|
-
if (!validate(value, { draft, original, field })) {
|
|
206
|
-
errors.add(`${field}.${validationName}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return [...errors];
|
|
212
|
-
},
|
|
213
|
-
get isValid() {
|
|
214
|
-
return instance.errors.length === 0;
|
|
215
|
-
},
|
|
216
|
-
validate: () => {
|
|
217
|
-
state.set("hasTriggeredValidations", true);
|
|
218
|
-
return instance.isValid;
|
|
219
|
-
},
|
|
220
|
-
reset() {
|
|
221
|
-
state.set("draft", void 0);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
return instance;
|
|
225
|
-
}, [original, options, state]);
|
|
234
|
+
return useMemo(() => getFormInstance(original, options, state), [original, options, state]);
|
|
235
|
+
}
|
|
236
|
+
useFormState(selector) {
|
|
237
|
+
const { original, options } = useContext(this.context);
|
|
238
|
+
const state = useScope(this.state);
|
|
239
|
+
return useStore(state.map(() => selector(getFormInstance(original, options, state))));
|
|
226
240
|
}
|
|
227
241
|
useField(path, useStoreOptions) {
|
|
228
242
|
const form = this.useForm();
|
|
@@ -252,6 +266,7 @@ class Form {
|
|
|
252
266
|
original,
|
|
253
267
|
defaultValue,
|
|
254
268
|
validations,
|
|
269
|
+
localizeError,
|
|
255
270
|
urlState,
|
|
256
271
|
...formProps
|
|
257
272
|
}) {
|
|
@@ -260,7 +275,8 @@ class Form {
|
|
|
260
275
|
original,
|
|
261
276
|
options: {
|
|
262
277
|
defaultValue: { ...this.options.defaultValue, ...defaultValue },
|
|
263
|
-
validations: { ...this.options.validations, ...validations }
|
|
278
|
+
validations: { ...this.options.validations, ...validations },
|
|
279
|
+
localizeError: localizeError ?? this.options.localizeError
|
|
264
280
|
}
|
|
265
281
|
}),
|
|
266
282
|
[original, defaultValue, validations]
|
|
@@ -279,6 +295,13 @@ class Form {
|
|
|
279
295
|
}, [store, hash(urlState)]);
|
|
280
296
|
return /* @__PURE__ */ jsx(this.context.Provider, { value, children: /* @__PURE__ */ jsx(ScopeProvider, { scope: this.state, store, children: /* @__PURE__ */ jsx(FormContainer, { ...formProps, form: this }) }) });
|
|
281
297
|
}
|
|
298
|
+
Subscribe({
|
|
299
|
+
selector,
|
|
300
|
+
children
|
|
301
|
+
}) {
|
|
302
|
+
const selectedState = this.useFormState(selector);
|
|
303
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(selectedState) });
|
|
304
|
+
}
|
|
282
305
|
Field(props) {
|
|
283
306
|
return Reflect.apply(FormField, this, [props]);
|
|
284
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../src/react/form/formError.tsx","../../../src/react/form/formField.tsx","../../../src/lib/wildcardMatch.ts","../../../src/react/form/form.tsx","../../../src/react/read.ts","../../../src/react/useDecoupledState.ts","../../../src/lib/castArray.ts","../../../src/react/useUrlParamScope.ts"],"sourcesContent":["import { type Form } from './form';\nimport { type PathAsString } from '@lib/path';\n\nexport type FormErrorProps<TDraft, TPath extends PathAsString<TDraft>> = {\n name: TPath;\n};\n\nexport function FormError<TDraft, TPath extends PathAsString<TDraft>>(\n this: Form<TDraft, any>,\n { name }: FormErrorProps<TDraft, TPath>,\n) {\n const { errors, isDirty } = this.useField(name);\n\n return isDirty ? <>{errors.join(', ')}</> : null;\n}\n","import {\n createElement,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ElementType,\n type HTMLProps,\n} from 'react';\nimport { type Form } from './form';\nimport { type PathAsString } from '@index';\nimport { type Value } from '@lib/path';\nimport { useScope } from '@react/scope';\n\nexport type FormFieldComponent<T> = ElementType<{\n id: string;\n value: T;\n onChange: (event: { target: { value: T } } | T | undefined, ...args: any[]) => void;\n onFocus: (...args: any[]) => void;\n onBlur: (...args: any[]) => void;\n}>;\n\ntype FieldValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<T & 'input'> extends {\n value: infer U;\n}\n ? U\n : ComponentPropsWithoutRef<T & 'input'> extends {\n value?: infer U;\n }\n ? U | undefined\n : never;\n\ntype FieldChangeValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<\n T & 'input'\n> extends {\n onChange?: (update: infer U) => void;\n}\n ? U extends { target: { value: infer V } }\n ? V\n : U\n : never;\n\nexport type FormFieldProps<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n> = {\n name: TPath;\n commitOnBlur?: boolean;\n commitDebounce?: number;\n inputFilter?: (value: FieldChangeValue<TComponent>) => boolean;\n onChange?: ComponentPropsWithoutRef<TComponent>['onChange'];\n onBlur?: ComponentPropsWithoutRef<TComponent>['onBlur'];\n} & (TComponent extends 'input' | ((props: HTMLProps<HTMLInputElement>) => JSX.Element)\n ? { component?: TComponent } | { children?: TComponent }\n : { component: TComponent } | { children: TComponent }) &\n Omit<\n ComponentPropsWithoutRef<TComponent>,\n | 'form'\n | 'name'\n | 'component'\n | 'commitOnBlur'\n | 'commitDebounce'\n | 'value'\n | 'onChange'\n | 'onBlur'\n | 'children'\n > &\n (Value<TDraft, TPath> extends FieldValue<TComponent>\n ? { serialize?: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }\n : { serialize: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }) &\n (FieldChangeValue<TComponent> extends Value<TDraft, TPath>\n ? { deserialize?: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> }\n : { deserialize: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> });\n\nexport function FormField<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n>(\n this: Form<TDraft, any>,\n {\n // id,\n name,\n commitOnBlur,\n commitDebounce,\n inputFilter,\n serialize = (x) => x as FieldValue<TComponent>,\n deserialize = (x) => x as Value<TDraft, TPath>,\n ...restProps\n }: FormFieldProps<TDraft, TPath, TComponent>,\n): JSX.Element {\n type T = FieldChangeValue<TComponent>;\n const id = '';\n const component = (('component' in restProps\n ? restProps.component\n : 'children' in restProps\n ? restProps.children\n : undefined) ?? 'input') as TComponent;\n\n const state = useScope(this.state);\n const { value, setValue, errors } = this.useField(name);\n const errorString = useMemo(() => errors.join('\\n'), [errors]);\n const [localValue, setLocalValue] = useState<T>();\n const _id = useMemo(\n () =>\n id || `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,\n\n [id],\n );\n\n useEffect(() => {\n if (localValue === undefined || !commitDebounce) {\n return;\n }\n\n const timeout = setTimeout(() => {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }, commitDebounce);\n\n return () => clearTimeout(timeout);\n }, [localValue, commitDebounce]);\n\n useEffect(() => {\n const element = document.querySelector(\n [`#${_id} input`, `#${_id} select`, `#${_id} textarea`, `#${_id}`].join(','),\n );\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n )\n ) {\n return;\n }\n\n element.setCustomValidity(errorString);\n }, [_id, errorString]);\n\n const props = {\n ...restProps,\n component: undefined,\n children: undefined,\n id: _id,\n name,\n value: localValue ?? serialize(value),\n onChange: (event: { target: { value: T } } | T, ...moreArgs: any[]) => {\n const value =\n typeof event === 'object' && event !== null && 'target' in event\n ? event.target.value\n : event;\n\n if (inputFilter && !inputFilter(value)) {\n return;\n }\n\n if (commitOnBlur || commitDebounce) {\n setLocalValue(value);\n } else {\n setValue(deserialize(value));\n }\n\n restProps.onChange?.(event, ...moreArgs);\n },\n onFocus(...args: any[]) {\n state.set('touched', (touched) => {\n touched = new Set(touched);\n touched.add(_id);\n return touched;\n });\n\n restProps.onFocus?.apply(null, args);\n },\n onBlur(...args: any[]) {\n if (localValue !== undefined) {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }\n\n restProps.onBlur?.apply(null, args);\n },\n };\n\n return createElement(component, props);\n}\n","import { type KeyType } from './path';\nimport { castArrayPath } from './propAccess';\n\nexport function wildcardMatch(s: KeyType[] | string, w: KeyType[] | string): boolean {\n if (typeof s === 'string') {\n s = castArrayPath(s);\n }\n\n if (typeof w === 'string') {\n w = castArrayPath(w);\n }\n\n return s.length === w.length && s.every((s, i) => w[i] === '*' || s === w[i]);\n}\n\nexport function getWildCardMatches(object: any, path: KeyType[] | string): Record<KeyType, any> {\n const matches: Record<KeyType, any> = {};\n const [first, ...rest] = castArrayPath(path);\n\n if (first === undefined) {\n return object;\n }\n\n if (!(object instanceof Object)) {\n return {};\n }\n\n if (first === '*') {\n for (const [key, value] of Object.entries(object)) {\n matches[key] = getWildCardMatches(value, rest);\n }\n } else {\n matches[first] = getWildCardMatches(object[first], rest);\n }\n\n return matches;\n}\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ComponentPropsWithoutRef,\n type HTMLProps,\n} from 'react';\nimport { ScopeProvider, useScope } from '../scope';\nimport { useStore, type UseStoreOptions } from '../useStore';\nimport { FormError, type FormErrorProps } from './formError';\nimport { FormField, type FormFieldComponent, type FormFieldProps } from './formField';\nimport { Scope, connectUrl, createStore, type UrlStoreOptions } from '@core';\nimport { autobind } from '@lib/autobind';\nimport { deepEqual } from '@lib/equals';\nimport { hash } from '@lib/hash';\nimport {\n type PathAsString,\n type Value,\n type WildcardPathAsString,\n type WildcardValue,\n} from '@lib/path';\nimport { get } from '@lib/propAccess';\nimport { getWildCardMatches, wildcardMatch } from '@lib/wildcardMatch';\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Form types\n/// /////////////////////////////////////////////////////////////////////////////\n\nexport interface FormOptions<TDraft, TOriginal> {\n defaultValue: TDraft;\n validations?: Validations<TDraft, TOriginal>;\n urlState?: boolean | UrlStoreOptions<TDraft>;\n}\n\nexport type Validations<TDraft, TOriginal> = {\n [P in WildcardPathAsString<TDraft>]?: Record<\n string,\n Validation<WildcardValue<TDraft, P>, TDraft, TOriginal>\n >;\n};\n\nexport type Validation<TValue, TDraft, TOriginal> = (\n value: TValue,\n context: { draft: TDraft; original: TOriginal; field: PathAsString<TDraft> },\n) => boolean;\n\nexport interface Field<TDraft, TOriginal, TPath extends PathAsString<TDraft>> {\n originalValue: Value<TOriginal, TPath> | undefined;\n value: Value<TDraft, TPath>;\n setValue: (\n value: Value<TDraft, TPath> | ((value: Value<TDraft, TPath>) => Value<TDraft, TPath>),\n ) => void;\n isDirty: boolean;\n errors: string[];\n}\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Implementation\n/// /////////////////////////////////////////////////////////////////////////////\n\nfunction FormContainer({\n form,\n ...formProps\n}: { form: Form<any, any> } & Omit<HTMLProps<HTMLFormElement>, 'form'>) {\n const { validate } = form.useForm();\n\n return (\n <form\n {...formProps}\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n\n validate();\n event.currentTarget.reportValidity();\n }}\n />\n );\n}\n\nexport class Form<TDraft, TOriginal extends TDraft = TDraft> {\n context = createContext({\n original: undefined as TOriginal | undefined,\n options: this.options,\n });\n\n state = new Scope<{\n draft?: TDraft;\n touched: Set<string>;\n errors: Map<string, string[]>;\n hasTriggeredValidations?: boolean;\n }>({\n touched: new Set(),\n errors: new Map(),\n });\n\n constructor(public readonly options: FormOptions<TDraft, TOriginal>) {\n autobind(Form);\n }\n\n useForm() {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useMemo(() => {\n const instance = {\n original,\n\n draft: state.map(\n (state) => state.draft ?? original ?? options.defaultValue,\n (draft) => (state) => ({ ...state, draft }),\n ),\n\n getField: <TPath extends PathAsString<TDraft>>(\n path: TPath,\n ): Field<TDraft, TOriginal, TPath> => {\n const { draft } = instance;\n\n return {\n get originalValue() {\n return original !== undefined ? get(original as any, path as any) : undefined;\n },\n\n get value() {\n return get(draft.get(), path);\n },\n\n setValue(update) {\n draft.set(path, update);\n },\n\n get isDirty() {\n const comparisonValue = this.originalValue ?? get(options.defaultValue, path);\n\n return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);\n },\n\n get errors() {\n const blocks: Record<string, Validation<any, any, any>>[] = Object.entries(\n options.validations ?? {},\n )\n .filter(([key]) => wildcardMatch(path, key))\n .map(([, value]) => value);\n\n const value = this.value;\n const draftValue = draft.get();\n const errors: string[] = [];\n\n for (const block of blocks ?? []) {\n for (const [validationName, validate] of Object.entries(block)) {\n if (!validate(value, { draft: draftValue, original, field: path })) {\n errors.push(validationName);\n }\n }\n }\n\n return errors;\n },\n };\n },\n\n get hasChanges() {\n const { draft } = state.get();\n return !!draft && !deepEqual(draft, original ?? options.defaultValue);\n },\n\n get errors(): string[] {\n const draft = instance.draft.get();\n const errors = new Set<string>();\n\n for (const [path, block] of Object.entries(options.validations ?? {})) {\n for (const [validationName, validate] of Object.entries(\n block as Record<string, Validation<any, any, any>>,\n )) {\n for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {\n if (!validate(value, { draft, original, field })) {\n errors.add(`${field}.${validationName}`);\n }\n }\n }\n }\n\n return [...errors] as any;\n },\n\n get isValid() {\n return instance.errors.length === 0;\n },\n\n validate: () => {\n state.set('hasTriggeredValidations', true);\n return instance.isValid;\n },\n\n reset() {\n state.set('draft', undefined);\n },\n };\n\n return instance;\n }, [original, options, state]);\n }\n\n useField<TPath extends PathAsString<TDraft>>(path: TPath, useStoreOptions?: UseStoreOptions) {\n const form = this.useForm();\n const state = useScope(this.state);\n\n useStore(\n form.draft.map((draft) => get(draft, path)),\n useStoreOptions,\n );\n\n useStore(\n state.map((state) => state.hasTriggeredValidations),\n useStoreOptions,\n );\n\n return form.getField(path);\n }\n\n useHasChanges() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.hasChanges));\n }\n\n useIsValid() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.isValid));\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // React Components\n // ///////////////////////////////////////////////////////////////////////////\n\n Form({\n original,\n defaultValue,\n validations,\n urlState,\n ...formProps\n }: {\n original?: TOriginal;\n } & Partial<FormOptions<TDraft, TOriginal>> &\n Omit<HTMLProps<HTMLFormElement>, 'defaultValue'>) {\n const value = useMemo(\n () => ({\n original,\n options: {\n defaultValue: { ...this.options.defaultValue, ...defaultValue },\n validations: { ...this.options.validations, ...validations } as Validations<\n TDraft,\n TOriginal\n >,\n },\n }),\n [original, defaultValue, validations],\n );\n\n const store = useMemo(() => {\n return createStore(this.state.defaultValue);\n }, []);\n\n useEffect(() => {\n if (urlState) {\n return connectUrl(\n store.map('draft'),\n typeof urlState === 'object' ? urlState : { key: 'form' },\n );\n }\n\n return undefined;\n }, [store, hash(urlState)]);\n\n return (\n <this.context.Provider value={value}>\n <ScopeProvider scope={this.state} store={store}>\n <FormContainer {...formProps} form={this} />\n </ScopeProvider>\n </this.context.Provider>\n );\n }\n\n Field<\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any> = (\n props: ComponentPropsWithoutRef<'input'>,\n ) => JSX.Element,\n >(props: FormFieldProps<TDraft, TPath, TComponent>): JSX.Element {\n return Reflect.apply(FormField, this, [props]);\n }\n\n Error<TPath extends PathAsString<TDraft>>({ name }: FormErrorProps<TDraft, TPath>) {\n return Reflect.apply(FormError, this, [{ name }]);\n }\n}\n\nexport function createForm<TDraft, TOriginal extends TDraft = TDraft>(\n options: FormOptions<TDraft, TOriginal>,\n) {\n return new Form(options);\n}\n","import { useCache } from './useCache';\nimport type { UseStoreOptions } from './useStore';\nimport type { Cache } from '@core';\n\nexport function read<T>(cache: Cache<T>, options?: UseStoreOptions): T {\n const { status, value, error } = useCache(cache, options);\n\n if (status === 'value') {\n return value;\n }\n\n if (status === 'error') {\n throw error;\n }\n\n throw cache.state.once((state) => state.status !== 'pending');\n}\n","import { startTransition, useEffect, useMemo, useRef, useState } from 'react';\nimport { type Duration } from '@core';\nimport { debounce } from '@lib/debounce';\nimport { hash } from '@lib/hash';\nimport { throttle } from '@lib/throttle';\n\nexport interface UseDecoupledStateOptions<T> {\n debounce?: Duration;\n throttle?: Duration;\n onCommit?: (value: T) => void;\n}\n\nexport function useDecoupledState<T>(\n value: T,\n onChange: (value: T) => void,\n options: UseDecoupledStateOptions<T> = {},\n): [state: T, setState: (value: T) => void] {\n const [dirty, setDirty] = useState<{ v: T }>();\n const ref = useRef({ onChange, onCommit: options.onCommit });\n\n useEffect(() => {\n ref.current = { onChange, onCommit: options.onCommit };\n }, [onChange]);\n\n const update = useMemo(() => {\n const { onChange, onCommit } = ref.current;\n\n const update = (value: T) => {\n onChange(value);\n setDirty(undefined);\n onCommit?.(value);\n };\n\n let delayedUpdate: (value: T) => void;\n\n if (options.debounce) {\n delayedUpdate = debounce(update, options.debounce);\n } else if (options.throttle) {\n delayedUpdate = throttle(update, options.throttle);\n } else {\n delayedUpdate = (value) => startTransition(() => update(value));\n }\n\n return (value: T) => {\n setDirty({ v: value });\n delayedUpdate(value);\n };\n }, [hash([options.debounce, options.throttle])]);\n\n return [dirty ? dirty.v : value, update];\n}\n","export function castArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n","import { type ReactNode, useEffect } from 'react';\nimport { castArray } from '@lib/castArray';\nimport { hash } from '@lib/hash';\n\nexport function useUrlParamScope({\n key,\n type = 'search',\n}: {\n key: string | string[];\n type?: 'search' | 'hash';\n}) {\n useEffect(\n () => () => {\n const url = new URL(window.location.href);\n const parameters = new URLSearchParams(url[type].slice(1));\n\n for (const _key of castArray(key)) {\n parameters.delete(_key);\n }\n\n url[type] = parameters.toString();\n window.history.replaceState(null, '', url.toString());\n },\n [hash(key), type],\n );\n}\n"],"names":["value","s","state","onChange","update"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../src/lib/wildcardMatch.ts","../../../src/react/form/formError.tsx","../../../src/react/form/formField.tsx","../../../src/react/form/form.tsx","../../../src/react/read.ts","../../../src/react/useDecoupledState.ts","../../../src/lib/castArray.ts","../../../src/react/useUrlParamScope.ts"],"sourcesContent":["import { type KeyType } from './path';\nimport { castArrayPath } from './propAccess';\n\nexport function wildcardMatch(s: KeyType[] | string, w: KeyType[] | string): boolean {\n if (typeof s === 'string') {\n s = castArrayPath(s);\n }\n\n if (typeof w === 'string') {\n w = castArrayPath(w);\n }\n\n return s.length === w.length && s.every((s, i) => w[i] === '*' || s === w[i]);\n}\n\nexport function getWildCardMatches(object: any, path: KeyType[] | string): Record<KeyType, any> {\n const matches: Record<KeyType, any> = {};\n const [first, ...rest] = castArrayPath(path);\n\n if (first === undefined) {\n return object;\n }\n\n if (!(object instanceof Object)) {\n return {};\n }\n\n if (first === '*') {\n for (const [key, value] of Object.entries(object)) {\n matches[key] = getWildCardMatches(value, rest);\n }\n } else {\n matches[first] = getWildCardMatches(object[first], rest);\n }\n\n return matches;\n}\n","import { type Form } from './form';\nimport { type PathAsString } from '@lib/path';\n\nexport type FormErrorProps<TDraft, TPath extends PathAsString<TDraft>> = {\n name: TPath;\n};\n\nexport function FormError<TDraft, TPath extends PathAsString<TDraft>>(\n this: Form<TDraft, any>,\n { name }: FormErrorProps<TDraft, TPath>,\n) {\n const { errors, isDirty } = this.useField(name);\n\n return isDirty ? <>{errors.join(', ')}</> : null;\n}\n","import {\n createElement,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ElementType,\n type HTMLProps,\n} from 'react';\nimport { type Form } from './form';\nimport { type PathAsString } from '@index';\nimport { type Value } from '@lib/path';\nimport { useScope } from '@react/scope';\n\nexport type FormFieldComponent<T> = ElementType<{\n id: string;\n value: T;\n onChange: (event: { target: { value: T } } | T | undefined, ...args: any[]) => void;\n onFocus: (...args: any[]) => void;\n onBlur: (...args: any[]) => void;\n}>;\n\ntype FieldValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<T & 'input'> extends {\n value: infer U;\n}\n ? U\n : ComponentPropsWithoutRef<T & 'input'> extends {\n value?: infer U;\n }\n ? U | undefined\n : never;\n\ntype FieldChangeValue<T extends FormFieldComponent<any>> = ComponentPropsWithoutRef<\n T & 'input'\n> extends {\n onChange?: (update: infer U) => void;\n}\n ? U extends { target: { value: infer V } }\n ? V\n : U\n : never;\n\nexport type FormFieldProps<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n> = {\n name: TPath;\n commitOnBlur?: boolean;\n commitDebounce?: number;\n inputFilter?: (value: FieldChangeValue<TComponent>) => boolean;\n onChange?: ComponentPropsWithoutRef<TComponent>['onChange'];\n onBlur?: ComponentPropsWithoutRef<TComponent>['onBlur'];\n} & (TComponent extends 'input' | ((props: HTMLProps<HTMLInputElement>) => JSX.Element)\n ? { component?: TComponent } | { children?: TComponent }\n : { component: TComponent } | { children: TComponent }) &\n Omit<\n ComponentPropsWithoutRef<TComponent>,\n | 'form'\n | 'name'\n | 'component'\n | 'commitOnBlur'\n | 'commitDebounce'\n | 'value'\n | 'onChange'\n | 'onBlur'\n | 'children'\n > &\n (Value<TDraft, TPath> extends FieldValue<TComponent>\n ? { serialize?: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }\n : { serialize: (value: Value<TDraft, TPath>) => FieldValue<TComponent> }) &\n (FieldChangeValue<TComponent> extends Value<TDraft, TPath>\n ? { deserialize?: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> }\n : { deserialize: (value: FieldChangeValue<TComponent>) => Value<TDraft, TPath> });\n\nexport function FormField<\n TDraft,\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any>,\n>(\n this: Form<TDraft, any>,\n {\n // id,\n name,\n commitOnBlur,\n commitDebounce,\n inputFilter,\n serialize = (x) => x as FieldValue<TComponent>,\n deserialize = (x) => x as Value<TDraft, TPath>,\n ...restProps\n }: FormFieldProps<TDraft, TPath, TComponent>,\n): JSX.Element {\n type T = FieldChangeValue<TComponent>;\n const id = '';\n const component = (('component' in restProps\n ? restProps.component\n : 'children' in restProps\n ? restProps.children\n : undefined) ?? 'input') as TComponent;\n\n const form = this.useForm();\n const state = useScope(this.state);\n const { value, setValue, errors } = this.useField(name);\n\n const errorString = useMemo(\n () => errors.map((error) => form.options.localizeError?.(error) ?? error).join('\\n'),\n [errors, form.options.localizeError],\n );\n const [localValue, setLocalValue] = useState<T>();\n const _id = useMemo(\n () =>\n id || `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,\n\n [id],\n );\n\n useEffect(() => {\n if (localValue === undefined || !commitDebounce) {\n return;\n }\n\n const timeout = setTimeout(() => {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }, commitDebounce);\n\n return () => clearTimeout(timeout);\n }, [localValue, commitDebounce]);\n\n useEffect(() => {\n const element = document.querySelector(\n [`#${_id} input`, `#${_id} select`, `#${_id} textarea`, `#${_id}`].join(','),\n );\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n )\n ) {\n return;\n }\n\n element.setCustomValidity(errorString);\n }, [_id, errorString]);\n\n const props = {\n ...restProps,\n component: undefined,\n children: undefined,\n id: _id,\n name,\n value: localValue ?? serialize(value),\n onChange: (event: { target: { value: T } } | T, ...moreArgs: any[]) => {\n const value =\n typeof event === 'object' && event !== null && 'target' in event\n ? event.target.value\n : event;\n\n if (inputFilter && !inputFilter(value)) {\n return;\n }\n\n if (commitOnBlur || commitDebounce) {\n setLocalValue(value);\n } else {\n setValue(deserialize(value));\n }\n\n restProps.onChange?.(event, ...moreArgs);\n },\n onFocus(...args: any[]) {\n state.set('touched', (touched) => {\n touched = new Set(touched);\n touched.add(_id);\n return touched;\n });\n\n restProps.onFocus?.apply(null, args);\n },\n onBlur(...args: any[]) {\n if (localValue !== undefined) {\n setValue(deserialize(localValue));\n setLocalValue(undefined);\n }\n\n restProps.onBlur?.apply(null, args);\n },\n };\n\n return createElement(component, props);\n}\n","import { Scope, type Store, connectUrl, createStore, type UrlStoreOptions } from '@core';\nimport { autobind } from '@lib/autobind';\nimport { deepEqual } from '@lib/equals';\nimport { hash } from '@lib/hash';\nimport {\n type PathAsString,\n type Value,\n type WildcardPathAsString,\n type WildcardValue,\n} from '@lib/path';\nimport { get } from '@lib/propAccess';\nimport { getWildCardMatches, wildcardMatch } from '@lib/wildcardMatch';\nimport {\n type ReactNode,\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ComponentPropsWithoutRef,\n type HTMLProps,\n} from 'react';\nimport { ScopeProvider, useScope } from '../scope';\nimport { useStore, type UseStoreOptions } from '../useStore';\nimport { FormError, type FormErrorProps } from './formError';\nimport { FormField, type FormFieldComponent, type FormFieldProps } from './formField';\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Form types\n/// /////////////////////////////////////////////////////////////////////////////\n\nexport interface FormOptions<TDraft, TOriginal> {\n defaultValue: TDraft;\n validations?: Validations<TDraft, TOriginal>;\n localizeError?: (error: string) => string | undefined;\n urlState?: boolean | UrlStoreOptions<TDraft>;\n}\n\nexport type Validations<TDraft, TOriginal> = {\n [P in WildcardPathAsString<TDraft>]?: Record<\n string,\n Validation<WildcardValue<TDraft, P>, TDraft, TOriginal>\n >;\n};\n\nexport type Validation<TValue, TDraft, TOriginal> = (\n value: TValue,\n context: { draft: TDraft; original: TOriginal; field: PathAsString<TDraft> },\n) => boolean;\n\nexport interface Field<TDraft, TOriginal, TPath extends PathAsString<TDraft>> {\n originalValue: Value<TOriginal, TPath> | undefined;\n value: Value<TDraft, TPath>;\n setValue: (\n value: Value<TDraft, TPath> | ((value: Value<TDraft, TPath>) => Value<TDraft, TPath>),\n ) => void;\n isDirty: boolean;\n errors: string[];\n}\n\ninterface FormState<TDraft> {\n draft?: TDraft;\n touched: Set<string>;\n errors: Map<string, string[]>;\n hasTriggeredValidations?: boolean;\n}\n\n/// /////////////////////////////////////////////////////////////////////////////\n// Implementation\n/// /////////////////////////////////////////////////////////////////////////////\n\nfunction FormContainer({\n form,\n ...formProps\n}: { form: Form<any, any> } & Omit<HTMLProps<HTMLFormElement>, 'form'>) {\n const { validate } = form.useForm();\n\n return (\n <form\n {...formProps}\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n\n validate();\n event.currentTarget.reportValidity();\n }}\n />\n );\n}\n\nfunction getFormInstance<TDraft, TOriginal extends TDraft>(\n original: TOriginal | undefined,\n options: FormOptions<TDraft, TOriginal>,\n state: Store<FormState<TDraft>>,\n) {\n const instance = {\n original,\n\n draft: state.map(\n (state) => state.draft ?? original ?? options.defaultValue,\n (draft) => (state) => ({ ...state, draft }),\n ),\n\n options,\n\n getField: <TPath extends PathAsString<TDraft>>(\n path: TPath,\n ): Field<TDraft, TOriginal, TPath> => {\n const { draft } = instance;\n\n return {\n get originalValue() {\n return original !== undefined ? get(original as any, path as any) : undefined;\n },\n\n get value() {\n return get(draft.get(), path);\n },\n\n setValue(update) {\n draft.set(path, update);\n },\n\n get isDirty() {\n const comparisonValue = this.originalValue ?? get(options.defaultValue, path);\n\n return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);\n },\n\n get errors() {\n const blocks: Record<string, Validation<any, any, any>>[] = Object.entries(\n options.validations ?? {},\n )\n .filter(([key]) => wildcardMatch(path, key))\n .map(([, value]) => value);\n\n const value = this.value;\n const draftValue = draft.get();\n const errors: string[] = [];\n\n for (const block of blocks ?? []) {\n for (const [validationName, validate] of Object.entries(block)) {\n if (!validate(value, { draft: draftValue, original, field: path })) {\n errors.push(validationName);\n }\n }\n }\n\n return errors;\n },\n };\n },\n\n get hasChanges() {\n const { draft } = state.get();\n return !!draft && !deepEqual(draft, original ?? options.defaultValue);\n },\n\n get errors(): string[] {\n const draft = instance.draft.get();\n const errors = new Set<string>();\n\n for (const [path, block] of Object.entries(options.validations ?? {})) {\n for (const [validationName, validate] of Object.entries(\n block as Record<string, Validation<any, any, any>>,\n )) {\n for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {\n if (!validate(value, { draft, original, field })) {\n errors.add(`${field}.${validationName}`);\n }\n }\n }\n }\n\n return [...errors];\n },\n\n get isValid() {\n return instance.errors.length === 0;\n },\n\n validate: () => {\n state.set('hasTriggeredValidations', true);\n return instance.isValid;\n },\n\n reset() {\n state.set('draft', undefined);\n },\n };\n\n return instance;\n}\n\nexport class Form<TDraft, TOriginal extends TDraft = TDraft> {\n context = createContext({\n original: undefined as TOriginal | undefined,\n options: this.options,\n });\n\n state = new Scope<FormState<TDraft>>({\n touched: new Set(),\n errors: new Map(),\n });\n\n constructor(public readonly options: FormOptions<TDraft, TOriginal>) {\n autobind(Form);\n }\n\n useForm() {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useMemo(() => getFormInstance(original, options, state), [original, options, state]);\n }\n\n useFormState<S>(selector: (state: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S) {\n const { original, options } = useContext(this.context);\n const state = useScope(this.state);\n\n return useStore(state.map(() => selector(getFormInstance(original, options, state))));\n }\n\n useField<TPath extends PathAsString<TDraft>>(path: TPath, useStoreOptions?: UseStoreOptions) {\n const form = this.useForm();\n const state = useScope(this.state);\n\n useStore(\n form.draft.map((draft) => get(draft, path)),\n useStoreOptions,\n );\n\n useStore(\n state.map((state) => state.hasTriggeredValidations),\n useStoreOptions,\n );\n\n return form.getField(path);\n }\n\n useHasChanges() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.hasChanges));\n }\n\n useIsValid() {\n const form = this.useForm();\n\n return useStore(form.draft.map(() => form.isValid));\n }\n\n // ///////////////////////////////////////////////////////////////////////////\n // React Components\n // ///////////////////////////////////////////////////////////////////////////\n\n Form({\n original,\n defaultValue,\n validations,\n localizeError,\n urlState,\n ...formProps\n }: {\n original?: TOriginal;\n } & Partial<FormOptions<TDraft, TOriginal>> &\n Omit<HTMLProps<HTMLFormElement>, 'defaultValue'>) {\n const value = useMemo(\n () => ({\n original,\n options: {\n defaultValue: { ...this.options.defaultValue, ...defaultValue },\n validations: { ...this.options.validations, ...validations } as Validations<\n TDraft,\n TOriginal\n >,\n localizeError: localizeError ?? this.options.localizeError,\n },\n }),\n [original, defaultValue, validations],\n );\n\n const store = useMemo(() => {\n return createStore(this.state.defaultValue);\n }, []);\n\n useEffect(() => {\n if (urlState) {\n return connectUrl(\n store.map('draft'),\n typeof urlState === 'object' ? urlState : { key: 'form' },\n );\n }\n\n return undefined;\n }, [store, hash(urlState)]);\n\n return (\n <this.context.Provider value={value}>\n <ScopeProvider scope={this.state} store={store}>\n <FormContainer {...formProps} form={this} />\n </ScopeProvider>\n </this.context.Provider>\n );\n }\n\n Subscribe<S>({\n selector,\n children,\n }: {\n selector: (form: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S;\n children: (selectedState: S) => ReactNode;\n }) {\n const selectedState = this.useFormState(selector);\n return <>{children(selectedState)}</>;\n }\n\n Field<\n TPath extends PathAsString<TDraft>,\n TComponent extends FormFieldComponent<any> = (\n props: ComponentPropsWithoutRef<'input'>,\n ) => JSX.Element,\n >(props: FormFieldProps<TDraft, TPath, TComponent>): JSX.Element {\n return Reflect.apply(FormField, this, [props]);\n }\n\n Error<TPath extends PathAsString<TDraft>>({ name }: FormErrorProps<TDraft, TPath>) {\n return Reflect.apply(FormError, this, [{ name }]);\n }\n}\n\nexport function createForm<TDraft, TOriginal extends TDraft = TDraft>(\n options: FormOptions<TDraft, TOriginal>,\n) {\n return new Form(options);\n}\n","import { useCache } from './useCache';\nimport type { UseStoreOptions } from './useStore';\nimport type { Cache } from '@core';\n\nexport function read<T>(cache: Cache<T>, options?: UseStoreOptions): T {\n const { status, value, error } = useCache(cache, options);\n\n if (status === 'value') {\n return value;\n }\n\n if (status === 'error') {\n throw error;\n }\n\n throw cache.state.once((state) => state.status !== 'pending');\n}\n","import { startTransition, useEffect, useMemo, useRef, useState } from 'react';\nimport { type Duration } from '@core';\nimport { debounce } from '@lib/debounce';\nimport { hash } from '@lib/hash';\nimport { throttle } from '@lib/throttle';\n\nexport interface UseDecoupledStateOptions<T> {\n debounce?: Duration;\n throttle?: Duration;\n onCommit?: (value: T) => void;\n}\n\nexport function useDecoupledState<T>(\n value: T,\n onChange: (value: T) => void,\n options: UseDecoupledStateOptions<T> = {},\n): [state: T, setState: (value: T) => void] {\n const [dirty, setDirty] = useState<{ v: T }>();\n const ref = useRef({ onChange, onCommit: options.onCommit });\n\n useEffect(() => {\n ref.current = { onChange, onCommit: options.onCommit };\n }, [onChange]);\n\n const update = useMemo(() => {\n const { onChange, onCommit } = ref.current;\n\n const update = (value: T) => {\n onChange(value);\n setDirty(undefined);\n onCommit?.(value);\n };\n\n let delayedUpdate: (value: T) => void;\n\n if (options.debounce) {\n delayedUpdate = debounce(update, options.debounce);\n } else if (options.throttle) {\n delayedUpdate = throttle(update, options.throttle);\n } else {\n delayedUpdate = (value) => startTransition(() => update(value));\n }\n\n return (value: T) => {\n setDirty({ v: value });\n delayedUpdate(value);\n };\n }, [hash([options.debounce, options.throttle])]);\n\n return [dirty ? dirty.v : value, update];\n}\n","export function castArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n","import { type ReactNode, useEffect } from 'react';\nimport { castArray } from '@lib/castArray';\nimport { hash } from '@lib/hash';\n\nexport function useUrlParamScope({\n key,\n type = 'search',\n}: {\n key: string | string[];\n type?: 'search' | 'hash';\n}) {\n useEffect(\n () => () => {\n const url = new URL(window.location.href);\n const parameters = new URLSearchParams(url[type].slice(1));\n\n for (const _key of castArray(key)) {\n parameters.delete(_key);\n }\n\n url[type] = parameters.toString();\n window.history.replaceState(null, '', url.toString());\n },\n [hash(key), type],\n );\n}\n"],"names":["s","value","state","onChange","update"],"mappings":"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"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { Scope, type Store, type UrlStoreOptions } from '../../core';
|
|
2
|
+
import { type PathAsString, type Value, type WildcardPathAsString, type WildcardValue } from '../../lib/path';
|
|
3
|
+
import { type ReactNode, type ComponentPropsWithoutRef, type HTMLProps } from 'react';
|
|
2
4
|
import { type UseStoreOptions } from '../useStore';
|
|
3
5
|
import { type FormErrorProps } from './formError';
|
|
4
6
|
import { type FormFieldComponent, type FormFieldProps } from './formField';
|
|
5
|
-
import { Scope, type UrlStoreOptions } from '../../core';
|
|
6
|
-
import { type PathAsString, type Value, type WildcardPathAsString, type WildcardValue } from '../../lib/path';
|
|
7
7
|
export interface FormOptions<TDraft, TOriginal> {
|
|
8
8
|
defaultValue: TDraft;
|
|
9
9
|
validations?: Validations<TDraft, TOriginal>;
|
|
10
|
+
localizeError?: (error: string) => string | undefined;
|
|
10
11
|
urlState?: boolean | UrlStoreOptions<TDraft>;
|
|
11
12
|
}
|
|
12
13
|
export type Validations<TDraft, TOriginal> = {
|
|
@@ -24,36 +25,55 @@ export interface Field<TDraft, TOriginal, TPath extends PathAsString<TDraft>> {
|
|
|
24
25
|
isDirty: boolean;
|
|
25
26
|
errors: string[];
|
|
26
27
|
}
|
|
28
|
+
interface FormState<TDraft> {
|
|
29
|
+
draft?: TDraft;
|
|
30
|
+
touched: Set<string>;
|
|
31
|
+
errors: Map<string, string[]>;
|
|
32
|
+
hasTriggeredValidations?: boolean;
|
|
33
|
+
}
|
|
34
|
+
declare function getFormInstance<TDraft, TOriginal extends TDraft>(original: TOriginal | undefined, options: FormOptions<TDraft, TOriginal>, state: Store<FormState<TDraft>>): {
|
|
35
|
+
original: TOriginal | undefined;
|
|
36
|
+
draft: Store<TDraft>;
|
|
37
|
+
options: FormOptions<TDraft, TOriginal>;
|
|
38
|
+
getField: <TPath extends PathAsString<TDraft>>(path: TPath) => Field<TDraft, TOriginal, TPath>;
|
|
39
|
+
readonly hasChanges: boolean;
|
|
40
|
+
readonly errors: string[];
|
|
41
|
+
readonly isValid: boolean;
|
|
42
|
+
validate: () => boolean;
|
|
43
|
+
reset(): void;
|
|
44
|
+
};
|
|
27
45
|
export declare class Form<TDraft, TOriginal extends TDraft = TDraft> {
|
|
28
46
|
readonly options: FormOptions<TDraft, TOriginal>;
|
|
29
47
|
context: import("react").Context<{
|
|
30
48
|
original: TOriginal | undefined;
|
|
31
49
|
options: FormOptions<TDraft, TOriginal>;
|
|
32
50
|
}>;
|
|
33
|
-
state: Scope<
|
|
34
|
-
draft?: TDraft | undefined;
|
|
35
|
-
touched: Set<string>;
|
|
36
|
-
errors: Map<string, string[]>;
|
|
37
|
-
hasTriggeredValidations?: boolean | undefined;
|
|
38
|
-
}>;
|
|
51
|
+
state: Scope<FormState<TDraft>>;
|
|
39
52
|
constructor(options: FormOptions<TDraft, TOriginal>);
|
|
40
53
|
useForm(): {
|
|
41
54
|
original: TOriginal | undefined;
|
|
42
|
-
draft:
|
|
43
|
-
|
|
55
|
+
draft: Store<TDraft>;
|
|
56
|
+
options: FormOptions<TDraft, TOriginal>;
|
|
57
|
+
getField: <TPath extends import("../../lib/typeHelpers").ArrayToStringPath<import("../../lib/path")._PathAsArray<TDraft, false, 5, []>>>(path: TPath) => Field<TDraft, TOriginal, TPath>;
|
|
44
58
|
readonly hasChanges: boolean;
|
|
45
59
|
readonly errors: string[];
|
|
46
60
|
readonly isValid: boolean;
|
|
47
61
|
validate: () => boolean;
|
|
48
62
|
reset(): void;
|
|
49
63
|
};
|
|
64
|
+
useFormState<S>(selector: (state: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S): S;
|
|
50
65
|
useField<TPath extends PathAsString<TDraft>>(path: TPath, useStoreOptions?: UseStoreOptions): Field<TDraft, TOriginal, TPath>;
|
|
51
66
|
useHasChanges(): boolean;
|
|
52
67
|
useIsValid(): boolean;
|
|
53
|
-
Form({ original, defaultValue, validations, urlState, ...formProps }: {
|
|
68
|
+
Form({ original, defaultValue, validations, localizeError, urlState, ...formProps }: {
|
|
54
69
|
original?: TOriginal;
|
|
55
70
|
} & Partial<FormOptions<TDraft, TOriginal>> & Omit<HTMLProps<HTMLFormElement>, 'defaultValue'>): import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
Subscribe<S>({ selector, children, }: {
|
|
72
|
+
selector: (form: ReturnType<typeof getFormInstance<TDraft, TOriginal>>) => S;
|
|
73
|
+
children: (selectedState: S) => ReactNode;
|
|
74
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
56
75
|
Field<TPath extends PathAsString<TDraft>, TComponent extends FormFieldComponent<any> = (props: ComponentPropsWithoutRef<'input'>) => JSX.Element>(props: FormFieldProps<TDraft, TPath, TComponent>): JSX.Element;
|
|
57
76
|
Error<TPath extends PathAsString<TDraft>>({ name }: FormErrorProps<TDraft, TPath>): any;
|
|
58
77
|
}
|
|
59
78
|
export declare function createForm<TDraft, TOriginal extends TDraft = TDraft>(options: FormOptions<TDraft, TOriginal>): Form<TDraft, TOriginal>;
|
|
79
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cross-state",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "(React) state library",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"repository": "schummar/cross-state",
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
{
|
|
192
192
|
"name": "react",
|
|
193
193
|
"path": "dist/es/react/index.mjs",
|
|
194
|
-
"limit": "
|
|
194
|
+
"limit": "9 KB"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
"name": "react: useStore",
|