npm package: @nestledjs/forms
Description: A flexible React form library supporting both declarative and imperative usage patterns with TypeScript support
Author: Nestled Contributors
Latest version: 0.7.7

# @nestledjs/forms

A flexible React form library that supports both **declarative** and **imperative** usage patterns with full TypeScript support.

## 🚀 Features

- **Dual API**: Use declaratively with field arrays or imperatively with individual components
- **TypeScript First**: Full type safety and IntelliSense support
- **Flexible**: Mix and match declarative and imperative patterns
- **Conditional Logic**: Dynamic show/hide, required, and disabled field behavior based on form values
- **Themeable**: Customizable styling system
- **Validation**: Built-in validation with react-hook-form
- **Read-only Support**: Toggle between editable and read-only modes
- **Rich Field Types**: 20+ field types including text, email, select, date pickers, markdown editor, and more

## 📦 Installation

```bash
npm install @nestledjs/forms
# or
yarn add @nestledjs/forms
# or
pnpm add @nestledjs/forms
```

## 📋 Requirements

### For MarkdownEditor Component

If you plan to use the `MarkdownEditor` field type, you'll need to:

1. **Install the peer dependency:**
   ```bash
   npm install @mdxeditor/editor
   # or
   yarn add @mdxeditor/editor
   # or
   pnpm add @mdxeditor/editor
   ```

2. **Import the required CSS** in your application entry point (e.g., `main.tsx`, `App.tsx`):
   ```tsx
   import '@mdxeditor/editor/style.css'
   ```

## 🎯 Quick Start

### Declarative Usage (Recommended)

Perfect for forms where you can define all fields upfront:

```tsx
import { Form, FormFieldClass } from '@nestledjs/forms'

function UserRegistrationForm() {
  const fields = [
    FormFieldClass.text('firstName', { 
      label: 'First Name', 
      required: true 
    }),
    FormFieldClass.text('lastName', { 
      label: 'Last Name', 
      required: true 
    }),
    FormFieldClass.email('email', { 
      label: 'Email Address', 
      required: true,
      placeholder: 'user@example.com'
    }),
    FormFieldClass.password('password', { 
      label: 'Password', 
      required: true,
      validate: (value) => value.length >= 8 || 'Password must be at least 8 characters'
    }),
    FormFieldClass.select('role', {
      label: 'Role',
      options: [
        { value: 'user', label: 'User' },
        { value: 'admin', label: 'Admin' }
      ]
    })
  ]

  return (
    <Form
      id="registration-form"
      fields={fields}
      submit={(values) => {
        console.log('Form submitted:', values)
        // Handle form submission
      }}
    >
      <button type="submit">Register</button>
    </Form>
  )
}
```

### Imperative Usage

Perfect for dynamic forms or when you need fine-grained control. Supports all the same features including conditional logic:

```tsx
import { Form, RenderFormField, FormFieldClass } from '@nestledjs/forms'

function DynamicContactForm() {
  return (
    <Form
      id="contact-form"
      submit={(values) => console.log('Submitted:', values)}
    >
      <RenderFormField 
        field={FormFieldClass.text('name', { label: 'Name', required: true })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.email('email', { label: 'Email', required: true })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.checkbox('includePhone', { 
          label: 'Include phone number' 
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.phone('phone', { 
          label: 'Phone Number',
          showWhen: (values) => values.includePhone === true,
          requiredWhen: (values) => values.includePhone === true
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.textArea('message', { 
          label: 'Message', 
          rows: 4,
          placeholder: 'Tell us how we can help...'
        })} 
      />
      
      <button type="submit">Send Message</button>
    </Form>
  )
}
```

### Mixed Usage

Combine both approaches for maximum flexibility:

