{"_id":"@artemstakhov/ghost-form","_rev":"6-f2158be078abb378181501e143e0254e","name":"@artemstakhov/ghost-form","dist-tags":{"latest":"1.1.2"},"versions":{"1.0.0":{"name":"@artemstakhov/ghost-form","version":"1.0.0","keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","_id":"@artemstakhov/ghost-form@1.0.0","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"dist":{"shasum":"9be1c11666fb6ac495cc940af32a7574368fefb4","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.0.0.tgz","fileCount":8,"integrity":"sha512-TuAnh49ds4sM+Nhk1J2niBxBiRePeKlyF6CL/kOC8M3satB617RhlS+2OJyWOe7sbcGeBlJJraAkcE2lTOuhXg==","signatures":[{"sig":"MEYCIQDAnQHPGQZbIp9AvbWqlujhXBL346VsKq1I1R2SqWqAugIhAM7ZqrbeW/vwlFO6nCFEZFKi1BzTaePtY4T1k+JaPzuu","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":105752},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"74ea2865f7dc12563dfbc728b376514451331ef5","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","test":"vitest","build":"tsup"},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"_npmVersion":"11.5.1","description":"Atomic, framework-agnostic form engine with persistence and performance.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^28.0.0","react":"^18.0.0","vitest":"^4.0.18","typescript":"^5.0.0","@types/react":"^18.0.0","@testing-library/dom":"^9.3.4","@testing-library/react":"^14.3.1","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ghost-form_1.0.0_1770330889133_0.02830759173427655","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@artemstakhov/ghost-form","version":"1.0.1","keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","_id":"@artemstakhov/ghost-form@1.0.1","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"dist":{"shasum":"91956df313a073598802f7ab75eba30c3141a375","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.0.1.tgz","fileCount":8,"integrity":"sha512-/xKUa+1kw3wjFOiQHGVNUkVcsia8FG7D5sXr1d5r5CMQ+pOyM/1jTJKXfKgqjSfBioh7pcd4G45NThJ3wTzeAw==","signatures":[{"sig":"MEYCIQDQKJxwEAtcH9GFCgEm9F2agiipZ5iwu4a9d+VWOK4/DgIhAJeo95jbdk/By1syYDWJDgbB50sfL14/zDt97zhiSsV5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107816},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"5f9feaab933c4275173bfa6828e00cfacd7554b3","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","test":"vitest","build":"tsup"},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"_npmVersion":"11.5.1","description":"Atomic, framework-agnostic form engine with persistence and performance.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^28.0.0","react":"^18.0.0","vitest":"^4.0.18","typescript":"^5.0.0","@types/react":"^18.0.0","@testing-library/dom":"^9.3.4","@testing-library/react":"^14.3.1","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ghost-form_1.0.1_1770332333291_0.47585529081552536","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@artemstakhov/ghost-form","version":"1.0.2","keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","_id":"@artemstakhov/ghost-form@1.0.2","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"dist":{"shasum":"e7ccd4e8ffb0c8f66f01d46c16c1351a58c4a6cf","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.0.2.tgz","fileCount":8,"integrity":"sha512-1XWy69NpHN7hZUtjaZgRSOhwq07PuZ5dueDFdoLbDIPRvuRmL3dKTAqPb4WdhMFYQo1+QdACTQL5aa3ljIUDhQ==","signatures":[{"sig":"MEUCIQCHe597pECwzfjX0I9weru1zv+PqY16QtSKJiWgWEa6OwIgSLDnX+GHc+NRs9lKJvXCJNRK47OZLLk4NaL5RSBfpcI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107928},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"ce6736630a779630ab28c2dd3e55185b94deb547","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","test":"vitest","build":"tsup"},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"_npmVersion":"11.5.1","description":"Atomic, framework-agnostic form engine with persistence and performance.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^28.0.0","react":"^18.0.0","vitest":"^4.0.18","typescript":"^5.0.0","@types/react":"^18.0.0","@testing-library/dom":"^9.3.4","@testing-library/react":"^14.3.1","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ghost-form_1.0.2_1770332559150_0.41612147380361786","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@artemstakhov/ghost-form","version":"1.1.0","keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","_id":"@artemstakhov/ghost-form@1.1.0","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"dist":{"shasum":"4cd4ab8103b106c86d7d4866f25b038d325cdf49","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.1.0.tgz","fileCount":8,"integrity":"sha512-qWfRYLUFe+1lXXHEzzPxFRxJD86j3Y/anoeY362PMipFWRvWC35TPG89TUI/E7FI+kCvOlV9TNDU2QccA4UNzQ==","signatures":[{"sig":"MEQCICkmYtp2Wl9B7tY5FQqIzwjbUcSh7sfqQlfN+i7hkXjIAiAmnIKiwA9Y6Q+iJFo+R19vcWbauuW+MrzXOXe6KugCMQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":116063},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"c6e8fbde25e951ff1442707f82ff3233598dac78","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","test":"vitest","build":"tsup"},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"_npmVersion":"11.5.1","description":"Atomic, framework-agnostic form engine with persistence and performance.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^28.0.0","react":"^18.0.0","vitest":"^4.0.18","typescript":"^5.0.0","@types/react":"^18.0.0","@testing-library/dom":"^9.3.4","@testing-library/react":"^14.3.1","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ghost-form_1.1.0_1770367369039_0.7424927919209827","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@artemstakhov/ghost-form","version":"1.1.1","keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","_id":"@artemstakhov/ghost-form@1.1.1","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"dist":{"shasum":"5290f7129f1c41c7c53ff3162d5fb8fe8c5f0080","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.1.1.tgz","fileCount":8,"integrity":"sha512-O8YxaOJ0qS6k2ByOy/f+MhAo6lCeQR10luvbMB1Z/nr47xYhMqawFuT4StBnodGMKPRlpK4Wlnhqr8D9c0EKlQ==","signatures":[{"sig":"MEUCIQC18Ckn/p8RF2253/G0ALb1gEbBq+Kk6mZPq9E8OpZ8ywIgXXujDWaUqP19JnxeSwvKg00jOchkYrUZnFHPwNmfgew=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119238},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"c6e8fbde25e951ff1442707f82ff3233598dac78","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","test":"vitest","build":"tsup"},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"_npmVersion":"11.5.1","description":"Atomic, framework-agnostic form engine with persistence and performance.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^28.0.0","react":"^18.0.0","vitest":"^4.0.18","typescript":"^5.0.0","@types/react":"^18.0.0","@testing-library/dom":"^9.3.4","@testing-library/react":"^14.3.1","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ghost-form_1.1.1_1770368665945_0.1432584131856589","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@artemstakhov/ghost-form","version":"1.1.2","description":"Atomic, framework-agnostic form engine with persistence and performance.","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","require":"./dist/index.js","import":"./dist/index.mjs"}},"sideEffects":false,"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest","lint":"tsc --noEmit"},"keywords":["form","react","typescript","atomic","performance"],"author":"","license":"MIT","peerDependencies":{"react":">=18.0.0"},"devDependencies":{"@testing-library/dom":"^9.3.4","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^14.3.1","@types/react":"^18.0.0","jsdom":"^28.0.0","react":"^18.0.0","tsup":"^8.0.0","typescript":"^5.0.0","vitest":"^4.0.18"},"_id":"@artemstakhov/ghost-form@1.1.2","gitHead":"ce1e60edaf83089b84df40052fcc808cc1bc5427","_nodeVersion":"24.7.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-V8+eX7Gb4oX9hHxbLyqgmeBe7QDJ9Yb9YGyGfsY6c6FAau96F5kZw9yKyZ9s2mPbuGjuodc7uwqUXDaVyB/78A==","shasum":"4447791a5b672c24ce32c7d77c23009d6488eb70","tarball":"https://registry.npmjs.org/@artemstakhov/ghost-form/-/ghost-form-1.1.2.tgz","fileCount":8,"unpackedSize":119590,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCgKkIIWJX0LItZ8hklDusU8nQMCp9hLJgA8lvFZu8S2wIgSr1v/GOuhnVcSxGZT+JqtXvVsyrPWuLxXT6aZcrpzII="}]},"_npmUser":{"name":"artemstakhov","email":"artyomstahov33@gmail.com"},"directories":{},"maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ghost-form_1.1.2_1770369240340_0.09393108606224532"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-05T22:34:49.022Z","modified":"2026-02-06T09:14:00.577Z","1.0.0":"2026-02-05T22:34:49.291Z","1.0.1":"2026-02-05T22:58:53.459Z","1.0.2":"2026-02-05T23:02:39.290Z","1.1.0":"2026-02-06T08:42:49.185Z","1.1.1":"2026-02-06T09:04:26.092Z","1.1.2":"2026-02-06T09:14:00.470Z"},"license":"MIT","keywords":["form","react","typescript","atomic","performance"],"description":"Atomic, framework-agnostic form engine with persistence and performance.","maintainers":[{"name":"artemstakhov","email":"artyomstahov33@gmail.com"}],"readme":"# GhostForm 👻\n\n**High-performance, Atomic, Framework-Agnostic Form Engine.**\n\n[![npm version](https://img.shields.io/npm/v/@artemstakhov/ghost-form.svg)](https://www.npmjs.com/package/@artemstakhov/ghost-form)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nGhostForm is designed to outperform traditional form libraries in complex scenarios by separating form logic from the UI lifecycle. It uses a **\"Ghost-like\"** architecture (no Context Providers) and **Atomic** point-to-point updates (only the changing field re-renders).\n\n## Key Features\n\n- ⚛️ **Atomic Updates**: Only the field specifically subscribed to changes re-renders. No root re-renders.\n- 🚀 **Performance**: Built on vanilla TS + `useSyncExternalStore`.\n- 💾 **Persistence**: Built-in support for Sync/Async storage (localStorage, AsyncStorage, etc.).\n- 🛡️ **Strict Types**: Deeply typed paths (`user.profile.bio`) and values.\n- 🔌 **Framework Agnostic Core**: Logic is separated from React bindings.\n- 🎮 **Controller Support**: Easy integration with third-party libraries (MUI, Shadcn UI, etc).\n\n## Installation\n\n```bash\nnpm install @artemstakhov/ghost-form\n```\n\n## Quick Start\n\n### 1. Basic Usage with `GhostInput` Pattern\n\nThe most performant way to use GhostForm is by creating a wrapper component for your inputs. This ensures only that specific input re-renders when typed into.\n\n```tsx\nimport { useForm, useField, FormEngine } from '@artemstakhov/ghost-form';\n\n// 1. Create a Reusable Field Wrapper\nconst Input = ({ form, name, label }: { form: FormEngine<any>, name: string, label: string }) => {\n  // useField subscribes ONLY to this field's changes\n  const { value, onChange, onBlur, error, isTouched } = useField(form, name);\n\n  return (\n    <div className=\"mb-4\">\n      <label>{label}</label>\n      <input\n        value={value || ''}\n        onChange={onChange}\n        onBlur={onBlur}\n        className={error && isTouched ? 'error' : ''}\n      />\n      {error && isTouched && <span className=\"text-red-500\">{error}</span>}\n    </div>\n  );\n};\n\n// 2. Use it in your form\nexport const App = () => {\n  const { form, handleSubmit } = useForm({\n    initialValues: { name: 'Alice', email: '' },\n    validate: (values) => {\n      const errors: any = {};\n      if (!values.name) errors.name = 'Required';\n      if (!values.email.includes('@')) errors.email = 'Invalid email';\n      return errors;\n    }\n  });\n\n  const onSubmit = (data) => console.log('Submitted:', data);\n\n  return (\n    <form onSubmit={handleSubmit(onSubmit)}>\n      <Input form={form} name=\"name\" label=\"Name\" />\n      <Input form={form} name=\"email\" label=\"Email\" />\n      <button type=\"submit\">Submit</button>\n    </form>\n  );\n};\n```\n\n### 2. Using `Controller` (Third-party integrations)\n\nFor complex components like Select, DatePicker, or PhoneInputs that don't expose a simple `onChange(e)` event, use the `<Controller />` component.\n\n```tsx\nimport { Controller } from '@artemstakhov/ghost-form';\nimport PhoneInput from 'react-phone-number-input';\n\n<Controller\n  control={form}\n  name=\"phone\"\n  render={({ field: { value, onChange, onBlur }, fieldState: { error } }) => (\n    <div>\n      <PhoneInput\n        value={value}\n        onChange={onChange}\n        onBlur={onBlur}\n      />\n      {error && <span>{error}</span>}\n    </div>\n  )}\n/>\n```\n\n## API Reference\n\n### `useForm<T>(config)`\n\nInitializes the form engine.\n\n**Config:**\n- `initialValues`: Initial state object.\n- `mode`: Validation mode (`'onChange' | 'onBlur' | 'all'`).\n- `validate`: Synchronous validation function returning an error object.\n- `storage`: Optional persistence configuration.\n\n**Returns:**\n- `form`: The `FormEngine` instance (pass this to fields).\n- `formState`: Reactive object containing `isValid`, `isSubmitting`, `isDirty`, etc.\n- `handleSubmit`: Wrapper for form submission.\n- `reset`: Function to reset form to initial values.\n\n### `useField(form, name)`\n\nHook to subscribe a component to a specific field.\n\n**Returns:**\n- `value`: Current value.\n- `onChange`: Handler for HTML inputs or direct values.\n- `onBlur`: Blur handler.\n- `error`: Error message string (if any).\n- `isTouched`: Boolean indicating if field has been touched.\n- `isDirty`: Boolean indicating if value differs from initial.\n\n### `Controller`\n\nComponent wrapper for integrating uncontrolled components or complex UI libraries.\n\n**Props:**\n- `control`: The `form` instance returned from `useForm`.\n- `name`: Path to the field value.\n- `render`: Render prop receiving `field` (onChange, onBlur, value) and `fieldState`.\n\n## Advanced: Dynamic Validation\n\nYou can update validation logic on the fly. This is useful for dynamic fields where the schema changes based on user interaction.\n\n```tsx\nconst { form } = useForm({\n  initialValues: {},\n  validate: (values) => {\n    // This function will re-run whenever this component renders \n    // ensuring it catches closure variables (like dynamic field lists)\n    const errors = {};\n    dynamicFields.forEach(field => {\n        if(!values[field.id]) errors[field.id] = \"Required\";\n    });\n    return errors;\n  }\n});\n```\n\n## License\n\nMIT © [Artem Stakhov](https://github.com/artemstakhov)\n","readmeFilename":"README.md"}