npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@npulse/form-builder-widget

v1.0.12

Published

Widget JavaScript pour intégrer facilement des formulaires dynamiques Form Builder

Readme

@npulse/form-builder-widget

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

🚀 Installation

npm install @npulse/form-builder-widget

📦 Utilisation

1. HTML Simple (Recommandé)

<!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="[email protected]"
        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="[email protected]">
    </div>
</body>
</html>

2. JavaScript/TypeScript

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: '[email protected]',
    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: '[email protected]'
  },
  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: '[email protected]'
});

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

3. React

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

<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

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

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: '[email protected]'
});

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

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

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

📡 Événements

// É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

/* 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

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é

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

// 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

// 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

// 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)

// 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

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

<!-- É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="[email protected]"
  data-field-telephone="0123456789">
</div>

2. Formulaires de mise à jour

// 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

// 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

// 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

<!-- 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

<!-- 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)

// 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

// 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 pour plus de détails.

📞 Support