# Form Builder Node Creation Guide

This guide explains how to create new nodes/components for the form builder system. The form builder supports various types of components from simple text inputs to complex container components like repeating groups and layout grids.

## Table of Contents

1. [Overview](#overview)
2. [Component Types](#component-types)
3. [Basic Component Creation](#basic-component-creation)
4. [Container Component Creation](#container-component-creation)
5. [Complex Component Creation](#complex-component-creation)
6. [Component Registration](#component-registration)
7. [Settings Modal Integration](#settings-modal-integration)
8. [Component Preview Integration](#component-preview-integration)
9. [Drag & Drop Integration](#drag--drop-integration)
10. [Best Practices](#best-practices)
11. [Examples](#examples)

## Overview

The form builder system consists of several key files:

- **`FormBuilderComponents.vue`**: Defines available components and their metadata
- **`ComponentPreview.vue`**: Renders components in the form builder
- **`FormBuilderFieldSettingsModal.vue`**: Provides settings interface for components
- **`stores/formBuilder.js`**: Manages form state and component operations

## Component Types

### 1. Basic Components
Simple input components that collect data:
- Text inputs, textareas, numbers
- Select dropdowns, checkboxes, radio buttons
- Date/time pickers, file uploads

### 2. Container Components
Components that can hold other components:
- **Form Section**: Groups related fields
- **Repeating Group**: Allows multiple instances of the same fields
- **Layout Grid**: Custom grid layout with spanning capabilities

### 3. Advanced Components
Complex components with special functionality:
- Custom HTML with CSS/JS
- Conditional logic components
- API integration components

## Basic Component Creation

### Step 1: Define Component Metadata

Add your component to `FormBuilderComponents.vue`:

```javascript
{
  type: 'my-component',
  name: 'My Component',
  category: 'Basic Inputs', // or 'Selection Inputs', 'Date and Time', 'Advanced', 'Layout'
  icon: 'heroicons:document-text', // Use appropriate icon
  description: 'Description of what this component does',
  defaultProps: {
    label: 'My Component',
    name: 'my_component',
    help: 'Help text for users',
    required: false,
    placeholder: 'Enter value...',
    width: '100%',
    gridColumn: 'span 6',
    // Component-specific properties
    myCustomProp: 'default value',
    // Conditional Logic Properties
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

### Step 2: Add Component Preview

Add rendering logic to `ComponentPreview.vue`:

```vue
<!-- My Component -->
<div v-else-if="component.type === 'my-component'" class="py-2">
  <label v-if="component.props.label && component.props.showLabel !== false" 
         class="block text-sm font-medium text-gray-700 mb-2">
    {{ component.props.label }}
    <span v-if="component.props.required" class="text-red-500">*</span>
  </label>

  <!-- Component-specific rendering -->
  <div class="my-component-wrapper">
    <input 
      type="text"
      :placeholder="component.props.placeholder"
      :required="component.props.required"
      class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
    />
  </div>

  <div v-if="component.props.help" class="mt-1 text-xs text-gray-500">
    {{ component.props.help }}
  </div>
</div>
```

### Step 3: Add Settings Modal Support

Add settings to `FormBuilderFieldSettingsModal.vue`:

```javascript
// In the script section, add to getComponentTypeName function
getComponentTypeName(type) {
  const typeNames = {
    // ... existing types
    'my-component': 'My Component'
  }
  return typeNames[type] || 'Unknown Component'
}

// Add to getComponentIcon function
getComponentIcon(type) {
  const icons = {
    // ... existing icons
    'my-component': 'heroicons:document-text'
  }
  return icons[type] || 'heroicons:question-mark-circle'
}

// Add to getComponentDescription function
getComponentDescription(type) {
  const descriptions = {
    // ... existing descriptions
    'my-component': 'A custom component for collecting specific data'
  }
  return descriptions[type] || 'Component description'
}
```

## Container Component Creation

Container components are more complex as they can hold other components.

### Example: Creating a Custom Container

```javascript
// In FormBuilderComponents.vue
{
  type: 'custom-container',
  name: 'Custom Container',
  category: 'Layout',
  icon: 'material-symbols:view-in-ar',
  description: 'A custom container that can hold other components',
  defaultProps: {
    label: 'Custom Container',
    name: 'custom_container',
    help: 'Drag components here to add them',
    showHeader: true,
    headerBackground: '#f9fafb',
    backgroundColor: '#ffffff',
    showBorder: true,
    borderStyle: 'solid', // 'solid', 'dashed', 'dotted'
    spacing: 'normal', // 'compact', 'normal', 'relaxed'
    children: [], // Array to hold nested components
    // Conditional Logic Properties
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

### Container Component Preview

```vue
<!-- Custom Container Component -->
<div v-else-if="component.type === 'custom-container'" class="py-2">
  <div 
    class="custom-container-wrapper"
    :class="{
      'has-border': component.props.showBorder,
      'border-dashed': component.props.borderStyle === 'dashed',
      'border-dotted': component.props.borderStyle === 'dotted',
      'spacing-compact': component.props.spacing === 'compact',
      'spacing-relaxed': component.props.spacing === 'relaxed'
    }"
    :style="{
      backgroundColor: component.props.backgroundColor || '#ffffff',
      borderColor: component.props.showBorder ? '#e5e7eb' : 'transparent'
    }"
  >
    <!-- Container Header -->
    <div 
      v-if="component.props.showHeader"
      class="container-header"
      :style="{ backgroundColor: component.props.headerBackground || '#f9fafb' }"
    >
      <h3 class="container-title">
        {{ component.props.label || 'Custom Container' }}
      </h3>
      <p v-if="component.props.description" class="container-description">
        {{ component.props.description }}
      </p>
    </div>

    <!-- Container Content -->
    <div class="container-content">
      <!-- In preview mode, show only the nested components -->
      <div v-if="isPreview" class="container-fields">
        <div v-if="component.props.children && component.props.children.length > 0" 
             class="grid grid-cols-12 gap-2">
          <template v-for="(childComponent, childIndex) in component.props.children" 
                    :key="childIndex">
            <div 
              class="form-component"
              :style="{
                gridColumn: childComponent.props.gridColumn || 'span 6'
              }"
            >
              <component-preview :component="childComponent" :is-preview="true" />
            </div>
          </template>
        </div>
      </div>
      
      <!-- In form builder mode, show drop zone -->
      <div v-else class="container-drop-zone">
        <!-- Nested components rendered in grid -->
        <div v-if="component.props.children && component.props.children.length > 0" 
             class="nested-components">
          <draggable
            v-model="component.props.children"
            group="form-components"
            item-key="id"
            handle=".drag-handle"
            ghost-class="ghost"
            animation="300"
            class="grid grid-cols-12 gap-2"
            @end="onNestedDragEnd"
            @add="onNestedComponentAdd"
          >
            <template #item="{ element: childElement, index: childIndex }">
              <div 
                class="form-component relative border rounded-md overflow-hidden transition-all duration-200 bg-white"
                :class="'border-gray-200 hover:border-blue-300 hover:shadow-md'"
                :style="{
                  gridColumn: childElement.props.gridColumn || 'span 6'
                }"
              >
                <!-- Component actions -->
                <div class="component-actions absolute right-1 top-1 flex space-x-1 z-10">
                  <button 
                    class="p-1 text-gray-400 hover:text-blue-600 rounded"
                    title="Component settings"
                    @click.stop="openNestedComponentSettings(childElement)"
                  >
                    <Icon name="heroicons:cog-6-tooth" class="w-3 h-3" />
                  </button>
                  <button 
                    class="p-1 text-gray-400 hover:text-red-500 rounded"
                    title="Remove from container"
                    @click.stop="removeFromSection(component.id, childIndex)"
                  >
                    <Icon name="material-symbols:close" class="w-3 h-3" />
                  </button>
                </div>
                
                <div class="p-2">
                  <component-preview :component="childElement" :is-preview="false" />
                </div>
              </div>
            </template>
          </draggable>
        </div>
        
        <!-- Drop zone placeholder -->
        <div v-else class="drop-placeholder">
          <Icon name="material-symbols:add-box-outline" class="w-8 h-8 text-gray-300 mx-auto mb-2" />
          <p class="text-sm text-gray-500 text-center mb-1">Drop Components Here</p>
          <p class="text-xs text-gray-400 text-center">
            Drag form fields from the sidebar to add them to this container
          </p>
        </div>
      </div>
    </div>
  </div>
</div>
```

## Complex Component Creation

### Example: Repeating Group Component

Repeating groups are complex container components that allow multiple instances of the same fields.

#### 1. Component Definition

```javascript
{
  type: 'repeating-group',
  name: 'Repeating Group',
  category: 'Layout',
  icon: 'material-symbols:view-in-ar',
  description: 'Group of fields that can be repeated multiple times',
  defaultProps: {
    label: 'Repeating Group',
    name: 'repeating_group',
    help: 'Add multiple instances of the same fields',
    buttonText: 'Add Item',
    showPlaceholder: true,
    children: [], // Array to hold nested components
    // Conditional Logic Properties
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

#### 2. Drag & Drop Integration

Container components need special drag & drop handling:

```javascript
// In ComponentPreview.vue, add these functions:

const handleSectionDrop = (event, containerId) => {
  event.preventDefault();
  event.stopPropagation();
  
  // Reset drag state
  if (sectionDropStates.value[containerId]) {
    sectionDropStates.value[containerId].isDraggingOver = false;
  }
  
  try {
    // Get the dropped component data
    let componentData = null;
    
    try {
      componentData = JSON.parse(event.dataTransfer.getData('text/plain') || '{}');
    } catch (parseError) {
      componentData = window.__draggedComponentData || {};
    }
    
    if (!componentData.type) {
      console.warn('No valid component data found in drop event');
      return;
    }
    
    // Create a new component instance
    const newComponent = {
      id: `${componentData.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      type: componentData.type,
      name: componentData.name,
      props: {
        ...componentData.defaultProps,
        gridColumn: 'span 6',
        width: '50%',
        label: componentData.defaultProps.label || componentData.name || `${componentData.type.charAt(0).toUpperCase() + componentData.type.slice(1)} Field`,
        name: componentData.defaultProps.name || `${componentData.type}_${Date.now()}`
      }
    };
    
    // Find the target container
    const container = findContainerRecursively(containerId);
    if (container && (container.type === 'form-section' || container.type === 'repeating-group')) {
      // Initialize children array if it doesn't exist
      if (!container.props.children) {
        container.props.children = [];
      }
      
      // Add the component to the container
      container.props.children.push(newComponent);
      
      // Update the container in the form store
      formStore.updateComponent(container);
      
      console.log('Component added to container:', newComponent);
    } else {
      console.warn('Container not found or invalid container type:', containerId);
    }
  } catch (error) {
    console.error('Error dropping component into container:', error);
  }
};
```

#### 3. Container Search Function

```javascript
// Helper function to find container at any nesting level
const findContainerRecursively = (containerId, components = formStore.formComponents, childId = null) => {
  for (const component of components) {
    // Check if this is the target container
    if (containerId && component.id === containerId) {
      return component;
    }
    
    // If searching for parent by child ID, check if this component contains the child
    if (childId && component.props.children && Array.isArray(component.props.children)) {
      const hasChild = component.props.children.some(child => child.id === childId);
      if (hasChild) {
        return component;
      }
    }
    
    // If this component has children, search recursively
    if (component.props.children && Array.isArray(component.props.children)) {
      const found = findContainerRecursively(containerId, component.props.children, childId);
      if (found) {
        return found;
      }
    }
    
    // Special handling for Layout Grid components - search in their cells
    if (component.type === 'layout-grid' && component.props.cells) {
      for (const cell of component.props.cells) {
        if (cell.component) {
          // Check if this cell's component is the target container
          if (containerId && cell.component.id === containerId) {
            return cell.component;
          }
          
          // If searching for parent by child ID, check if this cell's component contains the child
          if (childId && cell.component.props.children && Array.isArray(cell.component.props.children)) {
            const hasChild = cell.component.props.children.some(child => child.id === childId);
            if (hasChild) {
              return cell.component;
            }
          }
          
          // Recursively search in the cell's component children
          if (cell.component.props.children && Array.isArray(cell.component.props.children)) {
            const found = findContainerRecursively(containerId, cell.component.props.children, childId);
            if (found) {
              return found;
            }
          }
        }
      }
    }
  }
  return null;
};
```

## Component Registration

### 1. Add to Available Components

In `FormBuilderComponents.vue`, add your component to the `availableComponents` array:

```javascript
const availableComponents = [
  // ... existing components
  {
    type: 'my-component',
    name: 'My Component',
    category: 'Basic Inputs',
    icon: 'heroicons:document-text',
    description: 'A custom component for collecting data',
    defaultProps: {
      // ... your default props
    }
  }
];
```

### 2. Add Category Support

If you're creating a new category, add it to the template:

```vue
<!-- My Category -->
<div class="component-category mb-4">
  <h3 class="text-gray-700 text-xs font-semibold px-3 mb-2 uppercase tracking-wider">My Category</h3>
  <div class="grid grid-cols-2 gap-1 px-2">
    <div
      v-for="component in getComponentsByCategory('My Category')"
      :key="component.type"
      class="component-item rounded p-2 flex flex-col items-center justify-center cursor-grab hover:bg-gray-100 transition-colors border border-gray-200"
      :class="{ 'hidden': !matchesSearch(component) }"
      draggable="true"
      @dragstart="onDragStart($event, component)"
      @dragend="onDragEnd($event)"
      @click="addComponent(component)"
    >
      <Icon :name="component.icon" class="mb-1 w-5 h-5 text-gray-600" />
      <span class="text-xs text-gray-600 text-center">{{ component.name }}</span>
    </div>
  </div>
</div>
```

## Settings Modal Integration

### 1. Add Component Type Support

In `FormBuilderFieldSettingsModal.vue`:

```javascript
// Add to getComponentTypeName function
getComponentTypeName(type) {
  const typeNames = {
    // ... existing types
    'my-component': 'My Component'
  }
  return typeNames[type] || 'Unknown Component'
}

// Add to getComponentIcon function
getComponentIcon(type) {
  const icons = {
    // ... existing icons
    'my-component': 'heroicons:document-text'
  }
  return icons[type] || 'heroicons:question-mark-circle'
}

// Add to getComponentDescription function
getComponentDescription(type) {
  const descriptions = {
    // ... existing descriptions
    'my-component': 'A custom component for collecting specific data'
  }
  return descriptions[type] || 'Component description'
}
```

### 2. Add Specific Settings

For components with specific settings, add them to the settings modal:

```vue
<!-- My Component Specific Settings -->
<div v-if="component.type === 'my-component'" class="settings-section">
  <div class="section-header">
    <h4 class="section-title">
      <Icon name="heroicons:cog-6-tooth" class="w-5 h-5 mr-2" />
      My Component Settings
    </h4>
    <p class="section-description">Configure specific settings for this component</p>
  </div>
  
  <div class="section-content">
    <FormKit
      type="text"
      label="Custom Property"
      name="myCustomProp"
      v-model="component.props.myCustomProp"
      help="Set a custom property for this component"
      :classes="{ outer: 'field-wrapper' }"
    />
    
    <!-- Add more component-specific settings -->
  </div>
</div>
```

## Component Preview Integration

### 1. Add Preview Rendering

In `ComponentPreview.vue`, add your component's preview rendering:

```vue
<!-- My Component Preview -->
<div v-else-if="component.type === 'my-component'" class="py-2">
  <label v-if="component.props.label && component.props.showLabel !== false" 
         class="block text-sm font-medium text-gray-700 mb-2">
    {{ component.props.label }}
    <span v-if="component.props.required" class="text-red-500">*</span>
  </label>

  <!-- Component-specific rendering -->
  <div class="my-component-wrapper">
    <input 
      type="text"
      :placeholder="component.props.placeholder"
      :required="component.props.required"
      class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
    />
  </div>

  <div v-if="component.props.help" class="mt-1 text-xs text-gray-500">
    {{ component.props.help }}
  </div>
</div>
```

### 2. Handle Preview Mode

Make sure your component handles the `isPreview` prop correctly:

```vue
<!-- My Component with Preview Mode -->
<div v-else-if="component.type === 'my-component'" class="py-2">
  <label v-if="component.props.label && component.props.showLabel !== false" 
         class="block text-sm font-medium text-gray-700 mb-2">
    {{ component.props.label }}
    <span v-if="component.props.required" class="text-red-500">*</span>
  </label>

  <!-- Component-specific rendering -->
  <div class="my-component-wrapper">
    <input 
      v-if="!isPreview"
      type="text"
      :placeholder="component.props.placeholder"
      :required="component.props.required"
      class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
    />
    <div v-else class="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-500">
      {{ component.props.placeholder || 'Preview mode' }}
    </div>
  </div>

  <div v-if="component.props.help" class="mt-1 text-xs text-gray-500">
    {{ component.props.help }}
  </div>
</div>
```

## Drag & Drop Integration

### 1. Basic Drag & Drop

For basic components, drag & drop is handled automatically by the form builder system.

### 2. Container Drag & Drop

For container components, you need to implement custom drag & drop handlers:

```javascript
// In ComponentPreview.vue

// Drag over handler
const handleSectionDragOver = (event, containerId) => {
  event.preventDefault();
  event.stopPropagation();
  
  // Initialize container drop state if it doesn't exist
  if (!sectionDropStates.value[containerId]) {
    sectionDropStates.value[containerId] = { isDraggingOver: false };
  }
  sectionDropStates.value[containerId].isDraggingOver = true;
};

// Drag leave handler
const handleSectionDragLeave = (event, containerId) => {
  event.preventDefault();
  event.stopPropagation();
  
  // Only hide the drag over state if we're actually leaving the drop zone
  const rect = event.currentTarget.getBoundingClientRect();
  const isOutside = (
    event.clientX < rect.left ||
    event.clientX > rect.right ||
    event.clientY < rect.top ||
    event.clientY > rect.bottom
  );
  
  if (isOutside && sectionDropStates.value[containerId]) {
    sectionDropStates.value[containerId].isDraggingOver = false;
  }
};

// Drag enter handler
const handleSectionDragEnter = (event, containerId) => {
  event.preventDefault();
  event.stopPropagation();
  
  // Initialize container drop state if it doesn't exist
  if (!sectionDropStates.value[containerId]) {
    sectionDropStates.value[containerId] = { isDraggingOver: false };
  }
};
```

### 3. Nested Component Management

For container components, you need to handle nested component operations:

```javascript
// Remove component from container
const removeFromSection = (sectionId, childIndex) => {
  const section = findContainerRecursively(sectionId);
  if (section && section.props.children) {
    const removedComponent = section.props.children.splice(childIndex, 1)[0];
    if (removedComponent) {
      formStore.updateComponent(section);
      console.log('Component deleted from section:', removedComponent);
    }
  }
};

// Open nested component settings
const openNestedComponentSettings = (component) => {
  selectedNestedComponent.value = { ...component };
  showNestedSettingsModal.value = true;
  console.log('Opening settings for component:', component);
};

// Save nested component settings
const saveNestedComponentSettings = (updatedComponent) => {
  if (!updatedComponent || !selectedNestedComponent.value) return;
  
  // Find the parent container at any nesting level
  const parentContainer = findContainerRecursively(null, formStore.formComponents, updatedComponent.id);
  
  if (parentContainer) {
    // Find and update the component in the parent's children
    const childIndex = parentContainer.props.children.findIndex(child => child.id === updatedComponent.id);
    if (childIndex !== -1) {
      parentContainer.props.children[childIndex] = updatedComponent;
      formStore.updateComponent(parentContainer);
      console.log('Nested component updated in parent:', parentContainer);
    }
  } else {
    // If not found in main form, it might be in a Layout Grid cell
    console.log('Parent container not found, component might be in Layout Grid');
  }
  
  showNestedSettingsModal.value = false;
  selectedNestedComponent.value = null;
};
```

## Best Practices

### 1. Component Design

- **Consistent Props**: Use consistent prop names across components
- **Default Values**: Always provide sensible default values
- **Validation**: Include validation rules where appropriate
- **Accessibility**: Ensure components are accessible (ARIA labels, keyboard navigation)

### 2. Container Components

- **Children Array**: Always initialize `children: []` for container components
- **Drag & Drop**: Implement proper drag & drop handlers
- **Nested Settings**: Support settings for nested components
- **Recursive Search**: Use `findContainerRecursively` for finding parent containers

### 3. Performance

- **Lazy Loading**: Consider lazy loading for complex components
- **Memoization**: Use computed properties for expensive calculations
- **Event Handling**: Properly clean up event listeners

### 4. Error Handling

- **Validation**: Validate component props
- **Fallbacks**: Provide fallback rendering for missing props
- **Logging**: Add appropriate console logging for debugging

## Examples

### Example 1: Simple Text Input

```javascript
// Component definition
{
  type: 'custom-text',
  name: 'Custom Text Input',
  category: 'Basic Inputs',
  icon: 'heroicons:document-text',
  description: 'A custom text input with special styling',
  defaultProps: {
    label: 'Custom Text',
    name: 'custom_text',
    help: 'Enter your custom text',
    placeholder: 'Type here...',
    required: false,
    width: '100%',
    gridColumn: 'span 6',
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

### Example 2: Advanced Container

```javascript
// Component definition
{
  type: 'advanced-container',
  name: 'Advanced Container',
  category: 'Layout',
  icon: 'material-symbols:view-in-ar',
  description: 'An advanced container with custom styling and behavior',
  defaultProps: {
    label: 'Advanced Container',
    name: 'advanced_container',
    help: 'A container with advanced features',
    showHeader: true,
    headerBackground: '#f9fafb',
    backgroundColor: '#ffffff',
    showBorder: true,
    borderStyle: 'solid',
    spacing: 'normal',
    children: [],
    customFeature: 'default',
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

### Example 3: Complex Component with API Integration

```javascript
// Component definition
{
  type: 'api-select',
  name: 'API Select',
  category: 'Advanced',
  icon: 'heroicons:globe-alt',
  description: 'Dropdown populated from API endpoint',
  defaultProps: {
    label: 'API Select',
    name: 'api_select',
    help: 'Select from API data',
    required: false,
    apiEndpoint: '',
    apiMethod: 'GET',
    valueField: 'id',
    labelField: 'name',
    placeholder: 'Select an option...',
    width: '100%',
    gridColumn: 'span 6',
    conditionalLogic: {
      enabled: false,
      conditions: [],
      action: 'show',
      operator: 'and'
    }
  }
}
```

## Conclusion

Creating new form builder nodes requires understanding the system architecture and following established patterns. Start with simple components and gradually work up to complex container components. Always test thoroughly and ensure proper integration with the existing form builder system.

For complex components, consider breaking them down into smaller, reusable parts and following the established patterns for drag & drop, settings, and preview rendering.
