cross-state 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/hash.cjs +18 -0
  2. package/dist/cjs/hash.cjs.map +1 -0
  3. package/dist/cjs/index.cjs +13 -12
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/react/index.cjs +249 -188
  6. package/dist/cjs/react/index.cjs.map +1 -1
  7. package/dist/cjs/react/register.cjs +12 -12
  8. package/dist/cjs/react/register.cjs.map +1 -1
  9. package/dist/cjs/scope.cjs +572 -19
  10. package/dist/cjs/scope.cjs.map +1 -1
  11. package/dist/cjs/{cache.cjs → scope2.cjs} +14 -3
  12. package/dist/cjs/scope2.cjs.map +1 -0
  13. package/dist/cjs/store.cjs +45 -10
  14. package/dist/cjs/store.cjs.map +1 -1
  15. package/dist/es/hash.mjs +19 -0
  16. package/dist/es/hash.mjs.map +1 -0
  17. package/dist/es/index.mjs +9 -8
  18. package/dist/es/index.mjs.map +1 -1
  19. package/dist/es/react/index.mjs +245 -184
  20. package/dist/es/react/index.mjs.map +1 -1
  21. package/dist/es/react/register.mjs +4 -4
  22. package/dist/es/scope.mjs +573 -20
  23. package/dist/es/scope.mjs.map +1 -1
  24. package/dist/es/{cache.mjs → scope2.mjs} +14 -3
  25. package/dist/es/scope2.mjs.map +1 -0
  26. package/dist/es/store.mjs +45 -10
  27. package/dist/es/store.mjs.map +1 -1
  28. package/dist/types/core/commonTypes.d.ts +0 -1
  29. package/dist/types/core/store.d.ts +1 -1
  30. package/dist/types/lib/debounce.d.ts +5 -1
  31. package/dist/types/react/form/form.d.ts +47 -48
  32. package/dist/types/react/form/formField.d.ts +0 -1
  33. package/dist/types/react/form/useFormAutosave.d.ts +9 -0
  34. package/package.json +17 -17
  35. package/dist/cjs/cache.cjs.map +0 -1
  36. package/dist/cjs/useCache.cjs +0 -583
  37. package/dist/cjs/useCache.cjs.map +0 -1
  38. package/dist/es/cache.mjs.map +0 -1
  39. package/dist/es/useCache.mjs +0 -584
  40. package/dist/es/useCache.mjs.map +0 -1
@@ -1,19 +1,10 @@
1
- import { a as useScope, d as useStore, S as ScopeProvider, u as useCache } from "../useCache.mjs";
2
- import { r, e } from "../useCache.mjs";
3
- import { useCallback, Fragment as Fragment$1, useMemo, useState, useEffect, createElement, createContext, useContext, useRef, startTransition } from "react";
4
- import { e as castArrayPath, a as autobind, g as get, c as createStore, d as deepEqual, l as debounce, t as throttle } from "../store.mjs";
5
- import { S as Scope, h as hash } from "../scope.mjs";
1
+ import { d as useStore, u as useCache } from "../scope.mjs";
2
+ import { S, r, e, a } from "../scope.mjs";
3
+ import { useCallback, Fragment as Fragment$1, useState, useMemo, useEffect, createElement, useRef, createContext, useContext, startTransition } from "react";
4
+ import { e as castArrayPath, b as calcDuration, q as queue, l as debounce, d as deepEqual, a as autobind, c as createStore, g as get, t as throttle } from "../store.mjs";
5
+ import { h as hash } from "../hash.mjs";
6
6
  import { jsxs, Fragment, jsx } 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
