@formality-ui/react 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -581,7 +581,15 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
581
581
  record?: Partial<TFieldValues>;
582
582
  /** Enable auto-save on field changes */
583
583
  autoSave?: boolean;
584
- /** Debounce milliseconds for auto-save. false = immediate submission, number = delay in milliseconds (default: 1000) */
584
+ /**
585
+ * Form-level auto-save debounce, used as the fallback for any field whose
586
+ * `InputConfig.debounce` is unset.
587
+ * - `false` — submit immediately (no debounce timer).
588
+ * - `number` — delay auto-save by this many milliseconds (default: 1000).
589
+ *
590
+ * A field can override its own cadence via `InputConfig.debounce`
591
+ * (`false` for immediate, or a number for a per-field delay).
592
+ */
585
593
  debounce?: number | false;
586
594
  /** Form-level validation */
587
595
  validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
package/dist/index.d.ts CHANGED
@@ -581,7 +581,15 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
581
581
  record?: Partial<TFieldValues>;
582
582
  /** Enable auto-save on field changes */
583
583
  autoSave?: boolean;
584
- /** Debounce milliseconds for auto-save. false = immediate submission, number = delay in milliseconds (default: 1000) */
584
+ /**
585
+ * Form-level auto-save debounce, used as the fallback for any field whose
586
+ * `InputConfig.debounce` is unset.
587
+ * - `false` — submit immediately (no debounce timer).
588
+ * - `number` — delay auto-save by this many milliseconds (default: 1000).
589
+ *
590
+ * A field can override its own cadence via `InputConfig.debounce`
591
+ * (`false` for immediate, or a number for a per-field delay).
592
+ */
585
593
  debounce?: number | false;
586
594
  /** Form-level validation */
587
595
  validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useMemo, useRef, useState, useCallback, useEffect } from 'react';
1
+ import { createContext, useContext, useMemo, useRef, useState, useCallback, useEffect, createElement } from 'react';
2
2
  import { jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useForm, FormProvider, useWatch, Controller, useFormContext as useFormContext$1 } from 'react-hook-form';
4
4
  import { debounce } from 'lodash-es';