```tsx
import { Form, RenderFormField, FormFieldClass } from '@nestledjs/forms'

function MixedForm() {
  const staticFields = [
    FormFieldClass.text('username', { label: 'Username', required: true }),
    FormFieldClass.email('email', { label: 'Email', required: true }),
  ]

  return (
    <Form
      id="mixed-form"
      fields={staticFields}
      submit={(values) => console.log('Submitted:', values)}
    >
      {/* Static fields are rendered automatically from the fields prop */}
      
      {/* Add dynamic fields as children */}
      <RenderFormField 
        field={FormFieldClass.password('password', { 
          label: 'Password', 
          required: true 
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.checkbox('terms', { 
          label: 'I agree to the terms and conditions',
          required: true
        })} 
      />
      
      <button type="submit">Sign Up</button>
    </Form>
  )
}
```

### Rich Text Editing with Markdown

Create forms with rich text editing capabilities:

```tsx
import { Form, RenderFormField, FormFieldClass } from '@nestledjs/forms'

function BlogPostForm() {
  const handleImageUpload = async (file: File): Promise<string> => {
    // Upload image to your server/CDN
    const formData = new FormData()
    formData.append('image', file)
    
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData
    })
    
    const { url } = await response.json()
    return url
  }

  return (
    <Form
      id="blog-post-form"
      submit={(values) => console.log('Blog post:', values)}
    >
      <RenderFormField 
        field={FormFieldClass.text('title', { 
          label: 'Post Title', 
          required: true,
          placeholder: 'Enter your blog post title...'
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.text('slug', { 
          label: 'URL Slug', 
          placeholder: 'my-blog-post'
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.markdownEditor('content', { 
          label: 'Post Content',
          required: true,
          height: 400,
          placeholder: 'Write your blog post content...\n\n**Use markdown** for formatting!\n\n- Bullet lists\n1. Numbered lists\n- [x] Checkboxes\n\n```javascript\nconsole.log("Code blocks work too!")\n```',
          enableImageUpload: true,
          imageUploadHandler: handleImageUpload,
          maxImageSize: 5 * 1024 * 1024, // 5MB
          allowedImageTypes: ['image/png', 'image/jpeg', 'image/gif'],
          maxLength: 10000,
          helpText: 'Supports markdown formatting, images, and code blocks'
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.select('category', {
          label: 'Category',
          options: [
            { value: 'tech', label: 'Technology' },
            { value: 'design', label: 'Design' },
            { value: 'business', label: 'Business' }
          ]
        })} 
      />
      
      <RenderFormField 
        field={FormFieldClass.switch('published', { 
          label: 'Publish immediately'
        })} 
      />
      
      <button type="submit">Save Post</button>
    </Form>
  )
}
```

## 🛠️ Available Field Types

The `FormFieldClass` provides methods for creating all field types:

```tsx
// Text inputs
FormFieldClass.text('field', { label: 'Text Field' })
FormFieldClass.textArea('field', { label: 'Text Area', rows: 4 })
FormFieldClass.email('field', { label: 'Email Field' })
FormFieldClass.password('field', { label: 'Password Field' })
FormFieldClass.url('field', { label: 'URL Field' })
FormFieldClass.phone('field', { label: 'Phone Field' })

// Rich text editing
FormFieldClass.markdownEditor('field', { 
  label: 'Content', 
  height: 300,
  placeholder: 'Enter your markdown content...',
  enableImageUpload: true,
  maxLength: 5000
})

// Numbers and currency
FormFieldClass.number('field', { label: 'Number', min: 0, max: 100 })
FormFieldClass.currency('field', { label: 'Price', currency: 'USD' })

// Selections
FormFieldClass.select('field', { 
  label: 'Select', 
  options: [{ value: 'a', label: 'Option A' }] 
})
FormFieldClass.multiSelect('field', { 
  label: 'Multi Select', 
  options: [{ value: 'a', label: 'Option A' }] 
})
FormFieldClass.radio('field', { 
  label: 'Radio', 
  radioOptions: [{ value: 'a', label: 'Option A' }] 
})

// Checkboxes and switches
FormFieldClass.checkbox('field', { label: 'Checkbox' })
FormFieldClass.switch('field', { label: 'Switch' })

// Date and time
FormFieldClass.datePicker('field', { label: 'Date' })
FormFieldClass.dateTimePicker('field', { label: 'Date & Time' })
FormFieldClass.timePicker('field', { label: 'Time' })

// Search and select fields (v0.4.17+ with enhanced clear functionality)
FormFieldClass.searchSelect('field', { 
  label: 'Search Select',
  options: [{ value: 'a', label: 'Option A' }],
  placeholder: 'Search or select...'
})
FormFieldClass.searchSelectApollo('field', { 
  label: 'Apollo Search Select',
  document: MY_GRAPHQL_QUERY,
  dataType: 'users',
  searchFields: ['name', 'email', 'firstName'],
  selectOptionsFunction: (items) => items.map(item => ({ 
    value: item.id, 
    label: item.name 
  }))
})
FormFieldClass.searchSelectMulti('field', { 
  label: 'Multi Search Select',
  options: [{ value: 'a', label: 'Option A' }]
})
FormFieldClass.searchSelectMultiApollo('field', { 
  label: 'Apollo Multi Search Select',
  document: MY_GRAPHQL_QUERY,
  dataType: 'users',
  searchFields: ['name', 'email', 'firstName'],
  selectOptionsFunction: (items) => items.map(item => ({ 
    value: item.id, 
    label: item.name 
  }))
})

FormFieldClass.custom('field', {
  label: 'Custom Field',
  customField: ({ value, onChange }) => (
    <MyCustomComponent value={value} onChange={onChange} />
  )
})
```

