{"_id":"@ahmad_009/querycraft","_rev":"3-cc14684a7325a36e75ae0a41dba9b951","name":"@ahmad_009/querycraft","dist-tags":{"latest":"2.0.2"},"versions":{"2.0.0":{"name":"@ahmad_009/querycraft","version":"2.0.0","keywords":["react","query-builder","sql","filter","mongodb","graphql","typescript"],"author":{"name":"QueryCraft Contributors"},"license":"MIT","_id":"@ahmad_009/querycraft@2.0.0","maintainers":[{"name":"ahmad_009","email":"malikahmadse@gmail.com"}],"homepage":"https://github.com/yourname/querycraft#readme","bugs":{"url":"https://github.com/yourname/querycraft/issues"},"dist":{"shasum":"f7b95cd0c6875ac6feb89a5af3713da331ed92e5","tarball":"https://registry.npmjs.org/@ahmad_009/querycraft/-/querycraft-2.0.0.tgz","fileCount":34,"integrity":"sha512-jNoSAArrz8DSUn/foHTcRM3wjev3nBdhhAmnTJVECRETS6PGmBtuPsydrwkGXmP/zDrXc2xmGUu1+dCGF7tiSw==","signatures":[{"sig":"MEUCIQDngzn4ETCE0g/YHnjI1uYHHb1PpjHY0X7sq4Uynvrq7QIgUCBmZkfnxsPvk7dLiy5HgiMog2PXqX3dQtCSOhlGX90=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":224548},"main":"dist/cjs/index.js","types":"dist/cjs/index.d.ts","module":"dist/esm/index.js","engines":{"node":">=16.0.0"},"exports":{".":{"types":"./dist/cjs/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"scripts":{"test":"vitest run","build":"npm run build:cjs && npm run build:esm","clean":"rimraf dist","build:cjs":"tsc -p tsconfig.cjs.json","build:esm":"tsc -p tsconfig.esm.json","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run build"},"_npmUser":{"name":"ahmad_009","email":"malikahmadse@gmail.com"},"repository":{"url":"git+https://github.com/yourname/querycraft.git","type":"git"},"_npmVersion":"11.9.0","description":"A flexible production-ready React query builder with custom operators, SQL/JSON/MongoDB/GraphQL output, drag-and-drop, undo/redo, and a headless hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.1.0","rimraf":"^5.0.10","vitest":"^1.6.0","typescript":"^5.4.0","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"react":">=17.0.0"},"_npmOperationalInternal":{"tmp":"tmp/querycraft_2.0.0_1774043335480_0.43507650111854845","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@ahmad_009/querycraft","version":"2.0.1","keywords":["react","query-builder","sql","filter","mongodb","graphql","typescript"],"author":{"name":"QueryCraft Contributors"},"license":"MIT","_id":"@ahmad_009/querycraft@2.0.1","maintainers":[{"name":"ahmad_009","email":"malikahmadse@gmail.com"}],"homepage":"https://github.com/yourname/querycraft#readme","bugs":{"url":"https://github.com/yourname/querycraft/issues"},"dist":{"shasum":"0105a811210d94e6d1d1e52dbfb9c9d0aae68f90","tarball":"https://registry.npmjs.org/@ahmad_009/querycraft/-/querycraft-2.0.1.tgz","fileCount":59,"integrity":"sha512-gs/w77bXHCv64HSf4TiUaRRL9JHIeZOF+m2tqZ9jGimbqtn9jHeSyroKK6BJ6wmGb09xZ9My6PAIfl8Rx4op/w==","signatures":[{"sig":"MEQCIC4D2BYuevHLz7z78wX0JQ11ymHIcqKy6jT8BT6wukEOAiAPw3IHpf3KVloBXJvDRdbyKdrTQR0/mTm5exqtkEWBDg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":621589},"main":"dist/cjs/index.js","types":"dist/cjs/index.d.ts","module":"dist/esm/index.js","engines":{"node":">=16.0.0"},"exports":{".":{"types":"./dist/cjs/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"scripts":{"test":"vitest run","build":"npm run build:cjs && npm run build:esm","clean":"rimraf dist","build:cjs":"tsc -p tsconfig.cjs.json","build:esm":"tsc -p tsconfig.esm.json","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run build"},"_npmUser":{"name":"ahmad_009","email":"malikahmadse@gmail.com"},"repository":{"url":"git+https://github.com/yourname/querycraft.git","type":"git"},"_npmVersion":"11.9.0","description":"A flexible production-ready React query builder with custom operators, SQL/JSON/MongoDB/GraphQL output, drag-and-drop, undo/redo, and a headless hook.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.1.0","rimraf":"^5.0.10","vitest":"^1.6.0","typescript":"^5.4.0","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"react":">=17.0.0"},"_npmOperationalInternal":{"tmp":"tmp/querycraft_2.0.1_1774059199316_0.08511031590420304","host":"s3://npm-registry-packages-npm-production"}},"2.0.2":{"name":"@ahmad_009/querycraft","publishConfig":{"access":"public"},"version":"2.0.2","description":"A flexible production-ready React query builder with custom operators, SQL/JSON/MongoDB/GraphQL output, drag-and-drop, undo/redo, and a headless hook.","main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/cjs/index.d.ts","sideEffects":false,"scripts":{"build:cjs":"tsc -p tsconfig.cjs.json","build:esm":"tsc -p tsconfig.esm.json","build":"npm run build:cjs && npm run build:esm","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run","test:watch":"vitest","clean":"rimraf dist","prepublishOnly":"npm run typecheck && npm run build"},"exports":{".":{"import":"./dist/esm/index.js","require":"./dist/cjs/index.js","types":"./dist/cjs/index.d.ts"}},"keywords":["react","query-builder","sql","filter","mongodb","graphql","typescript"],"engines":{"node":">=16.0.0"},"peerDependencies":{"react":">=17.0.0"},"devDependencies":{"@types/react":"^18.3.0","@types/react-dom":"^18.3.0","jsdom":"^24.1.0","rimraf":"^5.0.10","typescript":"^5.4.0","vitest":"^1.6.0"},"repository":{"type":"git","url":"git+https://github.com/yourname/querycraft.git"},"license":"MIT","author":{"name":"QueryCraft Contributors"},"_id":"@ahmad_009/querycraft@2.0.2","bugs":{"url":"https://github.com/yourname/querycraft/issues"},"homepage":"https://github.com/yourname/querycraft#readme","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-JWy7w0tz3XTu00wb+UQ0WExRl3iB0R1FH8OQXdIl5KE0TJhFnl0oWFmhEMAQo0p2aOEG7lVyap/bDCv6jwQedQ==","shasum":"f39bd88932f1d8a3aa26c4376743a29717f20d9c","tarball":"https://registry.npmjs.org/@ahmad_009/querycraft/-/querycraft-2.0.2.tgz","fileCount":59,"unpackedSize":625421,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD7S2FekahnBBwKMN+GMoOoJvxcoxAJCWGB5coSzBKtuQIgIhGdG2rXhS5lxuYypU3pGFEnnqC+HbOULkUU+kcP5JI="}]},"_npmUser":{"name":"ahmad_009","email":"malikahmadse@gmail.com"},"directories":{},"maintainers":[{"name":"ahmad_009","email":"malikahmadse@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/querycraft_2.0.2_1774059761607_0.32970937575019765"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-20T21:48:55.378Z","modified":"2026-03-21T02:22:41.943Z","2.0.0":"2026-03-20T21:48:55.652Z","2.0.1":"2026-03-21T02:13:19.484Z","2.0.2":"2026-03-21T02:22:41.822Z"},"bugs":{"url":"https://github.com/yourname/querycraft/issues"},"author":{"name":"QueryCraft Contributors"},"license":"MIT","homepage":"https://github.com/yourname/querycraft#readme","keywords":["react","query-builder","sql","filter","mongodb","graphql","typescript"],"repository":{"type":"git","url":"git+https://github.com/yourname/querycraft.git"},"description":"A flexible production-ready React query builder with custom operators, SQL/JSON/MongoDB/GraphQL output, drag-and-drop, undo/redo, and a headless hook.","maintainers":[{"name":"ahmad_009","email":"malikahmadse@gmail.com"}],"readme":"# QueryCraft\n\nA flexible, production-ready React query builder library. Build visual filter UIs that export to SQL, JSON, MongoDB, or GraphQL — with custom operators, drag-and-drop, undo/redo, and a headless hook. The default UI lays out each rule as **numbered steps** (column → match → value) and optional **IF / THEN / ELSE** and **formula** flows in the same style.\n\n---\n\n## Install\n\n```bash\nnpm i @ahmad_009/querycraft\n```\n\n---\n\n## Quick Start\n\n```tsx\nimport { QueryBuilder } from 'querycraft';\nimport type { Field, RuleGroup } from 'querycraft';\nimport { useState } from 'react';\n\nconst fields: Field[] = [\n  { name: 'name',   label: 'Name',   type: 'string'  },\n  { name: 'age',    label: 'Age',    type: 'number'  },\n  { name: 'active', label: 'Active', type: 'boolean' },\n  { name: 'role',   label: 'Role',   type: 'select',\n    options: [\n      { label: 'Admin', value: 'admin' },\n      { label: 'User',  value: 'user'  },\n    ],\n  },\n];\n\nexport default function App() {\n  const [query, setQuery] = useState<RuleGroup | undefined>();\n\n  return (\n    <QueryBuilder\n      fields={fields}\n      onChange={setQuery}\n      showOutput\n      draggable\n    />\n  );\n}\n```\n\n---\n\n## Built-in UI layout\n\nThe default `QueryBuilder` uses a **step hierarchy** so filters read top to bottom instead of a wide grid of equal columns.\n\n- **Root group** — Pick **AND / OR / NOT** to define how rules inside the group combine. A short **structure** line (root only) reminds you: combinator → list of rules → optional nested groups.\n- **Each rule** — One card with **three numbered steps**:\n  1. **Column** — Which field you are filtering.\n  2. **Match** — The operator (equals, contains, greater than, …).\n  3. **Value** — What to compare against (or a note when no value is needed).\n- **Header row** — Drag handle (if `draggable`) plus remove; a one-line hint explains the flow.\n- **Nested groups** — Indented blocks with their own combinator; rules inside use the same three-step layout.\n\n**Flexible operands** (default `enableFlexibleOperands !== false`): for unary operators without a custom value editor, step **Value** includes a **Compare to** dropdown — *text or number I type*, *another column*, or *saved value* (from `variables`) — then the matching control.\n\n---\n\n## Expression operators & visual formulas\n\nWith **`enableExpressionOperators`** (default `true`), two extra operators are merged in for supported field types:\n\n| Operator (label) | What the UI looks like |\n|------------------|-------------------------|\n| **IF / THEN / ELSE** | **Three numbered blocks**: (1) **When “{field}”…** — condition (simple compare or optional custom expression), (2) **Then show**, (3) **Otherwise show**. Each branch can be a column, fixed text, a variable, or **calculate** (opens the visual formula builder). |\n| **Calculate / Formula** | **Step 1** — Choose the kind of calculation (combine text, math, round, checks, nested IF, or type your own). **Step 2** — Parameters in a nested card (only the fields that choice needs). |\n\n**Custom condition** (link under the simple IF condition) reuses the same **column → how it relates → compared to** steps; **Type formula yourself (expert)** switches to a raw formula field.\n\nYou can reuse pieces in your own operators:\n\n```tsx\nimport {\n  EXPRESSION_EXTENSION_OPERATORS,\n  VisualFormulaBuilder,\n  IfThenElseValueEditor,\n  MathFormulaValueEditor,\n} from 'querycraft';\n\n// Turn off the built-in IF/formula operators\n<QueryBuilder fields={fields} enableExpressionOperators={false} />\n\n// Plain value inputs only (no “another column” / variables on unary rules)\n<QueryBuilder fields={fields} enableFlexibleOperands={false} />\n```\n\nPass **`variables`** / **`variableValues`** on `QueryBuilder` so “saved value” modes and `@[name]` in formulas resolve correctly (see types: `QueryVariable`).\n\n---\n\n## Field Types\n\n| Type       | Value editor rendered    | Default operators             |\n|------------|--------------------------|-------------------------------|\n| `string`   | Text input               | equals, contains, starts with… |\n| `number`   | Number input             | =, !=, >, <, >=, <=, between… |\n| `boolean`  | True / False dropdown    | is true, is false             |\n| `date`     | Date picker              | before, after, between…       |\n| `datetime` | Datetime picker          | same as date                  |\n| `select`   | Options dropdown         | equals, not equals, is one of |\n| `array`    | Text input               | contains, is empty, length is |\n| `custom`   | Your own component       | all operators                 |\n\n---\n\n## Output Formats\n\n```tsx\n// Auto-output on every change\n<QueryBuilder\n  fields={fields}\n  outputFormat=\"sql\"           // 'sql' | 'json' | 'mongodb' | 'graphql'\n  onOutputChange={str => console.log(str)}\n  showOutput                   // built-in output panel\n/>\n```\n\nOr use the hook directly:\n\n```tsx\nimport { useQueryBuilder } from 'querycraft';\n\nconst qb = useQueryBuilder({ fields });\n\n// SQL — parameterized (safe for DB drivers)\nconst { sql, params } = qb.toSQL('users', true);\n// → SELECT * FROM users WHERE name = $1 AND age > $2\n// params: ['Ali', 18]\n\n// SQL — inline values (for display only)\nconst { rawSql } = qb.toSQL('users', false);\n// → SELECT * FROM users WHERE name = 'Ali' AND age > 18\n\n// JSON\nconst json = qb.toJSON();\n// → { combinator: 'AND', rules: [{ field: 'age', operator: '>', value: 18 }] }\n\n// MongoDB\nconst mongo = qb.toMongoDB();\n// → { $and: [{ age: { $gt: 18 } }] }\n\n// GraphQL\nconst gql = qb.toGraphQL();\n```\n\n---\n\n## Custom Operators\n\n### Add extra operators (merged into defaults)\n\n```tsx\nimport type { Operator } from 'querycraft';\n\nconst extraOperators: Operator[] = [\n  {\n    name: 'soundsLike',\n    label: 'sounds like',\n    arity: 'unary',\n    fieldTypes: ['string'],\n    sqlTemplate: \"SOUNDEX({field}) = SOUNDEX({value})\",\n    evaluator: (fv, rv) => String(fv)[0]?.toLowerCase() === String(rv)[0]?.toLowerCase(),\n  },\n  {\n    name: 'withinKm',\n    label: 'within km',\n    arity: 'unary',\n    fieldTypes: ['number'],\n    sqlTemplate: \"ST_Distance({field}, POINT({value})) < 1000\",\n  },\n];\n\n<QueryBuilder fields={fields} extraOperators={extraOperators} />\n```\n\n### Replace all operators\n\n```tsx\nconst myOperators: Operator[] = [\n  { name: '=',    label: 'Is',        arity: 'unary', fieldTypes: ['string', 'number'] },\n  { name: '!=',   label: 'Is not',    arity: 'unary', fieldTypes: ['string', 'number'] },\n  { name: 'like', label: 'Like',      arity: 'unary', fieldTypes: ['string'],\n    sqlTemplate: \"{field} LIKE '%{value}%'\" },\n];\n\n<QueryBuilder fields={fields} operators={myOperators} />\n```\n\n### Restrict operators per field\n\n```tsx\nconst fields: Field[] = [\n  {\n    name: 'status',\n    label: 'Status',\n    type: 'string',\n    operators: ['=', '!=', 'in'],   // only these operators shown for this field\n  },\n];\n```\n\n---\n\n## Operator Interface\n\n```ts\ninterface Operator {\n  name: string;       // unique key: '=', 'contains', 'withinKm'\n  label: string;      // shown in UI: 'equals', 'contains', 'within km'\n\n  arity?:\n    | 'none'          // no value input  (e.g. 'is null', 'is today')\n    | 'unary'         // single input    (default)\n    | 'between';      // two inputs      (from / to)\n\n  fieldTypes?: FieldType[];   // which field types this appears for\n                              // omit = appears for ALL types\n\n  sqlTemplate?: string;       // '{field} LIKE \\'%{value}%\\''\n                              // use {value[0]}, {value[1]} for between\n\n  mongoOp?: string;           // '$gt', '$regex', etc.\n\n  evaluator?: (fieldValue: unknown, ruleValue: RuleValue) => boolean;\n              // for client-side filterRecords()\n\n  valueEditor?: React.ComponentType<ValueEditorProps>;\n              // custom value input for this operator\n}\n```\n\n---\n\n## Custom Renderers\n\n### Custom value editor (per field)\n\n```tsx\nimport type { ValueEditorProps } from 'querycraft';\n\nfunction SliderEditor({ value, onChange }: ValueEditorProps) {\n  return (\n    <input\n      type=\"range\" min={0} max={100}\n      value={Number(value) || 0}\n      onChange={e => onChange(e.target.value)}\n    />\n  );\n}\n\nconst fields: Field[] = [\n  { name: 'score', label: 'Score', type: 'number', valueEditor: SliderEditor },\n];\n```\n\n### Custom value editor (per operator)\n\n```tsx\nconst operators: Operator[] = [\n  { name: 'between', label: 'between', arity: 'between',\n    fieldTypes: ['number'], valueEditor: MyRangePickerComponent },\n];\n```\n\n### Override priority\n\n`operator.valueEditor` → `field.valueEditor` → `props.valueEditor` → built-in default\n\n### Custom field / operator selectors\n\n```tsx\nimport type { FieldSelectorProps, OperatorSelectorProps } from 'querycraft';\n\nfunction MyFieldSelector({ fields, value, onChange }: FieldSelectorProps) {\n  return (\n    <div className=\"my-field-selector\">\n      {fields.map(f => (\n        <button key={f.name} onClick={() => onChange(f.name)}\n          className={value === f.name ? 'active' : ''}>\n          {f.label}\n        </button>\n      ))}\n    </div>\n  );\n}\n\n<QueryBuilder\n  fields={fields}\n  fieldSelector={MyFieldSelector}\n  operatorSelector={MyOperatorSelector}\n  combinatorSelector={MyCombinatorSelector}\n/>\n```\n\n### Custom rule / group actions\n\n```tsx\n<QueryBuilder\n  fields={fields}\n  ruleActions={({ rule, onRemove }) => (\n    <>\n      <button onClick={onRemove}>Delete</button>\n      <button onClick={() => duplicate(rule)}>Duplicate</button>\n    </>\n  )}\n  groupActions={({ group, onRemove }) => (\n    <button onClick={onRemove}>Remove group</button>\n  )}\n/>\n```\n\n---\n\n## Headless Mode\n\nUse the engine without any UI:\n\n```tsx\nimport { useQueryBuilder } from 'querycraft';\n\nfunction MyCustomUI() {\n  const qb = useQueryBuilder({\n    fields,\n    extraOperators: myOps,\n    onChange: q => console.log(q),\n    validator: (rule, field) => {\n      if (field?.type === 'number' && isNaN(Number(rule.value)))\n        return 'Must be a number';\n      return true;\n    },\n  });\n\n  return (\n    <div>\n      <p>Rules: {qb.ruleCount} · Valid: {String(qb.isValid)}</p>\n\n      {/* mutations */}\n      <button onClick={() => qb.addRule(qb.query.id)}>Add rule</button>\n      <button onClick={qb.undo} disabled={!qb.canUndo}>Undo</button>\n      <button onClick={qb.redo} disabled={!qb.canRedo}>Redo</button>\n      <button onClick={qb.clear}>Clear</button>\n\n      {/* outputs */}\n      <pre>{qb.toSQL().rawSql}</pre>\n    </div>\n  );\n}\n```\n\n---\n\n## Client-side Filtering\n\nFilter data arrays in the browser without a database:\n\n```tsx\nimport { filterRecords, useQueryBuilder } from 'querycraft';\n\nconst qb = useQueryBuilder({ fields });\n\nconst records = [\n  { name: 'Ali', age: 25, active: true },\n  { name: 'Bob', age: 15, active: false },\n];\n\nconst matched = filterRecords(records, qb.query, qb.operators);\n// uses each operator's evaluator function\n```\n\n---\n\n## Controlled vs Uncontrolled\n\n```tsx\n// Uncontrolled — QueryBuilder manages state internally\n<QueryBuilder fields={fields} onChange={handleChange} />\n\n// Controlled — you own the state\nconst [query, setQuery] = useState<RuleGroup>(createDefaultQuery(fields));\n\n<QueryBuilder\n  fields={fields}\n  query={query}\n  onChange={setQuery}\n/>\n```\n\n---\n\n## Validation\n\n```tsx\n<QueryBuilder\n  fields={fields}\n  validator={(rule, field) => {\n    if (field?.type === 'number' && isNaN(Number(rule.value)))\n      return 'Value must be a valid number';\n    if (rule.operator === 'in' && !String(rule.value).includes(','))\n      return 'Use comma-separated values for \"is one of\"';\n    return true;\n  }}\n/>\n```\n\n---\n\n## All Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `fields` | `Field[]` | required | Field definitions |\n| `operators` | `Operator[]` | built-ins | Full operator replacement |\n| `extraOperators` | `Operator[]` | — | Merged into defaults |\n| `combinators` | `Combinator[]` | `['AND','OR','NOT']` | Shown combinators |\n| `defaultQuery` | `RuleGroup` | auto | Initial query (uncontrolled) |\n| `query` | `RuleGroup` | — | Controlled query |\n| `onChange` | `(q: RuleGroup) => void` | — | Called on every change |\n| `outputFormat` | `OutputFormat` | `'sql'` | Auto-output format |\n| `onOutputChange` | `(s: string) => void` | — | Called with rendered output |\n| `customExporter` | `CustomExporter` | — | For `outputFormat='custom'` |\n| `showFormulaBar` | `boolean` | `true` | Formula preview bar |\n| `showOutput` | `boolean` | `true` | Output panel |\n| `outputFormats` | `OutputFormat[]` | all | Tabs in output panel (e.g. omit `expression`) |\n| `showExpressionOutput` | `boolean` | `true` | Include bracket-style **expression** tab |\n| `showAddGroup` | `boolean` | `true` | Add Group button |\n| `maxDepth` | `number` | unlimited | Max nesting depth |\n| `draggable` | `boolean` | `true` | Drag to reorder rules |\n| `enableExpressionOperators` | `boolean` | `true` | Merge IF/THEN/ELSE + Calculate operators |\n| `enableFlexibleOperands` | `boolean` | `true` | Unary rules: compare to const / column / variable |\n| `variables` | `QueryVariable[]` | — | Saved values for RHS + formulas |\n| `variableValues` | `Record<string, unknown>` | — | Runtime values for SQL / preview |\n| `variablesFilterByFieldType` | `boolean` | `false` | Restrict variable list by column type |\n| `searchableFields` | `boolean` | auto | Searchable column picker (`true` / `false` / default when ≥20 fields) |\n| `compact` | `boolean` | `false` | Denser padding and type size |\n| `variant` | `'light' \\| 'dark'` | — | Light/dark preset |\n| `theme` | `Partial<QueryCraftTheme>` | — | Override CSS variable colors |\n| `disabled` | `boolean` | `false` | Disable all inputs |\n| `className` | `string` | — | CSS class on root |\n| `style` | `CSSProperties` | — | Inline style on root |\n| `fieldSelector` | `ComponentType` | built-in | Custom field selector |\n| `operatorSelector` | `ComponentType` | built-in | Custom operator selector |\n| `combinatorSelector` | `ComponentType` | built-in | Custom combinator UI |\n| `valueEditor` | `ComponentType` | built-in | Global value editor override |\n| `ruleActions` | `ComponentType` | built-in | Extra actions per rule |\n| `groupActions` | `ComponentType` | built-in | Extra actions per group |\n| `validator` | `Function` | — | Per-rule validation |\n| `translations` | `Partial<Translations>` | English | i18n strings |\n\n---\n\n## i18n\n\n```tsx\n<QueryBuilder\n  fields={fields}\n  translations={{\n    addRule:    '+ Condition',\n    addGroup:   '+ Group',\n    removeRule: 'Delete',\n    removeGroup:'Delete group',\n    emptyQuery: 'Add your first condition…',\n  }}\n/>\n```\n\n---\n\n## Undo / Redo\n\nAvailable on the hook (also shown as ↩ ↪ buttons in the formula bar):\n\n```tsx\nconst qb = useQueryBuilder({ fields });\n\nqb.undo();      // undo last change\nqb.redo();      // redo\nqb.canUndo;     // boolean\nqb.canRedo;     // boolean\n```\n\n---\n\n## Programmatic Engine\n\nAll core functions are exported individually:\n\n```ts\nimport {\n  createRule, createGroup, createDefaultQuery,\n  addRule, addGroup, removeNode, updateRule, updateGroup, moveRule,\n  flattenRules, countRules, isEmptyQuery,\n  validateQuery,\n  toSQL, toJSON, toMongoDB, toGraphQL,\n  evaluateQuery, filterRecords,\n} from 'querycraft';\n```\n\n---\n\n## Testing Locally (before publishing)\n\n### Method 1 — npm link\n\n```bash\n# In querycraft-final/:\nnpm install\nnpm run build\nnpm link\n\n# In your React project:\nnpm link querycraft\n```\n\n### Method 2 — file path in package.json\n\nIn your React project's `package.json`:\n\n```json\n{\n  \"dependencies\": {\n    \"querycraft\": \"file:../querycraft-final\"\n  }\n}\n```\n\nThen `npm install`.\n\n### Method 3 — copy src folder\n\nCopy `src/` into your project:\n\n```\nyour-app/src/querycraft/   ← paste the src/ folder here\n```\n\nImport directly:\n```tsx\nimport { QueryBuilder } from './querycraft';\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}