@@ -179,6 +179,8 @@ function Form({
179
179
  const pendingChangedFields = useRef(/* @__PURE__ */ new Set());
180
180
  const pendingAffectedFields = useRef(/* @__PURE__ */ new Set());
181
181
  const executionVersionRef = useRef(0);
182
+ const fieldDebouncersRef = useRef(/* @__PURE__ */ new Map());
183
+ const getOrCreateDebouncedRef = useRef();
182
184
  const registerField = useCallback((name) => {
183
185
  fieldRegistry.current.add(name);
184
186
  setRegisteredFields(new Set(fieldRegistry.current));
@@ -279,8 +281,11 @@ function Form({
279
281
  for (const field of affected) {
280
282
  pendingAffectedFields.current.add(field);
281
283
  }
282
- if (inputConfig?.debounce === false) {
284
+ const fieldDebounce = inputConfig?.debounce;
285
+ if (fieldDebounce === false) {
283
286
  executeAutoSaveRef.current?.();
287
+ } else if (typeof fieldDebounce === "number") {
288
+ getOrCreateDebouncedRef.current?.(fieldDebounce)();
284
289
  } else {
285
290
  debouncedSubmitRef.current?.();
286
291
  }
@@ -421,53 +426,67 @@ function Form({
421
426
  return;
422
427
  }
423
428
  }
424
- const formState = methods.formState;
425
- if (Object.keys(formState.errors).length > 0) {
426
- return;
427
- }
428
429
  const values = methods.getValues();
429
430
  await handleSubmit(values);
430
431
  }, [methods, handleSubmit, waitForFieldValidation]);
431
432
  executeAutoSaveRef.current = executeAutoSave;
433
+ const getOrCreateDebounced = useCallback(
434
+ (ms) => {
435
+ const cached = fieldDebouncersRef.current.get(ms);
436
+ if (cached) return cached;
437
+ const debouncedFn = debounce(() => {
438
+ executeAutoSaveRef.current?.();
439
+ }, ms);
440
+ const fn = Object.assign(debouncedFn, {
441
+ pending: () => false
442
+ // lodash debounce tracks pending internally
443
+ });
444
+ fieldDebouncersRef.current.set(ms, fn);
445
+ return fn;
446
+ },
447
+ []
448
+ );
449
+ getOrCreateDebouncedRef.current = getOrCreateDebounced;
432
450
  useEffect(() => {
451
+ return () => {
452
+ fieldDebouncersRef.current.forEach((fn) => fn.cancel());
453
+ fieldDebouncersRef.current.clear();
454
+ };
455
+ }, [getOrCreateDebounced]);
456
+ const debouncedSubmit = useMemo(() => {
433
457
  if (debounceMs === false) {
434
- const immediateFn = Object.assign(
458
+ return Object.assign(
435
459
  () => {
436
- executeAutoSave();
460
+ executeAutoSaveRef.current?.();
437
461
  },
438
462
  {
439
463
  cancel: () => {
440
464
  },
441
465
  // No-op for immediate function
442
- flush: () => executeAutoSave(),
466
+ flush: () => executeAutoSaveRef.current?.(),
443
467
  // Execute immediately on flush
444
468
  pending: () => false
445
469
  // Never pending when immediate
446
470
  }
447
471
  );
448
- debouncedSubmitRef.current = immediateFn;
449
- return () => {
450
- };
451
472
  }
452
473
  const debouncedFn = debounce(() => {
453
- executeAutoSave();
474
+ executeAutoSaveRef.current?.();
454
475
  }, debounceMs);
455
- const fn = Object.assign(debouncedFn, {
476
+ return Object.assign(debouncedFn, {
456
477
  pending: () => false
457
478
  // lodash debounce handles this internally
458
479
  });
459
- debouncedSubmitRef.current = fn;
480
+ }, [debounceMs]);
481
+ debouncedSubmitRef.current = debouncedSubmit;
482
+ useEffect(() => {
460
483
  return () => {
461
- debouncedFn.cancel();
484
+ debouncedSubmit.cancel();
462
485
  };
463
- }, [executeAutoSave, debounceMs]);
486
+ }, [debouncedSubmit]);
464
487
  const submitImmediate = useCallback(() => {
465
- if (debouncedSubmitRef.current?.flush) {
466
- debouncedSubmitRef.current.flush();
467
- } else {
468
- executeAutoSave();
469
- }
470
- }, [executeAutoSave]);
488
+ debouncedSubmitRef.current?.flush();
489
+ }, []);
471
490
  const unusedFields = useMemo(() => {
472
491
  const allFields = Object.keys(config);
473
492
  return allFields.filter((name) => !registeredFields.has(name));
@@ -506,7 +525,7 @@ function Form({
506
525
  setFieldValidating,
507
526
  getFormState,
508
527
  onSubmit,
509
- debouncedSubmit: debouncedSubmitRef.current,
528
+ debouncedSubmit,
510
529
  submitImmediate,
511
530
  unusedFields,
512
531
  methods
@@ -525,6 +544,7 @@ function Form({
525
544
  setFieldValidating,
526
545
  getFormState,
527
546
  onSubmit,
547
+ debouncedSubmit,
528
548
  submitImmediate,
529
549
  unusedFields,
530
550
  methods
@@ -560,11 +580,18 @@ function useInferredInputs(options) {
560
580
  selectProps,
561
581
  formDefaultFieldProps,
562
582
  providerDefaultFieldProps,
563
- conditions = [],
564
- subscribesTo = []
583
+ conditions,
584
+ subscribesTo
565
585
  } = options;
586
+ const signature = JSON.stringify({
587
+ selectProps,
588
+ formDefaultFieldProps,
589
+ providerDefaultFieldProps,
590
+ conditions,
591
+ subscribesTo
592
+ });
566
593
  return useMemo(() => {
567
- const inferred = [...subscribesTo];
594
+ const inferred = [...subscribesTo ?? []];
568
595
  if (providerDefaultFieldProps) {
569
596
  inferred.push(...inferFieldsFromDescriptor(providerDefaultFieldProps));
570
597
  }
@@ -574,17 +601,11 @@ function useInferredInputs(options) {
574
601
  if (selectProps) {
575
602
  inferred.push(...inferFieldsFromDescriptor(selectProps));
576
603
  }
577
- if (conditions.length > 0) {
604
+ if (conditions && conditions.length > 0) {
578
605
  inferred.push(...inferFieldsFromConditions(conditions));
579
606
  }
580
607
  return [...new Set(inferred)];
581
- }, [
582
- providerDefaultFieldProps,
583
- formDefaultFieldProps,
584
- selectProps,
585
- conditions,
586
- subscribesTo
587
- ]);
608
+ }, [signature]);
588
609
  }
589
610
 
590
611
  // src/hooks/useConditions.ts
@@ -1119,17 +1140,29 @@ function Field({
1119
1140
  }
1120
1141
  });
1121
1142
  const Component = inputConfig.component;
1143
+ const isHostComponent = typeof inputConfig.component === "string";
1122
1144
  const template = inputConfig.template ?? providerConfig.inputTemplates[type] ?? providerConfig.defaultInputTemplate;
1123
1145
  const TemplateComponent = template;
1124
- const renderedField = TemplateComponent ? /* @__PURE__ */ jsx(
1125
- TemplateComponent,
1126
- {
1127
- Field: Component,
1128
- fieldProps: finalProps,
1129
- fieldState,
1130
- formState
1131
- }
1132
- ) : /* @__PURE__ */ jsx(Component, { ...finalProps });
1146
+ let renderedField;
1147
+ if (TemplateComponent) {
1148
+ renderedField = /* @__PURE__ */ jsx(
1149
+ TemplateComponent,
1150
+ {
1151
+ Field: Component,
1152
+ fieldProps: finalProps,
1153
+ fieldState,
1154
+ formState
1155
+ }
1156
+ );
1157
+ } else if (isHostComponent) {
1158
+ const { forwardRef: hostRef, ...restHostProps } = finalProps;
1159
+ renderedField = createElement(inputConfig.component, {
1160
+ ...restHostProps,
1161
+ ref: hostRef
1162
+ });
1163
+ } else {
1164
+ renderedField = /* @__PURE__ */ jsx(Component, { ...finalProps });
1165
+ }
1133
1166
  if (typeof children === "function") {
1134
1167
  const result = children({
1135
1168
  fieldState,