npm package: @izan-sanchez/izan-form
Description: Un componente de formulario web component universal construido con Svelte. Compatible con React, Vue, Angular y JavaScript vanilla.
Author: Izan Sanchez
Latest version: 1.0.1

# @izan-sanchez/izan-form 🚀

Un componente de formulario web moderno y accesible construido con Svelte que funciona en cualquier framework o HTML vanilla. Diseño glassmorphism, validación integrada, y totalmente personalizable.

![npm version](https://img.shields.io/npm/v/@izan-sanchez/izan-form)
![npm downloads](https://img.shields.io/npm/dm/@izan-sanchez/izan-form)
![license](https://img.shields.io/npm/l/@izan-sanchez/izan-form)
![bundle size](https://img.shields.io/bundlephobia/minzip/@izan-sanchez/izan-form)

## ✨ Características

- 🎨 **Diseño Moderno**: Estilo glassmorphism con animaciones suaves
- 🌙 **Modo Oscuro/Claro**: Soporte nativo para temas
- ♿ **Accesible**: Cumple con estándares WCAG
- 📱 **Responsive**: Optimizado para móviles y desktop
- 🔧 **Flexible**: Múltiples tipos de campos soportados
- ⚡ **Ligero**: Sin dependencias externas
- 🌐 **Universal**: Funciona en cualquier framework
- 🔒 **Validación**: Validación HTML5 integrada
- 🎭 **Animaciones**: Transiciones suaves y micro-interacciones
- 🎯 **TypeScript**: Incluye definiciones de tipos

## 📦 Instalación

```bash
npm install @izan-sanchez/izan-form
```

```bash
yarn add @izan-sanchez/izan-form
```

```bash
pnpm add @izan-sanchez/izan-form
```

## 📋 Propiedades (Attributes)

| Propiedad | Tipo | Valor por defecto | Descripción |
|-----------|------|-------------------|-------------|
| `title` | string | `''` | Título del formulario |
| `submittext` | string | `'Enviar'` | Texto del botón de envío |
| `loadingtext` | string | `'Enviando...'` | Texto mostrado durante el envío |
| `theme` | string | `'light'` | Tema del formulario (`'light'` \| `'dark'`) |
| `fields` | string \| array | `'[]'` | Configuración de campos (JSON string o array) |
| `privacypolicyurl` | string | `''` | URL de la política de privacidad |

## 🔧 Configuración de Campos

### Tipos de Campo Soportados

#### Text Input
```javascript
{
    name: 'firstName',
    type: 'text',
    label: 'Nombre',
    required: true,
    minlength: 2,
    maxlength: 50,
}
```

#### Email
```javascript
{
    name: 'email',
    type: 'email',
    label: 'Correo electrónico',
    required: true,
}
```

#### Password
```javascript
{
    name: 'password',
    type: 'password',
    label: 'Contraseña',
    required: true,
    minlength: 8,
    maxlength: 128,}
```

#### Number
```javascript
{
    name: 'age',
    type: 'number',
    label: 'Edad',
    required: true,
    min: 18,
    max: 120,
}
```

#### Tel
```javascript
{
    name: 'phone',
    type: 'tel',
    label: 'Teléfono',
}
```

#### URL
```javascript
{
    name: 'website',
    type: 'url',
    label: 'Sitio web',
}
```

#### Date
```javascript
{
    name: 'birthdate',
    type: 'date',
    label: 'Fecha de nacimiento',
    required: true,
    min: '1900-01-01',
    max: '2024-12-31'
}
```

#### Time
```javascript
{
    name: 'appointment',
    type: 'time',
    label: 'Hora de cita',
    required: true,
    min: '09:00',
    max: '18:00'
}
```

#### Textarea
```javascript
{
    name: 'message',
    type: 'textarea',
    label: 'Mensaje',
    required: true,
    rows: 5,
    minlength: 10,
    maxlength: 1000,
}
```

#### Select
```javascript
{
    name: 'country',
    type: 'select',
    label: 'País',
    required: true,
    options: [
        { value: 'es', label: 'España' },
        { value: 'mx', label: 'México' },
        { value: 'ar', label: 'Argentina' },
        { value: 'co', label: 'Colombia', selected: true }
    ],
}
```

#### Multiple Select
```javascript
{
    name: 'skills',
    type: 'select',
    label: 'Habilidades',
    multiple: true,
    options: [
        { value: 'js', label: 'JavaScript' },
        { value: 'react', label: 'React' },
        { value: 'vue', label: 'Vue.js' },
        { value: 'svelte', label: 'Svelte' }
    ]
}
```

#### Radio Buttons
```javascript
{
    name: 'plan',
    type: 'radio',
    label: 'Plan de suscripción',
    required: true,
    options: [
        { value: 'basic', label: 'Básico - €9/mes' },
        { value: 'pro', label: 'Pro - €19/mes', selected: true },
        { value: 'premium', label: 'Premium - €39/mes' }
    ]
}
```

#### Checkbox
```javascript
{
    name: 'newsletter',
    type: 'checkbox',
    label: 'Suscribirme al newsletter',
}
```

#### Privacy Checkbox (especial)
```javascript
{
    name: 'privacy',
    type: 'checkbox',
    label: 'Acepto la política de privacidad',
    required: true
}
```

### Propiedades Comunes de Campos

| Propiedad | Tipo | Descripción |
|-----------|------|-------------|
| `name` | string | **Requerido**. Nombre único del campo |
| `type` | string | **Requerido**. Tipo de campo |
| `label` | string | **Requerido**. Etiqueta visible del campo |
| `required` | boolean | Si el campo es obligatorio |

### Propiedades Específicas por Tipo

#### Text, Email, Password, Tel, URL
- `minlength`: Longitud mínima
- `maxlength`: Longitud máxima  

#### Number, Date, Time
- `min`: Valor mínimo
- `max`: Valor máximo

#### Textarea
- `rows`: Número de filas
- `minlength`: Longitud mínima
- `maxlength`: Longitud máxima

#### Select
- `multiple`: Permitir selección múltiple
- `options`: Array de opciones con `value`, `label` y `selected`

#### Radio
- `options`: Array de opciones con `value`, `label` y `selected`


## 🎯 Uso Básico

### HTML Vanilla

```html
<!DOCTYPE html>
<html>
<head>
    <script type="module" src="node_modules/@izan-sanchez/izan-form/dist/izan-form.js"></script>
</head>
<body>
    <izan-form
        title="Formulario de Contacto"
        submittext="Enviar Mensaje"
        theme="light"
        fields='[
            {
                "name": "name",
                "type": "text",
                "label": "Nombre",
                "required": true
            },
            {
                "name": "email",
                "type": "email",
                "label": "Email",
                "required": true
            },
            {
                "name": "message",
                "type": "textarea",
                "label": "Mensaje",
                "required": true
            }
        ]'>
    </izan-form>

    <script>
        document.querySelector('izan-form').addEventListener('formsubmit', (event) => {
            console.log('Datos:', event.detail.values);
            // Procesar datos aquí
        });
    </script>
</body>
</html>
```

### React

```jsx
import React, { useEffect, useRef } from 'react';
import '@izan-sanchez/izan-form';

function ContactForm() {
    const formRef = useRef(null);

    const fields = [
        {
            name: 'name',
            type: 'text',
            label: 'Nombre',
            required: true,
            placeholder: 'Tu nombre completo'
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true,
            placeholder: 'tu@email.com'
        },
        {
            name: 'phone',
            type: 'tel',
            label: 'Teléfono',
            placeholder: '+34 600 000 000'
        },
        {
            name: 'message',
            type: 'textarea',
            label: 'Mensaje',
            required: true,
            rows: 5,
            placeholder: 'Escribe tu mensaje aquí...'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto la política de privacidad',
            required: true
        }
    ];

    useEffect(() => {
        const form = formRef.current;
        if (form) {
            const handleSubmit = (event) => {
                console.log('Form data:', event.detail.values);
                // Aquí puedes enviar los datos a tu API
                fetch('/api/contact', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(event.detail.values)
                });
            };

            const handleReady = () => {
                console.log('Form is ready');
            };

            form.addEventListener('formsubmit', handleSubmit);
            form.addEventListener('ready', handleReady);
            
            return () => {
                form.removeEventListener('formsubmit', handleSubmit);
                form.removeEventListener('ready', handleReady);
            };
        }
    }, []);

    return (
        <izan-form
            ref={formRef}
            title="Formulario de Contacto"
            submittext="Enviar Mensaje"
            loadingtext="Enviando..."
            theme="light"
            fields={JSON.stringify(fields)}
            successmessage="¡Mensaje enviado correctamente!"
            errormessage="Error al enviar el mensaje. Inténtalo de nuevo."
            privacypolicyurl="https://tu-sitio.com/privacy"
            autocomplete="on"
        />
    );
}

export default ContactForm;
```

### Vue 3

```vue
<template>
    <izan-form
        ref="formComponent"
        :title="formConfig.title"
        :submittext="formConfig.submitText"
        :theme="currentTheme"
        :fields="JSON.stringify(formConfig.fields)"
        :successmessage="formConfig.successMessage"
        :errormessage="formConfig.errorMessage"
        :privacypolicyurl="formConfig.privacyUrl"
        @formsubmit="handleFormSubmit"
        @ready="onFormReady"
    />
    
    <div class="form-controls">
        <button @click="toggleTheme">
            Cambiar a tema {{ currentTheme === 'light' ? 'oscuro' : 'claro' }}
        </button>
        <button @click="resetForm">Resetear formulario</button>
        <button @click="validateForm">Validar formulario</button>
    </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import '@izan-sanchez/izan-form';

const formComponent = ref(null);
const currentTheme = ref('light');

const formConfig = reactive({
    title: 'Registro de Usuario',
    submitText: 'Crear Cuenta',
    successMessage: '¡Cuenta creada exitosamente!',
    errorMessage: 'Error al crear la cuenta',
    privacyUrl: 'https://tu-sitio.com/privacy',
    fields: [
        {
            name: 'firstName',
            type: 'text',
            label: 'Nombre',
            required: true,
            minlength: 2
        },
        {
            name: 'lastName',
            type: 'text',
            label: 'Apellidos',
            required: true,
            minlength: 2
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true
        },
        {
            name: 'password',
            type: 'password',
            label: 'Contraseña',
            required: true,
            minlength: 8
        },
        {
            name: 'country',
            type: 'select',
            label: 'País',
            required: true,
            options: [
                { value: 'es', label: 'España' },
                { value: 'mx', label: 'México' },
                { value: 'ar', label: 'Argentina' },
                { value: 'co', label: 'Colombia' }
            ]
        },
        {
            name: 'gender',
            type: 'radio',
            label: 'Género',
            options: [
                { value: 'male', label: 'Masculino' },
                { value: 'female', label: 'Femenino' },
                { value: 'other', label: 'Otro' },
                { value: 'prefer-not-to-say', label: 'Prefiero no decir' }
            ]
        },
        {
            name: 'newsletter',
            type: 'checkbox',
            label: 'Suscribirme al newsletter'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto la política de privacidad',
            required: true
        }
    ]
});

const handleFormSubmit = async (event) => {
    console.log('Form submitted:', event.detail.values);
    
    try {
        const response = await fetch('/api/register', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(event.detail.values)
        });
        
        if (!response.ok) {
            throw new Error('Registration failed');
        }
        
        const result = await response.json();
        console.log('Registration successful:', result);
    } catch (error) {
        console.error('Registration error:', error);
    }
};

const onFormReady = () => {
    console.log('Form component is ready');
};

const toggleTheme = () => {
    currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
};

const resetForm = () => {
    if (formComponent.value) {
        formComponent.value.reset();
    }
};

const validateForm = () => {
    if (formComponent.value) {
        const isValid = formComponent.value.validate();
        console.log('Form is valid:', isValid);
    }
};
</script>

<style scoped>
.form-controls {
    margin-top: 20px;
    display: flex;
    gap: 10px;
}

.form-controls button {
    padding: 8px 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
}
</style>
```

### Angular

```typescript
// app.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import '@izan-sanchez/izan-form';

@Component({
  selector: 'app-contact',
  template: `
    <izan-form
      #formElement
      [attr.title]="formTitle"
      [attr.submittext]="submitText"
      [attr.theme]="theme"
      [attr.fields]="fieldsJson"
      [attr.successmessage]="successMessage"
      [attr.errormessage]="errorMessage"
      [attr.privacypolicyurl]="privacyUrl"
    ></izan-form>
    
    <div class="controls">
      <button (click)="toggleTheme()">Cambiar Tema</button>
      <button (click)="resetForm()">Reset</button>
      <button (click)="getFormValues()">Ver Valores</button>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('formElement') formElement!: ElementRef;

  formTitle = 'Formulario de Contacto';
  submitText = 'Enviar';
  theme = 'light';
  successMessage = '¡Formulario enviado correctamente!';
  errorMessage = 'Error al enviar el formulario';
  privacyUrl = 'https://tu-sitio.com/privacy';

  fields = [
    {
      name: 'company',
      type: 'text',
      label: 'Empresa',
      required: true
    },
    {
      name: 'contact_name',
      type: 'text',
      label: 'Nombre de contacto',
      required: true
    },
    {
      name: 'email',
      type: 'email',
      label: 'Email corporativo',
      required: true
    },
    {
      name: 'budget',
      type: 'select',
      label: 'Presupuesto',
      required: true,
      options: [
        { value: '1000-5000', label: '1.000€ - 5.000€' },
        { value: '5000-10000', label: '5.000€ - 10.000€' },
        { value: '10000-25000', label: '10.000€ - 25.000€' },
        { value: '25000+', label: 'Más de 25.000€' }
      ]
    },
    {
      name: 'services',
      type: 'checkbox',
      label: 'Desarrollo web'
    },
    {
      name: 'privacy',
      type: 'checkbox',
      label: 'Acepto la política de privacidad',
      required: true
    }
  ];

  get fieldsJson(): string {
    return JSON.stringify(this.fields);
  }

  ngAfterViewInit() {
    const form = this.formElement.nativeElement;
    
    form.addEventListener('formsubmit', (event: any) => {
      console.log('Form submitted:', event.detail.values);
      this.handleFormSubmit(event.detail.values);
    });

    form.addEventListener('ready', () => {
      console.log('Form is ready');
    });
  }

  handleFormSubmit(values: any) {
    // Procesar los datos del formulario
    console.log('Processing form data:', values);
  }

  toggleTheme() {
    this.theme = this.theme === 'light' ? 'dark' : 'light';
  }

  resetForm() {
    const form = this.formElement.nativeElement;
    if (form && form.reset) {
      form.reset();
    }
  }

  getFormValues() {
    const form = this.formElement.nativeElement;
    if (form && form.getValues) {
      const values = form.getValues();
      console.log('Current form values:', values);
    }
  }
}
```

### Astro

```astro
---
// pages/contact.astro
const fields = [
  {
    name: 'name',
    type: 'text',
    label: 'Nombre',
    required: true
  },
  {
    name: 'email',
    type: 'email',
    label: 'Email',
    required: true
  },
  {
    name: 'subject',
    type: 'select',
    label: 'Asunto',
    required: true,
    options: [
      { value: 'general', label: 'Consulta general' },
      { value: 'support', label: 'Soporte técnico' },
      { value: 'sales', label: 'Ventas' },
      { value: 'partnership', label: 'Colaboración' }
    ]
  },
  {
    name: 'priority',
    type: 'radio',
    label: 'Prioridad',
    options: [
      { value: 'low', label: 'Baja' },
      { value: 'medium', label: 'Media', selected: true },
      { value: 'high', label: 'Alta' }
    ]
  },
  {
    name: 'message',
    type: 'textarea',
    label: 'Mensaje',
    required: true,
    rows: 6
  },
  {
    name: 'privacy',
    type: 'checkbox',
    label: 'Acepto la política de privacidad',
    required: true
  }
];
---

<html lang="es">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Contacto - Mi Sitio</title>
</head>
<body>
    <main>
        <h1>Contacto</h1>
        
        <izan-form
            id="contact-form"
            title="Envíanos un mensaje"
            submittext="Enviar mensaje"
            loadingtext="Enviando..."
            theme="light"
            fields={JSON.stringify(fields)}
            successmessage="¡Mensaje enviado correctamente!"
            errormessage="Error al enviar el mensaje"
            privacypolicyurl="/privacy"
            action="/api/contact"
            method="POST"
            autocomplete="on"
        />
    </main>

    <script>
        import '@izan-sanchez/izan-form';
        
        document.addEventListener('DOMContentLoaded', () => {
            const form = document.getElementById('contact-form');
            
            if (form) {
                form.addEventListener('formsubmit', async (event) => {
                    console.log('Form data:', event.detail.values);
                    
                    // Ejemplo de envío a API
                    try {
                        const response = await fetch('/api/contact', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            },
                            body: JSON.stringify(event.detail.values)
                        });
                        
                        if (response.ok) {
                            console.log('Message sent successfully');
                        } else {
                            console.error('Failed to send message');
                        }
                    } catch (error) {
                        console.error('Error:', error);
                    }
                });

                form.addEventListener('ready', () => {
                    console.log('Contact form is ready');
                });
            }
        });
    </script>

    <style>
        main {
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
        }
        
        h1 {
            text-align: center;
            margin-bottom: 2rem;
            color: #333;
        }
    </style>
</body>
</html>
```

### Svelte/SvelteKit

```svelte
<script>
    import { onMount } from 'svelte';
    import '@izan-sanchez/izan-form';

    let formElement;
    let currentTheme = 'light';

    const fields = [
        {
            name: 'username',
            type: 'text',
            label: 'Nombre de usuario',
            required: true,
            minlength: 3,
            maxlength: 20,
            pattern: '^[a-zA-Z0-9_]+$',
            help: 'Solo letras, números y guiones bajos'
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true
        },
        {
            name: 'age',
            type: 'number',
            label: 'Edad',
            min: 18,
            max: 120,
            required: true
        },
        {
            name: 'website',
            type: 'url',
            label: 'Sitio web',
            placeholder: 'https://tu-sitio.com'
        },
        {
            name: 'bio',
            type: 'textarea',
            label: 'Biografía',
            maxlength: 500,
            rows: 4,
            help: 'Cuéntanos un poco sobre ti'
        },
        {
            name: 'notifications',
            type: 'checkbox',
            label: 'Recibir notificaciones por email'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto los términos y condiciones',
            required: true
        }
    ];

    function handleFormSubmit(event) {
        console.log('Form submitted:', event.detail.values);
        
        // Ejemplo de validación personalizada
        const { username, email, age } = event.detail.values;
        
        if (username.toLowerCase().includes('admin')) {
            alert('El nombre de usuario no puede contener "admin"');
            return;
        }
        
        if (age < 18) {
            alert('Debes ser mayor de edad');
            return;
        }
        
        // Simular envío a servidor
        setTimeout(() => {
            alert('¡Registro completado exitosamente!');
            formElement.reset();
        }, 1000);
    }

    function toggleTheme() {
        currentTheme = currentTheme === 'light' ? 'dark' : 'light';
    }

    function resetForm() {
        if (formElement) {
            formElement.reset();
        }
    }

    function validateForm() {
        if (formElement) {
            const isValid = formElement.validate();
            alert(`El formulario es ${isValid ? 'válido' : 'inválido'}`);
        }
    }

    function getFormValues() {
        if (formElement) {
            const values = formElement.getValues();
            console.log('Valores actuales:', values);
        }
    }

    onMount(() => {
        if (formElement) {
            formElement.addEventListener('formsubmit', handleFormSubmit);
            formElement.addEventListener('ready', () => {
                console.log('Form is ready');
            });
        }

        return () => {
            if (formElement) {
                formElement.removeEventListener('formsubmit', handleFormSubmit);
            }
        };
    });
</script>

<div class="container">
    <h1>Registro de Usuario</h1>
    
    <izan-form
        bind:this={formElement}
        title="Crea tu cuenta"
        submittext="Registrarse"
        loadingtext="Creando cuenta..."
        theme={currentTheme}
        fields={JSON.stringify(fields)}
        successmessage="¡Cuenta creada correctamente!"
        errormessage="Error al crear la cuenta"
        privacypolicyurl="/privacy"
        novalidate={false}
        autocomplete="on"
    />

    <div class="controls">
        <button on:click={toggleTheme}>
            Cambiar a tema {currentTheme === 'light' ? 'oscuro' : 'claro'}
        </button>
        <button on:click={resetForm}>Resetear formulario</button>
        <button on:click={validateForm}>Validar formulario</button>
        <button on:click={getFormValues}>Ver valores actuales</button>
    </div>
</div>

<style>
    .container {
        max-width: 600px;
        margin: 0 auto;
        padding: 2rem;
    }

    h1 {
        text-align: center;
        margin-bottom: 2rem;
        color: #333;
    }

    .controls {
        margin-top: 2rem;
        display: flex;
        gap: 1rem;
        flex-wrap: wrap;
        justify-content: center;
    }

    .controls button {
        padding: 0.5rem 1rem;
        border: 1px solid #ddd;
        border-radius: 4px;
        background: #fff;
        cursor: pointer;
        transition: background-color 0.2s;
    }

    .controls button:hover {
        background: #f5f5f5;
    }
</style>
```


## 👨‍💻 Autor

**Izan Sanchez**
- Email: sanchezginesizan@gmail.com
- GitHub: [@izan-sanchez](https://github.com/izan-sanchez)


## 📞 Soporte

¿Necesitas ayuda? Puedes:

- 📖 Revisar esta documentación
- 📧 Contactar al autor: sanchezginesizan@gmail.com