## 🚀 Apollo GraphQL Integration

For applications using Apollo Client, the forms library provides specialized search components that integrate with your GraphQL API:

### SearchSelectApollo

Single-select dropdown with server-side search:

```tsx
import { gql } from '@apollo/client'

const SEARCH_USERS_QUERY = gql`
  query SearchUsers($input: SearchInput) {
    users(input: $input) {
      id
      name
      firstName
      lastName
      email
    }
  }
`

FormFieldClass.searchSelectApollo('selectedUser', {
  label: 'Select User',
  document: SEARCH_USERS_QUERY,
  dataType: 'users',
  searchFields: ['name', 'firstName', 'lastName', 'email'], // Configure which fields to search
  selectOptionsFunction: (users) => users.map(user => ({
    value: user.id,
    label: `${user.firstName} ${user.lastName}`
  })),
  filter: (users) => users.slice(0, 10) // Optional client-side filtering
})
```

### SearchSelectMultiApollo

Multi-select dropdown with server-side search:

```tsx
FormFieldClass.searchSelectMultiApollo('selectedUsers', {
  label: 'Select Team Members',
  document: SEARCH_USERS_QUERY,
  dataType: 'users',
  searchFields: ['name', 'firstName', 'lastName', 'email'],
  selectOptionsFunction: (users) => users.map(user => ({
    value: user.id,
    label: `${user.firstName} ${user.lastName}`
  }))
})
```

### Key Features

- **Dynamic Search Fields**: Use `searchFields` to specify which backend fields should be searched
- **Server-side Search**: Efficient handling of large datasets
- **Custom Data Mapping**: Transform API responses with `selectOptionsFunction`
- **Debounced Search**: 500ms delay to reduce API calls
- **Loading States**: Built-in loading indicators
- **Type Safety**: Full TypeScript support with generic data types
- **Enhanced Clear Functionality** (v0.4.17+): Multiple ways to clear selections

### GraphQL Requirements

Your GraphQL queries must accept an `input` parameter:

```graphql
type SearchInput {
  search: String
  searchFields: [String!]  # Frontend specifies which fields to search
  limit: Int
  offset: Int
}

query SearchUsers($input: SearchInput) {
  users(input: $input) {
    id
    name
    firstName
    lastName
    email
  }
}
```

### 🔄 Search Select Clear Functionality (v0.4.17+)

All search select components now include comprehensive clear functionality with three intuitive methods:

#### 1. Clear Button
- **Visual X icon** appears when a value is selected
- Located between the input field and dropdown arrow
- Single click to instantly clear the selection

#### 2. Manual Text Deletion
- Focus the field and delete all text
- When you blur (click away), the selection automatically clears
- Works naturally like users expect from modern forms

