@classytic/formkit 1.0.1 → 1.0.3

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.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { useFormContext, useWatch } from 'react-hook-form';
2
2
  import { createContext, useMemo, useContext } from 'react';
3
- import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { jsx } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
 
7
- // src/FormGenerator.tsx
8
7
  var FormSystemContext = createContext(null);
8
+ FormSystemContext.displayName = "FormSystemContext";
9
9
  function FormSystemProvider({
10
10
  components,
11
11
  layouts,
@@ -13,8 +13,8 @@ function FormSystemProvider({
13
13
  }) {
14
14
  const value = useMemo(
15
15
  () => ({
16
- components: components || {},
17
- layouts: layouts || {}
16
+ components: components ?? {},
17
+ layouts: layouts ?? {}
18
18
  }),
19
19
  [components, layouts]
20
20
  );
@@ -23,42 +23,60 @@ function FormSystemProvider({
23
23
  function useFormSystem() {
24
24
  const context = useContext(FormSystemContext);
25
25
  if (!context) {
26
- throw new Error("useFormSystem must be used within a FormSystemProvider");
26
+ throw new Error(
27
+ "[FormKit] useFormSystem must be used within a FormSystemProvider. Make sure to wrap your form components with <FormSystemProvider>."
28
+ );
27
29
  }
28
30
  return context;
29
31
  }
30
32
  function useFieldComponent(type, variant) {
31
33
  const { components } = useFormSystem();
32
- if (variant && typeof components[variant] === "object" && components[variant]) {
34
+ if (variant && typeof components[variant] === "object" && components[variant] !== null) {
33
35
  const variantComponents = components[variant];
34
- if (variantComponents[type]) {
35
- return variantComponents[type];
36
+ const variantComponent = variantComponents[type];
37
+ if (variantComponent) {
38
+ return variantComponent;
36
39
  }
37
40
  }
38
- const Component = components[type] || components["default"] || components["text"];
39
- if (!Component) {
40
- if (process.env.NODE_ENV === "development") {
41
- console.warn(`FormKit: No component found for type "${type}" (variant: ${variant})`);
42
- return () => /* @__PURE__ */ jsxs("div", { style: { color: "red", padding: 4, border: "1px dashed red" }, children: [
43
- "Missing: ",
44
- type
45
- ] });
46
- }
47
- return () => null;
41
+ const typeComponent = components[type];
42
+ if (typeComponent && typeof typeComponent === "function") {
43
+ return typeComponent;
44
+ }
45
+ const defaultComponent = components["default"];
46
+ if (defaultComponent && typeof defaultComponent === "function") {
47
+ return defaultComponent;
48
+ }
49
+ const textComponent = components["text"];
50
+ if (textComponent && typeof textComponent === "function") {
51
+ return textComponent;
48
52
  }
49
- return Component;
53
+ return NullComponent;
50
54
  }
51
55
  function useLayoutComponent(type, variant) {
52
56
  const { layouts } = useFormSystem();
53
- if (variant && typeof layouts[variant] === "object" && layouts[variant]) {
57
+ if (variant && typeof layouts[variant] === "object" && layouts[variant] !== null) {
54
58
  const variantLayouts = layouts[variant];
55
- if (variantLayouts[type]) {
56
- return variantLayouts[type];
59
+ const variantLayout = variantLayouts[type];
60
+ if (variantLayout) {
61
+ return variantLayout;
57
62
  }
58
63
  }
59
- return layouts[type] || layouts["default"] || DefaultLayout;
64
+ const typeLayout = layouts[type];
65
+ if (typeLayout && typeof typeLayout === "function") {
66
+ return typeLayout;
67
+ }
68
+ const defaultLayout = layouts["default"];
69
+ if (defaultLayout && typeof defaultLayout === "function") {
70
+ return defaultLayout;
71
+ }
72
+ return DefaultLayout;
73
+ }
74
+ function DefaultLayout({ children, className }) {
75
+ return /* @__PURE__ */ jsx("div", { className, children });
76
+ }
77
+ function NullComponent() {
78
+ return null;
60
79
  }
61
- var DefaultLayout = ({ children, className }) => /* @__PURE__ */ jsx("div", { className, children });
62
80
  function cn(...inputs) {
63
81
  return twMerge(clsx(inputs));
64
82
  }
@@ -66,21 +84,35 @@ function FormGenerator({
66
84
  schema,
67
85
  control,
68
86
  disabled = false,
69
- variant
87
+ variant,
88
+ className
70
89
  }) {
71
90
  const formContext = useFormContext();
72
- const activeControl = control || formContext?.control;
73
- if (!schema?.sections) return null;
74
- return /* @__PURE__ */ jsx("div", { className: cn("form-generator-root", variant && `form-variant-${variant}`), children: schema.sections.map((section, idx) => /* @__PURE__ */ jsx(
75
- SectionRenderer,
91
+ const activeControl = control ?? formContext?.control;
92
+ if (!schema?.sections || schema.sections.length === 0) {
93
+ return null;
94
+ }
95
+ return /* @__PURE__ */ jsx(
96
+ "div",
76
97
  {
77
- section,
78
- control: activeControl,
79
- disabled,
80
- variant
81
- },
82
- section.id || idx
83
- )) });
98
+ className: cn(
99
+ "formkit-root",
100
+ variant && `formkit-variant-${variant}`,
101
+ className
102
+ ),
103
+ "data-formkit-root": "",
104
+ children: schema.sections.map((section, index) => /* @__PURE__ */ jsx(
105
+ SectionRenderer,
106
+ {
107
+ section,
108
+ control: activeControl,
109
+ disabled,
110
+ variant
111
+ },
112
+ section.id ?? `section-${index}`
113
+ ))
114
+ }
115
+ );
84
116
  }
85
117
  function SectionRenderer({
86
118
  section,
@@ -88,7 +120,7 @@ function SectionRenderer({
88
120
  disabled,
89
121
  variant
90
122
  }) {
91
- const activeVariant = section.variant || variant;
123
+ const activeVariant = section.variant ?? variant;
92
124
  const SectionLayout = useLayoutComponent("section", activeVariant);
93
125
  if (section.condition && !section.condition(control)) {
94
126
  return null;
@@ -101,15 +133,24 @@ function SectionRenderer({
101
133
  icon: section.icon,
102
134
  variant: activeVariant,
103
135
  className: section.className,
104
- children: section.render ? section.render({ control, disabled, section }) : /* @__PURE__ */ jsx(
105
- GridRenderer,
106
- {
107
- fields: section.fields,
108
- cols: section.cols,
109
- control,
110
- disabled,
111
- variant: activeVariant
112
- }
136
+ collapsible: section.collapsible,
137
+ defaultCollapsed: section.defaultCollapsed,
138
+ children: section.render ? (
139
+ // Custom render function
140
+ section.render({ control, disabled, section })
141
+ ) : (
142
+ // Standard grid rendering
143
+ /* @__PURE__ */ jsx(
144
+ GridRenderer,
145
+ {
146
+ fields: section.fields,
147
+ cols: section.cols,
148
+ gap: section.gap,
149
+ control,
150
+ disabled,
151
+ variant: activeVariant
152
+ }
153
+ )
113
154
  )
114
155
  }
115
156
  );
@@ -117,13 +158,16 @@ function SectionRenderer({
117
158
  function GridRenderer({
118
159
  fields,
119
160
  cols = 1,
161
+ gap,
120
162
  control,
121
163
  disabled,
122
164
  variant
123
165
  }) {
124
166
  const GridLayout = useLayoutComponent("grid", variant);
125
- if (!fields || fields.length === 0) return null;
126
- return /* @__PURE__ */ jsx(GridLayout, { cols, children: fields.map((field, idx) => /* @__PURE__ */ jsx(
167
+ if (!fields || fields.length === 0) {
168
+ return null;
169
+ }
170
+ return /* @__PURE__ */ jsx(GridLayout, { cols, gap, children: fields.map((field, index) => /* @__PURE__ */ jsx(
127
171
  FieldWrapper,
128
172
  {
129
173
  field,
@@ -131,7 +175,7 @@ function GridRenderer({
131
175
  disabled,
132
176
  variant
133
177
  },
134
- field.name || idx
178
+ field.name || `field-${index}`
135
179
  )) });
136
180
  }
137
181
  function FieldWrapper({
@@ -144,21 +188,36 @@ function FieldWrapper({
144
188
  if (field.condition && !field.condition(formValues)) {
145
189
  return null;
146
190
  }
147
- const activeVariant = field.variant || variant;
191
+ const activeVariant = field.variant ?? variant;
148
192
  const FieldComponent = useFieldComponent(field.type, activeVariant);
149
- if (!FieldComponent) return null;
150
- return /* @__PURE__ */ jsx("div", { className: field.fullWidth ? "col-span-full" : "", children: /* @__PURE__ */ jsx(
151
- FieldComponent,
193
+ if (!FieldComponent) {
194
+ return null;
195
+ }
196
+ const isDisabled = disabled || field.disabled;
197
+ return /* @__PURE__ */ jsx(
198
+ "div",
152
199
  {
153
- field,
154
- control,
155
- disabled: disabled || field.disabled,
156
- variant: activeVariant,
157
- ...field
200
+ className: cn(
201
+ "formkit-field",
202
+ field.fullWidth && "col-span-full",
203
+ field.className
204
+ ),
205
+ "data-formkit-field": field.name,
206
+ "data-field-type": field.type,
207
+ children: /* @__PURE__ */ jsx(
208
+ FieldComponent,
209
+ {
210
+ ...field,
211
+ field,
212
+ control,
213
+ disabled: isDisabled,
214
+ variant: activeVariant
215
+ }
216
+ )
158
217
  }
159
- ) });
218
+ );
160
219
  }
161
220
 
162
- export { FormGenerator, FormSystemProvider, cn, useFormSystem };
221
+ export { FieldWrapper, FormGenerator, FormSystemProvider, GridRenderer, SectionRenderer, cn, useFieldComponent, useFormSystem, useLayoutComponent };
163
222
  //# sourceMappingURL=index.js.map
164
223
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n} from \"./types\";\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Provides the component registry to the form system.\r\n * This is the root provider that enables FormGenerator to work.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): JSX.Element {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components || {},\r\n layouts: layouts || {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access the form system context\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\"useFormSystem must be used within a FormSystemProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Helper to resolve a component for a specific field type and variant\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant]) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n if (variantComponents[type]) {\r\n return variantComponents[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard component\r\n const Component =\r\n (components[type] as FieldComponent) ||\r\n (components[\"default\"] as FieldComponent) ||\r\n (components[\"text\"] as FieldComponent);\r\n\r\n if (!Component) {\r\n // Library-safe fallback: Don't crash, just warn and render nothing or a placeholder\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.warn(`FormKit: No component found for type \"${type}\" (variant: ${variant})`);\r\n return () => (\r\n <div style={{ color: \"red\", padding: 4, border: \"1px dashed red\" }}>\r\n Missing: {type}\r\n </div>\r\n );\r\n }\r\n return () => null;\r\n }\r\n\r\n return Component;\r\n}\r\n\r\n/**\r\n * Helper to resolve a layout component\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant]) {\r\n const variantLayouts = layouts[variant] as Record<string, LayoutComponent>;\r\n if (variantLayouts[type]) {\r\n return variantLayouts[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard layout\r\n return (\r\n (layouts[type] as LayoutComponent) ||\r\n (layouts[\"default\"] as LayoutComponent) ||\r\n DefaultLayout\r\n );\r\n}\r\n\r\n/**\r\n * Default layout component - simple div wrapper\r\n */\r\nconst DefaultLayout: LayoutComponent = ({ children, className }: any) => (\r\n <div className={className}>{children}</div>\r\n);\r\n","import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\n/**\r\n * Utility function to merge Tailwind CSS classes\r\n * Combines clsx and tailwind-merge for conflict-free class merging\r\n *\r\n * @param inputs - Class values to merge\r\n * @returns Merged class string\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type { FormGeneratorProps, Section, BaseField, Variant } from \"./types\";\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema, using components injected via FormSystemProvider.\r\n * Supports conditional fields, dynamic layouts, and variants.\r\n *\r\n * @template TFieldValues - Form field values type\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n}: FormGeneratorProps<TFieldValues>): JSX.Element | null {\r\n // Use provided control or fall back to context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control || formContext?.control;\r\n\r\n if (!schema?.sections) return null;\r\n\r\n return (\r\n <div className={cn(\"form-generator-root\", variant && `form-variant-${variant}`)}>\r\n {schema.sections.map((section, idx) => (\r\n <SectionRenderer\r\n key={section.id || idx}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Section Renderer Props\r\n */\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): JSX.Element | null {\r\n // Allow section to override variant, or use global variant\r\n const activeVariant = section.variant || variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check condition if present\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n >\r\n {section.render ? (\r\n section.render({ control, disabled, section })\r\n ) : (\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Grid Renderer Props\r\n */\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField[];\r\n cols?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): JSX.Element | null {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) return null;\r\n\r\n return (\r\n <GridLayout cols={cols}>\r\n {fields.map((field, idx) => (\r\n <FieldWrapper\r\n key={field.name || idx}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Field Wrapper Props\r\n */\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single field wrapper\r\n * Handles conditional rendering and field visibility\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): JSX.Element | null {\r\n // Watch values for conditional rendering\r\n const formValues = useWatch({ control });\r\n\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Allow field to override variant\r\n const activeVariant = field.variant || variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) return null;\r\n\r\n return (\r\n <div className={field.fullWidth ? \"col-span-full\" : \"\"}>\r\n <FieldComponent\r\n field={field}\r\n control={control as any}\r\n disabled={disabled || field.disabled}\r\n variant={activeVariant}\r\n // Pass all field props to the component\r\n {...field}\r\n />\r\n </div>\r\n );\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["jsx"],"mappings":";;;;;;AAqBA,IAAM,iBAAA,GAAoB,cAA6C,IAAI,CAAA;AAG3E,iBAAA,CAAkB,WAAA,GAAc,mBAAA;AAuCzB,SAAS,kBAAA,CAAmB;AAAA,EACjC,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAyC;AACvC,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,UAAA,EAAY,cAAc,EAAC;AAAA,MAC3B,OAAA,EAAS,WAAW;AAAC,KACvB,CAAA;AAAA,IACA,CAAC,YAAY,OAAO;AAAA,GACtB;AAEA,EAAA,uBAAO,GAAA,CAAC,iBAAA,CAAkB,QAAA,EAAlB,EAA2B,OAAe,QAAA,EAAS,CAAA;AAC7D;AAiBO,SAAS,aAAA,GAAwC;AACtD,EAAA,MAAM,OAAA,GAAU,WAAW,iBAAiB,CAAA;AAC5C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAiBO,SAAS,iBAAA,CAAkB,MAAiB,OAAA,EAAmC;AACpF,EAAA,MAAM,EAAE,UAAA,EAAW,GAAI,aAAA,EAAc;AAGrC,EAAA,IAAI,OAAA,IAAW,OAAO,UAAA,CAAW,OAAO,MAAM,QAAA,IAAY,UAAA,CAAW,OAAO,CAAA,KAAM,IAAA,EAAM;AACtF,IAAA,MAAM,iBAAA,GAAoB,WAAW,OAAO,CAAA;AAC5C,IAAA,MAAM,gBAAA,GAAmB,kBAAkB,IAAI,CAAA;AAC/C,IAAA,IAAI,gBAAA,EAAkB;AACpB,MAAA,OAAO,gBAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,MAAM,aAAA,GAAgB,WAAW,IAAI,CAAA;AACrC,EAAA,IAAI,aAAA,IAAiB,OAAO,aAAA,KAAkB,UAAA,EAAY;AACxD,IAAA,OAAO,aAAA;AAAA,EACT;AAGA,EAAA,MAAM,gBAAA,GAAmB,WAAW,SAAS,CAAA;AAC7C,EAAA,IAAI,gBAAA,IAAoB,OAAO,gBAAA,KAAqB,UAAA,EAAY;AAC9D,IAAA,OAAO,gBAAA;AAAA,EACT;AAGA,EAAA,MAAM,aAAA,GAAgB,WAAW,MAAM,CAAA;AACvC,EAAA,IAAI,aAAA,IAAiB,OAAO,aAAA,KAAkB,UAAA,EAAY;AACxD,IAAA,OAAO,aAAA;AAAA,EACT;AAYA,EAAA,OAAO,aAAA;AACT;AAiBO,SAAS,kBAAA,CACd,MACA,OAAA,EAC4E;AAC5E,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,aAAA,EAAc;AAGlC,EAAA,IAAI,OAAA,IAAW,OAAO,OAAA,CAAQ,OAAO,MAAM,QAAA,IAAY,OAAA,CAAQ,OAAO,CAAA,KAAM,IAAA,EAAM;AAChF,IAAA,MAAM,cAAA,GAAiB,QAAQ,OAAO,CAAA;AAItC,IAAA,MAAM,aAAA,GAAgB,eAAe,IAAI,CAAA;AACzC,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,OAAO,aAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,MAAM,UAAA,GAAa,QAAQ,IAAI,CAAA;AAG/B,EAAA,IAAI,UAAA,IAAc,OAAO,UAAA,KAAe,UAAA,EAAY;AAClD,IAAA,OAAO,UAAA;AAAA,EACT;AAGA,EAAA,MAAM,aAAA,GAAgB,QAAQ,SAAS,CAAA;AAGvC,EAAA,IAAI,aAAA,IAAiB,OAAO,aAAA,KAAkB,UAAA,EAAY;AACxD,IAAA,OAAO,aAAA;AAAA,EACT;AAGA,EAAA,OAAO,aAAA;AACT;AAUA,SAAS,aAAA,CAAc,EAAE,QAAA,EAAU,SAAA,EAAU,EAAoC;AAC/E,EAAA,uBAAO,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAuB,QAAA,EAAS,CAAA;AAC9C;AAKA,SAAS,aAAA,GAA6B;AACpC,EAAA,OAAO,IAAA;AACT;AChNO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AC6BO,SAAS,aAAA,CAA8D;AAAA,EAC5E,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,OAAA;AAAA,EACA;AACF,CAAA,EAAkD;AAEhD,EAAA,MAAM,cAAc,cAAA,EAA6B;AACjD,EAAA,MAAM,aAAA,GAAgB,WAAW,WAAA,EAAa,OAAA;AAG9C,EAAA,IAAI,CAAC,MAAA,EAAQ,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,WAAW,CAAA,EAAG;AACrD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACEA,GAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,cAAA;AAAA,QACA,OAAA,IAAW,mBAAmB,OAAO,CAAA,CAAA;AAAA,QACrC;AAAA,OACF;AAAA,MACA,mBAAA,EAAkB,EAAA;AAAA,MAEjB,iBAAO,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,EAAS,0BAC7BA,GAAAA;AAAA,QAAC,eAAA;AAAA,QAAA;AAAA,UAEC,OAAA;AAAA,UACA,OAAA,EAAS,aAAA;AAAA,UACT,QAAA;AAAA,UACA;AAAA,SAAA;AAAA,QAJK,OAAA,CAAQ,EAAA,IAAM,CAAA,QAAA,EAAW,KAAK,CAAA;AAAA,OAMtC;AAAA;AAAA,GACH;AAEJ;AAiBA,SAAS,eAAA,CAAgE;AAAA,EACvE,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAoD;AAElD,EAAA,MAAM,aAAA,GAAgB,QAAQ,OAAA,IAAW,OAAA;AACzC,EAAA,MAAM,aAAA,GAAgB,kBAAA,CAAmB,SAAA,EAAW,aAAa,CAAA;AAGjE,EAAA,IAAI,QAAQ,SAAA,IAAa,CAAC,OAAA,CAAQ,SAAA,CAAU,OAAO,CAAA,EAAG;AACpD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACEA,GAAAA;AAAA,IAAC,aAAA;AAAA,IAAA;AAAA,MACC,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,OAAA,EAAS,aAAA;AAAA,MACT,WAAW,OAAA,CAAQ,SAAA;AAAA,MACnB,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,kBAAkB,OAAA,CAAQ,gBAAA;AAAA,MAEzB,QAAA,EAAA,OAAA,CAAQ,MAAA;AAAA;AAAA,QAEP,QAAQ,MAAA,CAAO,EAAE,OAAA,EAAS,QAAA,EAAU,SAAS;AAAA;AAAA;AAAA,wBAG7CA,GAAAA;AAAA,UAAC,YAAA;AAAA,UAAA;AAAA,YACC,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,MAAM,OAAA,CAAQ,IAAA;AAAA,YACd,KAAK,OAAA,CAAQ,GAAA;AAAA,YACb,OAAA;AAAA,YACA,QAAA;AAAA,YACA,OAAA,EAAS;AAAA;AAAA;AACX;AAAA;AAAA,GAEJ;AAEJ;AAkBA,SAAS,YAAA,CAA6D;AAAA,EACpE,MAAA;AAAA,EACA,IAAA,GAAO,CAAA;AAAA,EACP,GAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAiD;AAC/C,EAAA,MAAM,UAAA,GAAa,kBAAA,CAAmB,MAAA,EAAQ,OAAO,CAAA;AAErD,EAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAClC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACEA,GAAAA,CAAC,UAAA,EAAA,EAAW,IAAA,EAAY,GAAA,EACrB,iBAAO,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,qBAClBA,GAAAA;AAAA,IAAC,YAAA;AAAA,IAAA;AAAA,MAEC,KAAA;AAAA,MACA,OAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KAAA;AAAA,IAJK,KAAA,CAAM,IAAA,IAAQ,CAAA,MAAA,EAAS,KAAK,CAAA;AAAA,GAMpC,CAAA,EACH,CAAA;AAEJ;AAiBA,SAAS,YAAA,CAA6D;AAAA,EACpE,KAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAiD;AAE/C,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,EAAE,OAAA,EAAS,CAAA;AAGvC,EAAA,IAAI,MAAM,SAAA,IAAa,CAAC,KAAA,CAAM,SAAA,CAAU,UAAU,CAAA,EAAG;AACnD,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,MAAM,aAAA,GAAgB,MAAM,OAAA,IAAW,OAAA;AACvC,EAAA,MAAM,cAAA,GAAiB,iBAAA,CAAkB,KAAA,CAAM,IAAA,EAAM,aAAa,CAAA;AAElE,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,MAAM,UAAA,GAAa,YAAY,KAAA,CAAM,QAAA;AAErC,EAAA,uBACEA,GAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,eAAA;AAAA,QACA,MAAM,SAAA,IAAa,eAAA;AAAA,QACnB,KAAA,CAAM;AAAA,OACR;AAAA,MACA,sBAAoB,KAAA,CAAM,IAAA;AAAA,MAC1B,mBAAiB,KAAA,CAAM,IAAA;AAAA,MAEvB,QAAA,kBAAAA,GAAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UACE,GAAI,KAAA;AAAA,UACL,KAAA;AAAA,UACA,OAAA;AAAA,UACA,QAAA,EAAU,UAAA;AAAA,UACV,OAAA,EAAS;AAAA;AAAA;AACX;AAAA,GACF;AAEJ","file":"index.js","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n SectionLayoutProps,\r\n GridLayoutProps,\r\n DefaultLayoutProps,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// Context\r\n// ============================================================================\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n// Display name for React DevTools\r\nFormSystemContext.displayName = \"FormSystemContext\";\r\n\r\n// ============================================================================\r\n// Provider\r\n// ============================================================================\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Root provider that enables the form system. Provides component and layout\r\n * registries to FormGenerator and its descendants.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * // Variant-specific components\r\n * compact: {\r\n * text: CompactTextInput,\r\n * },\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): FormElement {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components ?? {},\r\n layouts: layouts ?? {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n// ============================================================================\r\n// Hooks\r\n// ============================================================================\r\n\r\n/**\r\n * Hook to access the form system context.\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\r\n \"[FormKit] useFormSystem must be used within a FormSystemProvider. \" +\r\n \"Make sure to wrap your form components with <FormSystemProvider>.\"\r\n );\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Hook to get a field component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific component: `components[variant][type]`\r\n * 2. Type-specific component: `components[type]`\r\n * 3. Default component: `components[\"default\"]`\r\n * 4. Text fallback: `components[\"text\"]`\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant] !== null) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n const variantComponent = variantComponents[type];\r\n if (variantComponent) {\r\n return variantComponent;\r\n }\r\n }\r\n\r\n // 2. Try type-specific component\r\n const typeComponent = components[type] as FieldComponent | undefined;\r\n if (typeComponent && typeof typeComponent === \"function\") {\r\n return typeComponent;\r\n }\r\n\r\n // 3. Try default component\r\n const defaultComponent = components[\"default\"] as FieldComponent | undefined;\r\n if (defaultComponent && typeof defaultComponent === \"function\") {\r\n return defaultComponent;\r\n }\r\n\r\n // 4. Try text fallback\r\n const textComponent = components[\"text\"] as FieldComponent | undefined;\r\n if (textComponent && typeof textComponent === \"function\") {\r\n return textComponent;\r\n }\r\n\r\n // 5. Development warning and placeholder\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.warn(\r\n `[FormKit] No component found for type \"${type}\"${variant ? ` (variant: \"${variant}\")` : \"\"}. ` +\r\n \"Register a component for this type in your FormSystemProvider.\"\r\n );\r\n return MissingFieldComponent;\r\n }\r\n\r\n // Production: silent fallback\r\n return NullComponent;\r\n}\r\n\r\n/**\r\n * Hook to get a layout component by type and optional variant.\r\n *\r\n * Resolution order:\r\n * 1. Variant-specific layout: `layouts[variant][type]`\r\n * 2. Type-specific layout: `layouts[type]`\r\n * 3. Default layout: `layouts[\"default\"]`\r\n * 4. Built-in default layout\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(\r\n type: LayoutType,\r\n variant?: Variant\r\n): LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps> {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant] !== null) {\r\n const variantLayouts = layouts[variant] as Record<\r\n string,\r\n LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n >;\r\n const variantLayout = variantLayouts[type];\r\n if (variantLayout) {\r\n return variantLayout;\r\n }\r\n }\r\n\r\n // 2. Try type-specific layout\r\n const typeLayout = layouts[type] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (typeLayout && typeof typeLayout === \"function\") {\r\n return typeLayout;\r\n }\r\n\r\n // 3. Try default layout\r\n const defaultLayout = layouts[\"default\"] as\r\n | LayoutComponent<SectionLayoutProps | GridLayoutProps | DefaultLayoutProps>\r\n | undefined;\r\n if (defaultLayout && typeof defaultLayout === \"function\") {\r\n return defaultLayout;\r\n }\r\n\r\n // 4. Built-in default layout\r\n return DefaultLayout;\r\n}\r\n\r\n// ============================================================================\r\n// Fallback Components\r\n// ============================================================================\r\n\r\n/**\r\n * Default layout component - simple div wrapper.\r\n * Used when no layout is registered.\r\n */\r\nfunction DefaultLayout({ children, className }: DefaultLayoutProps): FormElement {\r\n return <div className={className}>{children}</div>;\r\n}\r\n\r\n/**\r\n * Null component for production fallback.\r\n */\r\nfunction NullComponent(): FormElement {\r\n return null;\r\n}\r\n\r\n/**\r\n * Development placeholder for missing field components.\r\n */\r\nfunction MissingFieldComponent({ field }: { field: { type: string; name: string } }): FormElement {\r\n return (\r\n <div\r\n style={{\r\n color: \"#dc2626\",\r\n padding: \"8px 12px\",\r\n border: \"1px dashed #dc2626\",\r\n borderRadius: \"4px\",\r\n fontSize: \"12px\",\r\n fontFamily: \"monospace\",\r\n backgroundColor: \"#fef2f2\",\r\n }}\r\n >\r\n Missing component: <strong>{field.type}</strong> (field: {field.name})\r\n </div>\r\n );\r\n}\r\n","import { clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\nimport type { ClassValue } from \"clsx\";\r\n\r\n/**\r\n * Utility function to merge CSS classes with Tailwind CSS conflict resolution.\r\n *\r\n * Combines `clsx` for conditional class handling with `tailwind-merge`\r\n * for proper Tailwind CSS class conflict resolution.\r\n *\r\n * @param inputs - Class values to merge (strings, arrays, objects, or conditionals)\r\n * @returns Merged and deduplicated class string\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage\r\n * cn(\"px-2 py-1\", \"px-4\") // \"py-1 px-4\"\r\n *\r\n * // Conditional classes\r\n * cn(\"base\", isActive && \"active\", { \"disabled\": isDisabled })\r\n *\r\n * // Arrays\r\n * cn([\"flex\", \"items-center\"], \"gap-2\")\r\n * ```\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n\r\n/**\r\n * Re-export ClassValue type for consumers who need it.\r\n */\r\nexport type { ClassValue };\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type {\r\n FormGeneratorProps,\r\n Section,\r\n BaseField,\r\n Variant,\r\n FormElement,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// FormGenerator Component\r\n// ============================================================================\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema definition, using components registered\r\n * via FormSystemProvider. Supports conditional fields, dynamic layouts,\r\n * and component variants.\r\n *\r\n * @template TFieldValues - Form field values type for type safety\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * interface FormData {\r\n * firstName: string;\r\n * email: string;\r\n * }\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm<FormData>();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n className,\r\n}: FormGeneratorProps<TFieldValues>): FormElement {\r\n // Use provided control or fall back to FormProvider context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control ?? formContext?.control;\r\n\r\n // Early return if no schema\r\n if (!schema?.sections || schema.sections.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-root\",\r\n variant && `formkit-variant-${variant}`,\r\n className\r\n )}\r\n data-formkit-root=\"\"\r\n >\r\n {schema.sections.map((section, index) => (\r\n <SectionRenderer\r\n key={section.id ?? `section-${index}`}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Section Renderer\r\n// ============================================================================\r\n\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields.\r\n * Handles conditional rendering and variant resolution.\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): FormElement {\r\n // Section can override variant\r\n const activeVariant = section.variant ?? variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check section condition\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n collapsible={section.collapsible}\r\n defaultCollapsed={section.defaultCollapsed}\r\n >\r\n {section.render ? (\r\n // Custom render function\r\n section.render({ control, disabled, section })\r\n ) : (\r\n // Standard grid rendering\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n gap={section.gap}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Grid Renderer\r\n// ============================================================================\r\n\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField<TFieldValues>[];\r\n cols?: number;\r\n gap?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout.\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n gap,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): FormElement {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) {\r\n return null;\r\n }\r\n\r\n return (\r\n <GridLayout cols={cols} gap={gap}>\r\n {fields.map((field, index) => (\r\n <FieldWrapper\r\n key={field.name || `field-${index}`}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Field Wrapper\r\n// ============================================================================\r\n\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField<TFieldValues>;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Wraps individual fields with conditional rendering logic.\r\n * Handles field visibility and variant resolution.\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): FormElement {\r\n // Watch form values for conditional rendering\r\n const formValues = useWatch({ control }) as TFieldValues;\r\n\r\n // Check field condition\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Field can override variant\r\n const activeVariant = field.variant ?? variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) {\r\n return null;\r\n }\r\n\r\n // Merge disabled states\r\n const isDisabled = disabled || field.disabled;\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"formkit-field\",\r\n field.fullWidth && \"col-span-full\",\r\n field.className\r\n )}\r\n data-formkit-field={field.name}\r\n data-field-type={field.type}\r\n >\r\n <FieldComponent\r\n {...(field as BaseField<FieldValues>)}\r\n field={field as BaseField<FieldValues>}\r\n control={control as Control<FieldValues>}\r\n disabled={isDisabled}\r\n variant={activeVariant}\r\n />\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Named Exports for Subcomponents (for advanced use cases)\r\n// ============================================================================\r\n\r\nexport { SectionRenderer, GridRenderer, FieldWrapper };\r\n"]}
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@classytic/formkit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Headless, type-safe form generation engine for React. Schema-driven with full TypeScript support.",
5
+ "author": "Classytic",
6
+ "license": "MIT",
5
7
  "type": "module",
8
+ "sideEffects": false,
6
9
  "main": "./dist/index.cjs",
7
10
  "module": "./dist/index.js",
8
11
  "types": "./dist/index.d.ts",
@@ -10,21 +13,35 @@
10
13
  ".": {
11
14
  "types": "./dist/index.d.ts",
12
15
  "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
16
+ "require": "./dist/index.cjs",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "typesVersions": {
22
+ "*": {
23
+ "*": [
24
+ "./dist/*",
25
+ "./dist/index.d.ts"
26
+ ]
14
27
  }
15
28
  },
16
29
  "files": [
17
30
  "dist",
18
31
  "README.md",
19
- "LICENSE"
32
+ "LICENSE",
33
+ "CHANGELOG.md"
20
34
  ],
21
35
  "scripts": {
22
36
  "build": "tsup",
23
37
  "dev": "tsup --watch",
24
- "lint": "tsc --noEmit",
25
- "test": "node --test tests/*.test.js",
26
- "test:watch": "node --test --watch tests/*.test.js",
27
- "prepublishOnly": "npm run lint && npm run build",
38
+ "typecheck": "tsc --noEmit",
39
+ "lint": "eslint src --ext .ts,.tsx",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "test:coverage": "vitest run --coverage",
43
+ "clean": "rimraf dist",
44
+ "prepublishOnly": "npm run clean && npm run typecheck && npm run build && npm run test",
28
45
  "version:patch": "npm version patch",
29
46
  "version:minor": "npm version minor",
30
47
  "version:major": "npm version major",
@@ -35,9 +52,11 @@
35
52
  },
36
53
  "keywords": [
37
54
  "react",
38
- "react19",
55
+ "react-19",
56
+ "react-18",
39
57
  "forms",
40
58
  "form-generator",
59
+ "form-builder",
41
60
  "headless",
42
61
  "schema-driven",
43
62
  "typescript",
@@ -47,10 +66,9 @@
47
66
  "tailwind",
48
67
  "shadcn",
49
68
  "dynamic-forms",
50
- "conditional-fields"
69
+ "conditional-fields",
70
+ "form-schema"
51
71
  ],
52
- "author": "Classytic",
53
- "license": "MIT",
54
72
  "repository": {
55
73
  "type": "git",
56
74
  "url": "git+https://github.com/classytic/formkit.git"
@@ -60,28 +78,43 @@
60
78
  },
61
79
  "homepage": "https://github.com/classytic/formkit#readme",
62
80
  "peerDependencies": {
63
- "react": ">=18.3.0",
64
- "react-dom": ">=18.3.0",
65
- "react-hook-form": ">=7.50.0"
81
+ "react": "^18.0.0 || ^19.0.0",
82
+ "react-dom": "^18.0.0 || ^19.0.0",
83
+ "react-hook-form": "^7.50.0"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "react-dom": {
87
+ "optional": true
88
+ }
66
89
  },
67
90
  "dependencies": {
68
91
  "clsx": "^2.1.1",
69
92
  "tailwind-merge": "^2.5.5"
70
93
  },
71
94
  "devDependencies": {
95
+ "@testing-library/jest-dom": "^6.9.1",
96
+ "@testing-library/react": "^16.0.0",
72
97
  "@types/node": "^22.8.7",
73
98
  "@types/react": "^18.3.12",
74
99
  "@types/react-dom": "^18.3.1",
100
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
101
+ "@typescript-eslint/parser": "^8.0.0",
102
+ "@vitejs/plugin-react": "^4.3.0",
103
+ "eslint": "^9.0.0",
104
+ "happy-dom": "^15.0.0",
75
105
  "react": "^19.0.0",
76
106
  "react-dom": "^19.0.0",
77
107
  "react-hook-form": "^7.53.2",
108
+ "rimraf": "^6.0.0",
78
109
  "tsup": "^8.3.5",
79
- "typescript": "^5.6.3"
110
+ "typescript": "^5.6.3",
111
+ "vitest": "^2.0.0"
80
112
  },
81
113
  "engines": {
82
114
  "node": ">=18.0.0"
83
115
  },
84
116
  "publishConfig": {
85
- "access": "public"
117
+ "access": "public",
118
+ "registry": "https://registry.npmjs.org/"
86
119
  }
87
120
  }