npm package: @jguerena15/form-builder
Description: Headless form builder which exposes client side and admin side hooks for multi-step form validation, and multi-step form creation.
Author: jguerena15@gmail.com
Latest version: 1.0.12

# Form Builder

A headless, type-safe React form builder package that provides comprehensive form functionality while allowing complete UI flexibility. Build complex forms with conditional logic, validation, and external data sources - all without being locked into a specific UI framework.

## Features

- 🎯 **Headless Architecture** - Pure business logic with no UI components
- 🔧 **Builder & Renderer Hooks** - Separate hooks for form creation and form filling
- 📝 **Rich Question Types** - Text, number, select, multi-select, date/time, slider, Likert scale, and more
- 🔀 **Conditional Logic** - Show/hide questions based on previous answers or external conditions
- ✅ **Built-in Validation** - Zod-powered runtime validation with custom rules
- 🔌 **External Data Sources** - Load dropdown options from APIs or databases
- 💾 **Persistence Support** - Save and resume form progress
- 📊 **Progress Tracking** - Track completion percentage and navigation state
- 🚀 **TypeScript First** - Full type safety and IntelliSense support
- ⚡ **Performance Optimized** - Memoization and efficient re-renders

## Installation

```bash
npm install @jguerena15/form-builder
# or
yarn add @jguerena15/form-builder
# or
pnpm add @jguerena15/form-builder
```

## Quick Start

### Building Forms (Admin/Builder Interface)

```tsx
import { useFormBuilder } from '@jguerena15/form-builder';

function FormBuilderInterface() {
  const {
    form,
    addQuestion,
    updateQuestion,
    deleteQuestion,
    addConditional,
    save
  } = useFormBuilder();

  // Add a text input question
  const handleAddTextQuestion = () => {
    addQuestion({
      id: 'name',
      type: 'text-input',
      label: 'What is your name?',
      required: true,
      order: 0
    });
  };

  // Add conditional logic
  const handleAddConditional = () => {
    addConditional({
      questionId: 'followup',
      showIf: {
        logic: 'AND',
        conditions: [{
          sourceType: 'question',
          sourceId: 'name',
          operator: 'not_equals',
          value: ''
        }]
      }
    });
  };

  // Your UI implementation
  return (
    <div>
      {/* Render your form builder UI */}
    </div>
  );
}
```

### Rendering Forms (Client/User Interface)

```tsx
import { useFormRenderer } from '@jguerena15/form-builder';

function FormRenderer({ formDefinition }) {
  const {
    form,
    currentQuestion,
    handleNext,
    handlePrevious,
    handleSubmit,
    canProceed,
    progress,
    isLastStep
  } = useFormRenderer({
    formDefinition,
    onComplete: async (response) => {
      console.log('Form completed!', response);
      // Submit to your backend
    }
  });

  // Your UI implementation
  return (
    <div>
      <div>Progress: {progress}%</div>

      {currentQuestion && (
        <div>
          <label>{currentQuestion.label}</label>
          {/* Render input based on currentQuestion.type */}
          <input {...form.register(currentQuestion.id)} />
        </div>
      )}

      <button onClick={handlePrevious} disabled={!canGoBack}>
        Previous
      </button>

      <button
        onClick={isLastStep ? handleSubmit : handleNext}
        disabled={!canProceed}
      >
        {isLastStep ? 'Submit' : 'Next'}
      </button>
    </div>
  );
}
```

## Advanced Features

### External Data Sources

Load dropdown options from APIs or other external sources:

```tsx
const countryDataSource = {
  id: 'countries',
  name: 'Country List',
  fetchOptions: async () => {
    const response = await fetch('/api/countries');
    const countries = await response.json();
    return countries.map(country => ({
      id: country.code,
      label: country.name,
      value: country.code
    }));
  }
};

const { form } = useFormBuilder({
  externalDataSources: [countryDataSource]
});

// Add a question that uses the data source
addQuestion({
  id: 'country',
  type: 'single-select',
  label: 'Select your country',
  dataSourceId: 'countries',
  required: true
});
```

### External Conditions