#### 3. Backspace on Empty Input
- Press backspace when the input is already empty to clear the selection
- Backspacing through search text doesn't affect the selection until the input is empty

#### Apollo Search Improvements
The Apollo-powered search selects now properly refresh data when cleared:
- Clearing the search input refetches with empty search parameters
- Shows the full unfiltered dataset instead of stale results
- No need for page refresh to see all options

#### Theme Customization
Customize the clear button appearance with new theme properties:

```tsx
const customTheme = {
  searchSelectField: {
    input: 'your-input-classes',
    inputWithClear: 'pr-20', // Extra padding when clear button is shown
    clearButton: 'absolute inset-y-0 right-10 flex items-center pr-2',
    clearIcon: 'h-5 w-5 text-gray-400 hover:text-gray-600',
    // ... other properties
  }
}
```

### 🐛 Dropdown Positioning Issues

If the dropdown options render at the bottom of the page with excessive blank space instead of next to the input field, this is a CSS positioning context issue. This commonly happens when parent elements have CSS properties that create new stacking contexts.

**Common Causes:**
- Parent elements with `transform`, `filter`, or `perspective` CSS properties
- Containers with `position: relative/absolute/fixed` 
- CSS frameworks that apply transforms for animations
- Modal dialogs or overlay components

**Solutions:**

**1. Remove problematic CSS properties from parent containers:**
```css
/* Instead of this: */
.container {
  transform: translateX(0); /* Creates positioning context */
}

/* Use this: */
.container {
  /* Remove or replace transform */
}
```

**2. Override dropdown positioning with higher z-index:**
```css
/* Target the dropdown specifically */
[data-headlessui-state="open"] [role="listbox"] {
  position: fixed !important;
  z-index: 9999 !important;
}
```

**3. Isolate the form field:**
```css
/* Wrap your form field in a container with isolation */
.form-field-container {
  isolation: isolate;
  position: relative;
}
```

**Debugging Steps:**
1. Inspect the dropdown element in browser dev tools when mispositioned
2. Check what element it's positioned relative to
3. Look for CSS transforms/filters on parent elements
4. Try the CSS solutions above to fix positioning context issues

## 🎨 Theming

Customize the appearance of your forms:

```tsx
import { Form, FormFieldClass, tailwindTheme } from '@nestledjs/forms'

const customTheme = {
  textField: {
    input: 'border-2 border-blue-500 rounded-lg px-3 py-2',
    error: 'border-red-500',
    disabled: 'bg-gray-100'
  },
  button: {
    primary: 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded'
  }
}

function ThemedForm() {
  return (
    <Form
      id="themed-form"
      theme={customTheme}
      fields={[
        FormFieldClass.text('name', { label: 'Name' }),
        FormFieldClass.email('email', { label: 'Email' })
      ]}
      submit={(values) => console.log(values)}
    >
      <button type="submit" className={customTheme.button.primary}>
        Submit
      </button>
    </Form>
  )
}
```

## 📐 Multi-Column Layouts

Create responsive multi-column layouts using CSS Grid or Flexbox:

### CSS Grid Approach

```tsx
import { Form, RenderFormField, FormFieldClass } from '@nestledjs/forms'

function MultiColumnForm() {
  return (
    <Form id="multi-column-form" submit={(values) => console.log(values)}>
      {/* Two-column grid */}
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <RenderFormField 
          field={FormFieldClass.text('firstName', { label: 'First Name' })}
          className="col-span-1"
        />
        <RenderFormField 
          field={FormFieldClass.text('lastName', { label: 'Last Name' })}
          className="col-span-1"
        />
      </div>

      {/* Full-width field */}
      <RenderFormField 
        field={FormFieldClass.email('email', { label: 'Email Address' })}
      />

      {/* Three-column grid for address */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        <RenderFormField 
          field={FormFieldClass.text('street', { label: 'Street' })}
          className="col-span-2"
        />
        <RenderFormField 
          field={FormFieldClass.text('zipCode', { label: 'ZIP' })}
          className="col-span-1"
        />
      </div>

      <button type="submit">Submit</button>
    </Form>
  )
}
```

