npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@feedal/embed

v0.0.56

Published

Feedal embed script to load feedback forms via JS or NPM

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 page
  • position: 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:

  1. Conditional Animation Control: Form components check the disableAnimations parameter
  2. Query Parameter Passing: ?disableAnimations=1 passed from embed script to form
  3. Smart Framer Motion: Animations gracefully disabled without errors
  4. 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 containerId option 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

  1. Lazy Loading: Use lazyLoad: true to load resources only when needed
  2. Manual Trigger: Use manual triggers for better control
  3. Performance Monitoring: Enable with performanceMonitoring: true to 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

  1. When a user submits a form, a record is stored in the browser (localStorage, sessionStorage, or cookie)
  2. The next time the form would be shown, the script checks for previous submissions
  3. If a submission is found and still valid, the form is not displayed
  4. 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:

  1. Forms with minimal content don't show unnecessary white space
  2. Forms with extensive content are properly contained with scrolling when needed
  3. 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 positioning
  • test-autoclose-fix.html - Test autoClose behavior and time formats
  • test-default-values.html - Test default configuration values
  • test-responsive.html - Test responsive behavior and sizing
  • test-advanced-triggers.html - Test all trigger types and configurations
  • test-optimized-modes.html - Test all display modes and their features

Running Tests

  1. Build the embed script:

    npm run build
  2. Open test files in browser:

    # Open any test file in your browser
    open test-button-fix.html
  3. Check 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 position property 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 modes

Benefits:

  • 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 page
  • buttonSize: small (40px), medium (60px), large (80px), custom
  • buttonColor: Custom button color (hex, rgb, etc.)
  • buttonIcon: Custom emoji or icon (default: 💬)
  • buttonText: Custom text alternative to icon

Performance Improvements:

  • 🚀 Form Animation Control: disableFormAnimations option 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


📄 License

This project is licensed under the MIT License.


Made with ❤️ by the Feedal Team