npm package: @npulse/form-builder-widget
Description: Widget JavaScript pour intégrer facilement des formulaires dynamiques Form Builder
Author: npulse
Latest version: 1.0.12

# @npulse/form-builder-widget

Widget JavaScript pour intégrer facilement des formulaires dynamiques Form Builder dans vos applications.

## 🚀 Installation

```bash
npm install @npulse/form-builder-widget
```

## 📦 Utilisation

### 1. HTML Simple (Recommandé)

```html
<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/@npulse/form-builder-widget/dist/form-builder-widget.min.js"></script>
</head>
<body>
    <!-- Widget automatique (URL API requise) -->
    <div
        data-form-builder="YOUR_FORM_ID"
        data-api-base-url="https://your-api.com/api">
    </div>

    <!-- Avec configuration complète -->
    <div
        data-form-builder="YOUR_FORM_ID"
        data-api-base-url="https://your-api.com/api"
        data-theme="light"
        data-size="medium"
        data-auto-refresh="true"
        data-refresh-interval="30000">
    </div>

    <!-- Avec données pré-remplies -->
    <div
        data-form-builder="YOUR_FORM_ID"
        data-api-base-url="https://your-api.com/api"
        data-field-nom="Jean Dupont"
        data-field-email="jean@example.com"
        data-field-telephone="0123456789">
    </div>

    <!-- Mode édition -->
    <div
        data-form-builder="YOUR_FORM_ID"
        data-api-base-url="https://your-api.com/api"
        data-edit-mode="true"
        data-submission-id="sub-123"
        data-field-nom="Marie Martin"
        data-field-email="marie@example.com">
    </div>
</body>
</html>
```

### 2. JavaScript/TypeScript

```javascript
import { createFormBuilder } from '@form-builder/widget';

// Créer un widget (apiBaseUrl est maintenant requis)
const widget = createFormBuilder(document.getElementById('my-form'), {
  formId: 'YOUR_FORM_ID',
  apiBaseUrl: 'https://your-api.com/api', // ← REQUIS
  theme: 'light',
  size: 'medium',
  autoRefresh: true,
  refreshInterval: 30000,
  onLoad: (form) => console.log('Formulaire chargé:', form),
  onSubmit: (data) => console.log('Soumis:', data),
  onError: (error) => console.error('Erreur:', error)
});

// Avec données pré-remplies
const widgetWithData = createFormBuilder(document.getElementById('my-form'), {
  formId: 'YOUR_FORM_ID',
  apiBaseUrl: 'https://your-api.com/api',
  initialData: {
    nom: 'Jean Dupont',
    email: 'jean@example.com',
    telephone: '0123456789'
  },
  onSubmit: (data) => console.log('Soumis:', data)
});

// Mode édition
const editWidget = createFormBuilder(document.getElementById('my-form'), {
  formId: 'YOUR_FORM_ID',
  apiBaseUrl: 'https://your-api.com/api',
  editMode: true,
  submissionId: 'sub-123',
  initialData: {
    nom: 'Marie Martin',
    email: 'marie@example.com'
  },
  onSubmit: (data) => console.log('Mis à jour:', data)
});

// Avec composants personnalisés
const customWidget = createFormBuilder(document.getElementById('my-form'), {
  formId: 'YOUR_FORM_ID',
  apiBaseUrl: 'https://your-api.com/api',
  customComponents: {
    rating: {
      name: 'CustomRating',
      fieldType: 'rating',
      render: (field, value, onChange) => {
        const container = document.createElement('div');
        // Votre logique de rendu personnalisée
        return container;
      },
      validate: (value, field) => {
        // Votre logique de validation personnalisée
        return [];
      }
    }
  },
  onSubmit: (data) => console.log('Soumis:', data)
});

// Mettre à jour les données dynamiquement
widgetWithData.setInitialData({
  nom: 'Nouveau Nom',
  email: 'nouveau@example.com'
});

// Obtenir les données actuelles
const currentData = widgetWithData.getUserData();
console.log('Données actuelles:', currentData);
```

### 3. React

```jsx
import React, { useEffect, useRef } from 'react';
import { createFormBuilder } from '@form-builder/widget';

function FormBuilder({ formId, config }) {
  const containerRef = useRef(null);
  const widgetRef = useRef(null);

  useEffect(() => {
    if (containerRef.current && formId) {
      widgetRef.current = createFormBuilder(containerRef.current, {
        formId,
        ...config
      });
    }

    return () => {
      if (widgetRef.current) {
        widgetRef.current.destroy();
      }
    };
  }, [formId, config]);

  return <div ref={containerRef} />;
}

// Utilisation
<FormBuilder 
  formId="YOUR_FORM_ID" 
  config={{
    theme: 'dark',
    size: 'large',
    onSubmit: (data) => console.log('Soumis:', data)
  }} 
/>
```