### Using wrapperClassName in Field Options

```tsx
function GridFormWithFieldOptions() {
  const fields = [
    FormFieldClass.text('firstName', { 
      label: 'First Name',
      wrapperClassName: 'col-span-1'
    }),
    FormFieldClass.text('lastName', { 
      label: 'Last Name',
      wrapperClassName: 'col-span-1'
    }),
    FormFieldClass.email('email', { 
      label: 'Email Address',
      wrapperClassName: 'col-span-2'
    }),
  ]

  return (
    <Form id="grid-form" submit={(values) => console.log(values)}>
      <div className="grid grid-cols-2 gap-4">
        {fields.map(field => (
          <RenderFormField key={field.key} field={field} />
        ))}
      </div>
      <button type="submit">Submit</button>
    </Form>
  )
}
```

### Horizontal Field Layout

```tsx
function HorizontalLayoutForm() {
  return (
    <Form id="horizontal-form" submit={(values) => console.log(values)}>
      <RenderFormField 
        field={FormFieldClass.checkbox('newsletter', { 
          label: 'Subscribe to newsletter',
          layout: 'horizontal'  // Label and input on same line
        })}
      />
      
      <RenderFormField 
        field={FormFieldClass.checkbox('terms', { 
          label: 'I agree to the terms and conditions',
          layout: 'horizontal'
        })}
      />
      
      <button type="submit">Submit</button>
    </Form>
  )
}
```

### Custom Wrapper Functions

```tsx
function CustomWrapperForm() {
  return (
    <Form id="custom-wrapper-form" submit={(values) => console.log(values)}>
      <RenderFormField 
        field={FormFieldClass.text('amount', { 
          label: 'Amount',
          customWrapper: (children) => (
            <div className="flex items-end space-x-2">
              <div className="flex-1">{children}</div>
              <span className="text-gray-500 pb-2">USD</span>
            </div>
          )
        })}
      />
      
      <button type="submit">Submit</button>
    </Form>
  )
}
```

## 🔒 Read-only Mode

Toggle forms between editable and read-only modes:

```tsx
function ReadOnlyForm() {
  const [isReadOnly, setIsReadOnly] = useState(false)
  
  return (
    <div>
      <button onClick={() => setIsReadOnly(!isReadOnly)}>
        {isReadOnly ? 'Edit' : 'View'}
      </button>
      
      <Form
        id="readonly-form"
        readOnly={isReadOnly}
        readOnlyStyle="value" // 'value' or 'disabled'
        fields={[
          FormFieldClass.text('name', { label: 'Name' }),
          FormFieldClass.email('email', { label: 'Email' })
        ]}
        submit={(values) => console.log(values)}
      />
    </div>
  )
}
```

## 🧩 Custom Field Components

Create custom field components using the provided hooks:

```tsx
import { useFormContext, useFormTheme } from '@nestledjs/forms'

function CustomColorPicker({ fieldKey, label }) {
  const form = useFormContext()
  const theme = useFormTheme()
  
  return (
    <div>
      <label>{label}</label>
      <input
        type="color"
        {...form.register(fieldKey)}
        className={theme.textField.input}
      />
    </div>
  )
}

// Usage
function FormWithCustomField() {
  return (
    <Form id="custom-form" submit={(values) => console.log(values)}>
      <CustomColorPicker fieldKey="favoriteColor" label="Favorite Color" />
      <button type="submit">Submit</button>
    </Form>
  )
}
```

## 🔀 Conditional Field Logic

Create dynamic forms where fields show, hide, become required, or get disabled based on other field values:

### Basic Conditional Visibility

```tsx
const fields = [
  FormFieldClass.select('contactMethod', {
    label: 'Contact Method',
    required: true,
    options: [
      { value: 'email', label: 'Email' },
      { value: 'phone', label: 'Phone' },
      { value: 'mail', label: 'Mail' }
    ]
  }),
  
  // This field only appears when email is selected
  FormFieldClass.email('email', {
    label: 'Email Address',
    required: true,
    showWhen: (formValues) => formValues.contactMethod === 'email'
  }),
  
  // This field only appears when phone is selected
  FormFieldClass.phone('phone', {
    label: 'Phone Number',
    required: true,
    showWhen: (formValues) => formValues.contactMethod === 'phone'
  })
]
```

