npm package: @lucaismyname/stepper-js
Description: A lightweight, TypeScript-first multi-step form library that works with any existing form
Author: Luca Mack
Latest version: 0.0.2

# StepperJS 🚀

A TypeScript multi-step form library that works with any existing form. Zero dependencies, framework-agnostic, and designed to be completely non-intrusive.

[![npm version](https://badge.fury.io/js/%40lucamack%2Fstepper-js.svg)](https://badge.fury.io/js/%40lucamack%2Fstepper-js)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## ✨ Features

- **🎯 Drop-in compatibility** - Works with any existing form without modifications
- **📱 Framework agnostic** - Vanilla JS/TS, works with React, Vue, Angular, etc.
- **🔧 TypeScript-first** - Full type safety and IntelliSense support
- **🪶 Lightweight** - <5KB gzipped, zero dependencies
- **🎨 Unstyled** - Bring your own CSS, works with any design system
- **✅ Validation ready** - HTML5 validation + custom validation functions + server-side error handling
- **🔄 Server-side errors** - Handle and display backend validation errors
- **♿ Accessible** - ARIA attributes and keyboard navigation
- **📊 Progress tracking** - Built-in step completion tracking

## 🚀 Quick Start

### Installation

```bash
npm install @lucaismyname/stepper-js
```

### Basic Usage

```typescript
import { MultiStepForm } from '@lucaismyname/stepper-js';

const stepper = new MultiStepForm({
  formSelector: '#my-form',
  steps: [
    { selector: '.step-1', validate: true },
    { selector: '.step-2', validate: true },
    { selector: '.step-3', validate: false }
  ],
  buttonLabels: {
    next: 'Continue',
    prev: 'Go Back',
    submit: 'Complete'
  }
});
```

### HTML Structure

```html
<form id="my-form">
  <div class="step-1">
    <h2>Personal Info</h2>
    <input type="text" name="firstName" required>
    <input type="email" name="email" required>
  </div>
  
  <div class="step-2">
    <h2>Address</h2>
    <input type="text" name="street" required>
    <input type="text" name="city" required>
  </div>
  
  <div class="step-3">
    <h2>Confirmation</h2>
    <p>Please review your information...</p>
  </div>
</form>
```

## ⚙️ **Form Requirements & Compatibility**

### **✅ What Forms Work**
StepperJS works with **any HTML form** that has:
- A `<form>` element (can be selected by any CSS selector)
- Container elements for each step (divs, fieldsets, sections, etc.)
- Standard form fields (input, select, textarea)

The library is designed to be non-intrusive - it doesn't modify your form structure, only adds navigation functionality and validation handling.

### **🏗️ Required Structure**
```html
<form id="any-form">
  <!-- Step 1: Any container element -->
  <div class="step-1">
    <input name="field1" required>
    <input name="field2">
  </div>
  
  <!-- Step 2: Can be any element type -->
  <fieldset id="step-2">
    <select name="field3" required></select>
    <textarea name="field4"></textarea>
  </fieldset>
  
  <!-- Step 3: Nested elements work too -->
  <section data-step="final">
    <div class="form-group">
      <input type="checkbox" name="terms" required>
    </div>
  </section>
</form>
```

### **🚫 What Doesn't Work**
- **No form element**: Must have a `<form>` tag
- **Invalid selectors**: CSS selectors that don't exist
- **Empty steps**: Step containers with no form fields
- **Duplicate selectors**: Multiple elements matching the same selector

### **🛡️ Error Handling**
The library provides comprehensive error handling:

```javascript
try {
  const stepper = new MultiStepForm({
    formSelector: '#nonexistent-form', // ❌ Will throw error
    steps: [
      { selector: '.missing-step' },    // ❌ Will be filtered out
      { selector: '#valid-step' }       // ✅ Will work
    ]
  });
} catch (error) {
  console.error('Initialization failed:', error.message);
  // "Form not found: #nonexistent-form"
}
```

**Error Types:**
- **Form not found**: Invalid `formSelector`
- **No valid steps**: All step selectors are invalid
- **Timeout errors**: When using `initializeWhenReady()`

### **📊 Step Limitations**
- **Minimum steps**: 1 (at least one valid step required)
- **Maximum steps**: Unlimited! Create as many as needed
- **Step validation**: Each step is validated independently
- **Navigation**: Automatically generated for any number of steps

## 📖 API Reference

### Configuration Options

```typescript
interface MultiStepConfig {
  formSelector: string;              // CSS selector for form
  steps: StepConfig[];               // Array of step configurations
  buttonLabels?: ButtonLabels;       // Global button text overrides
  validation?: ValidationFunction;   // Custom validation function
  onStepChange?: StepChangeCallback; // Step change event handler
  onValidationError?: ErrorCallback; // Validation error handler
  beforeStepChange?: BeforeChangeCallback; // Pre-step-change hook
  autoScroll?: boolean;              // Auto-scroll on step change (default: true)
  scrollOffset?: number;             // Scroll offset in pixels (default: 20)
  buttonClasses?: ButtonClasses;     // Custom CSS classes for buttons
}

interface StepConfig {
  selector: string;                  // CSS selector for step container
  validate?: boolean;                // Validate step on change (default: true)
  prevButton?: string;               // Custom text for previous button on this step
  nextButton?: string;               // Custom text for next button on this step
}

interface ButtonLabels {
  next?: string;                     // Next button text
  prev?: string;                     // Previous button text
  submit?: string;                   // Submit button text
}

interface ValidationFunction {
  (fields: NodeListOf<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>): boolean; // Custom validation function
}

interface StepChangeCallback {
  (current: number, previous: number): void; // Step change callback
}

interface ErrorCallback {
  (errors: [
    {
      step: number;                      // Step index (0-based)
      field: string;                     // Field name
      message: string;                   // Error message
    }
  ]): void; // Error callback
}

interface BeforeChangeCallback {
  (current: number, next: number): boolean; // Pre-step-change callback
}

interface ButtonClasses {
  prev?: string;                     // Previous button class
  next?: string;                     // Next button class
  submit?: string;                   // Submit button class
}
```

### Public Methods

```js
// Navigation
stepper.goToStep(2);                    // Go to specific step
stepper.nextStep();                     // Go to next step
stepper.prevStep();                     // Go to previous step

// Validation
stepper.validateCurrentStep();          // Validate current step
stepper.isStepValid(1);                // Check if step is valid
stepper.showServerErrors(errors);       // Display server errors
stepper.clearErrors();                  // Clear all errors

// Data
stepper.getFormData();                  // Get FormData object
stepper.getFormDataObject();            // Get plain object

// State
stepper.getCurrentStep();               // Get current step index
stepper.getTotalSteps();               // Get total number of steps

// Lifecycle
stepper.destroy();                      // Clean up and destroy instance
```

## 🎨 Styling

StepperJS is completely unstyled by default. It adds these CSS classes that you can style:

```css
.stepper-nav          /* Navigation container */
.stepper-prev         /* Previous button */
.stepper-next         /* Next button */
.stepper-submit       /* Submit button */
.stepper-error-message /* Field error message */
.stepper-step-error   /* Step-level error message */
```

## 🔧 Advanced Usage

### Validation System

StepperJS offers a comprehensive validation system with multiple layers:

1. **HTML5 Validation** (Default)
   - When `validate: true` is set for a step (default), the library uses the browser's native HTML5 validation
   - This includes checking attributes like `required`, `pattern`, `min`, `max`, `minlength`, `maxlength`, etc.
   - The library calls `field.checkValidity()` and `field.reportValidity()` to utilize native validation UI

2. **Custom Validation Functions**
   - You can provide a custom validation function to implement more complex validation logic
   - This function receives all form fields in the current step and must return a boolean
   - Custom validation overrides the default HTML5 validation

3. **Server-Side Error Handling**
   - After form submission, you can display server-side validation errors with `showServerErrors()`
   - This maps errors to specific fields and steps, and automatically navigates to the first step with errors

4. **Validation Utilities**
   - The library includes helper functions for common validation scenarios:
     - `isValidEmail()` - Email format validation
     - `isValidPhone()` - Basic phone number validation
     - Various validation rules in the `validationRules` object

### Custom Validation

```js
const stepper = new MultiStepForm({
  formSelector: '#form',
  steps: [{ selector: '.step-1' }],
  validation: (fields) => {
    // Custom validation logic
    for (const field of fields) {
      if (field.name === 'email' && !isValidEmail(field.value)) {
        return false;
      }
    }
    return true;
  }
});
```

### Server-Side Validation

```js
// Handle form submission
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  
  try {
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: stepper.getFormData()
    });
    
    if (!response.ok) {
      const errors = await response.json();
      stepper.showServerErrors(errors.validationErrors);
    }
  } catch (error) {
    console.error('Submission failed:', error);
  }
});
```

### Event Handling

```js
const stepper = new MultiStepForm({
  formSelector: '#form',
  steps: [/* ... */],
  onStepChange: (current, previous) => {
    console.log(`Step changed: ${previous} → ${current}`);
    updateProgressBar(current);
  },
  beforeStepChange: async (current, next) => {
    // Async validation or data fetching
    if (next === 2) {
      const isValid = await validateWithServer();
      return isValid; // Return false to prevent step change
    }
    return true;
  },
  onValidationError: (errors) => {
    // Custom error handling
    showNotification('Please fix the errors before continuing');
  }
});
```

## 🔧 **Troubleshooting**

### **Common Issues & Solutions**

#### **❌ "Form not found" Error**
```javascript
// Problem: Invalid form selector
const stepper = new MultiStepForm({
  formSelector: '#wrong-id' // Form doesn't exist
});

// Solution: Check your form selector
const form = document.querySelector('#your-form-id');
console.log('Form exists:', !!form);
```

#### **❌ "No valid steps found" Error**
```javascript
// Problem: All step selectors are invalid
const stepper = new MultiStepForm({
  formSelector: '#my-form',
  steps: [
    { selector: '.nonexistent' },  // Doesn't exist
    { selector: '#missing' }       // Doesn't exist
  ]
});

// Solution: Verify step selectors exist
const steps = ['.step-1', '.step-2', '.step-3'];
steps.forEach(selector => {
  const element = document.querySelector(selector);
  console.log(`${selector} exists:`, !!element);
});
```

#### **❌ Steps Not Hiding/Showing**
```css
/* Problem: CSS conflicts with display property */
.my-step {
  display: block !important; /* Overrides stepper */
}

/* Solution: Use more specific CSS or avoid !important */
.stepper-hidden {
  display: none !important;
}
```

#### **❌ Third-Party Form Not Loading**
```javascript
// Problem: Form loads after timeout
const stepper = await initializeWhenReady(config, {
  timeout: 1000 // Too short
});

// Solution: Increase timeout and add debugging
const stepper = await initializeWhenReady(config, {
  timeout: 10000,
  onTimeout: () => {
    console.log('Form still not found after 10 seconds');
    // Check if form exists manually
    const form = document.querySelector('#fbPaymentForm');
    console.log('Form exists now:', !!form);
  }
});
```

### **🐛 Debug Mode**
Enable detailed logging:

```javascript
const stepper = new MultiStepForm({
  formSelector: '#my-form',
  steps: [/* your steps */],
  onStepChange: (current, previous) => {
    console.log(`Debug: Step ${previous} → ${current}`);
  },
  beforeStepChange: (current, next) => {
    console.log(`Debug: Validating step ${current} before going to ${next}`);
    return true;
  },
  onValidationError: (errors) => {
    console.log('Debug: Validation errors:', errors);
  }
});

// Check step elements
stepper.steps?.forEach((step, index) => {
  console.log(`Step ${index}:`, step.element, 'Validate:', step.validate);
});
```

### **✅ Best Practices**

1. **Always check selectors exist** before initializing
2. **Use specific selectors** to avoid conflicts
3. **Test with different form structures** 
4. **Handle errors gracefully** with try/catch
5. **Use `initializeWhenReady()`** for third-party forms
6. **Add debug logging** during development

## 🌟 Examples

Check out the `/examples` directory for complete working examples:

- **Basic Example** - Simple 3-step form with validation
- **FundraisingBox Example** - Real-world integration with third-party donation forms
- **CDN Usage** - Using StepperJS via CDN with dynamic forms
- **Error Handling Demo** - Server-side error handling examples

### Running Examples Locally

The project includes a simple server script to run the examples:

```bash
# Using the included server script
node serve-examples.js

# Or using Vite for development
npx vite examples --host
```

The server will start at http://localhost:3000/ with the following examples available:
- http://localhost:3000/examples/basic.html
- http://localhost:3000/examples/cdn-usage.html
- http://localhost:3000/examples/error-handling-demo.html
- http://localhost:3000/examples/fundraisingbox-example.html

## 🔄 Third-Party Form Integration

StepperJS is designed to work seamlessly with forms loaded by third-party scripts (like FundraisingBox, Typeform, etc.).

### Dynamic Form Loading

StepperJS provides several methods for handling dynamically loaded forms:

1. **`initializeWhenReady()`** - Basic polling for form availability
2. **`initializeOnDOMReady()`** - Waits for DOMContentLoaded event before initializing
3. **`initializeWithObserver()`** - Uses MutationObserver for better performance

All these methods accept the same configuration options:

```typescript
interface DynamicLoaderOptions {
  timeout?: number;           // Maximum time to wait (default: 10000ms)
  pollInterval?: number;      // Polling interval (default: 100ms)
  onInitialized?: Function;   // Callback when initialized
  onTimeout?: Function;       // Callback when timeout reached
  waitForSelectors?: string[]; // Additional selectors to wait for
}
```

```javascript
import { initializeWhenReady } from '@lucaismyname/stepper-js';

// Wait for third-party form to load
const stepper = await initializeWhenReady({
  formSelector: '#third-party-form',
  steps: [
    { selector: '#step-1' },
    { selector: '#step-2' }
  ]
}, {
  timeout: 10000,
  waitForSelectors: ['#important-field'], // Wait for specific elements
  onInitialized: (stepper) => {
    console.log('Form ready!');
  }
});
```

### CDN Usage (No Build Step)

```html
<!-- Include via CDN -->
<script src="https://unpkg.com/@lucaismyname/stepper-js@latest/dist/stepper.umd.js"></script>

<script>
  const { MultiStepForm, initializeWhenReady } = window.StepperJS;
  
  // Use after third-party form loads
  initializeWhenReady({
    formSelector: '#dynamic-form',
    steps: [
      { selector: '#step-1' },
      { selector: '#step-2' }
    ]
  });
</script>
```

### FundraisingBox Integration

Perfect for NGO donation forms:

```javascript
// Configuration for FundraisingBox forms
const stepper = await initializeWhenReady({
  formSelector: '#fbPaymentForm',
  steps: [
    { selector: '#amountBox', validate: true },
    { selector: '#donorData', validate: true },
    { selector: '#paymentMethodBox', validate: true }
  ],
  validation: (fields) => {
    // Custom validation for donation amounts, email, etc.
    return validateDonationForm(fields);
  }
}, {
  timeout: 5000,
  waitForSelectors: ['#payment_amount', '#payment_email']
});
```

### Handling Complex Third-Party Logic

```javascript
const stepper = new MultiStepForm({
  formSelector: '#complex-form',
  steps: [/* steps */],
  beforeStepChange: async (current, next) => {
    // Handle third-party dependencies
    if (next === 2) {
      await loadPaymentMethods();
      updateFormFields();
    }
    return true;
  },
  onStepChange: (current, previous) => {
    // Trigger third-party events
    triggerThirdPartyValidation(current);
  }
});
```

## 📎 Project Structure

```
src/
├── MultiStepForm.ts   # Main class implementation
├── DynamicLoader.ts   # Dynamic form loading utilities
├── validation.ts     # Validation utilities
├── utils.ts          # Helper functions
├── types.ts          # TypeScript interfaces
└── index.ts          # Entry point and exports

examples/            # Example implementations
dist/                # Built files (after build)
```

## 💿 Build Commands

```bash
# Install dependencies
npm install

# Development mode
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

# Run examples server
node serve-examples.js
npm run serve-examples
```

The build process creates multiple output formats:
- ESM: `dist/stepper.es.js`
- CommonJS: `dist/stepper.cjs.js`
- UMD: `dist/stepper.umd.js` (for direct browser usage)
- TypeScript declarations: `dist/*.d.ts` (full type definitions for all exports)
- TypeScript source files: `dist/src/*.ts` (original TypeScript source code)

### TypeScript Support

This library is built with TypeScript and provides comprehensive type definitions:

```typescript
// Import with full TypeScript support
import { MultiStepForm, ValidationError } from '@lucaismyname/stepper-js';

// All interfaces and types are exported
import type { MultiStepConfig, StepConfig } from '@lucaismyname/stepper-js';

// Example with type safety
const config: MultiStepConfig = {
  formSelector: '#my-form',
  steps: [
    { selector: '.step-1', validate: true }
  ]
};
```

## 📝 License

MIT © [Luca Mack](https://github.com/LucaIsMyName)

## 🔗 Links

- [GitHub Repository](https://github.com/LucaIsMyName/stepper.js)
- [NPM Package](https://www.npmjs.com/package/@lucaismyname/stepper-js)
- [Documentation](https://github.com/LucaIsMyName/stepper.js#readme)