@startsimpli/forms 0.1.2 → 0.1.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 +0 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +0 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useForm.ts","../src/components/FormField.tsx","../src/components/FormSection.tsx","../src/components/FormError.tsx","../src/components/FormRow.tsx"],"names":["useRef","useState","useCallback","jsxs","jsx"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useForm.ts","../src/components/FormField.tsx","../src/components/FormSection.tsx","../src/components/FormError.tsx","../src/components/FormRow.tsx"],"names":["useRef","useState","useCallback","jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { useState, useCallback, useRef } from 'react'\nimport type { ZodSchema, ZodError } from 'zod'\n\nexport interface UseFormOptions<T> {\n initial?: Partial<T>\n}\n\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n /** Current form values. Partial because fields may not be set yet.\n * For the fully validated T, use the data passed to handleSubmit's onValid callback. */\n values: Partial<T>\n errors: Partial<Record<keyof T, string>>\n isSubmitting: boolean\n isDirty: boolean\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void\n setError: (field: keyof T, message: string) => void\n clearError: (field: keyof T) => void\n /** Returns a submit handler. The onValid callback receives the fully validated T from schema.safeParse. */\n handleSubmit: (onValid: (data: T) => Promise<void>) => (e?: React.FormEvent) => Promise<void>\n reset: (values?: Partial<T>) => void\n}\n\nfunction mapZodErrors<T extends Record<string, unknown>>(\n error: ZodError\n): Partial<Record<keyof T, string>> {\n const mapped: Partial<Record<keyof T, string>> = {}\n for (const issue of error.issues) {\n const key = issue.path[0]\n if (key !== undefined && !(key in mapped)) {\n mapped[key as keyof T] = issue.message\n }\n }\n return mapped\n}\n\nexport function useForm<T extends Record<string, unknown>>(\n schema: ZodSchema<T>,\n options?: UseFormOptions<T>\n): UseFormReturn<T> {\n const initialValues = options?.initial ?? {}\n const initialRef = useRef(initialValues)\n\n const [values, setValues] = useState<Partial<T>>(initialValues)\n const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [isDirty, setIsDirty] = useState(false)\n\n const setFieldValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => {\n setValues(prev => ({ ...prev, [field]: value }))\n setIsDirty(true)\n // Clear error for this field when user changes it\n setErrors(prev => {\n if (!(field in prev)) return prev\n const next = { ...prev }\n delete next[field]\n return next\n })\n }, [])\n\n const setError = useCallback((field: keyof T, message: string) => {\n setErrors(prev => ({ ...prev, [field]: message }))\n }, [])\n\n const clearError = useCallback((field: keyof T) => {\n setErrors(prev => {\n if (!(field in prev)) return prev\n const next = { ...prev }\n delete next[field]\n return next\n })\n }, [])\n\n const handleSubmit = useCallback(\n (onValid: (data: T) => Promise<void>) => {\n return async (e?: React.FormEvent) => {\n if (e) e.preventDefault()\n\n const result = schema.safeParse(values)\n\n if (!result.success) {\n setErrors(mapZodErrors<T>(result.error))\n return\n }\n\n setIsSubmitting(true)\n try {\n await onValid(result.data)\n } finally {\n setIsSubmitting(false)\n }\n }\n },\n [schema, values]\n )\n\n const reset = useCallback(\n (newValues?: Partial<T>) => {\n setValues(newValues ?? initialRef.current)\n setErrors({})\n setIsDirty(false)\n },\n []\n )\n\n return {\n values,\n errors,\n isSubmitting,\n isDirty,\n setFieldValue,\n setError,\n clearError,\n handleSubmit,\n reset,\n }\n}\n","import React from 'react'\n\nexport interface FormFieldProps {\n label: string\n error?: string\n required?: boolean\n children: React.ReactNode\n className?: string\n}\n\nexport function FormField({\n label,\n error,\n required = false,\n children,\n className = '',\n}: FormFieldProps) {\n return (\n <div className={`space-y-2 ${className}`.trim()}>\n <label className=\"text-sm font-medium leading-none\">\n {label}\n {required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {children}\n {error && <p className=\"text-sm text-red-600\">{error}</p>}\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormSectionProps {\n title?: string\n children: React.ReactNode\n className?: string\n}\n\nexport function FormSection({\n title,\n children,\n className = '',\n}: FormSectionProps) {\n return (\n <div className={`space-y-4 ${className}`.trim()}>\n {title && (\n <h3 className=\"text-sm font-medium leading-none\">{title}</h3>\n )}\n <div className=\"space-y-4\">{children}</div>\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormErrorProps {\n message: string\n className?: string\n}\n\nexport function FormError({ message, className = '' }: FormErrorProps) {\n return (\n <div\n role=\"alert\"\n className={`text-sm text-red-600 bg-red-50 p-3 rounded border border-red-200 ${className}`.trim()}\n >\n {message}\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormRowProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FormRow({ children, className = '' }: FormRowProps) {\n return (\n <div className={`grid gap-4 sm:grid-cols-2 ${className}`.trim()}>\n {children}\n </div>\n )\n}\n"]}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useForm.ts","../src/components/FormField.tsx","../src/components/FormSection.tsx","../src/components/FormError.tsx","../src/components/FormRow.tsx"],"names":["jsxs","jsx"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useForm.ts","../src/components/FormField.tsx","../src/components/FormSection.tsx","../src/components/FormError.tsx","../src/components/FormRow.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useCallback, useRef } from 'react'\nimport type { ZodSchema, ZodError } from 'zod'\n\nexport interface UseFormOptions<T> {\n initial?: Partial<T>\n}\n\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n /** Current form values. Partial because fields may not be set yet.\n * For the fully validated T, use the data passed to handleSubmit's onValid callback. */\n values: Partial<T>\n errors: Partial<Record<keyof T, string>>\n isSubmitting: boolean\n isDirty: boolean\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void\n setError: (field: keyof T, message: string) => void\n clearError: (field: keyof T) => void\n /** Returns a submit handler. The onValid callback receives the fully validated T from schema.safeParse. */\n handleSubmit: (onValid: (data: T) => Promise<void>) => (e?: React.FormEvent) => Promise<void>\n reset: (values?: Partial<T>) => void\n}\n\nfunction mapZodErrors<T extends Record<string, unknown>>(\n error: ZodError\n): Partial<Record<keyof T, string>> {\n const mapped: Partial<Record<keyof T, string>> = {}\n for (const issue of error.issues) {\n const key = issue.path[0]\n if (key !== undefined && !(key in mapped)) {\n mapped[key as keyof T] = issue.message\n }\n }\n return mapped\n}\n\nexport function useForm<T extends Record<string, unknown>>(\n schema: ZodSchema<T>,\n options?: UseFormOptions<T>\n): UseFormReturn<T> {\n const initialValues = options?.initial ?? {}\n const initialRef = useRef(initialValues)\n\n const [values, setValues] = useState<Partial<T>>(initialValues)\n const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [isDirty, setIsDirty] = useState(false)\n\n const setFieldValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => {\n setValues(prev => ({ ...prev, [field]: value }))\n setIsDirty(true)\n // Clear error for this field when user changes it\n setErrors(prev => {\n if (!(field in prev)) return prev\n const next = { ...prev }\n delete next[field]\n return next\n })\n }, [])\n\n const setError = useCallback((field: keyof T, message: string) => {\n setErrors(prev => ({ ...prev, [field]: message }))\n }, [])\n\n const clearError = useCallback((field: keyof T) => {\n setErrors(prev => {\n if (!(field in prev)) return prev\n const next = { ...prev }\n delete next[field]\n return next\n })\n }, [])\n\n const handleSubmit = useCallback(\n (onValid: (data: T) => Promise<void>) => {\n return async (e?: React.FormEvent) => {\n if (e) e.preventDefault()\n\n const result = schema.safeParse(values)\n\n if (!result.success) {\n setErrors(mapZodErrors<T>(result.error))\n return\n }\n\n setIsSubmitting(true)\n try {\n await onValid(result.data)\n } finally {\n setIsSubmitting(false)\n }\n }\n },\n [schema, values]\n )\n\n const reset = useCallback(\n (newValues?: Partial<T>) => {\n setValues(newValues ?? initialRef.current)\n setErrors({})\n setIsDirty(false)\n },\n []\n )\n\n return {\n values,\n errors,\n isSubmitting,\n isDirty,\n setFieldValue,\n setError,\n clearError,\n handleSubmit,\n reset,\n }\n}\n","import React from 'react'\n\nexport interface FormFieldProps {\n label: string\n error?: string\n required?: boolean\n children: React.ReactNode\n className?: string\n}\n\nexport function FormField({\n label,\n error,\n required = false,\n children,\n className = '',\n}: FormFieldProps) {\n return (\n <div className={`space-y-2 ${className}`.trim()}>\n <label className=\"text-sm font-medium leading-none\">\n {label}\n {required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {children}\n {error && <p className=\"text-sm text-red-600\">{error}</p>}\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormSectionProps {\n title?: string\n children: React.ReactNode\n className?: string\n}\n\nexport function FormSection({\n title,\n children,\n className = '',\n}: FormSectionProps) {\n return (\n <div className={`space-y-4 ${className}`.trim()}>\n {title && (\n <h3 className=\"text-sm font-medium leading-none\">{title}</h3>\n )}\n <div className=\"space-y-4\">{children}</div>\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormErrorProps {\n message: string\n className?: string\n}\n\nexport function FormError({ message, className = '' }: FormErrorProps) {\n return (\n <div\n role=\"alert\"\n className={`text-sm text-red-600 bg-red-50 p-3 rounded border border-red-200 ${className}`.trim()}\n >\n {message}\n </div>\n )\n}\n","import React from 'react'\n\nexport interface FormRowProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FormRow({ children, className = '' }: FormRowProps) {\n return (\n <div className={`grid gap-4 sm:grid-cols-2 ${className}`.trim()}>\n {children}\n </div>\n )\n}\n"]}
|