### Conditional Required Fields

```tsx
const fields = [
  FormFieldClass.select('accountType', {
    label: 'Account Type',
    required: true,
    options: [
      { value: 'personal', label: 'Personal' },
      { value: 'business', label: 'Business' }
    ]
  }),
  
  // This field becomes required only for business accounts
  FormFieldClass.text('companyName', {
    label: 'Company Name',
    requiredWhen: (formValues) => formValues.accountType === 'business'
  }),
  
  FormFieldClass.text('taxId', {
    label: 'Tax ID',
    requiredWhen: (formValues) => formValues.accountType === 'business'
  })
]
```

### Conditional Disabled Fields

```tsx
const fields = [
  FormFieldClass.checkbox('useCompanyEmail', {
    label: 'Use company email address'
  }),
  
  // This field becomes disabled when checkbox is checked
  FormFieldClass.email('personalEmail', {
    label: 'Personal Email',
    disabledWhen: (formValues) => formValues.useCompanyEmail === true
  })
]
```

### Complex Conditional Logic

```tsx
const fields = [
  FormFieldClass.select('userType', {
    label: 'User Type',
    options: [
      { value: 'student', label: 'Student' },
      { value: 'teacher', label: 'Teacher' },
      { value: 'admin', label: 'Administrator' }
    ]
  }),
  
  FormFieldClass.checkbox('isHeadOfDepartment', {
    label: 'Head of Department',
    showWhen: (values) => values.userType === 'teacher'
  }),
  
  // Multiple conditions combined
  FormFieldClass.text('officeNumber', {
    label: 'Office Number',
    showWhen: (values) => values.userType === 'teacher' && values.isHeadOfDepartment,
    requiredWhen: (values) => values.userType === 'teacher' && values.isHeadOfDepartment
  })
]
```

### Available Conditional Properties

- **`showWhen(formValues): boolean`** - Controls field visibility
- **`requiredWhen(formValues): boolean`** - Makes field required dynamically
- **`disabledWhen(formValues): boolean`** - Disables field interaction

All conditional functions:
- Receive the current form values as their first parameter
- Are re-evaluated whenever any form value changes
- Work with both declarative and imperative APIs
- Include error handling for robust operation

## 📝 Validation

Built-in validation support with custom validators:

```tsx
const fields = [
  FormFieldClass.text('username', {
    label: 'Username',
    required: true,
    validate: (value) => {
      if (value.length < 3) return 'Username must be at least 3 characters'
      if (!/^[a-zA-Z0-9_]+$/.test(value)) return 'Username can only contain letters, numbers, and underscores'
      return true
    }
  }),
  FormFieldClass.email('email', {
    label: 'Email',
    required: true,
    validate: async (value) => {
      const response = await fetch(`/api/check-email?email=${value}`)
      const { exists } = await response.json()
      return exists ? 'Email already exists' : true
    }
  })
]
```

## ✍️ Markdown Editor Configuration

The markdown editor provides rich text editing with full markdown support:

```tsx
FormFieldClass.markdownEditor('content', {
  label: 'Content',
  required: true,
  
  // Editor appearance
  height: 400,
  placeholder: 'Enter your content...',
  
  // Content validation
  maxLength: 5000,
  
  // Output format configuration
  outputFormat: 'both', // 'markdown' | 'html' | 'both'
  onHtmlChange: (html) => {
    // Handle HTML output when outputFormat is 'html' or 'both'
    console.log('Generated HTML:', html)
  },
  
  // Image upload configuration
  enableImageUpload: true,
  imageUploadHandler: async (file: File) => {
    // Custom upload logic
    const formData = new FormData()
    formData.append('image', file)
    const response = await fetch('/api/upload', { method: 'POST', body: formData })
    const { url } = await response.json()
    return url
  },
  imageUploadMode: 'custom', // 'base64' | 'custom' | 'immediate'
  maxImageSize: 5 * 1024 * 1024, // 5MB
  allowedImageTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
  
  // Help text
  helpText: 'Supports markdown formatting, images, and code blocks',
  
  // Read-only configuration
  readOnly: false,
  readOnlyStyle: 'value', // 'value' | 'disabled'

  // Advanced: custom MDXEditor plugins (replaces all defaults)
  plugins: [
    headingsPlugin(),
    listsPlugin(),
    tablePlugin(),
    toolbarPlugin({ toolbarContents: () => <MyCustomToolbar /> }),
  ],
})
```

