UNPKG

turiy-form

Version:

This package is built on shadcn, zod

614 lines (595 loc) 24.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Button: () => Button, Formx: () => Formx, FormxField: () => FormxField, buttonVariants: () => buttonVariants, equal: () => equal, isObject: () => isObject, regex: () => regex, reset: () => reset }); module.exports = __toCommonJS(src_exports); // src/form/Formx.tsx var import_react4 = __toESM(require("react")); // src/ui/form.tsx var React2 = __toESM(require("react")); var import_react_slot = require("@radix-ui/react-slot"); var import_react_hook_form = require("react-hook-form"); // src/lib/utils.ts var import_clsx = require("clsx"); var import_tailwind_merge = require("tailwind-merge"); function cn(...inputs) { return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs)); } // src/ui/label.tsx var React = __toESM(require("react")); var LabelPrimitive = __toESM(require("@radix-ui/react-label")); var import_class_variance_authority = require("class-variance-authority"); var labelVariants = (0, import_class_variance_authority.cva)( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ); var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement( LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props } )); Label.displayName = LabelPrimitive.Root.displayName; // src/ui/form.tsx var Form = import_react_hook_form.FormProvider; var FormFieldContext = React2.createContext( {} ); var FormField = ({ ...props }) => { return /* @__PURE__ */ React2.createElement(FormFieldContext.Provider, { value: { name: props.name } }, /* @__PURE__ */ React2.createElement(import_react_hook_form.Controller, { ...props })); }; var useFormField = () => { const fieldContext = React2.useContext(FormFieldContext); const itemContext = React2.useContext(FormItemContext); const { getFieldState, formState } = (0, import_react_hook_form.useFormContext)(); const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { throw new Error("useFormField should be used within <FormField>"); } const { id } = itemContext; return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState }; }; var FormItemContext = React2.createContext( {} ); var FormItem = React2.forwardRef(({ className, ...props }, ref) => { const id = React2.useId(); return /* @__PURE__ */ React2.createElement(FormItemContext.Provider, { value: { id } }, /* @__PURE__ */ React2.createElement("div", { ref, className: cn("space-y-2", className), ...props })); }); FormItem.displayName = "FormItem"; var FormLabel = React2.forwardRef(({ className, ...props }, ref) => { const { error, formItemId } = useFormField(); return /* @__PURE__ */ React2.createElement( Label, { ref, className: cn(error && "text-destructive", className), htmlFor: formItemId, ...props } ); }); FormLabel.displayName = "FormLabel"; var FormControl = React2.forwardRef(({ ...props }, ref) => { const { error, formItemId, formDescriptionId, formMessageId } = useFormField(); return /* @__PURE__ */ React2.createElement( import_react_slot.Slot, { ref, id: formItemId, "aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`, "aria-invalid": !!error, ...props } ); }); FormControl.displayName = "FormControl"; var FormDescription = React2.forwardRef(({ className, ...props }, ref) => { const { formDescriptionId } = useFormField(); return /* @__PURE__ */ React2.createElement( "p", { ref, id: formDescriptionId, className: cn("text-sm text-muted-foreground", className), ...props } ); }); FormDescription.displayName = "FormDescription"; var FormMessage = React2.forwardRef(({ className, children, ...props }, ref) => { const { error, formMessageId } = useFormField(); const body = error ? String(error?.message) : children; if (!body) { return null; } return /* @__PURE__ */ React2.createElement( "p", { ref, id: formMessageId, className: cn("text-sm font-medium text-destructive", className), ...props }, body ); }); FormMessage.displayName = "FormMessage"; // src/form/FomxField.tsx var import_react = __toESM(require("react")); var FormxField = import_react.default.forwardRef((props, ref) => /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null)); FormxField.displayName = "FormxField"; // src/form/Formx.tsx var import_zod2 = require("zod"); var import_react_hook_form2 = require("react-hook-form"); var import_zod3 = require("@hookform/resolvers/zod"); // src/form/FormxSelect.tsx var import_react2 = __toESM(require("react")); // src/ui/select.tsx var React4 = __toESM(require("react")); var SelectPrimitive = __toESM(require("@radix-ui/react-select")); var import_lucide_react = require("lucide-react"); var Select = SelectPrimitive.Root; var SelectValue = SelectPrimitive.Value; var SelectTrigger = React4.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.Trigger, { ref, className: cn( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className ), ...props }, children, /* @__PURE__ */ React4.createElement(SelectPrimitive.Icon, { asChild: true }, /* @__PURE__ */ React4.createElement(import_lucide_react.ChevronDown, { className: "h-4 w-4 opacity-50" })) )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; var SelectScrollUpButton = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.ScrollUpButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props }, /* @__PURE__ */ React4.createElement(import_lucide_react.ChevronUp, { className: "h-4 w-4" }) )); SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; var SelectScrollDownButton = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.ScrollDownButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props }, /* @__PURE__ */ React4.createElement(import_lucide_react.ChevronDown, { className: "h-4 w-4" }) )); SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; var SelectContent = React4.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ React4.createElement(SelectPrimitive.Portal, null, /* @__PURE__ */ React4.createElement( SelectPrimitive.Content, { ref, className: cn( "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className ), position, ...props }, /* @__PURE__ */ React4.createElement(SelectScrollUpButton, null), /* @__PURE__ */ React4.createElement( SelectPrimitive.Viewport, { className: cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" ) }, children ), /* @__PURE__ */ React4.createElement(SelectScrollDownButton, null) ))); SelectContent.displayName = SelectPrimitive.Content.displayName; var SelectLabel = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.Label, { ref, className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className), ...props } )); SelectLabel.displayName = SelectPrimitive.Label.displayName; var SelectItem = React4.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.Item, { ref, className: cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className ), ...props }, /* @__PURE__ */ React4.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React4.createElement(SelectPrimitive.ItemIndicator, null, /* @__PURE__ */ React4.createElement(import_lucide_react.Check, { className: "h-4 w-4" }))), /* @__PURE__ */ React4.createElement(SelectPrimitive.ItemText, null, children) )); SelectItem.displayName = SelectPrimitive.Item.displayName; var SelectSeparator = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( SelectPrimitive.Separator, { ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props } )); SelectSeparator.displayName = SelectPrimitive.Separator.displayName; // src/ui/button.tsx var React5 = __toESM(require("react")); var import_react_slot2 = require("@radix-ui/react-slot"); var import_class_variance_authority2 = require("class-variance-authority"); var buttonVariants = (0, import_class_variance_authority2.cva)( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" } }, defaultVariants: { variant: "default", size: "default" } } ); var Button = React5.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? import_react_slot2.Slot : "button"; return /* @__PURE__ */ React5.createElement( Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props } ); } ); Button.displayName = "Button"; // src/form/FormxSelect.tsx var import_lucide_react2 = require("lucide-react"); var FormxSelect = ({ control, name, label, placeholder, options, className, onAdd, description, disabled, required, autoFocus, onFocusChanged }) => { const ref = import_react2.default.useRef(null); import_react2.default.useEffect(() => { if (!!ref.current && !!autoFocus) ref.current.focus(); }, [autoFocus, ref]); return /* @__PURE__ */ import_react2.default.createElement("div", { className: cn(className) }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex w-full gap-2" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-1" }, /* @__PURE__ */ import_react2.default.createElement( FormField, { control, name, render: ({ field }) => /* @__PURE__ */ import_react2.default.createElement(FormItem, null, /* @__PURE__ */ import_react2.default.createElement(FormLabel, null, " ", !disabled && !!required ? /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-red-700" }, "*") : /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null), label), /* @__PURE__ */ import_react2.default.createElement( Select, { onValueChange: field.onChange, defaultValue: field.value, disabled, required: !disabled && !!required, onOpenChange: (focused) => onFocusChanged(focused) }, /* @__PURE__ */ import_react2.default.createElement(FormControl, null, /* @__PURE__ */ import_react2.default.createElement(SelectTrigger, { ref }, /* @__PURE__ */ import_react2.default.createElement(SelectValue, { placeholder }))), /* @__PURE__ */ import_react2.default.createElement(SelectContent, null, options.map((op) => /* @__PURE__ */ import_react2.default.createElement(SelectItem, { key: op.value, value: op.value }, op.label))) ), !!description ? /* @__PURE__ */ import_react2.default.createElement(FormDescription, null, description) : /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null), /* @__PURE__ */ import_react2.default.createElement(FormMessage, null)) } )), onAdd ? /* @__PURE__ */ import_react2.default.createElement("div", { className: "h-full flex flex-col items-center justify-center" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-1" }), /* @__PURE__ */ import_react2.default.createElement( Button, { type: "button", variant: "secondary", onClick: onAdd, className: cn(!!onAdd ? "visible" : "hidden"), disabled }, /* @__PURE__ */ import_react2.default.createElement(import_lucide_react2.PlusIcon, null) )) : /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null))); }; var FormxSelect_default = FormxSelect; // src/form/FormxInput.tsx var import_react3 = __toESM(require("react")); // src/ui/input.tsx var React7 = __toESM(require("react")); var Input = React7.forwardRef( ({ className, type, ...props }, ref) => { return /* @__PURE__ */ React7.createElement( "input", { type, className: cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className ), ref, ...props } ); } ); Input.displayName = "Input"; // src/form/FormxInput.tsx var FormxInput = ({ control, name, label, placeholder, type, className, description, disabled, required, autoFocus, onFocusChanged }) => { const ref = import_react3.default.useRef(null); import_react3.default.useEffect(() => { if (!!ref.current && !!autoFocus) ref.current.focus(); }, [autoFocus, ref]); return /* @__PURE__ */ import_react3.default.createElement("div", { className: cn(className) }, /* @__PURE__ */ import_react3.default.createElement( FormField, { control, name, render: ({ field }) => /* @__PURE__ */ import_react3.default.createElement(FormItem, null, /* @__PURE__ */ import_react3.default.createElement(FormLabel, null, !disabled && !!required ? /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-red-700" }, "*") : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null), label), /* @__PURE__ */ import_react3.default.createElement(FormControl, null, /* @__PURE__ */ import_react3.default.createElement( Input, { ...field, ref, placeholder, type, disabled, required: !disabled && !!required, onFocus: () => onFocusChanged(true), onBlur: () => onFocusChanged(true) } )), !!description ? /* @__PURE__ */ import_react3.default.createElement(FormDescription, null, description) : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null), /* @__PURE__ */ import_react3.default.createElement(FormMessage, null)) } )); }; var FormxInput_default = FormxInput; // src/form/validation.ts var import_zod = require("zod"); var zValid = (label, required, match) => import_zod.z.string(required ? { required_error: `${label} is required!` } : void 0).refine( (x) => required || x.length > 0 ? !!match ? !!match.type ? ( //for string match.type === "string" && (!match.eq || x.length === match.eq) && (!match.min || x.length >= (match.min ?? x.length)) && (!match.max || x.length <= (match.max ?? x.length)) || //for number !isNaN(x) && (!match.min || +x >= (match.min ?? +x)) && (!match.max || +x <= (match.max ?? +x)) ) : x.match(match) : true : true, !!match ? !!match.type ? `${label} ${match.type === "string" ? "length" : ""} should be ${!!match.eq ? `=${match.eq}` : ""} ${!!match.min ? `>=${match.min} ${!!match.max ? `<=${match.max}` : ""}` : ""} !` : `Invalid ${label}!` : "" ); var regex = { email: /^([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})$/i, mobile: /^\+?[1-9]\d{1,14}$/, url: /^(https?:\/\/)?([\w-]+(\.[\w-]+)+)(\/[\w-]*)*\/?$/, pan: /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/, gstin: /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/, aadhar: /^[2-9]{1}[0-9]{3}[0-9]{4}[0-9]{4}$/, cin: /^[LU][0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$/, epic: /^[A-Z]{3}[0-9]{7}$/, pin: /^[1-9][0-9]{2}\s?[0-9]{3}$/, password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{6,}$/ }; // src/form/Formx.tsx var Formx = ({ children, className, onSubmit, onChange, onInit, refine, submitButton, resetButton }) => { const validators = []; const defaultValues = []; const childs = []; import_react4.default.Children.forEach(children, (child) => { if (import_react4.default.isValidElement(child)) { childs.push(child); if (child.type === FormxField) { validators.push([ child.props.name, zValid(child.props.label, !!child.props.required, child.props.match) ]); if (child.props.cached) { const dv = localStorage.getItem(`cached_${child.props.name}`); defaultValues.push([ child.props.name, dv || child.props.defaultValue ]); } else { defaultValues.push([child.props.name, child.props.defaultValue]); } } } }); const validateSchema = import_zod2.z.object(Object.fromEntries(validators)); const refinedSchema = !!refine ? validateSchema.refine((data) => refine.on(data), { message: refine.message, path: refine.path }) : validateSchema; const defaultSchemaValue = Object.fromEntries(defaultValues); const form = (0, import_react_hook_form2.useForm)({ resolver: (0, import_zod3.zodResolver)(refinedSchema), defaultValues: defaultSchemaValue }); import_react4.default.useEffect(() => { if (!!onInit) onInit(form); }, [form]); const _onSubmit = (data) => { childs.forEach((c) => { if (c.props.cached) { localStorage.setItem(`cached_${c.props.name}`, data[c.props.name]); } }); onSubmit(data); }; const [v, setV] = import_react4.default.useState({ selectedFieldName: "" }); const values = form.watch(); import_react4.default.useEffect(() => { if (!!onChange) { const newV = { ...values, selectedFieldName: v.selectedFieldName }; if (JSON.stringify(v) !== JSON.stringify(newV)) { setV(newV); onChange(newV); } } }, [values]); return /* @__PURE__ */ import_react4.default.createElement(Form, { ...form }, /* @__PURE__ */ import_react4.default.createElement("form", { onSubmit: form.handleSubmit(_onSubmit), className: "space-y-8" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: cn(className) }, childs.map( (child) => !!child.props.selectProps ? /* @__PURE__ */ import_react4.default.createElement( FormxSelect_default, { key: child.props.name, control: form.control, name: child.props.name, label: child.props.label, placeholder: child.props.placeholder, options: child.props.selectProps.options, className: child.props.className, onAdd: child.props.selectProps.onAdd, description: child.props.description, disabled: child.props.disabled, required: child.props.required, autoFocus: child.props.autoFocus, onFocusChanged: (focused) => { if (focused) { setV({ ...v, selectedFieldName: child.props.name }); } } } ) : /* @__PURE__ */ import_react4.default.createElement( FormxInput_default, { key: child.props.name, control: form.control, name: child.props.name, label: child.props.label, placeholder: child.props.placeholder, className: child.props.className, type: child.props.type, description: child.props.description, disabled: child.props.disabled, required: child.props.required, autoFocus: child.props.autoFocus, onFocusChanged: (focused) => { if (focused) { setV({ ...v, selectedFieldName: child.props.name }); } } } ) ), submitButton, resetButton))); }; Formx.displayName = "Formx"; // src/form/util.ts var reset = (form, defaultValues) => Object.entries(defaultValues).forEach( ([key, value]) => form.setValue(key, `${value}`) ); function equal(obj1, obj2) { const areObjects = isObject(obj1) && isObject(obj2); if (!areObjects) return obj1 === obj2; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (const key of keys1) if (!equal(obj1[key], obj2[key])) return false; return true; } function isObject(value) { return value !== null && typeof value === "object"; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Button, Formx, FormxField, buttonVariants, equal, isObject, regex, reset });