### 4. Vue.js

```vue
<template>
  <div ref="formContainer"></div>
</template>

<script>
import { createFormBuilder } from '@form-builder/widget';

export default {
  props: ['formId', 'config'],
  mounted() {
    if (this.formId) {
      this.widget = createFormBuilder(this.$refs.formContainer, {
        formId: this.formId,
        ...this.config
      });
    }
  },
  beforeDestroy() {
    if (this.widget) {
      this.widget.destroy();
    }
  }
}
</script>
```

## ⚙️ Configuration

### Attributs HTML

| Attribut | Description | Valeurs | Défaut |
|----------|-------------|---------|---------|
| `data-form-builder` | ID du formulaire | string | **requis** |
| `data-api-base-url` | URL de l'API | string | **requis** |
| `data-theme` | Thème du widget | `light`, `dark` | `light` |
| `data-size` | Taille du widget | `small`, `medium`, `large` | `medium` |
| `data-position` | Position du widget | `inline`, `modal`, `sidebar` | `inline` |
| `data-auto-refresh` | Rechargement auto | `true`, `false` | `true` |
| `data-refresh-interval` | Intervalle de refresh (ms) | number | `30000` |
| `data-language` | Langue | `fr`, `en` | `fr` |
| `data-edit-mode` | Mode édition | `true`, `false` | `false` |
| `data-submission-id` | ID de soumission à modifier | string | - |
| `data-field-*` | Données pré-remplies | string | - |

### Configuration JavaScript

```typescript
interface FormBuilderConfig {
  formId: string;                    // ID du formulaire (requis)
  apiBaseUrl: string;                // URL de l'API (requis)
  theme?: 'light' | 'dark';          // Thème
  size?: 'small' | 'medium' | 'large'; // Taille
  position?: 'inline' | 'modal' | 'sidebar'; // Position
  autoRefresh?: boolean;             // Rechargement automatique
  refreshInterval?: number;          // Intervalle en ms
  language?: 'fr' | 'en';            // Langue
  initialData?: Record<string, any>; // Données pré-remplies
  editMode?: boolean;                // Mode édition
  submissionId?: string;              // ID de soumission à modifier
  customClasses?: {                  // Classes CSS personnalisées
    container?: string;
    form?: string;
    field?: string;
    button?: string;
  };
  onLoad?: (form: FormData) => void;           // Callback chargement
  onSubmit?: (data: FormSubmissionData) => void; // Callback soumission
  onError?: (error: FormBuilderError) => void;   // Callback erreur
  onRefresh?: (changes: FormChanges) => void;    // Callback refresh
}
```

## 🎯 API du Widget

```javascript
const widget = createFormBuilder(element, config);

// Recharger manuellement
await widget.refresh();

// Activer/désactiver auto-refresh
widget.setAutoRefresh(true, 10000); // 10 secondes

// Obtenir les données du formulaire
const formData = widget.getFormData();

// Obtenir les données saisies par l'utilisateur
const userData = widget.getUserData();

// Pré-remplir le formulaire avec des données
widget.setInitialData({
  nom: 'Jean Dupont',
  email: 'jean@example.com'
});

// Valider le formulaire
const validation = await widget.validate();

// Soumettre le formulaire
const result = await widget.submit();

// Détruire le widget
widget.destroy();
```

## 📡 Événements

```javascript
// Écouter les événements
element.addEventListener('formbuilder:loaded', (event) => {
  console.log('Widget chargé:', event.detail);
});

element.addEventListener('formbuilder:submit', (event) => {
  console.log('Formulaire soumis:', event.detail);
});

element.addEventListener('formbuilder:error', (event) => {
  console.error('Erreur:', event.detail);
});

element.addEventListener('formbuilder:refresh', (event) => {
  console.log('Formulaire mis à jour:', event.detail);
});
```

## 🎨 Personnalisation CSS

```css
/* Personnaliser les styles */
.form-builder-widget {
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

.form-builder-widget .btn-primary {
  background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
}

.form-builder-widget .field-input:focus {
  border-color: #667eea;
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
```

## 🔧 Configuration Globale

```javascript
import { configure } from '@form-builder/widget';

// Configuration globale
configure({
  apiBaseUrl: 'https://my-api.com/api',
  theme: 'dark',
  language: 'en'
});
```

## 📱 Responsive

Le widget s'adapte automatiquement aux différentes tailles d'écran :