### Markdown Editor Features

- **Rich text toolbar** with formatting options including a heading picker (Normal / H1 / H2 / H3)
- **Live preview** of markdown content
- **Dual format output** - Get both markdown and HTML
- **Image upload** with drag-and-drop support
- **Code blocks** with syntax highlighting
- **Lists** (bullet, numbered, checkbox)
- **Links** and **blockquotes**
- **Keyboard shortcuts** for common actions
- **Read-only mode** for viewing content

### Markdown Editor Usage Tips

1. **Lists**: Use the toolbar button to toggle between bullet and numbered lists
2. **Images**: Drag and drop images directly into the editor
3. **Code blocks**: Use triple backticks (```) for code blocks
4. **Shortcuts**: Press `Ctrl+B` for bold, `Ctrl+I` for italic, etc.
5. **Checkboxes**: Type `- [ ]` for unchecked or `- [x]` for checked items

### Dual Format Output

The Markdown Editor can output in three modes:

- **`'markdown'`** (default): Only outputs markdown content
- **`'html'`**: Converts and outputs HTML content
- **`'both'`**: Outputs both markdown (in the main field) and HTML (in a `_html` suffixed field)

```tsx
// Example: Get both markdown and HTML
FormFieldClass.markdownEditor('content', {
  outputFormat: 'both',
  onHtmlChange: (html) => {
    // Access HTML as it's generated
    setGeneratedHtml(html)
  }
})

// When outputFormat is 'both', form data will contain:
// - content: "# Hello\n\nThis is **bold**"
// - content_html: "<h1>Hello</h1><p>This is <strong>bold</strong></p>"
```

**⚠️ Security Note**: The built-in markdown-to-HTML conversion is basic and designed for simple use cases. For production use:
- Use robust, security-tested parsers like `marked`, `markdown-it`, or `remark`
- The built-in converter includes ReDoS protection (input size limits, safe regex patterns)
- Consider server-side conversion for untrusted input to avoid client-side DoS attacks

### Custom Plugins

The `plugins` option gives you full control over the MDXEditor plugin set. When provided, it completely replaces the default plugins — you get exactly what you pass in, nothing more.

This is useful when you need features beyond the defaults (tables, diff view, custom MDX elements) or want a completely custom toolbar:

```tsx
import {
  headingsPlugin,
  listsPlugin,
  tablePlugin,
  toolbarPlugin,
  BlockTypeSelect,
  BoldItalicUnderlineToggles,
  InsertTable,
  Separator,
} from '@mdxeditor/editor'

FormFieldClass.markdownEditor('content', {
  label: 'Content',
  plugins: [
    headingsPlugin(),
    listsPlugin(),
    tablePlugin(),
    toolbarPlugin({
      toolbarContents: () => (
        <>
          <BlockTypeSelect />
          <Separator />
          <BoldItalicUnderlineToggles />
          <Separator />
          <InsertTable />
        </>
      ),
    }),
  ],
})
```

When `plugins` is not provided, the editor uses sensible defaults: headings, lists, blockquotes, links, link dialogs, markdown shortcuts, and a toolbar with a heading picker, undo/redo, bold/italic/underline, code, lists, and links.

### Modal-on-Modal Conflicts

If your MarkdownEditor is inside a modal and the link/image dialogs don't appear properly, this is due to z-index conflicts. Here's how to fix it:

**Option 1: Custom Overlay Container**
```tsx
FormFieldClass.markdownEditor('content', {
  label: 'Content',
  overlayContainer: document.getElementById('your-modal-container'), // Render popups inside your modal
})
```

**Option 2: Higher Z-Index**
```tsx
FormFieldClass.markdownEditor('content', {
  label: 'Content', 
  popupZIndex: 10000, // Set higher than your modal's z-index
})
```

**Option 3: CSS Override**
```css
/* In your global CSS */
.mdxeditor-popup-container {
  z-index: 9999 !important; /* Higher than your modal */
}
```

## 🎛️ Advanced Configuration

### Label Display Control

```tsx
<Form
  id="form"
  labelDisplay="none" // 'all' | 'default' | 'none'
  fields={fields}
  submit={handleSubmit}
/>
```

### Field-level Configuration

```tsx
const field = FormFieldClass.text('name', {
  label: 'Name',
  required: true,
  disabled: false,
  readOnly: false,
  readOnlyStyle: 'value', // 'value' | 'disabled'
  helpText: 'Enter your full name',
  placeholder: 'John Doe',
  defaultValue: 'Default Name',
  validate: (value) => value.length > 0 || 'Name is required',
  
  // Conditional logic properties
  showWhen: (formValues) => formValues.shouldShowName,
  requiredWhen: (formValues) => formValues.accountType === 'business',
  disabledWhen: (formValues) => formValues.useGeneratedName
})
```

## 📚 API Reference

### Core Components

- **`Form`**: Main form component supporting both declarative and imperative usage
- **`RenderFormField`**: Renders individual form fields (imperative usage)
- **`FormFieldClass`**: Factory class for creating field definitions

### Hooks

- **`useFormContext<T>()`**: Access form state and methods
- **`useFormConfig()`**: Access form configuration (label display, etc.)
- **`useFormTheme()`**: Access current theme configuration

### Types

- **`FormField`**: Union type of all possible field definitions
- **`FormFieldType`**: Enum of available field types
- **`FormProps<T>`**: Props interface for the Form component
- **`FormTheme`**: Theme configuration interface

## 🤝 TypeScript Support

The library is built with TypeScript-first design:

```tsx
interface UserFormData {
  name: string
  email: string
  age: number
  preferences: string[]
}

function TypedForm() {
  return (
    <Form<UserFormData>
      id="typed-form"
      fields={[
        FormFieldClass.text('name', { label: 'Name' }),
        FormFieldClass.email('email', { label: 'Email' }),
        FormFieldClass.number('age', { label: 'Age' }),
        FormFieldClass.multiSelect('preferences', { 
          label: 'Preferences',
          options: [
            { value: 'music', label: 'Music' },
            { value: 'sports', label: 'Sports' }
          ]
        })
      ]}
      submit={(values: UserFormData) => {
        // values is fully typed!
        console.log(values.name, values.email, values.age, values.preferences)
      }}
    />
  )
}
```

## 🎯 Best Practices

1. **Use declarative approach** when possible - it's more maintainable
2. **Combine with imperative** for dynamic scenarios
3. **Define field arrays outside render** to avoid unnecessary re-renders
4. **Use TypeScript generics** for type safety
5. **Leverage validation** for better user experience
6. **Consider read-only modes** for view/edit patterns

## 📄 License

MIT License - see LICENSE file for details.

## 📝 Changelog

### v0.4.17 (Latest)
- ✨ **Enhanced Search Select Clear Functionality**
  - Added clear button (X icon) for single select fields
  - Implemented manual text deletion to clear selections
  - Added backspace-to-clear on empty input
  - Fixed Apollo search to properly refetch when cleared
- 🐛 **Bug Fixes**
  - Apollo search selects now refresh data correctly when search is cleared
  - Form state properly updates when selections are cleared
- 🎨 **Theme Improvements**
  - Added `inputWithClear`, `clearButton`, and `clearIcon` theme properties
  - Improved type safety with proper null handling

### v0.4.16
- Initial clear functionality implementation

### v0.4.15
- Bug fixes and performance improvements