8
  function getWildCardMatches(object, path) {
18
9
  const matches = {};
19
10
  const [first, second, ...rest] = castArrayPath(path);
@@ -39,8 +30,8 @@ function getWildCardMatches(object, path) {
39
30
  }
40
31
  function FormArray({ name, renderElement, children }) {
41
32
  const form = this.useForm();
42
- const names = this.useFormState((form2) => {
43
- const field = form2.getField(name);
33
+ const names = this.useFormState(() => {
34
+ const field = form.getField(name);
44
35
  return field.names;
45
36
  });
46
37
  const append = useCallback(
@@ -79,8 +70,9 @@ function FormArray({ name, renderElement, children }) {
79
70
  ] });
80
71
  }
81
72
  function FormError({ name }) {
82
- const { errors, isDirty } = this.useField(name);
83
- return isDirty ? /* @__PURE__ */ jsx(Fragment, { children: errors.join(", ") }) : null;
73
+ const hasTriggeredValidations = this.useFormState((form) => form.hasTriggeredValidations);
74
+ const { errors } = this.useField(name);
75
+ return hasTriggeredValidations ? /* @__PURE__ */ jsx(Fragment, { children: errors.join(", ") }) : null;
84
76
  }
85
77
  function FormField({
86
78
  // id,
@@ -95,16 +87,12 @@ function FormField({
95
87
  ...restProps
96
88
  }) {
97
89
  const id = "";
98
- const form = this.useForm();
99
- const state = useScope(this.state);
90
+ const { options } = this.useForm();
100
91
  const { value, setValue, errors } = this.useField(name);
101
- const errorString = useMemo(
102
- () => errors.map((error) => {
103
- var _a, _b;
104
- return ((_b = (_a = form.options).localizeError) == null ? void 0 : _b.call(_a, error, name)) ?? error;
105
- }).join("\n"),
106
- [errors, form.options.localizeError]
107
- );
92
+ const errorString = errors.map((error) => {
93
+ var _a;
94
+ return ((_a = options.localizeError) == null ? void 0 : _a.call(options, error, name)) ?? error;
95
+ }).join("\n");
108
96
  const [localValue, setLocalValue] = useState();
109
97
  const _id = useMemo(
110
98
  () => `f${Math.random().toString(36).slice(2, 15)}${Math.random().toString(36).slice(2, 15)}`,
@@ -147,15 +135,6 @@ function FormField({
147
135
  }
148
136
  (_a = restProps.onChange) == null ? void 0 : _a.call(restProps, event, ...moreArgs);
149
137
  },
150
- onFocus(...args) {
151
- var _a;
152
- state.set("touched", (touched) => {
153
- touched = new Set(touched);
154
- touched.add(_id);
155
- return touched;
156
- });
157
- (_a = restProps.onFocus) == null ? void 0 : _a.apply(null, args);
158
- },
159
138
  onBlur(...args) {
160
139
  var _a;
161
140
  if (localValue !== void 0) {
@@ -170,11 +149,65 @@ function FormField({
170
149
  }
171
150
  return createElement(component, props);
172
151
  }
152
+ function useFormAutosave(form) {
153
+ var _a;
154
+ const { formState, options, getDraft } = form;
155
+ const debounceTime = calcDuration(((_a = options.autoSave) == null ? void 0 : _a.debounce) ?? 2e3);
156
+ const latestRef = useRef({ options });
157
+ const lastValue = useRef();
158
+ const q = useMemo(() => queue(), []);
159
+ const run = useMemo(
160
+ () => debounce(async () => {
161
+ var _a2;
162
+ const { options: options2 } = latestRef.current;
163
+ const save = (_a2 = options2.autoSave) == null ? void 0 : _a2.save;
164
+ const draft = getDraft();
165
+ lastValue.current = draft;
166
+ q.clear();
167
+ q(async () => {
168
+ var _a3;
169
+ try {
170
+ formState.set("saveInProgress", true);
171
+ await (save == null ? void 0 : save(draft, form));
172
+ if (q.size === 0 && ((_a3 = options2.autoSave) == null ? void 0 : _a3.resetAfterSave)) {
173
+ form.reset();
174
+ }
175
+ } finally {
176
+ formState.set("saveInProgress", false);
177
+ if (q.size === 0) {
178
+ formState.set("saveScheduled", false);
179
+ }
180
+ }
181
+ });
182
+ }, debounceTime),
183
+ [formState, debounceTime]
184
+ );
185
+ useEffect(() => {
186
+ var _a2;
187
+ if (!((_a2 = options.autoSave) == null ? void 0 : _a2.save)) {
188
+ return;
189
+ }
190
+ return formState.map((state) => state.draft).subscribe(
191
+ () => {
192
+ if (deepEqual(getDraft(), lastValue.current)) {
193
+ return;
194
+ }
195
+ run();
196
+ formState.set("saveScheduled", true);
197
+ },
198
+ { runNow: false }
199
+ );
200
+ }, [formState]);
201
+ useEffect(() => {
202
+ latestRef.current = { options };
203
+ });
204
+ }
173
205
  function FormContainer({
174
206
  form,
175
207
  ...formProps
176
208
  }) {
177
- const _form = form.useForm();
209
+ const { validate, options, getErrors: getErrors2 } = form.useForm();
210
+ const errors = getErrors2();
178
211
  const hasTriggeredValidations = form.useFormState((state) => state.hasTriggeredValidations);
179
212
  return /* @__PURE__ */ jsx(
180
213
  "form",
@@ -185,16 +218,16 @@ function FormContainer({
185
218
  onSubmit: (event) => {
186
219
  var _a;
187
220
  event.preventDefault();
188
- const isValid = _form.validate();
221
+ const isValid = validate();
189
222
  let button;
190
223
  if (event.nativeEvent instanceof SubmitEvent && (button = event.nativeEvent.submitter) && (button instanceof HTMLButtonElement || button instanceof HTMLInputElement) && button.setCustomValidity) {
191
- const errors = _form.errors.map(
192
- ({ field, error }) => {
193
- var _a2, _b;
194
- return ((_b = (_a2 = _form.options).localizeError) == null ? void 0 : _b.call(_a2, error, field)) ?? error;
195
- }
196
- );
197
- button.setCustomValidity(errors.join("\n"));
224
+ const errorString = [...errors.entries()].flatMap(
225
+ ([field, errors2]) => errors2.map((error) => {
226
+ var _a2;
227
+ return ((_a2 = options.localizeError) == null ? void 0 : _a2.call(options, error, field)) ?? error;
228
+ })
229
+ ).join("\n");
230
+ button.setCustomValidity(errorString);
198
231
  }
199
232
  event.currentTarget.reportValidity();
200
233
  if (isValid) {
@@ -204,147 +237,92 @@ function FormContainer({
204
237
  }
205
238
  );
206
239
  }
207
- function getFormInstance(original, options, state) {
208
- const instance = {
209
- original,
210
- draft: state.map(
211
- (state2) => state2.draft ?? original ?? options.defaultValue,
212
- (draft) => (state2) => ({ ...state2, draft })
213
- ),
214
- options,
215
- getField: (path) => {
216
- const { draft } = instance;
217
- return {
218
- get originalValue() {
219
- return original !== void 0 ? get(original, path) : void 0;
220
- },
221
- get value() {
222
- return get(draft.get(), path);
223
- },
224
- setValue(update) {
225
- draft.set(path, update);
226
- },
227
- get isDirty() {
228
- const comparisonValue = this.originalValue ?? get(options.defaultValue, path);
229
- return state.get().hasTriggeredValidations || !deepEqual(comparisonValue, this.value);
230
- },
231
- get errors() {
232
- const blocks = Object.entries(options.validations ?? {}).filter(([key]) => wildcardMatch(path, key)).map(([, value2]) => value2);
233
- const value = this.value;
234
- const draftValue = draft.get();
235
- const errors = [];
236
- for (const block of blocks ?? []) {
237
- for (const [validationName, validate] of Object.entries(block)) {
238
- if (!validate(value, { draft: draftValue, original, field: path })) {
239
- errors.push(validationName);
240
- }
241
- }
242
- }
243
- return errors;
244
- },
245
- get names() {
246
- const { value } = this;
247
- return Array.isArray(value) ? value.map((_, index) => `${path}.${index}`) : [];
248
- },
249
- append(...elements) {
250
- this.setValue(
251
- (value) => Array.isArray(value) ? [...value, ...elements] : elements
252
- );
253
- },
254
- remove(index) {
255
- this.setValue(
256
- (value) => Array.isArray(value) ? [...value.slice(0, index), ...value.slice(index + 1)] : value
257
- );
258
- }
259
- };
240
+ function getField(derivedState, original, path) {
241
+ return {
242
+ get originalValue() {
243
+ return original !== void 0 ? get(original, path) : void 0;
260
244
  },
261
- get hasChanges() {
262
- const { draft } = state.get();
263
- return !!draft && !deepEqual(draft, original ?? options.defaultValue);
245
+ get value() {
246
+ const { draft } = derivedState.get();
247
+ return get(draft, path);
264
248
  },
265
- get errors() {
266
- const draft = instance.draft.get();
267
- const errors = /* @__PURE__ */ new Set();
268
- for (const [path, block] of Object.entries(options.validations ?? {})) {
269
- for (const [validationName, validate] of Object.entries(
270
- block
271
- )) {
272
- let matched = false;
273
- for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
274
- matched = true;
275
- if (!validate(value, { draft, original, field })) {
276
- errors.add({ field, error: validationName });
277
- }
278
- }
279
- if (!matched && !path.includes("*")) {
280
- if (!validate(void 0, { draft, original, field: path })) {
281
- errors.add({ field: path, error: validationName });
282
- }
283
- }
284
- }
285
- }
286
- return [...errors];
249
+ setValue(update) {
250
+ derivedState.set(`draft.${path}`, update);
287
251
  },
288
- get isValid() {
289
- return instance.errors.length === 0;
252
+ get hasChange() {
253
+ return !deepEqual(this.originalValue, this.value);
290
254
  },
291
- validate: () => {
292
- state.set("hasTriggeredValidations", true);
293
- return instance.isValid;
255
+ get errors() {
256
+ const { errors } = derivedState.get();
257
+ return errors.get(path) ?? [];
258
+ },
259
+ get names() {
260
+ const { value } = this;
261
+ return Array.isArray(value) ? value.map((_, index) => `${path}.${index}`) : [];
294
262
  },
295
- get hasTriggeredValidations() {
296
- return state.get().hasTriggeredValidations;
263
+ append(...elements) {
264
+ this.setValue((value) => Array.isArray(value) ? [...value, ...elements] : elements);
297
265
  },
298
- reset() {
299
- state.set("draft", void 0);
300
- state.set("hasTriggeredValidations", false);
266
+ remove(index) {
267
+ this.setValue(
268
+ (value) => Array.isArray(value) ? [...value.slice(0, index), ...value.slice(index + 1)] : value
269
+ );
301
270
  }
302
271
  };
303
- return instance;
272
+ }
273
+ function getErrors(draft, original, validations) {
274
+ const errors = /* @__PURE__ */ new Map();
275
+ for (const [path, block] of Object.entries(validations ?? {})) {
276
+ for (const [validationName, validate] of Object.entries(
277
+ block
278
+ )) {
279
+ let matched = false;
280
+ for (const [field, value] of Object.entries(getWildCardMatches(draft, path))) {
281
+ matched = true;
282
+ if (!validate(value, { draft, original, field })) {
283
+ const fieldErrors = errors.get(field) ?? [];
284
+ fieldErrors.push(validationName);
285
+ errors.set(field, fieldErrors);
286
+ }
287
+ }
288
+ if (!matched && !path.includes("*")) {
289
+ if (!validate(void 0, { draft, original, field: path })) {
290
+ const fieldErrors = errors.get(path) ?? [];
291
+ fieldErrors.push(validationName);
292
+ errors.set(path, fieldErrors);
293
+ }
294
+ }
295
+ }
296
+ }
297
+ return errors;
304
298
  }
305
299
  class Form {
306
300
  constructor(options) {
307
301
  this.options = options;
308
- this.context = createContext({
309
- original: void 0,
310
- options: this.options
311
- });
312
- this.state = new Scope({
313
- touched: /* @__PURE__ */ new Set(),
314
- errors: /* @__PURE__ */ new Map()
315
- });
302
+ this.context = createContext(null);
316
303
  autobind(Form);
317
304
  }
318
305
  useForm() {
319
- const { original, options } = useContext(this.context);
320
- const state = useScope(this.state);
321
- return useMemo(() => getFormInstance(original, options, state), [original, options, state]);
322
- }
323
- useFormState(selector) {
324
- const { original, options } = useContext(this.context);
325
- const state = useScope(this.state);
326
- return useStore(state.map(() => selector(getFormInstance(original, options, state))));
306
+ const context = useContext(this.context);
307
+ if (!context) {
308
+ throw new Error("Form context not found");
309
+ }
310
+ return context;
327
311
  }
328
- useField(path, useStoreOptions) {
312
+ useFormState(selector, useStoreOptions) {
329
313
  const form = this.useForm();
330
- const state = useScope(this.state);
331
- useStore(
332
- form.draft.map((draft) => get(draft, path)),
314
+ return useStore(
315
+ form.derivedState.map(
316
+ (state) => selector({
317
+ ...form,
318
+ ...state
319
+ })
320
+ ),
333
321
  useStoreOptions
334
322
  );
335
- useStore(
336
- state.map((state2) => state2.hasTriggeredValidations),
337
- useStoreOptions
338
- );
339
- return form.getField(path);
340
323
  }
341
- useHasChanges() {
342
- const form = this.useForm();
343
- return useStore(form.draft.map(() => form.hasChanges));
344
- }
345
- useIsValid() {
346
- const form = this.useForm();
347
- return useStore(form.draft.map(() => form.isValid));
324
+ useField(path, useStoreOptions) {
325
+ return this.useFormState((form) => form.getField(path), useStoreOptions);
348
326
  }
349
327
  // ///////////////////////////////////////////////////////////////////////////
350
328
  // React Components
@@ -355,34 +333,117 @@ class Form {
355
333
  validations,
356
334
  localizeError,
357
335
  urlState,
336
+ autoSave,
337
+ transform,
358
338
  ...formProps
359
339
  }) {
360
- const value = useMemo(
361
- () => ({
340
+ const options = {
341
+ defaultValue: { ...this.options.defaultValue, ...defaultValue },
342
+ validations: { ...this.options.validations, ...validations },
343
+ localizeError: localizeError ?? this.options.localizeError,
344
+ autoSave: autoSave ?? this.options.autoSave,
345
+ transform: transform ?? this.options.transform
346
+ };
347
+ const formState = useMemo(() => {
348
+ return createStore({
349
+ draft: void 0,
350
+ hasTriggeredValidations: false,
351
+ saveScheduled: false,
352
+ saveInProgress: false
353
+ });
354
+ }, []);
355
+ const derivedState = useMemo(() => {
356
+ return formState.map(
357
+ (state) => {
358
+ const {
359
+ draft = original ?? options.defaultValue,
360
+ hasTriggeredValidations,
361
+ saveScheduled,
362
+ saveInProgress
363
+ } = state;
364
+ const errors = getErrors(draft, original, options.validations);
365
+ return {
366
+ draft,
367
+ hasTriggeredValidations,
368
+ saveScheduled,
369
+ saveInProgress,
370
+ hasChanges: !!draft && !deepEqual(draft, original),
371
+ errors,
372
+ isValid: errors.size === 0
373
+ };
374
+ },
375
+ (newState) => ({
376
+ draft: newState.draft,
377
+ hasTriggeredValidations: newState.hasTriggeredValidations,
378
+ saveScheduled: newState.saveScheduled,
379
+ saveInProgress: newState.saveInProgress
380
+ })
381
+ );
382
+ }, [formState, original, options.validations, options.defaultValue]);
383
+ const context = useMemo(() => {
384
+ return {
385
+ formState,
386
+ derivedState,
387
+ options,
362
388
  original,
363
- options: {
364
- defaultValue: { ...this.options.defaultValue, ...defaultValue },
365
- validations: { ...this.options.validations, ...validations },
366
- localizeError: localizeError ?? this.options.localizeError
389
+ getField(path) {
390
+ return getField(derivedState, original, path);
391
+ },
392
+ getDraft() {
393
+ return formState.get().draft ?? original ?? options.defaultValue;
394
+ },
395
+ hasTriggeredValidations() {
396
+ return formState.get().hasTriggeredValidations;
397
+ },
398
+ hasChanges() {
399
+ return derivedState.get().hasChanges;
400
+ },
401
+ getErrors() {
402
+ return derivedState.get().errors;
403
+ },
404
+ isValid() {
405
+ return derivedState.get().isValid;
406
+ },
407
+ validate() {
408
+ formState.set("hasTriggeredValidations", true);
409
+ return derivedState.get().isValid;
410
+ },
411
+ reset() {
412
+ formState.set("draft", void 0);
413
+ formState.set("hasTriggeredValidations", false);
367
414
  }
368
- }),
369
- [original, defaultValue, validations]
370
- );
371
- const store = useMemo(() => {
372
- return createStore(this.state.defaultValue);
373
- }, []);
415
+ };
416
+ }, [formState, derivedState, original, defaultValue, validations, localizeError, urlState]);
374
417
  useEffect(() => {
375
418
  if (urlState) {
376
419
  return connectUrl(
377
- store.map("draft"),
420
+ formState.map("draft"),
378
421
  typeof urlState === "object" ? urlState : { key: "form" }
379
422
  );
380
423
  }
381
424
  return void 0;
382
- }, [store, hash(urlState)]);
383
- return /* @__PURE__ */ jsx(this.context.Provider, { value, children: /* @__PURE__ */ jsx(ScopeProvider, { scope: this.state, store, children: /* @__PURE__ */ jsx(FormContainer, { ...formProps, form: this }) }) });
425
+ }, [formState, hash(urlState)]);
426
+ useEffect(() => {
427
+ var _a;
428
+ const handles = (_a = options.transform) == null ? void 0 : _a.map(({ trigger, update }) => {
429
+ const draft = derivedState.map("draft");
430
+ const triggerStore = trigger ? draft.map(trigger) : draft;
431
+ return triggerStore.subscribe(() => {
432
+ const value = trigger ? get(draft.get(), trigger) : draft.get();
433
+ const result = update(value, draft);
434
+ if (result !== void 0) {
435
+ draft.set(result);
436
+ }
437
+ });
438
+ });
439
+ return () => {
440
+ handles == null ? void 0 : handles.forEach((handle) => handle());
441
+ };
442
+ }, [options.transform]);
443
+ useFormAutosave(context);
444
+ return /* @__PURE__ */ jsx(this.context.Provider, { value: context, children: /* @__PURE__ */ jsx(FormContainer, { ...formProps, form: this }) });
384
445
  }
385
- Subscribe({
446
+ FormState({
386
447
  selector,
387
448
  children
388
449
  }) {
@@ -462,14 +523,14 @@ function useUrlParamScope({
462
523
  }
463
524
  export {
464
525
  Form,
465
- ScopeProvider,
526
+ S as ScopeProvider,
466
527
  createForm,
467
528
  r as reactMethods,
468
529
  read,
469
530
  useCache,
470
531
  useDecoupledState,
471
532
  e as useProp,
472
- useScope,
533
+ a as useScope,
473
534
  useStore,
474
535
  useUrlParamScope
475
536
  };