- **Mobile** : 1 colonne, boutons pleine largeur
- **Tablet** : 2 colonnes, layout adaptatif
- **Desktop** : Nombre de colonnes configuré

## 🧩 Composants Personnalisés

### Créer un composant personnalisé

```javascript
const customComponent = {
  name: 'CustomRating',
  fieldType: 'rating', // Type de champ qu'il remplace
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    // Créer votre interface personnalisée
    for (let i = 1; i <= 5; i++) {
      const star = document.createElement('span');
      star.textContent = '★';
      star.style.color = i <= value ? '#ffc107' : '#ddd';
      star.addEventListener('click', () => onChange(i));
      container.appendChild(star);
    }
    
    return container;
  },
  validate: (value, field) => {
    const errors = [];
    if (field.required && !value) {
      errors.push({
        field: field.name,
        message: 'Veuillez donner une note',
        code: 'REQUIRED'
      });
    }
    return errors;
  },
  destroy: (element) => {
    // Nettoyage si nécessaire
    element.innerHTML = '';
  }
};

// Utiliser le composant
const widget = createFormBuilder(element, {
  formId: 'YOUR_FORM_ID',
  apiBaseUrl: 'https://your-api.com/api',
  customComponents: {
    rating: customComponent
  }
});
```

### Composants React

```javascript
// Adapter un composant React existant
const reactComponent = {
  name: 'ReactDatePicker',
  fieldType: 'date',
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    // Créer un élément React (avec ReactDOM.render)
    const reactElement = React.createElement(YourDatePickerComponent, {
      value: value,
      onChange: onChange,
      field: field
    });
    
    ReactDOM.render(reactElement, container);
    return container;
  },
  destroy: (element) => {
    ReactDOM.unmountComponentAtNode(element);
  }
};
```

### Composants Vue

```javascript
// Adapter un composant Vue existant
const vueComponent = {
  name: 'VueColorPicker',
  fieldType: 'color',
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    // Créer une instance Vue
    const vueInstance = new Vue({
      el: container,
      template: '<your-color-picker :value="value" @change="onChange" />',
      data: { value },
      methods: { onChange }
    });
    
    return container;
  },
  destroy: (element) => {
    // Nettoyer l'instance Vue
    element.__vue__?.$destroy();
  }
};
```

### Composants Angular

```javascript
// Adapter un composant Angular existant
const angularComponent = {
  name: 'AngularSlider',
  fieldType: 'slider',
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    // Utiliser Angular Elements ou Dynamic Component Loading
    const sliderElement = document.createElement('your-slider');
    sliderElement.setAttribute('ng-reflect-value', value);
    sliderElement.addEventListener('valueChange', (e) => onChange(e.detail));
    
    container.appendChild(sliderElement);
    return container;
  }
};
```

### Composants intégrés (Upload & Cartes)

