cross-state 1.5.1 → 1.6.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.
@@ -59,8 +59,8 @@ function useClosestForm() {
59
59
  }
60
60
 
61
61
  //#endregion
62
- //#region src/react/form/formField.tsx
63
- function FormField({ name = "", component, commitOnBlur, commitDebounce, render, inputFilter, defaultValue, serialize, deserialize = (x) => x, onChange, onBlur,...restProps }) {
62
+ //#region src/react/form/legacyFormField.tsx
63
+ function LegacyFormField({ name = "", component, commitOnBlur, commitDebounce, inputFilter, defaultValue, serialize, deserialize = (x) => x, onChange, onBlur,...restProps }) {
64
64
  const form = this.useForm();
65
65
  const getFormState = () => ({
66
66
  ...form,
@@ -110,10 +110,6 @@ function FormField({ name = "", component, commitOnBlur, commitDebounce, render,
110
110
  ...form.getField(name),
111
111
  hasTriggeredValidations
112
112
  }, form);
113
- if (render) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: render(props, {
114
- ...form.getField(name),
115
- hasTriggeredValidations
116
- }, form) ?? null });
117
113
  if (component) return (0, react.createElement)(component, {
118
114
  ...restProps,
119
115
  ...props
@@ -121,6 +117,63 @@ function FormField({ name = "", component, commitOnBlur, commitDebounce, render,
121
117
  return null;
122
118
  }
123
119
 
120
+ //#endregion
121
+ //#region src/react/form/formField.tsx
122
+ function FormField({ name = "", commitOnBlur, commitDebounce, children, render = children, includeNestedErrors }) {
123
+ const form = this.useForm();
124
+ const field = this.useField(name);
125
+ const hasTriggeredValidations = this.useFormState((form$1) => form$1.hasTriggeredValidations);
126
+ const renderProps = useFormFieldProps.call(this, {
127
+ name,
128
+ commitOnBlur,
129
+ commitDebounce,
130
+ includeNestedErrors
131
+ });
132
+ if (render) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: render(renderProps, {
133
+ ...field,
134
+ hasTriggeredValidations
135
+ }, form) });
136
+ return null;
137
+ }
138
+ function useFormFieldProps({ name = "", commitOnBlur, commitDebounce, includeNestedErrors }) {
139
+ const form = this.useForm();
140
+ const field = this.useField(name, { includeNestedErrors });
141
+ const hasTriggeredValidations = this.useFormState((form$1) => form$1.hasTriggeredValidations);
142
+ const [localValue, setLocalValue] = (0, react.useState)();
143
+ const commitDebounceMs = commitDebounce !== void 0 ? require_store.calcDuration(commitDebounce) : void 0;
144
+ const commitLocalValue = require_storeMethods.useLatestFunction(() => {
145
+ field.setValue(localValue);
146
+ setLocalValue(void 0);
147
+ });
148
+ (0, react.useEffect)(() => {
149
+ if (localValue === void 0 || commitDebounceMs === void 0 || commitDebounceMs <= 0) return;
150
+ const timeout = setTimeout(commitLocalValue, commitDebounceMs);
151
+ return () => clearTimeout(timeout);
152
+ }, [
153
+ localValue,
154
+ commitDebounceMs,
155
+ commitLocalValue
156
+ ]);
157
+ let props = {
158
+ name,
159
+ value: localValue ?? field.value,
160
+ onChange: require_storeMethods.useLatestFunction((event) => {
161
+ const value = typeof event === "object" && event !== null && "target" in event ? event.target.value : event;
162
+ if (commitOnBlur || commitDebounceMs !== void 0 && commitDebounceMs > 0) setLocalValue(value);
163
+ else field.setValue(value);
164
+ }),
165
+ onBlur: require_storeMethods.useLatestFunction(() => {
166
+ if (localValue !== void 0) commitLocalValue();
167
+ }),
168
+ "data-invalid": field.errors.length > 0 ? true : void 0
169
+ };
170
+ if (this.options.transformFieldProps) props = this.options.transformFieldProps(props, {
171
+ ...field,
172
+ hasTriggeredValidations
173
+ }, form);
174
+ return props;
175
+ }
176
+
124
177
  //#endregion
125
178
  //#region src/react/form/formForEach.tsx
126
179
  function FormForEach({ name, renderElement, renderAdditionalElement, filter, children }) {
@@ -221,25 +274,12 @@ function useFormAutosave(form) {
221
274
 
222
275
  //#endregion
223
276
  //#region src/react/form/form.tsx
224
- function FormContainer({ form,...formProps }) {
277
+ const FormContainer = (0, react.forwardRef)(function FormContainer$1({ form,...formProps }, ref) {
225
278
  const formInstance = form.useForm();
226
279
  const hasTriggeredValidations = form.useFormState((state) => state.hasTriggeredValidations);
227
280
  const hasErrors = form.useFormState((state) => hasTriggeredValidations && state.errors.size > 0);
228
- const formRef = (0, react.useRef)(null);
229
- const updateValidity = require_storeMethods.useLatestFunction((errors, buttonElement) => {
230
- const formElement = formRef.current;
231
- if (!formElement) return;
232
- for (const element of Array.from(formElement.elements)) if ("name" in element && "setCustomValidity" in element) element.setCustomValidity(errors.get(element.name)?.join("\n") ?? "");
233
- if (buttonElement && "setCustomValidity" in buttonElement) {
234
- const errorString = [...errors.values()].flat().join("\n");
235
- buttonElement.setCustomValidity(errorString);
236
- }
237
- });
238
- (0, react.useEffect)(() => {
239
- return formInstance.formState.map(() => formInstance.getErrors()).subscribe((errors) => updateValidity(errors));
240
- }, [formInstance, updateValidity]);
241
281
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("form", {
242
- ref: formRef,
282
+ ref,
243
283
  noValidate: true,
244
284
  ...formProps,
245
285
  className: [formProps.className, hasTriggeredValidations ? formInstance.options.validatedClass ?? "validated" : void 0].filter(Boolean).join(" "),
@@ -250,11 +290,8 @@ function FormContainer({ form,...formProps }) {
250
290
  try {
251
291
  formInstance.formState.set("saveInProgress", true);
252
292
  event.preventDefault();
253
- const formElement = event.currentTarget;
254
- const buttonElement = event.nativeEvent instanceof SubmitEvent && event.nativeEvent.submitter instanceof HTMLButtonElement ? event.nativeEvent.submitter : void 0;
255
- updateValidity(formInstance.getErrors(), buttonElement);
256
- if (formInstance.options.reportValidity) formElement.reportValidity();
257
- if (formInstance.validate()) await formProps.onSubmit?.(event, {
293
+ const button = event.nativeEvent instanceof SubmitEvent && event.nativeEvent.submitter instanceof HTMLButtonElement ? event.nativeEvent.submitter : void 0;
294
+ if (formInstance.validate({ button })) await formProps.onSubmit?.(event, {
258
295
  ...formInstance,
259
296
  ...getDerivedState(formInstance)
260
297
  });
@@ -263,30 +300,32 @@ function FormContainer({ form,...formProps }) {
263
300
  }
264
301
  }
265
302
  });
266
- }
267
- function getField(form, path) {
303
+ });
304
+ function getField(form, name, { includeNestedErrors } = {}) {
268
305
  return {
269
306
  get originalValue() {
270
- return form.original !== void 0 ? require_propAccess.get(form.original, path) : void 0;
307
+ return form.original !== void 0 ? require_propAccess.get(form.original, name) : void 0;
271
308
  },
272
309
  get value() {
273
- return require_propAccess.get(form.getDraft() ?? form.original ?? form.options.defaultValue, path);
310
+ return require_propAccess.get(form.getDraft() ?? form.original ?? form.options.defaultValue, name);
274
311
  },
275
312
  setValue(update) {
276
313
  form.formState.set("draft", (draft = form.original ?? form.options.defaultValue) => {
277
- if (update instanceof Function) update = update(require_propAccess.get(draft, path));
278
- return require_propAccess.set(draft, path, update);
314
+ if (update instanceof Function) update = update(require_propAccess.get(draft, name));
315
+ return require_propAccess.set(draft, name, update);
279
316
  });
280
317
  },
281
318
  get hasChange() {
282
319
  return !require_propAccess.deepEqual(this.originalValue, this.value, { undefinedEqualsAbsent: true });
283
320
  },
284
321
  get errors() {
285
- return form.getErrors().get(path) ?? [];
322
+ const errors = form.getErrors();
323
+ if (includeNestedErrors) return Array.from(errors.entries()).filter(([key]) => key === name || key.startsWith(`${name}.`)).flatMap(([, value]) => value);
324
+ else return errors.get(name) ?? [];
286
325
  },
287
326
  get names() {
288
327
  const { value } = this;
289
- if (require_propAccess.isObject(value)) return Object.keys(value).map((key) => require_propAccess.join(path, key));
328
+ if (require_propAccess.isObject(value)) return Object.keys(value).map((key) => require_propAccess.join(name, key));
290
329
  return [];
291
330
  },
292
331
  add(...args) {
@@ -382,12 +421,18 @@ var Form = class Form {
382
421
  ...getDerivedState(form)
383
422
  }), useStoreOptions);
384
423
  }
385
- useField(path, useStoreOptions) {
424
+ useField(name, { includeNestedErrors,...useStoreOptions } = {}) {
386
425
  const form = this.useForm();
387
- this.useFormState((form$1) => [form$1.getField(path).value, form$1.original], useStoreOptions);
388
- return form.getField(path);
426
+ this.useFormState((form$1) => [form$1.getField(name).value, form$1.original], useStoreOptions);
427
+ return form.getField(name, { includeNestedErrors });
428
+ }
429
+ useFieldProps(name, options) {
430
+ return useFormFieldProps.call(this, {
431
+ name,
432
+ ...options
433
+ });
389
434
  }
390
- Form({ defaultValue, validations, localizeError, autoSave, transform, validatedClass, original, onSubmit, reportValidity, transformFieldProps,...formProps }) {
435
+ Form({ defaultValue, validations, initiallyTriggerValidations, localizeError, autoSave, transform, validatedClass, original, onSubmit, reportValidity, transformFieldProps,...formProps }) {
391
436
  const options = {
392
437
  defaultValue: {
393
438
  ...this.options.defaultValue,
@@ -403,16 +448,17 @@ var Form = class Form {
403
448
  validatedClass: validatedClass ?? this.options.validatedClass,
404
449
  original: original ?? this.options.original,
405
450
  onSubmit: onSubmit ?? this.options.onSubmit,
406
- reportValidity: reportValidity ?? this.options.reportValidity ?? true,
451
+ reportValidity: reportValidity ?? this.options.reportValidity ?? "browser",
407
452
  transformFieldProps: transformFieldProps ?? this.options.transformFieldProps
408
453
  };
409
454
  const formState = (0, react.useMemo)(() => {
410
455
  return require_store.createStore({
411
456
  draft: void 0,
412
- hasTriggeredValidations: false,
457
+ hasTriggeredValidations: initiallyTriggerValidations ?? false,
413
458
  saveInProgress: false
414
459
  });
415
460
  }, []);
461
+ const formRef = (0, react.useRef)(null);
416
462
  let lastDraft;
417
463
  const cache = /* @__PURE__ */ new Map();
418
464
  function lazy(key, fn) {
@@ -446,6 +492,9 @@ var Form = class Form {
446
492
  flushAutosave() {
447
493
  return autosave.flush();
448
494
  },
495
+ cancelAutosave() {
496
+ return autosave.cancel();
497
+ },
449
498
  hasChanges() {
450
499
  return lazy("hasChanges", () => !require_propAccess.deepEqual(this.getDraft(), options.original ?? options.defaultValue, { undefinedEqualsAbsent: true }));
451
500
  },
@@ -455,8 +504,27 @@ var Form = class Form {
455
504
  isValid() {
456
505
  return lazy("isValid", () => this.getErrors().size === 0);
457
506
  },
458
- validate() {
507
+ validate({ reportValidity: reportValidity$1 = options.reportValidity, button } = {}) {
459
508
  formState.set("hasTriggeredValidations", true);
509
+ updateValidity(this.getErrors(), button);
510
+ switch (reportValidity$1) {
511
+ case "browser":
512
+ formRef.current?.reportValidity();
513
+ break;
514
+ case true:
515
+ case "scrollTo":
516
+ {
517
+ const invalidElement = document.querySelector(":invalid, [data-invalid=\"true\"]");
518
+ if (invalidElement && invalidElement instanceof HTMLElement) {
519
+ invalidElement.scrollIntoView({
520
+ behavior: "smooth",
521
+ block: "center"
522
+ });
523
+ invalidElement.focus({ preventScroll: true });
524
+ }
525
+ }
526
+ break;
527
+ }
460
528
  return this.isValid();
461
529
  },
462
530
  reset() {
@@ -464,7 +532,7 @@ var Form = class Form {
464
532
  formState.set("hasTriggeredValidations", false);
465
533
  }
466
534
  };
467
- context.getField = (path) => lazy(path, () => getField(context, path));
535
+ context.getField = (path, options$1) => lazy(`${path}:${options$1?.includeNestedErrors}`, () => getField(context, path, options$1));
468
536
  (0, react.useEffect)(() => {
469
537
  const transform$1 = options.transform;
470
538
  if (!transform$1) return;
@@ -474,6 +542,18 @@ var Form = class Form {
474
542
  if (!require_propAccess.deepEqual(result, value)) context.formState.set("draft", result);
475
543
  });
476
544
  });
545
+ function updateValidity(errors, buttonElement) {
546
+ const formElement = formRef.current;
547
+ if (!formElement) return;
548
+ for (const element of Array.from(formElement.elements)) if ("name" in element && "setCustomValidity" in element) element.setCustomValidity(errors.get(element.name)?.join("\n") ?? "");
549
+ if (buttonElement && "setCustomValidity" in buttonElement) {
550
+ const errorString = [...errors.values()].flat().join("\n");
551
+ buttonElement.setCustomValidity(errorString);
552
+ }
553
+ }
554
+ (0, react.useEffect)(() => {
555
+ return formState.map(() => context.getErrors()).subscribe((errors) => updateValidity(errors));
556
+ });
477
557
  const autosave = useFormAutosave(context);
478
558
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GeneralFormContext.Provider, {
479
559
  value: this,
@@ -491,10 +571,8 @@ var Form = class Form {
491
571
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: children(this.useFormState(selector)) });
492
572
  }
493
573
  Field(props) {
494
- return Reflect.apply(FormField, this, [{
495
- component: "input",
496
- ...props
497
- }]);
574
+ if (props.component) return Reflect.apply(LegacyFormField, this, [props]);
575
+ return Reflect.apply(FormField, this, [props]);
498
576
  }
499
577
  ForEach(props) {
500
578
  return Reflect.apply(FormForEach, this, [props]);