Control question visibility based on external state:

```tsx
const isAuthenticatedCondition = {
  id: 'is-authenticated',
  name: 'User Authentication Status',
  getValue: () => userStore.isAuthenticated
};

const { form } = useFormRenderer({
  formDefinition,
  externalConditions: [isAuthenticatedCondition]
});
```

### Persistence

Save and resume form progress:

```tsx
const { form } = useFormRenderer({
  formDefinition,
  persistence: {
    save: async (response) => {
      localStorage.setItem('form-progress', JSON.stringify(response));
    },
    load: async (formId) => {
      const saved = localStorage.getItem('form-progress');
      return saved ? JSON.parse(saved) : null;
    }
  },
  autoSave: true // Auto-save after each question
});
```

### Complex Conditional Logic

Create sophisticated show/hide rules:

```tsx
addConditional({
  questionId: 'premium-features',
  showIf: {
    logic: 'OR',
    conditions: [
      {
        sourceType: 'question',
        sourceId: 'plan',
        operator: 'equals',
        value: 'premium'
      },
      {
        sourceType: 'external',
        sourceId: 'is-beta-user',
        operator: 'equals',
        value: true
      }
    ]
  }
});
```

## Question Types

### Text Input
```tsx
{
  type: 'text-input',
  minLength?: number,
  maxLength?: number,
  pattern?: string,
  placeholder?: string
}
```

### Number Input
```tsx
{
  type: 'number-input',
  min?: number,
  max?: number,
  step?: number
}
```

### Single Select
```tsx
{
  type: 'single-select',
  options?: AnswerOption[],
  dataSourceId?: string
}
```

### Multi Select
```tsx
{
  type: 'multi-select',
  options?: AnswerOption[],
  dataSourceId?: string,
  minSelections?: number,
  maxSelections?: number
}
```

### Date/Time Picker
```tsx
{
  type: 'date-time-picker',
  minDate?: string,
  maxDate?: string,
  includeTime?: boolean
}
```

### Slider
```tsx
{
  type: 'slider',
  min: number,
  max: number,
  step?: number,
  showValue?: boolean
}
```

### Likert Scale
```tsx
{
  type: 'likert-scale',
  minLabel?: string,
  maxLabel?: string
}
```

### Text Area
```tsx
{
  type: 'text-area',
  minLength?: number,
  maxLength?: number,
  placeholder?: string
}
```

## API Reference

### useFormBuilder Hook

```tsx
const {
  form,                    // Current form definition
  validationErrors,        // Array of validation errors
  updateMetadata,          // Update form title, description, etc.
  addQuestion,            // Add a new question
  updateQuestion,         // Update existing question
  deleteQuestion,         // Delete a question
  reorderQuestion,        // Change question order
  addConditional,         // Add conditional logic
  removeConditional,      // Remove conditional logic
  registerExternalCondition,   // Register external condition
  registerDataSource,     // Register data source
  fetchDataSourceOptions, // Fetch options from data source
  save,                   // Save form definition
  load,                   // Load form definition
  reset                   // Reset to initial state
} = useFormBuilder(initialForm?, options?);
```

### useFormRenderer Hook

```tsx
const {
  form,              // React Hook Form instance
  currentStep,       // Current step index
  totalSteps,        // Total visible steps
  currentQuestion,   // Current question object
  canProceed,        // Can navigate forward
  canGoBack,         // Can navigate backward
  handleNext,        // Go to next question
  handlePrevious,    // Go to previous question
  handleSubmit,      // Submit form
  progress,          // Completion percentage
  isFirstStep,       // On first question
  isLastStep,        // On last question
  isLoading,         // Loading external data
  isSaving,          // Saving form data
  errors,            // Error messages
  isValid,           // Current input is valid
  formResponse,      // Current response data
  resetForm          // Reset form
} = useFormRenderer(options);
```

## Examples

Check out the `/demo` directory for a complete example implementation with a working form builder and renderer.

## Development

```bash
# Install dependencies
yarn install

# Run development demo
yarn dev

# Run tests
yarn test

# Build package
yarn build

# Type check
yarn type-check
```