@feedal/embed
v0.0.56
Published
Feedal embed script to load feedback forms via JS or NPM
Maintainers
Readme
🚀 Feedal Embed SDK
A lightweight JavaScript SDK for embedding Feedal feedback forms across any platform. Built with TypeScript, featuring customization options, multiple display modes, and framework integration.
✨ Key Features
- 🎯 Framework Agnostic: Works with React, Angular, Vue, Svelte, and vanilla JavaScript
- 🎨 Multiple Display Modes: Popup, embedded, fullscreen, drawer, sidebar, toast, and more
- 🎭 Smart Triggers: Manual, auto, click, scroll, time-based, exit-intent triggers
- 📱 Responsive: Mobile-friendly design with adaptive positioning
- 🎬 Animations: Fade, slide, scale, bounce transitions
- ♿ Accessibility: ARIA labels, keyboard navigation, screen reader support
- 🔧 Configurable: Extensive customization options
📦 Installation
CDN (Recommended for quick setup)
<script src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"></script>NPM/Yarn
npm install @feedal/embed
# or
yarn add @feedal/embed🚀 Quick Start
Simple Popup Form
<script src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"></script>
<script>
// Using the global FeedalWidget constructor
const widget = new FeedalWidget({
formId: 'your-form-id',
mode: 'popup',
trigger: 'manual'
});
widget.open();
</script>High-Performance Popup Form
<script src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"></script>
<script>
// Performance-optimized form (eliminates 3+ second delay)
const widget = new FeedalWidget({
formId: 'your-form-id',
mode: 'popup',
trigger: 'manual',
disableFormAnimations: true, // 🚀 Fast loading
showLoadingIndicator: true
});
widget.open();
</script>Auto-trigger with Script Tag
<script
src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"
data-form-id="your-form-id"
data-mode="popup"
data-trigger="time"
data-trigger-delay="5000"
data-position="bottom-right"
data-animation="fade">
</script>Performance-Optimized Auto-Trigger
<script
src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"
data-form-id="your-form-id"
data-mode="popup"
data-trigger="time"
data-trigger-delay="5000"
data-position="bottom-right"
data-disable-form-animations="true"
data-animation="fade">
</script>Alternative Static Methods
<script src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"></script>
<script>
// Using static methods
const widget = FeedalWidget.createWidget({
formId: 'your-form-id',
mode: 'popup'
});
widget.open();
// Or create and open in one step
FeedalWidget.openForm({
formId: 'your-form-id',
mode: 'popup'
});
</script>🎛️ Configuration Options
The Feedal Embed SDK provides extensive customization options. Here's a complete reference:
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| Core Configuration |
| formId | string | ✅ Yes | - | Unique identifier for the form |
| sessionKey | string | ❌ No | - | Server-provided session key for persistence control |
| token | string | ❌ No | - | JWT or API token for authentication |
| host | string | ❌ No | "https://fedl.io/f/" | Backend API host URL |
| Display & Layout |
| mode | "fullscreen" \| "embedded" \| "popup" \| "drawer" \| "button" \| "sidebar" \| "toast" \| "inline" \| "modal" \| "slide-over" | ❌ No | "popup" | How the form is displayed |
| theme | 'light' \| 'dark' \| 'auto' \| 'custom' | ❌ No | "light" | Visual theme for the form |
| customCssUrl | string | ❌ No | - | URL to custom CSS file for styling |
| Positioning & Sizing (Unified System) |
| position | "top" \| "bottom" \| "left" \| "right" \| "center" \| "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" \| "center-left" \| "center-right" | ❌ No | "center" | Unified positioning for all modes |
| width | string \| number | ❌ No | "auto" | Form width (px, %, vw, etc.) |
| height | string \| number | ❌ No | "auto" | Form height (px, %, vh, etc.) |
| offset | { x?: number; y?: number } | ❌ No | { x: 0, y: 0 } | Fine-tune positioning with pixel offsets |
| Button-specific options |
| buttonPosition | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" \| "center-left" \| "center" \| "center-right" | ❌ No | "bottom-right" | Where the floating button appears (different from form positioning) |
| buttonSize | "small" \| "medium" \| "large" \| "custom" | ❌ No | "medium" | Button size (40px, 60px, 80px, or custom) |
| buttonColor | string | ❌ No | "#007bff" | Custom button color (hex, rgb, etc.) |
| buttonIcon | string | ❌ No | "👍" | Custom button icon or emoji |
| buttonText | string | ❌ No | "" | Custom button text (alternative to icon) |
| Behavior & Triggers |
| trigger | "manual" \| "auto" \| "click" \| "scroll" \| "time" \| "exit-intent" \| "element-visible" \| "session-duration" \| "idle" | ❌ No | "manual" | How the form is triggered |
| triggerDelay | number \| string | ❌ No | - | Delay before trigger (seconds for time, minutes for idle) |
| triggerElement | string \| HTMLElement | ❌ No | - | Element for click/element-visible triggers |
| triggerThreshold | number | ❌ No | - | Threshold percentage for scroll/element-visible triggers |
| triggerCooldown | number \| string | ❌ No | 1 | Cooldown period between triggers (minutes) |
| autoClose | boolean \| number \| string | ❌ No | true | Auto-close timeout. true = 3 seconds (default), false = disabled, number = custom seconds |
| closeOnOverlayClick | boolean | ❌ No | false | Close when clicking outside the form |
| showCloseButton | boolean | ❌ No | true | Show close button |
| Enhanced Behavioral Options |
| draggable | boolean | ❌ No | false | Allow dragging for repositioning |
| resizable | boolean | ❌ No | false | Allow resizing the form |
| collapsible | boolean | ❌ No | false | Allow collapsing to minimal state |
| persistent | boolean | ❌ No | false | Persist across page reloads |
| Responsive & Accessibility |
| responsive | boolean | ❌ No | true | Enable responsive behavior |
| maxWidth | string \| number | ❌ No | - | Maximum width constraint |
| minWidth | string \| number | ❌ No | - | Minimum width constraint |
| maxHeight | string \| number | ❌ No | - | Maximum height constraint |
| minHeight | string \| number | ❌ No | - | Minimum height constraint |
| zIndex | number | ❌ No | 9999 | Custom z-index for layering |
| ariaLabel | string | ❌ No | - | Accessibility label for screen readers |
| focusTrap | boolean | ❌ No | true | Enable focus trapping for modal modes |
| Performance & Optimization |
| lazyLoad | boolean | ❌ No | true | Enable lazy loading of resources |
| preloadResources | boolean | ❌ No | true | Preload critical resources |
| performanceMonitoring | boolean | ❌ No | false | Track performance metrics |
| Loading & User Experience |
| showLoadingIndicator | boolean | ❌ No | false | Show loading indicator while form loads |
| loadingText | string | ❌ No | - | Custom loading text |
| loadingSpinner | boolean | ❌ No | true | Show loading spinner |
| disableFormAnimations | boolean | ❌ No | true | NEW! Disable form loading animations for better performance |
| Submission Persistence |
| rememberSubmission | boolean | ❌ No | true | Remember if user has submitted the form |
| submissionExpiry | number \| string | ❌ No | 30 | How long to remember submissions (days) |
| storageType | 'local' \| 'session' \| 'cookie' | ❌ No | 'local' | Where to store submission data |
| Advanced |
| containerId | string | ❌ No | - | Container ID for embedded mode |
| useCard | boolean | ❌ No | false | Use card-style layout |
| animation | "fade" \| "slide" \| "scale" \| "bounce" \| "flip" \| "elastic" \| "none" | ❌ No | "fade" | Animation type for form appearance |
| overlay | boolean | ❌ No | false | Show overlay background |
| blurBackground | boolean | ❌ No | false | Blur page content behind modal |
| Data & Context |
| metadata | Record<string, any> | ❌ No | - | Pass additional context data |
| prefill | Record<string, any> | ❌ No | - | Prefill form fields with values |
| Callbacks |
| onLoad | () => void | ❌ No | - | Called when form is loaded and ready |
| onOpen | () => void | ❌ No | - | Called when form is opened/shown |
| onClose | () => void | ❌ No | - | Called when form is closed/hidden |
| onSubmit | (data?: any) => void | ❌ No | - | Called when form is submitted with data |
| onSkipped | () => void | ❌ No | - | Called when form is skipped due to previous submission |
| onError | (error: Error) => void | ❌ No | - | Called when an error occurs |
| onResize | (dimensions: { width: number; height: number }) => void | ❌ No | - | Called when form is resized |
| onMove | (position: { x: number; y: number }) => void | ❌ No | - | Called when form is moved |
🆕 New Performance Option: disableFormAnimations
The disableFormAnimations option is a new feature that can significantly improve form loading performance:
// Fast loading (eliminates 3+ second delay)
FeedalWidget.openForm({
formId: 'your-form-id',
disableFormAnimations: true, // 🚀 Eliminates form loading animations
mode: 'popup',
position: 'bottom-center'
});Benefits:
- ⚡ Eliminates 3+ second delay between header and form content
- 🚀 Faster rendering - questions appear all at once instead of staggered
- 📱 Better mobile performance - reduces animation overhead
- 🎯 Consistent loading - predictable behavior across devices
When to use:
- ✅ Production environments where performance is critical
- ✅ Mobile-first applications with limited resources
- ✅ High-traffic sites where every second counts
- ❌ Development/design where animations are needed for UX testing
🎯 Unified Position System
Single position Property for All Modes
The Feedal Embed SDK now uses a unified positioning system that eliminates the need for multiple position-related properties. The position property works consistently across all display modes:
Available Position Values:
- Edge Positions:
"top","bottom","left","right" - Center Positions:
"center" - Corner Positions:
"top-left","top-center","top-right","bottom-left","bottom-center","bottom-right" - Side Center Positions:
"center-left","center-right"
Position Support by Mode:
| Mode | Available Positions | Default | Behavior |
|------|-------------------|---------|----------|
| Popup | All 13 positions | "center" | Appears at specified position |
| Modal | All 13 positions | "center" | Centers at specified position |
| Drawer | "top", "bottom", "left", "right" | "bottom" | Slides from specified edge |
| Sidebar | "left", "right" | "right" | Appears on specified side |
| Toast | All 13 positions | "bottom-right" | Appears at specified corner/position |
| Slide-Over | "left", "right" | "right" | Slides in from specified side |
| Fullscreen | "center" (forced) | "center" | Always covers full viewport |
| Embedded | "center" (forced) | "center" | Always relative to container |
| Button | All 13 positions | "center" | Popup appears at specified position |
How It Works by Mode:
| Mode | Position Behavior | Example |
|------|------------------|---------|
| Drawer | Slides from specified edge | position: "top" → Slides down from top |
| Sidebar | Appears on specified side | position: "left" → Appears on left side |
| Toast | Appears at specified corner | position: "top-right" → Appears at top-right |
| Modal | Centers at specified position | position: "top" → Centers near top |
| Popup | Appears at specified position | position: "bottom-center" → Appears at bottom center |
Smart Position Mapping:
The SDK intelligently maps positions to appropriate behaviors for each mode:
// Drawer mode - slides from edge
new FeedalWidget({
formId: 'abc123',
mode: 'drawer',
position: 'top' // Slides DOWN from top
});
// Sidebar mode - appears on side
new FeedalWidget({
formId: 'abc123',
mode: 'sidebar',
position: 'left' // Appears on left side
});
// Toast mode - appears at corner
new FeedalWidget({
formId: 'abc123',
mode: 'toast',
position: 'top-right' // Appears at top-right corner
});
// Modal mode - centers at position
new FeedalWidget({
formId: 'abc123',
mode: 'modal',
position: 'top' // Centers near top of screen
});🎯 Independent Positioning System
Button Mode Positioning
The Button Mode features an independent positioning system that allows maximum flexibility:
buttonPosition: Controls where the floating button appears on the pageposition: Controls where the popup appears when the button is clicked
Example: Maximum Flexibility
// Button at bottom-left, popup at top-right
new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-left', // Button appears bottom-left
position: 'top-right', // Popup appears top-right when clicked
buttonSize: 'large',
buttonColor: '#ff6b35'
});Benefits:
- ✅ No positioning conflicts between button and popup
- ✅ Maximum flexibility for different screen sizes and layouts
- ✅ Professional appearance with strategic positioning
- ✅ Better mobile experience - button can be thumb-friendly while popup is optimally placed
🚀 Performance Improvements
Animation Performance Optimization
The latest version includes significant performance improvements to eliminate the 3+ second delay between header and form content:
Before (Default):
- Form header loads first
- Questions animate in one by one with staggered timing
- 3-4 second delay before full form is visible
- Smooth but slow user experience
After (With disableFormAnimations: true):
- Form header and content load simultaneously
- All questions appear at once
- 0.5-1 second total loading time
- Fast and responsive user experience
Performance Comparison:
| Setting | Loading Time | Animation | Use Case |
|---------|--------------|-----------|----------|
| disableFormAnimations: false | 3-4 seconds | Smooth staggered | Development, Design showcase |
| disableFormAnimations: true | 0.5-1 second | Instant | Production, Performance-critical |
Technical Details
The performance improvement works by:
- Conditional Animation Control: Form components check the
disableAnimationsparameter - Query Parameter Passing:
?disableAnimations=1passed from embed script to form - Smart Framer Motion: Animations gracefully disabled without errors
- Backward Compatibility: Existing code continues to work unchanged
🎮 Display Modes
1. Popup Mode (Default)
Displays form in a centered modal overlay.
new FeedalWidget({
formId: 'abc123',
mode: 'popup',
position: 'center',
overlay: true,
animation: 'fade'
});2. Embedded Mode
Embeds form inside a specific container.
<div id="feedback-container"></div>
<script>
new FeedalWidget({
formId: 'abc123',
mode: 'embedded',
containerId: 'feedback-container',
width: '100%'
});
</script>3. Drawer Mode
Slides in from the side of the screen.
new FeedalWidget({
formId: 'abc123',
mode: 'drawer',
position: 'bottom', // Slides UP from bottom
animation: 'slide'
});
// Or slide from top
new FeedalWidget({
formId: 'abc123',
mode: 'drawer',
position: 'top', // Slides DOWN from top
animation: 'slide'
});
// Or slide from left/right
new FeedalWidget({
formId: 'abc123',
mode: 'drawer',
position: 'left', // Slides IN from left
animation: 'slide'
});4. Sidebar Mode
Shows a fixed sidebar on the left or right.
new FeedalWidget({
formId: 'abc123',
mode: 'sidebar',
position: 'right', // Appears on right side
animation: 'slide'
});
// Or appear on left side
new FeedalWidget({
formId: 'abc123',
mode: 'sidebar',
position: 'left', // Appears on left side
animation: 'slide'
});5. Toast Mode
Shows a small notification-style form.
new FeedalWidget({
formId: 'abc123',
mode: 'toast',
position: 'bottom-right', // Appears at bottom-right corner
animation: 'fade'
});
// Or appear at top-left corner
new FeedalWidget({
formId: 'abc123',
mode: 'toast',
position: 'top-left', // Appears at top-left corner
animation: 'fade'
});
// Or appear at center positions
new FeedalWidget({
formId: 'abc123',
mode: 'toast',
position: 'top-center', // Appears at top center
animation: 'fade'
});6. Button Mode
Creates a floating action button that transforms into a popup form when clicked. The button and popup can have independent positions for maximum flexibility.
new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-left', // Where the button appears
position: 'top-right', // Where the popup appears when clicked
buttonSize: 'large', // small (40px), medium (60px), large (80px), custom
buttonColor: '#e91e63', // Custom button color
buttonIcon: '👍', // Custom emoji or icon
buttonText: 'Feedback', // Alternative to icon (text instead)
animation: 'scale'
});Key Features:
- Independent Positioning: Button and popup can be at different locations
- Customizable Appearance: Size, color, icon, and text
- Smart Transformation: Button disappears, popup appears at specified position
- 9 Button Positions: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right
- 9 Popup Positions: Same as standard positioning (center, top-left, bottom-right, etc.)
Use Cases:
- Mobile Apps: Button at bottom-right (thumb-friendly), popup at center (optimal viewing)
- Desktop Sites: Button at top-right (always visible), popup at center (professional)
- Landing Pages: Button at bottom-center (call-to-action), popup at top-center (attention-grabbing)
🎯 Smart Triggers
Manual Trigger
const widget = new FeedalWidget({
formId: 'abc123',
trigger: 'manual'
});
// Open programmatically
widget.open();Time-based Trigger
new FeedalWidget({
formId: 'abc123',
trigger: 'time',
triggerDelay: 10000 // 10 seconds
});Scroll Trigger
new FeedalWidget({
formId: 'abc123',
trigger: 'scroll',
triggerThreshold: 75 // Trigger at 75% scroll
});Exit Intent Trigger
new FeedalWidget({
formId: 'abc123',
trigger: 'exit-intent',
triggerCooldown: 300000 // Don't show again for 5 minutes
});🌐 Framework Integration
React
import { useEffect, useState } from 'react';
function FeedbackButton({ formId }) {
const [widget, setWidget] = useState(null);
useEffect(() => {
// Make sure FeedalWidget is available
if (window.FeedalWidget) {
const feedalWidget = new window.FeedalWidget({
formId,
mode: 'popup',
trigger: 'manual'
});
setWidget(feedalWidget);
return () => {
feedalWidget.destroy();
};
}
}, [formId]);
return (
<button onClick={() => widget?.open()}>
Give Feedback
</button>
);
}React with Performance Optimization
import { useEffect, useState } from 'react';
function HighPerformanceFeedbackButton({ formId }) {
const [widget, setWidget] = useState(null);
useEffect(() => {
if (window.FeedalWidget) {
const feedalWidget = new window.FeedalWidget({
formId,
mode: 'popup',
trigger: 'manual',
disableFormAnimations: true, // 🚀 Fast loading
showLoadingIndicator: true
});
setWidget(feedalWidget);
return () => {
feedalWidget.destroy();
};
}
}, [formId]);
return (
<button onClick={() => widget?.open()}>
Give Feedback (Fast)
</button>
);
}Vue 3
<template>
<button @click="openFeedback">Give Feedback</button>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps({
formId: String
});
let widget = null;
onMounted(() => {
if (window.FeedalWidget) {
widget = new window.FeedalWidget({
formId: props.formId,
mode: 'popup',
trigger: 'manual'
});
}
});
onUnmounted(() => {
widget?.destroy();
});
function openFeedback() {
widget?.open();
}
</script>Vue 3 with Performance Optimization
<template>
<button @click="openFeedback">Give Feedback (Fast)</button>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps({
formId: String
});
let widget = null;
onMounted(() => {
if (window.FeedalWidget) {
widget = new window.FeedalWidget({
formId: props.formId,
mode: 'popup',
trigger: 'manual',
disableFormAnimations: true, // 🚀 Fast loading
showLoadingIndicator: true
});
}
});
onUnmounted(() => {
widget?.destroy();
});
function openFeedback() {
widget?.open();
}
</script>TypeScript Direct Import
import { FeedalWidget, EmbedOptions } from '@feedal/embed';
const options: EmbedOptions = {
formId: 'abc123',
mode: 'popup',
theme: 'dark'
};
const widget = new FeedalWidget(options);
widget.open();TypeScript with Performance Optimization
import { FeedalWidget, EmbedOptions } from '@feedal/embed';
const options: EmbedOptions = {
formId: 'abc123',
mode: 'popup',
theme: 'dark',
disableFormAnimations: true, // 🚀 Fast loading
showLoadingIndicator: true
};
const widget = new FeedalWidget(options);
widget.open();🎨 Button Mode Examples
Basic Button Configuration
// Simple floating button
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-right',
position: 'center'
});Customized Button Appearance
// Large, colorful button with custom icon
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-center',
position: 'top-center',
buttonSize: 'large', // 80px button
buttonColor: '#e91e63', // Pink color
buttonIcon: '📝', // Custom emoji
animation: 'bounce'
});Text-Based Button
// Button with text instead of icon
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'top-right',
position: 'center',
buttonSize: 'medium',
buttonColor: '#4caf50', // Green color
buttonText: 'Feedback', // Text instead of icon
buttonIcon: '', // Clear icon when using text
animation: 'scale'
});Custom Size Button
// Custom-sized button with specific dimensions
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'center-left',
position: 'right',
buttonSize: 'custom',
width: '100px', // Custom width for button
height: '100px', // Custom height for button
buttonColor: '#ff9800', // Orange color
buttonIcon: '💬',
animation: 'elastic'
});Mobile-Optimized Button
// Thumb-friendly button for mobile
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-right', // Thumb-friendly position
position: 'center', // Popup at center for optimal viewing
buttonSize: 'large', // Easy to tap
buttonColor: '#2196f3', // High contrast blue
buttonIcon: '💬',
responsive: true, // Mobile-responsive
animation: 'fade'
});Professional Landing Page Button
// Strategic positioning for conversions
const widget = new FeedalWidget({
formId: 'abc123',
mode: 'button',
buttonPosition: 'bottom-center', // Call-to-action position
position: 'top-center', // Attention-grabbing popup
buttonSize: 'large',
buttonColor: '#f44336', // Attention-grabbing red
buttonIcon: '📊',
buttonText: 'Take Survey',
animation: 'scale',
overlay: true, // Professional overlay
blurBackground: true // Focus attention
});🛠️ API Reference
Global API (Browser)
// Create a widget directly (recommended)
const widget = new FeedalWidget(options);
// Static utility methods
const widget = FeedalWidget.createWidget(options);
const widget = FeedalWidget.openForm(options); // Creates and opens
// Utility methods
FeedalWidget.closeAll();
const isOpen = FeedalWidget.isAnyOpen();
const widget = FeedalWidget.getWidget('your-form-id');
// Reset submission history for all forms
FeedalWidget.resetAllSubmissions();Widget Methods
// Open the widget
widget.open();
// Close the widget
widget.close();
// Toggle the widget visibility
widget.toggle();
// Update widget options
widget.updateOptions({
theme: 'dark',
position: 'bottom-right'
});
// Check if user has submitted this form before
const hasSubmitted = widget.hasSubmitted();
// Record a submission (usually handled automatically)
widget.recordSubmission();
// Clear submission history for this form
widget.resetSubmissionHistory();
// Get performance metrics
const metrics = widget.getPerformanceMetrics();
// Check if widget is visible
const isVisible = widget.isVisible;
// Access the DOM element
const element = widget.element;
// Destroy the widget and clean up resources
widget.destroy();🔧 Troubleshooting
Common Issues
Form not loading:
- Check that your form ID is correct
- Verify your authentication token
- Check the browser console for errors
Styling conflicts:
- Use the
containerIdoption to isolate the form in a specific container - Adjust the z-index if the form is hidden behind other elements
Mobile issues:
- Enable responsive mode with
responsive: true - Set appropriate width constraints for mobile devices
📈 Performance Tips
- Lazy Loading: Use
lazyLoad: trueto load resources only when needed - Manual Trigger: Use manual triggers for better control
- Performance Monitoring: Enable with
performanceMonitoring: trueto track metrics
🔄 Submission Persistence
The embed script includes a powerful submission persistence feature that prevents showing forms to users who have already submitted them. This helps reduce survey fatigue and prevents duplicate submissions.
How It Works
- When a user submits a form, a record is stored in the browser (localStorage, sessionStorage, or cookie)
- The next time the form would be shown, the script checks for previous submissions
- If a submission is found and still valid, the form is not displayed
- Form owners can reset all submission records by generating a new session key in the dashboard
Configuration Options
const widget = new FeedalWidget({
formId: "your-form-id",
rememberSubmission: true, // Enable submission persistence (default: false)
submissionExpiry: 30 * 24 * 60 * 60 * 1000, // 30 days in milliseconds (default)
storageType: "local", // 'local', 'session', or 'cookie' (default: 'local')
// other options...
});Session Keys
Each form has a unique server-generated session key that's used to invalidate previous submission records:
- When a form owner resets the session key, all previous submission records become invalid
- This allows form owners to make all users see the form again, even if they've submitted it before
- The session key is also recorded with analytics data to differentiate between submission periods
Example: Remembering Submissions
const widget = new FeedalWidget({
formId: "your-form-id",
mode: "popup",
trigger: "time",
triggerDelay: 5000,
rememberSubmission: true,
submissionExpiry: 7 * 24 * 60 * 60 * 1000, // 7 days
storageType: "local"
});This will show the form after 5 seconds, but only if the user hasn't submitted it in the last 7 days.
Resetting Submission History
Form owners can reset the session key in the dashboard under Form Settings > Security. This invalidates all previous submission records, allowing the form to be shown to all users again.
Programmatically, you can also reset submission history for the current user:
// Reset history for a specific form
widget.resetSubmissionHistory();
// Reset history for all forms
FeedalWidget.resetAllSubmissions();Auto-Resizing Feature
The embed script now includes an intelligent auto-resizing feature that automatically adjusts the iframe height based on its content. This ensures that:
- Forms with minimal content don't show unnecessary white space
- Forms with extensive content are properly contained with scrolling when needed
- The iframe adapts to dynamic content changes
How It Works
- For embedded forms, the iframe height automatically adjusts to fit the content
- For modal-like displays (popup, modal, etc.), the widget respects maximum height constraints and adds scrolling when needed
- The widget continuously monitors content changes and adjusts height accordingly
Configuration Options
You can control the auto-resizing behavior with these options:
const widget = new FeedalWidget({
formId: "your-form-id",
minHeight: "100px", // Minimum height (default: 100px)
maxHeight: "80vh", // Maximum height (default: 90% of viewport)
// other options...
});minHeight: Sets the minimum height for the iframe (accepts px, vh, etc.)maxHeight: Sets the maximum height before scrolling is enabled (accepts px, vh, etc.)
Example: Embedded Form with Auto-Resize
<div id="feedback-container"></div>
<script src="https://cdn.jsdelivr.net/npm/@feedal/embed@latest/dist/embed.umd.js"></script>
<script>
const widget = new FeedalWidget({
formId: "your-form-id",
mode: "embedded",
containerId: "feedback-container",
minHeight: "150px",
maxHeight: "800px"
});
</script>This will render the form in the container and automatically adjust its height based on the content, ensuring optimal display without excessive white space.
🧪 Testing & Development
Available Test Files
The repository includes comprehensive test files for development and testing:
test-button-fix.html- Test Button Mode with independent positioningtest-autoclose-fix.html- Test autoClose behavior and time formatstest-default-values.html- Test default configuration valuestest-responsive.html- Test responsive behavior and sizingtest-advanced-triggers.html- Test all trigger types and configurationstest-optimized-modes.html- Test all display modes and their features
Running Tests
Build the embed script:
npm run buildOpen test files in browser:
# Open any test file in your browser open test-button-fix.htmlCheck browser console for detailed logging and error messages
Development Workflow
# Development mode with hot reload
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview📋 Changelog
v0.0.55 - Unified Position System 🆕
Major Improvement:
- ✨ Unified Position System: Single
positionproperty for all display modes - 🗑️ Removed Redundant Properties: Eliminated
drawerPosition,sidebarPosition,toastPosition,modalVariant - 🔄 Smart Position Mapping: Intelligent mapping of positions to appropriate behaviors for each mode
- 📚 Updated Documentation: Comprehensive examples and migration guide
New Position Values:
- Edge Positions:
"top","bottom","left","right" - Center Positions:
"center" - Corner Positions:
"top-left","top-center","top-right","bottom-left","bottom-center","bottom-right" - Side Center Positions:
"center-left","center-right"
Migration Guide:
// ❌ OLD: Multiple position properties
drawerPosition: 'top' → position: 'top'
sidebarPosition: 'left' → position: 'left'
toastPosition: 'top-right' → position: 'top-right'
modalVariant: 'center' → position: 'center'
// ✅ NEW: Single position property
position: 'top' // Works for all modesBenefits:
- ✅ Simpler Configuration - One property instead of multiple
- ✅ Consistent Behavior - Same positioning logic across all modes
- ✅ Easier Maintenance - Less code duplication
- ✅ Better Developer Experience - Intuitive and predictable
v0.0.45 - Button Mode & Independent Positioning 🆕
New Features:
- ✨ Button Mode: New floating action button display mode
- 🎯 Independent Positioning: Button and popup can have different positions
- 🎨 Button Customization: Size, color, icon, and text options
- 📱 9 Button Positions: Full positioning flexibility (top-left, center, bottom-right, etc.)
- 🔄 Smart Transformation: Button disappears, popup appears at specified location
Button Configuration Options:
buttonPosition: Control where the button appears on the pagebuttonSize: small (40px), medium (60px), large (80px), custombuttonColor: Custom button color (hex, rgb, etc.)buttonIcon: Custom emoji or icon (default: 💬)buttonText: Custom text alternative to icon
Performance Improvements:
- 🚀 Form Animation Control:
disableFormAnimationsoption for faster loading - ⚡ Reduced Loading Time: Eliminates 3+ second delay between header and content
- 📱 Mobile Optimization: Better performance on resource-constrained devices
Bug Fixes:
- 🐛 Fixed widget re-creation issues in drawer/sidebar modes
- 🐛 Fixed DOM manipulation errors in render methods
- 🐛 Fixed autoClose behavior (true now correctly equals 3 seconds)
- 🐛 Improved positioning logic for all display modes
🆘 Support & Resources
- 📖 Documentation: docs.feedal.io
- 💬 Support: [email protected]
📄 License
This project is licensed under the MIT License.
Made with ❤️ by the Feedal Team
