@classytic/formkit 1.3.0 → 1.3.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/index.d.mts CHANGED
@@ -761,10 +761,6 @@ declare const field: {
761
761
  * Multi-select field (tag choice).
762
762
  */
763
763
  multiselect: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
764
- /**
765
- * Tag choice field for selecting options as tags/chips.
766
- */
767
- tagChoice: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
768
764
  /**
769
765
  * Dependent select field that reacts to parent field changes.
770
766
  */
@@ -801,22 +797,6 @@ declare const field: {
801
797
  * File upload field.
802
798
  */
803
799
  file: (name: string, label: string, props?: FieldProps) => BaseField;
804
- /**
805
- * OTP/PIN input field.
806
- */
807
- otp: (name: string, label: string, props?: FieldProps) => BaseField;
808
- /**
809
- * Async searchable combobox with server-side search.
810
- */
811
- asyncCombobox: (name: string, label: string, props?: FieldProps) => BaseField;
812
- /**
813
- * Async searchable multi-select with server-side search.
814
- */
815
- asyncMultiselect: (name: string, label: string, props?: FieldProps) => BaseField;
816
- /**
817
- * Date and optional time picker field.
818
- */
819
- dateTime: (name: string, label: string, props?: FieldProps) => BaseField;
820
800
  /**
821
801
  * Hidden field (no UI).
822
802
  */
@@ -930,4 +910,4 @@ interface UseFormKitReturn<TFieldValues extends FieldValues = FieldValues> exten
930
910
  */
931
911
  declare function useFormKit<TFieldValues extends FieldValues = FieldValues>(options: UseFormKitOptions<TFieldValues>): UseFormKitReturn<TFieldValues>;
932
912
  //#endregion
933
- export { type BaseField, type ClassValue, type ComponentRegistry, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type UseFormKitOptions, type UseFormKitReturn, type ValidationRules, type Variant, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extractDefaultValues, extractWatchNames, field, section, sectionUntitled, shallowEqual, useFieldComponent, useFormKit, useFormSystem, useLayoutComponent };
913
+ export { type BaseField, type ClassValue, type ComponentRegistry, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FieldWrapperProps, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type GridRendererProps, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type SectionRendererProps, type UseFormKitOptions, type UseFormKitReturn, type ValidationRules, type Variant, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extractDefaultValues, extractWatchNames, field, section, sectionUntitled, shallowEqual, useFieldComponent, useFormKit, useFormSystem, useLayoutComponent };
package/dist/index.mjs CHANGED
@@ -279,7 +279,12 @@ function toRules(condition) {
279
279
  */
280
280
  function evaluateCondition(condition, formValues) {
281
281
  if (!condition) return true;
282
- if (typeof condition === "function") return condition(formValues);
282
+ if (typeof condition === "function") try {
283
+ return condition(formValues);
284
+ } catch (err) {
285
+ console.warn("[FormKit] Condition function threw:", err);
286
+ return false;
287
+ }
283
288
  const { rules, logic } = toRules(condition);
284
289
  const evalFn = (rule) => evaluateRule(rule, formValues);
285
290
  return logic === "or" ? rules.some(evalFn) : rules.every(evalFn);
@@ -369,10 +374,14 @@ function buildValidationRules(field) {
369
374
  value: field.max,
370
375
  message: `Must be at most ${field.max}`
371
376
  };
372
- if (field.pattern) rules.pattern = {
373
- value: new RegExp(field.pattern),
374
- message: "Invalid format"
375
- };
377
+ if (field.pattern) try {
378
+ rules.pattern = {
379
+ value: new RegExp(field.pattern),
380
+ message: "Invalid format"
381
+ };
382
+ } catch {
383
+ console.warn(`[FormKit] Invalid regex pattern "${field.pattern}" in field "${field.name}", skipping.`);
384
+ }
376
385
  if (field.validate) rules.validate = field.validate;
377
386
  return rules;
378
387
  }
@@ -548,8 +557,50 @@ function GridRenderer({ fields, cols = 1, gap, control, disabled, variant }) {
548
557
  */
549
558
  function FieldWrapper(props) {
550
559
  if (props.field.condition || props.field.loadOptions) return /* @__PURE__ */ jsx(DynamicFieldWrapper, { ...props });
560
+ if (props.field.render) return /* @__PURE__ */ jsx(RenderedFieldWrapper, { ...props });
551
561
  return /* @__PURE__ */ jsx(StaticFieldWrapper, { ...props });
552
562
  }
563
+ function RenderedFieldWrapper({ field, control, disabled, variant }) {
564
+ const fieldName = field.name;
565
+ const fieldId = toFieldId(fieldName);
566
+ const activeVariant = field.variant ?? variant;
567
+ const isDisabled = disabled || field.disabled;
568
+ const { errors, dirtyFields, touchedFields } = useFormState({
569
+ control,
570
+ name: fieldName
571
+ });
572
+ const fieldError = getNestedError(errors, fieldName);
573
+ const isDirty = Boolean(getNestedValue(dirtyFields, fieldName));
574
+ const isTouched = Boolean(getNestedValue(touchedFields, fieldName));
575
+ const fieldState = useMemo(() => ({
576
+ invalid: !!fieldError,
577
+ isDirty,
578
+ isTouched,
579
+ isValidating: false,
580
+ error: fieldError
581
+ }), [
582
+ fieldError,
583
+ isDirty,
584
+ isTouched
585
+ ]);
586
+ return /* @__PURE__ */ jsx("div", {
587
+ className: cn("formkit-field", field.fullWidth && "col-span-full", field.className),
588
+ id: fieldId,
589
+ "data-formkit-field": fieldName,
590
+ "data-field-type": field.type,
591
+ children: field.render?.({
592
+ ...field,
593
+ field,
594
+ control,
595
+ disabled: isDisabled,
596
+ variant: activeVariant,
597
+ error: fieldError,
598
+ fieldState,
599
+ fieldId,
600
+ isLoading: void 0
601
+ })
602
+ });
603
+ }
553
604
  /**
554
605
  * Dynamic Field Wrapper
555
606
  * Conditionally calls `useWatch` to trigger re-renders only when form values change.
@@ -615,7 +666,10 @@ function DynamicFieldWrapper({ field, control, disabled, variant }) {
615
666
  } else executeLoad();
616
667
  return () => {
617
668
  isActive = false;
618
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
669
+ if (timeoutRef.current) {
670
+ clearTimeout(timeoutRef.current);
671
+ timeoutRef.current = null;
672
+ }
619
673
  };
620
674
  }, [
621
675
  watchedValues,
@@ -671,23 +725,6 @@ function StaticFieldWrapper({ field, control, disabled, variant, isLoading }) {
671
725
  isTouched
672
726
  ]);
673
727
  const activeVariant = field.variant ?? variant;
674
- if (field.render) return /* @__PURE__ */ jsx("div", {
675
- className: cn("formkit-field", field.fullWidth && "col-span-full", field.className),
676
- id: fieldId,
677
- "data-formkit-field": fieldName,
678
- "data-field-type": field.type,
679
- children: field.render({
680
- ...field,
681
- field,
682
- control,
683
- disabled: isDisabled,
684
- variant: activeVariant,
685
- error: fieldError,
686
- fieldState,
687
- fieldId,
688
- isLoading
689
- })
690
- });
691
728
  if (!Boolean(components[field.type] || activeVariant && components[activeVariant] && typeof components[activeVariant] === "object" && components[activeVariant][field.type]) && field.itemFields && field.itemFields.length > 0) {
692
729
  if (field.type === "array") return /* @__PURE__ */ jsx(ArrayFieldFallback, {
693
730
  field,
@@ -708,23 +745,24 @@ function StaticFieldWrapper({ field, control, disabled, variant, isLoading }) {
708
745
  });
709
746
  }
710
747
  const FieldComponent = useFieldComponent(field.type, activeVariant);
711
- if (!FieldComponent) return null;
748
+ if (!FieldComponent && !field.render) return null;
749
+ const fieldProps = {
750
+ ...field,
751
+ field,
752
+ control,
753
+ disabled: isDisabled,
754
+ variant: activeVariant,
755
+ error: fieldError,
756
+ fieldState,
757
+ fieldId,
758
+ isLoading
759
+ };
712
760
  return /* @__PURE__ */ jsx("div", {
713
761
  className: cn("formkit-field", field.fullWidth && "col-span-full", field.className),
714
762
  id: fieldId,
715
763
  "data-formkit-field": fieldName,
716
764
  "data-field-type": field.type,
717
- children: /* @__PURE__ */ jsx(FieldComponent, {
718
- ...field,
719
- field,
720
- control,
721
- disabled: isDisabled,
722
- variant: activeVariant,
723
- error: fieldError,
724
- fieldState,
725
- fieldId,
726
- isLoading
727
- })
765
+ children: field.render ? field.render(fieldProps) : /* @__PURE__ */ jsx(FieldComponent, { ...fieldProps })
728
766
  });
729
767
  }
730
768
  function ArrayFieldFallback({ field, control, disabled, variant }) {
@@ -763,7 +801,13 @@ function ArrayFieldFallback({ field, control, disabled, variant }) {
763
801
  }, item.id)),
764
802
  /* @__PURE__ */ jsx("button", {
765
803
  type: "button",
766
- onClick: () => append({}),
804
+ onClick: () => {
805
+ const defaults = {};
806
+ if (field.itemFields) {
807
+ for (const f of field.itemFields) if (f.defaultValue !== void 0) defaults[f.name] = f.defaultValue;
808
+ }
809
+ append(defaults);
810
+ },
767
811
  disabled,
768
812
  className: "self-start mt-2 px-4 py-2 bg-blue-50 text-blue-600 rounded-md text-sm font-medium hover:bg-blue-100 disabled:opacity-50",
769
813
  children: "+ Add Item"
@@ -868,13 +912,6 @@ const field = {
868
912
  placeholder: "Select options...",
869
913
  ...props
870
914
  }),
871
- tagChoice: (name, label, options, props = {}) => ({
872
- type: "tagChoice",
873
- name,
874
- label,
875
- options,
876
- ...props
877
- }),
878
915
  dependentSelect: (name, label, props = {}) => ({
879
916
  type: "dependentSelect",
880
917
  name,
@@ -932,30 +969,6 @@ const field = {
932
969
  label,
933
970
  ...props
934
971
  }),
935
- otp: (name, label, props = {}) => ({
936
- type: "otp",
937
- name,
938
- label,
939
- ...props
940
- }),
941
- asyncCombobox: (name, label, props = {}) => ({
942
- type: "asyncCombobox",
943
- name,
944
- label,
945
- ...props
946
- }),
947
- asyncMultiselect: (name, label, props = {}) => ({
948
- type: "asyncMultiselect",
949
- name,
950
- label,
951
- ...props
952
- }),
953
- dateTime: (name, label, props = {}) => ({
954
- type: "dateTime",
955
- name,
956
- label,
957
- ...props
958
- }),
959
972
  hidden: (name, props = {}) => ({
960
973
  type: "hidden",
961
974
  name,
@@ -1046,13 +1059,12 @@ function sectionUntitled(fields, props = {}) {
1046
1059
  */
1047
1060
  function useFormKit(options) {
1048
1061
  const { schema, disabled, variant, className, defaultValues, ...formOptions } = options;
1049
- const mergedDefaults = {
1050
- ...extractDefaultValues(schema),
1062
+ const schemaDefaults = useMemo(() => extractDefaultValues(schema), [schema]);
1063
+ const mergedDefaults = useMemo(() => ({
1064
+ ...schemaDefaults,
1051
1065
  ...typeof defaultValues === "object" && defaultValues !== null ? defaultValues : {}
1052
- };
1066
+ }), [schemaDefaults, defaultValues]);
1053
1067
  const form = useForm({
1054
- mode: "onBlur",
1055
- reValidateMode: "onChange",
1056
1068
  ...formOptions,
1057
1069
  defaultValues: mergedDefaults
1058
1070
  });
package/dist/server.d.mts CHANGED
@@ -518,10 +518,6 @@ declare const field: {
518
518
  * Multi-select field (tag choice).
519
519
  */
520
520
  multiselect: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
521
- /**
522
- * Tag choice field for selecting options as tags/chips.
523
- */
524
- tagChoice: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
525
521
  /**
526
522
  * Dependent select field that reacts to parent field changes.
527
523
  */
@@ -558,22 +554,6 @@ declare const field: {
558
554
  * File upload field.
559
555
  */
560
556
  file: (name: string, label: string, props?: FieldProps) => BaseField;
561
- /**
562
- * OTP/PIN input field.
563
- */
564
- otp: (name: string, label: string, props?: FieldProps) => BaseField;
565
- /**
566
- * Async searchable combobox with server-side search.
567
- */
568
- asyncCombobox: (name: string, label: string, props?: FieldProps) => BaseField;
569
- /**
570
- * Async searchable multi-select with server-side search.
571
- */
572
- asyncMultiselect: (name: string, label: string, props?: FieldProps) => BaseField;
573
- /**
574
- * Date and optional time picker field.
575
- */
576
- dateTime: (name: string, label: string, props?: FieldProps) => BaseField;
577
557
  /**
578
558
  * Hidden field (no UI).
579
559
  */
package/dist/server.mjs CHANGED
@@ -95,7 +95,12 @@ function toRules(condition) {
95
95
  */
96
96
  function evaluateCondition(condition, formValues) {
97
97
  if (!condition) return true;
98
- if (typeof condition === "function") return condition(formValues);
98
+ if (typeof condition === "function") try {
99
+ return condition(formValues);
100
+ } catch (err) {
101
+ console.warn("[FormKit] Condition function threw:", err);
102
+ return false;
103
+ }
99
104
  const { rules, logic } = toRules(condition);
100
105
  const evalFn = (rule) => evaluateRule(rule, formValues);
101
106
  return logic === "or" ? rules.some(evalFn) : rules.every(evalFn);
@@ -185,10 +190,14 @@ function buildValidationRules(field) {
185
190
  value: field.max,
186
191
  message: `Must be at most ${field.max}`
187
192
  };
188
- if (field.pattern) rules.pattern = {
189
- value: new RegExp(field.pattern),
190
- message: "Invalid format"
191
- };
193
+ if (field.pattern) try {
194
+ rules.pattern = {
195
+ value: new RegExp(field.pattern),
196
+ message: "Invalid format"
197
+ };
198
+ } catch {
199
+ console.warn(`[FormKit] Invalid regex pattern "${field.pattern}" in field "${field.name}", skipping.`);
200
+ }
192
201
  if (field.validate) rules.validate = field.validate;
193
202
  return rules;
194
203
  }
@@ -289,13 +298,6 @@ const field = {
289
298
  placeholder: "Select options...",
290
299
  ...props
291
300
  }),
292
- tagChoice: (name, label, options, props = {}) => ({
293
- type: "tagChoice",
294
- name,
295
- label,
296
- options,
297
- ...props
298
- }),
299
301
  dependentSelect: (name, label, props = {}) => ({
300
302
  type: "dependentSelect",
301
303
  name,
@@ -353,30 +355,6 @@ const field = {
353
355
  label,
354
356
  ...props
355
357
  }),
356
- otp: (name, label, props = {}) => ({
357
- type: "otp",
358
- name,
359
- label,
360
- ...props
361
- }),
362
- asyncCombobox: (name, label, props = {}) => ({
363
- type: "asyncCombobox",
364
- name,
365
- label,
366
- ...props
367
- }),
368
- asyncMultiselect: (name, label, props = {}) => ({
369
- type: "asyncMultiselect",
370
- name,
371
- label,
372
- ...props
373
- }),
374
- dateTime: (name, label, props = {}) => ({
375
- type: "dateTime",
376
- name,
377
- label,
378
- ...props
379
- }),
380
358
  hidden: (name, props = {}) => ({
381
359
  type: "hidden",
382
360
  name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/formkit",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Headless, type-safe form generation engine for React 19. Schema-driven with full TypeScript support.",
5
5
  "author": "Classytic",
6
6
  "license": "MIT",
@@ -110,4 +110,4 @@
110
110
  "access": "public",
111
111
  "registry": "https://registry.npmjs.org/"
112
112
  }
113
- }
113
+ }