```javascript
// Upload de fichiers avec configuration personnalisée
const fileUploadComponent = {
  name: 'CustomFileUpload',
  fieldType: 'file',
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    const uploadConfig = {
      uploadUrl: 'https://your-api.com/upload',
      multiple: true,
      maxSize: 5 * 1024 * 1024, // 5MB
      accept: 'image/*,.pdf,.doc,.docx',
      headers: {
        'Authorization': 'Bearer ' + getAuthToken()
      },
      template: {
        button: '📁 Choisir vos fichiers',
        dropZone: 'Glissez-déposez vos fichiers ici',
        styles: `
          .file-upload-container {
            border: 2px dashed #007bff;
            border-radius: 12px;
            background: linear-gradient(45deg, #f8f9fa, #e9ecef);
          }
        `
      }
    };
    
    const fileUpload = new FileUploadComponent(container, uploadConfig, onChange);
    return container;
  }
};

// Cartes géographiques avec multi-providers
const mapComponent = {
  name: 'CustomMap',
  fieldType: 'map',
  render: (field, value, onChange) => {
    const container = document.createElement('div');
    
    const mapConfig = {
      provider: 'google', // ou 'openstreetmap', 'mapbox'
      apiKey: 'YOUR_API_KEY',
      center: { lat: 48.8566, lng: 2.3522 },
      zoom: 13,
      height: '400px',
      selectionMode: 'point',
      draggable: true,
      template: {
        controls: 'Vos contrôles personnalisés',
        styles: `
          .map-component-container {
            border: 2px solid #007bff;
            border-radius: 12px;
            box-shadow: 0 4px 12px rgba(0,123,255,0.15);
          }
        `
      }
    };
    
    const mapComponent = new MapComponent(container, mapConfig, onChange);
    return container;
  }
};
```

### Gestion dynamique des composants

```javascript
const widget = createFormBuilder(element, config);

// Enregistrer un nouveau composant
widget.registerCustomComponent({
  name: 'CustomToggle',
  fieldType: 'toggle',
  render: (field, value, onChange) => {
    // Votre logique de rendu
  }
});

// Désenregistrer un composant
widget.unregisterCustomComponent('toggle');

// Obtenir un composant
const component = widget.getCustomComponent('rating');
```

## 🎯 Cas d'usage avec données pré-remplies

### 1. Formulaires d'édition

```html
<!-- Édition d'un profil utilisateur -->
<div 
  data-form-builder="user-profile-form"
  data-api-base-url="https://api.myapp.com/api"
  data-edit-mode="true"
  data-submission-id="user-123"
  data-field-nom="Jean Dupont"
  data-field-email="jean@example.com"
  data-field-telephone="0123456789">
</div>
```

### 2. Formulaires de mise à jour

```javascript
// Mise à jour d'une commande
const editWidget = createFormBuilder(element, {
  formId: 'order-update-form',
  apiBaseUrl: 'https://api.mystore.com/api',
  editMode: true,
  submissionId: 'order-456',
  initialData: {
    quantite: 5,
    adresse: '123 Rue de la Paix',
    ville: 'Paris',
    codePostal: '75001'
  },
  onSubmit: (data) => {
    console.log('Commande mise à jour:', data);
    // Rediriger vers la page de confirmation
  }
});
```

### 3. Formulaires avec données contextuelles

```javascript
// Formulaire pré-rempli selon le contexte
function loadContextualForm(userContext) {
  const widget = createFormBuilder(element, {
    formId: 'contextual-form',
    apiBaseUrl: 'https://api.myapp.com/api',
    initialData: {
      // Données du contexte utilisateur
      nom: userContext.name,
      email: userContext.email,
      role: userContext.role,
      // Données de session
      sessionId: getCurrentSessionId(),
      timestamp: new Date().toISOString()
    }
  });
}
```

### 4. Formulaires multi-étapes avec sauvegarde

```javascript
// Sauvegarde automatique des données
const multiStepWidget = createFormBuilder(element, {
  formId: 'multi-step-form',
  apiBaseUrl: 'https://api.myapp.com/api',
  initialData: getSavedFormData(), // Récupérer les données sauvegardées
  onSubmit: (data) => {
    // Sauvegarder les données pour la prochaine étape
    saveFormData(data);
    // Soumettre au serveur
    submitToServer(data);
  }
});

// Fonction de sauvegarde locale
function saveFormData(data) {
  localStorage.setItem('form-draft', JSON.stringify(data));
}

function getSavedFormData() {
  const saved = localStorage.getItem('form-draft');
  return saved ? JSON.parse(saved) : {};
}
```

## 🚀 Exemples Complets

### E-commerce

```html
<!-- Page produit -->
<div class="product-form">
  <h2>Commander ce produit</h2>
  <div 
    data-form-builder="product-order-form"
    data-theme="light"
    data-size="large">
  </div>
</div>
```

### Contact

```html
<!-- Page contact -->
<div class="contact-section">
  <div 
    data-form-builder="contact-form"
    data-theme="dark"
    data-size="medium"
    data-auto-refresh="false">
  </div>
</div>
```

### SPA (Single Page Application)

```javascript
// Dans votre SPA
import { createFormBuilder } from '@form-builder/widget';

class FormManager {
  constructor() {
    this.widgets = new Map();
  }

  loadForm(containerId, formId) {
    const container = document.getElementById(containerId);
    if (container && !this.widgets.has(containerId)) {
      const widget = createFormBuilder(container, {
        formId,
        onSubmit: (data) => this.handleSubmit(data),
        onError: (error) => this.handleError(error)
      });
      this.widgets.set(containerId, widget);
    }
  }

  handleSubmit(data) {
    // Logique de soumission
    console.log('Données soumises:', data);
  }

  handleError(error) {
    // Gestion d'erreur
    console.error('Erreur:', error);
  }
}

const formManager = new FormManager();
```

## 🐛 Debug

```javascript
// Activer les logs
localStorage.setItem('form-builder-debug', 'true');

// Vérifier l'état du widget
console.log('Widget state:', widget.getFormData());
```

## 📄 Licence

MIT

## 🤝 Contribution

Les contributions sont les bienvenues ! Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour plus de détails.

## 📞 Support

- 📧 Email: support@form-builder.com
- 💬 Discord: [Form Builder Community](https://discord.gg/form-builder)
- 📖 Documentation: [docs.form-builder.com](https://docs.form-builder.com)