npm package: @squeletteapp/widget-builder
Description: Embeddable widgets
Author: Guillaume Badi
Latest version: 1.0.4

# @squeletteapp/widget-builder

Embeddable widget system for creating modal widgets with iframes. Built with Web Components, Preact, and CSS animations for smooth transitions.

## Features

- 🎨 **Web Components** - Clean, encapsulated custom elements
- ⚡ **Lightweight** - Small bundle size (~35KB ES, ~25KB UMD)
- 🎬 **Smooth Animations** - CSS transition-powered animations
- 📍 **Flexible Positioning** - 9 position options (center, corners, edges)
- 💬 **Message Passing** - Built-in postMessage communication
- 🎯 **Type-Safe** - Full TypeScript support
- 🚀 **Preload Support** - Load iframes hidden for instant opening

## Installation

```bash
npm install @squeletteapp/widget-builder
# or
bun add @squeletteapp/widget-builder
```

## Quick Start

```typescript
import { createModalWidget } from '@squeletteapp/widget-builder';

// Create a widget instance
const widget = createModalWidget({
  elementName: 'my-widget',
  source: 'https://example.com/widget',
  position: 'center',
  overlay: true,
});

// Open the widget
widget.open();

// Send messages to the iframe
widget.sendMessage({ type: 'hello', data: 'world' });

// Listen for state changes
widget.onOpenChange((isOpen) => {
  console.log('Widget is', isOpen ? 'open' : 'closed');
});
```

## API Reference

### `createModalWidget(params: CreateModalWidgetParams): ModalWidget`

Creates a new modal widget instance.

#### Parameters

```typescript
type CreateModalWidgetParams = {
  // Required: Unique custom element name
  elementName: string;

  // Required: URL to load in the iframe
  source: string;

  // Optional: Position on screen (default: 'center')
  position?: 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'center';

  // Optional: Show backdrop overlay (default: true)
  overlay?: boolean;

  // Optional: CSS class name(s) for the modal
  className?: string;

  // Optional: Z-index for the modal (default: 999999)
  zIndex?: number;
};
```

#### Returns

```typescript
type ModalWidget = {
  type: 'modal';

  // Open the modal
  open(): void;

  // Close the modal
  close(): void;

  // Preload the iframe without displaying
  preload(): void;

  // Update the iframe source URL
  setSource(source: string): void;

  // Send a message to the iframe
  sendMessage(message: unknown): void;

  // Register callback for open/close state changes
  onOpenChange(callback: (isOpen: boolean) => void): void;

  // Clean up and remove the widget
  destroy(): void;
};
```

## Position Options

The widget supports 9 different positions:

```
nw (↖)    n (↑)     ne (↗)
w  (←)    center    e  (→)
sw (↙)    s (↓)     se (↘)
```

## Message Passing

### From Parent to Iframe

```typescript
// Parent window
widget.sendMessage({
  type: 'custom-event',
  data: { foo: 'bar' }
});
```

```javascript
// Inside iframe
window.addEventListener('message', (event) => {
  if (event.data.type === 'widget-message') {
    console.log('Received:', event.data.payload);
  }
});
```

### From Iframe to Parent

```javascript
// Inside iframe
window.parent.postMessage({
  type: 'widget-message',
  payload: { hello: 'world' }
}, '*');
```

```typescript
// Parent window
import { listenToIframe } from '@squeletteapp/widget-builder';

const cleanup = listenToIframe((message) => {
  console.log('Received from iframe:', message);
});

// Clean up when done
cleanup();
```

## Custom Styling

Apply custom CSS classes to the modal container:

```typescript
const widget = createModalWidget({
  elementName: 'styled-widget',
  source: 'https://example.com/widget',
  className: 'my-custom-modal',
});
```

```css
.my-custom-modal {
  border-radius: 16px;
  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
  width: 600px;
  height: 400px;
}
```

## Real-World Example

Here's how a widget-maker company like Acme would use this package:

```typescript
// acme-widget-library.ts
import { createModalWidget } from '@squeletteapp/widget-builder';

export function initAcmeWidget(userId: string) {
  const widget = createModalWidget({
    elementName: 'acme-support-widget',
    source: `https://support.acme.com/widget?user=${userId}`,
    position: 'se', // Bottom-right corner
    overlay: true,
    className: 'acme-widget-modal',
  });

  // Create trigger button
  const button = document.createElement('button');
  button.textContent = '💬 Support';
  button.onclick = () => widget.open();
  document.body.appendChild(button);

  // Handle messages from widget
  widget.onOpenChange((isOpen) => {
    console.log('Support widget', isOpen ? 'opened' : 'closed');
  });

  return widget;
}

// Client integration
// <script src="https://cdn.acme.com/widget.js"></script>
// <script>window.initAcmeWidget('user-123')</script>
```

## Development

```bash
# Install dependencies
bun install

# Build the package
bun run build

# Watch mode
bun run dev

# Type checking
bun run check-types

# Run tests
bun run test
```

## Architecture

The package is organized into focused, single-responsibility modules:

```
src/
├── index.ts                    # Public API exports
├── types.ts                    # TypeScript type definitions
├── create-modal-widget.ts      # Widget factory function
├── web-component.ts            # Custom element definition
├── components/
│   ├── ModalWidget.tsx         # Main Preact component
│   ├── ModalContainer.tsx      # Modal with iframe
│   └── ModalOverlay.tsx        # Animated backdrop
└── utils/
    ├── messaging.ts            # postMessage helpers
    └── position.ts             # Position calculation
```

## Browser Support

- Modern browsers with ES2020 support
- Web Components (Custom Elements v1)
- Shadow DOM v1

## License

MIT

## Author

Guillaume Badi