@squeletteapp/widget-builder
v1.0.3
Published
Embeddable widgets
Maintainers
Readme
@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
npm install @squeletteapp/widget-builder
# or
bun add @squeletteapp/widget-builderQuick Start
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
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
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
// Parent window
widget.sendMessage({
type: 'custom-event',
data: { foo: 'bar' }
});// Inside iframe
window.addEventListener('message', (event) => {
if (event.data.type === 'widget-message') {
console.log('Received:', event.data.payload);
}
});From Iframe to Parent
// Inside iframe
window.parent.postMessage({
type: 'widget-message',
payload: { hello: 'world' }
}, '*');// 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:
const widget = createModalWidget({
elementName: 'styled-widget',
source: 'https://example.com/widget',
className: 'my-custom-modal',
});.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:
// 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
# 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 testArchitecture
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 calculationBrowser Support
- Modern browsers with ES2020 support
- Web Components (Custom Elements v1)
- Shadow DOM v1
License
MIT
Author
Guillaume Badi
