@perspective-ai/sdk-react 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -10,10 +10,11 @@ function useStableCallback(callback) {
10
10
  useIsomorphicLayoutEffect(() => {
11
11
  callbackRef.current = callback;
12
12
  });
13
- return react.useCallback(
13
+ const stable = react.useCallback(
14
14
  ((...args) => callbackRef.current?.(...args)),
15
15
  []
16
16
  );
17
+ return callback ? stable : callback;
17
18
  }
18
19
 
19
20
  // src/hooks/usePopup.ts
@@ -378,6 +379,35 @@ function useFloatBubble(options) {
378
379
  handle
379
380
  };
380
381
  }
382
+ function useAutoOpen(options) {
383
+ const { trigger, showOnce = "session", researchId, ...embedConfig } = options;
384
+ const cleanupRef = react.useRef(null);
385
+ const [triggered, setTriggered] = react.useState(false);
386
+ const triggerDelay = trigger.type === "timeout" ? trigger.delay : void 0;
387
+ const stableOnTrigger = useStableCallback(() => {
388
+ sdk.markShown(researchId, showOnce);
389
+ sdk.openPopup({ researchId, ...embedConfig });
390
+ });
391
+ react.useEffect(() => {
392
+ if (!sdk.shouldShow(researchId, showOnce)) return;
393
+ cleanupRef.current = sdk.setupTrigger(trigger, () => {
394
+ setTriggered((prev) => {
395
+ if (prev) return prev;
396
+ stableOnTrigger();
397
+ return true;
398
+ });
399
+ });
400
+ return () => {
401
+ cleanupRef.current?.();
402
+ cleanupRef.current = null;
403
+ };
404
+ }, [researchId, trigger.type, triggerDelay, showOnce]);
405
+ const cancel = react.useCallback(() => {
406
+ cleanupRef.current?.();
407
+ cleanupRef.current = null;
408
+ }, []);
409
+ return { cancel, triggered };
410
+ }
381
411
  function useThemeSync(theme = "system") {
382
412
  const [resolved, setResolved] = react.useState(
383
413
  theme !== "system" ? theme : "light"
@@ -567,6 +597,7 @@ function FloatBubble({
567
597
  exports.FloatBubble = FloatBubble;
568
598
  exports.Fullpage = Fullpage;
569
599
  exports.Widget = Widget;
600
+ exports.useAutoOpen = useAutoOpen;
570
601
  exports.useFloatBubble = useFloatBubble;
571
602
  exports.usePopup = usePopup;
572
603
  exports.useSlider = useSlider;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useStableCallback.ts","../src/hooks/usePopup.ts","../src/hooks/useSlider.ts","../src/hooks/useFloatBubble.ts","../src/hooks/useThemeSync.ts","../src/Widget.tsx","../src/Fullpage.tsx","../src/FloatBubble.tsx"],"names":["useLayoutEffect","useEffect","useRef","useCallback","useState","openPopup","openSlider","createFloatBubble","createWidget","jsx","createFullpage"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useRef, useCallback, useLayoutEffect, useEffect } from \"react\";\n\nconst useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStableCallback<T extends (...args: any[]) => any>(\n callback: T | undefined\n): T {\n const callbackRef = useRef(callback);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n return useCallback(\n ((...args: Parameters<T>) => callbackRef.current?.(...args)) as T,\n []\n );\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openPopup,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for usePopup hook */\nexport interface UsePopupOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for usePopup hook */\nexport interface UsePopupReturn {\n /** Open the popup */\n open: () => void;\n /** Close the popup */\n close: () => void;\n /** Toggle the popup */\n toggle: () => void;\n /** Whether the popup is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic popup control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * // Basic usage with custom trigger\n * const { open, isOpen } = usePopup({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Open Survey</MyCustomButton>\n *\n * // Controlled mode\n * const [isOpen, setIsOpen] = useState(false);\n * const popup = usePopup({\n * researchId: \"abc\",\n * open: isOpen,\n * onOpenChange: setIsOpen\n * });\n * ```\n */\nexport function usePopup(options: UsePopupOptions): UsePopupReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createPopup = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openPopup({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroyPopup = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createPopup();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createPopup]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroyPopup();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroyPopup]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createPopup();\n } else if (!controlledOpen && handleRef.current) {\n destroyPopup();\n }\n }, [controlledOpen, isControlled, createPopup, destroyPopup]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openSlider,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useSlider hook */\nexport interface UseSliderOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useSlider hook */\nexport interface UseSliderReturn {\n /** Open the slider */\n open: () => void;\n /** Close the slider */\n close: () => void;\n /** Toggle the slider */\n toggle: () => void;\n /** Whether the slider is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic slider control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * const { open, isOpen } = useSlider({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Give Feedback</MyCustomButton>\n * ```\n */\nexport function useSlider(options: UseSliderOptions): UseSliderReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createSlider = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openSlider({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroySlider = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createSlider();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createSlider]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroySlider();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroySlider]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createSlider();\n } else if (!controlledOpen && handleRef.current) {\n destroySlider();\n }\n }, [controlledOpen, isControlled, createSlider, destroySlider]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n createFloatBubble,\n type EmbedConfig,\n type FloatHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useFloatBubble hook */\nexport interface UseFloatBubbleOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useFloatBubble hook */\nexport interface UseFloatBubbleReturn {\n /** Open the float bubble window */\n open: () => void;\n /** Close the float bubble window */\n close: () => void;\n /** Toggle the float bubble window */\n toggle: () => void;\n /** Unmount the float bubble entirely */\n unmount: () => void;\n /** Whether the float bubble window is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null until mounted) */\n handle: FloatHandle | null;\n}\n\n/**\n * Headless hook for float bubble lifecycle management.\n * Creates a floating bubble button that expands into a chat window.\n * The bubble mounts on component mount and unmounts on component unmount.\n *\n * @example\n * ```tsx\n * // Basic usage - bubble mounts on component mount\n * useFloatBubble({ researchId: \"abc\" });\n *\n * // With programmatic control\n * const { open, close, isOpen } = useFloatBubble({ researchId: \"abc\" });\n * <button onClick={open}>Open Chat</button>\n * ```\n */\nexport function useFloatBubble(\n options: UseFloatBubbleOptions\n): UseFloatBubbleReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<FloatHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<FloatHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const handleClose = useCallback(() => {\n setInternalOpen(false);\n if (isControlled) {\n onOpenChange?.(false);\n }\n onClose?.();\n }, [isControlled, onOpenChange, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n useEffect(() => {\n const newHandle = createFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n\n return () => {\n if (handleRef.current === newHandle) {\n newHandle.unmount();\n handleRef.current = null;\n setHandle(null);\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n useEffect(() => {\n if (!isControlled || !handle) return;\n\n if (controlledOpen && !handle.isOpen) {\n handle.open();\n } else if (!controlledOpen && handle.isOpen) {\n handle.close();\n }\n }, [controlledOpen, isControlled, handle]);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n handleRef.current?.open();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n handleRef.current?.close();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange]);\n\n const toggleFn = useCallback(() => {\n const currentlyOpen = handleRef.current?.isOpen ?? internalOpen;\n if (currentlyOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [internalOpen, openFn, closeFn]);\n\n const unmountFn = useCallback(() => {\n handleRef.current?.unmount();\n handleRef.current = null;\n setHandle(null);\n setInternalOpen(false);\n }, []);\n\n const isOpen = isControlled\n ? controlledOpen\n : (handle?.isOpen ?? internalOpen);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n unmount: unmountFn,\n isOpen,\n handle,\n };\n}\n","import { useState, useEffect } from \"react\";\n\ntype Theme = \"light\" | \"dark\";\ntype ThemeInput = \"light\" | \"dark\" | \"system\";\n\n/**\n * Hook to resolve theme based on override and system preference.\n * Listens for system preference changes when theme is \"system\".\n */\nexport function useThemeSync(theme: ThemeInput = \"system\"): Theme {\n // Always start with a deterministic value for SSR hydration safety.\n // The actual system preference is synced in useEffect.\n const [resolved, setResolved] = useState<Theme>(\n theme !== \"system\" ? theme : \"light\"\n );\n\n useEffect(() => {\n if (theme !== \"system\") {\n setResolved(theme);\n return;\n }\n\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n // Set initial value\n setResolved(mq.matches ? \"dark\" : \"light\");\n\n const handler = (e: MediaQueryListEvent) =>\n setResolved(e.matches ? \"dark\" : \"light\");\n\n mq.addEventListener(\"change\", handler);\n return () => mq.removeEventListener(\"change\", handler);\n }, [theme]);\n\n return resolved;\n}\n","import { useRef, useEffect, type HTMLAttributes, type RefObject } from \"react\";\nimport {\n createWidget,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface WidgetProps\n extends\n Omit<EmbedConfig, \"type\">,\n Omit<HTMLAttributes<HTMLDivElement>, \"onError\" | \"onSubmit\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Inline widget embed component.\n * Renders the interview directly in a container.\n */\nexport function Widget({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n className,\n style,\n ...divProps\n}: WidgetProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks to avoid re-mounting on callback changes\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const handle = createWidget(container, {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ minHeight: 500, ...style }}\n data-testid=\"perspective-widget\"\n {...divProps}\n />\n );\n}\n","import { useRef, useEffect, type RefObject } from \"react\";\nimport {\n createFullpage,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface FullpageProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Full viewport embed component.\n * Takes over the entire screen with the interview.\n */\nexport function Fullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FullpageProps) {\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const handle = createFullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n // This component doesn't render anything - the fullpage overlay is added to document.body\n return null;\n}\n","import { useEffect, type RefObject } from \"react\";\nimport { type EmbedConfig, type FloatHandle } from \"@perspective-ai/sdk\";\nimport { useFloatBubble } from \"./hooks/useFloatBubble\";\n\nexport interface FloatBubbleProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the handle for programmatic control */\n embedRef?: RefObject<FloatHandle | null>;\n}\n\n/**\n * Floating bubble widget that expands into a chat window.\n * This is a convenience wrapper around useFloatBubble hook.\n *\n * @example\n * ```tsx\n * <FloatBubble researchId=\"abc\" onSubmit={handleSubmit} />\n * ```\n */\nexport function FloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FloatBubbleProps) {\n const { handle } = useFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n });\n\n useEffect(() => {\n if (embedRef) {\n embedRef.current = handle;\n }\n return () => {\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [embedRef, handle]);\n\n return null;\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useStableCallback.ts","../src/hooks/usePopup.ts","../src/hooks/useSlider.ts","../src/hooks/useFloatBubble.ts","../src/hooks/useAutoOpen.ts","../src/hooks/useThemeSync.ts","../src/Widget.tsx","../src/Fullpage.tsx","../src/FloatBubble.tsx"],"names":["useLayoutEffect","useEffect","useRef","useCallback","useState","openPopup","openSlider","createFloatBubble","markShown","shouldShow","setupTrigger","createWidget","jsx","createFullpage"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useRef, useCallback, useLayoutEffect, useEffect } from \"react\";\n\nconst useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nexport function useStableCallback<\n T extends ((...args: any[]) => any) | undefined,\n>(callback: T): T {\n const callbackRef = useRef(callback);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n // Always create the stable wrapper (hooks can't be conditional),\n // but return undefined when no callback is provided to preserve\n // truthiness semantics for consumers that branch on it.\n const stable = useCallback(\n ((...args: any[]) => callbackRef.current?.(...args)) as NonNullable<T>,\n []\n );\n\n return callback ? stable : callback;\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openPopup,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for usePopup hook */\nexport interface UsePopupOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for usePopup hook */\nexport interface UsePopupReturn {\n /** Open the popup */\n open: () => void;\n /** Close the popup */\n close: () => void;\n /** Toggle the popup */\n toggle: () => void;\n /** Whether the popup is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic popup control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * // Basic usage with custom trigger\n * const { open, isOpen } = usePopup({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Open Survey</MyCustomButton>\n *\n * // Controlled mode\n * const [isOpen, setIsOpen] = useState(false);\n * const popup = usePopup({\n * researchId: \"abc\",\n * open: isOpen,\n * onOpenChange: setIsOpen\n * });\n * ```\n */\nexport function usePopup(options: UsePopupOptions): UsePopupReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createPopup = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openPopup({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroyPopup = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createPopup();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createPopup]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroyPopup();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroyPopup]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createPopup();\n } else if (!controlledOpen && handleRef.current) {\n destroyPopup();\n }\n }, [controlledOpen, isControlled, createPopup, destroyPopup]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openSlider,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useSlider hook */\nexport interface UseSliderOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useSlider hook */\nexport interface UseSliderReturn {\n /** Open the slider */\n open: () => void;\n /** Close the slider */\n close: () => void;\n /** Toggle the slider */\n toggle: () => void;\n /** Whether the slider is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic slider control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * const { open, isOpen } = useSlider({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Give Feedback</MyCustomButton>\n * ```\n */\nexport function useSlider(options: UseSliderOptions): UseSliderReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createSlider = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openSlider({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroySlider = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createSlider();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createSlider]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroySlider();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroySlider]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createSlider();\n } else if (!controlledOpen && handleRef.current) {\n destroySlider();\n }\n }, [controlledOpen, isControlled, createSlider, destroySlider]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n createFloatBubble,\n type EmbedConfig,\n type FloatHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useFloatBubble hook */\nexport interface UseFloatBubbleOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useFloatBubble hook */\nexport interface UseFloatBubbleReturn {\n /** Open the float bubble window */\n open: () => void;\n /** Close the float bubble window */\n close: () => void;\n /** Toggle the float bubble window */\n toggle: () => void;\n /** Unmount the float bubble entirely */\n unmount: () => void;\n /** Whether the float bubble window is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null until mounted) */\n handle: FloatHandle | null;\n}\n\n/**\n * Headless hook for float bubble lifecycle management.\n * Creates a floating bubble button that expands into a chat window.\n * The bubble mounts on component mount and unmounts on component unmount.\n *\n * @example\n * ```tsx\n * // Basic usage - bubble mounts on component mount\n * useFloatBubble({ researchId: \"abc\" });\n *\n * // With programmatic control\n * const { open, close, isOpen } = useFloatBubble({ researchId: \"abc\" });\n * <button onClick={open}>Open Chat</button>\n * ```\n */\nexport function useFloatBubble(\n options: UseFloatBubbleOptions\n): UseFloatBubbleReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<FloatHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<FloatHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const handleClose = useCallback(() => {\n setInternalOpen(false);\n if (isControlled) {\n onOpenChange?.(false);\n }\n onClose?.();\n }, [isControlled, onOpenChange, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n useEffect(() => {\n const newHandle = createFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n\n return () => {\n if (handleRef.current === newHandle) {\n newHandle.unmount();\n handleRef.current = null;\n setHandle(null);\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n useEffect(() => {\n if (!isControlled || !handle) return;\n\n if (controlledOpen && !handle.isOpen) {\n handle.open();\n } else if (!controlledOpen && handle.isOpen) {\n handle.close();\n }\n }, [controlledOpen, isControlled, handle]);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n handleRef.current?.open();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n handleRef.current?.close();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange]);\n\n const toggleFn = useCallback(() => {\n const currentlyOpen = handleRef.current?.isOpen ?? internalOpen;\n if (currentlyOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [internalOpen, openFn, closeFn]);\n\n const unmountFn = useCallback(() => {\n handleRef.current?.unmount();\n handleRef.current = null;\n setHandle(null);\n setInternalOpen(false);\n }, []);\n\n const isOpen = isControlled\n ? controlledOpen\n : (handle?.isOpen ?? internalOpen);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n unmount: unmountFn,\n isOpen,\n handle,\n };\n}\n","import { useEffect, useRef, useState, useCallback } from \"react\";\nimport {\n openPopup,\n setupTrigger,\n shouldShow,\n markShown,\n} from \"@perspective-ai/sdk\";\nimport type { EmbedConfig, TriggerConfig, ShowOnce } from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\nexport interface UseAutoOpenOptions extends Omit<\n EmbedConfig,\n \"type\" | \"autoOpen\"\n> {\n trigger: TriggerConfig;\n showOnce?: ShowOnce; // default: \"session\"\n}\n\nexport interface UseAutoOpenReturn {\n /** Cancel the pending trigger */\n cancel: () => void;\n /** Whether the trigger has fired */\n triggered: boolean;\n}\n\nexport function useAutoOpen(options: UseAutoOpenOptions): UseAutoOpenReturn {\n const { trigger, showOnce = \"session\", researchId, ...embedConfig } = options;\n const cleanupRef = useRef<(() => void) | null>(null);\n const [triggered, setTriggered] = useState(false);\n const triggerDelay = trigger.type === \"timeout\" ? trigger.delay : undefined;\n\n // Fix #5: useStableCallback so the trigger always calls with latest config\n const stableOnTrigger = useStableCallback(() => {\n markShown(researchId, showOnce);\n openPopup({ researchId, ...embedConfig });\n });\n\n useEffect(() => {\n if (!shouldShow(researchId, showOnce)) return;\n\n cleanupRef.current = setupTrigger(trigger, () => {\n setTriggered((prev) => {\n if (prev) return prev; // already fired\n stableOnTrigger();\n return true;\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n // Primitive deps only — avoids re-triggering on object identity changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [researchId, trigger.type, triggerDelay, showOnce]);\n\n const cancel = useCallback(() => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n }, []);\n\n return { cancel, triggered };\n}\n","import { useState, useEffect } from \"react\";\n\ntype Theme = \"light\" | \"dark\";\ntype ThemeInput = \"light\" | \"dark\" | \"system\";\n\n/**\n * Hook to resolve theme based on override and system preference.\n * Listens for system preference changes when theme is \"system\".\n */\nexport function useThemeSync(theme: ThemeInput = \"system\"): Theme {\n // Always start with a deterministic value for SSR hydration safety.\n // The actual system preference is synced in useEffect.\n const [resolved, setResolved] = useState<Theme>(\n theme !== \"system\" ? theme : \"light\"\n );\n\n useEffect(() => {\n if (theme !== \"system\") {\n setResolved(theme);\n return;\n }\n\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n // Set initial value\n setResolved(mq.matches ? \"dark\" : \"light\");\n\n const handler = (e: MediaQueryListEvent) =>\n setResolved(e.matches ? \"dark\" : \"light\");\n\n mq.addEventListener(\"change\", handler);\n return () => mq.removeEventListener(\"change\", handler);\n }, [theme]);\n\n return resolved;\n}\n","import { useRef, useEffect, type HTMLAttributes, type RefObject } from \"react\";\nimport {\n createWidget,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface WidgetProps\n extends\n Omit<EmbedConfig, \"type\">,\n Omit<HTMLAttributes<HTMLDivElement>, \"onError\" | \"onSubmit\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Inline widget embed component.\n * Renders the interview directly in a container.\n */\nexport function Widget({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n className,\n style,\n ...divProps\n}: WidgetProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks to avoid re-mounting on callback changes\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const handle = createWidget(container, {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ minHeight: 500, ...style }}\n data-testid=\"perspective-widget\"\n {...divProps}\n />\n );\n}\n","import { useRef, useEffect, type RefObject } from \"react\";\nimport {\n createFullpage,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface FullpageProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Full viewport embed component.\n * Takes over the entire screen with the interview.\n */\nexport function Fullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FullpageProps) {\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const handle = createFullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n // This component doesn't render anything - the fullpage overlay is added to document.body\n return null;\n}\n","import { useEffect, type RefObject } from \"react\";\nimport { type EmbedConfig, type FloatHandle } from \"@perspective-ai/sdk\";\nimport { useFloatBubble } from \"./hooks/useFloatBubble\";\n\nexport interface FloatBubbleProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the handle for programmatic control */\n embedRef?: RefObject<FloatHandle | null>;\n}\n\n/**\n * Floating bubble widget that expands into a chat window.\n * This is a convenience wrapper around useFloatBubble hook.\n *\n * @example\n * ```tsx\n * <FloatBubble researchId=\"abc\" onSubmit={handleSubmit} />\n * ```\n */\nexport function FloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FloatBubbleProps) {\n const { handle } = useFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n });\n\n useEffect(() => {\n if (embedRef) {\n embedRef.current = handle;\n }\n return () => {\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [embedRef, handle]);\n\n return null;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { EmbedConfig, EmbedHandle, FloatHandle } from '@perspective-ai/sdk';
2
- export { BrandColors, EmbedConfig, EmbedError, EmbedHandle, FloatHandle, ThemeValue } from '@perspective-ai/sdk';
1
+ import { EmbedConfig, EmbedHandle, FloatHandle, TriggerConfig, ShowOnce } from '@perspective-ai/sdk';
2
+ export { AutoOpenConfig, BrandColors, EmbedConfig, EmbedError, EmbedHandle, FloatHandle, ShowOnce, ThemeValue, TriggerConfig } from '@perspective-ai/sdk';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { HTMLAttributes, RefObject } from 'react';
5
5
 
@@ -115,6 +115,18 @@ interface UseFloatBubbleReturn {
115
115
  */
116
116
  declare function useFloatBubble(options: UseFloatBubbleOptions): UseFloatBubbleReturn;
117
117
 
118
+ interface UseAutoOpenOptions extends Omit<EmbedConfig, "type" | "autoOpen"> {
119
+ trigger: TriggerConfig;
120
+ showOnce?: ShowOnce;
121
+ }
122
+ interface UseAutoOpenReturn {
123
+ /** Cancel the pending trigger */
124
+ cancel: () => void;
125
+ /** Whether the trigger has fired */
126
+ triggered: boolean;
127
+ }
128
+ declare function useAutoOpen(options: UseAutoOpenOptions): UseAutoOpenReturn;
129
+
118
130
  type Theme = "light" | "dark";
119
131
  type ThemeInput = "light" | "dark" | "system";
120
132
  /**
@@ -123,7 +135,7 @@ type ThemeInput = "light" | "dark" | "system";
123
135
  */
124
136
  declare function useThemeSync(theme?: ThemeInput): Theme;
125
137
 
126
- declare function useStableCallback<T extends (...args: any[]) => any>(callback: T | undefined): T;
138
+ declare function useStableCallback<T extends ((...args: any[]) => any) | undefined>(callback: T): T;
127
139
 
128
140
  interface WidgetProps extends Omit<EmbedConfig, "type">, Omit<HTMLAttributes<HTMLDivElement>, "onError" | "onSubmit"> {
129
141
  /** Ref to access the embed handle for programmatic control */
@@ -160,4 +172,4 @@ interface FloatBubbleProps extends Omit<EmbedConfig, "type"> {
160
172
  */
161
173
  declare function FloatBubble({ researchId, params, brand, theme, host, onReady, onSubmit, onNavigate, onClose, onError, embedRef, }: FloatBubbleProps): null;
162
174
 
163
- export { FloatBubble, type FloatBubbleProps, Fullpage, type FullpageProps, type UseFloatBubbleOptions, type UseFloatBubbleReturn, type UsePopupOptions, type UsePopupReturn, type UseSliderOptions, type UseSliderReturn, Widget, type WidgetProps, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
175
+ export { FloatBubble, type FloatBubbleProps, Fullpage, type FullpageProps, type UseAutoOpenOptions, type UseAutoOpenReturn, type UseFloatBubbleOptions, type UseFloatBubbleReturn, type UsePopupOptions, type UsePopupReturn, type UseSliderOptions, type UseSliderReturn, Widget, type WidgetProps, useAutoOpen, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { EmbedConfig, EmbedHandle, FloatHandle } from '@perspective-ai/sdk';
2
- export { BrandColors, EmbedConfig, EmbedError, EmbedHandle, FloatHandle, ThemeValue } from '@perspective-ai/sdk';
1
+ import { EmbedConfig, EmbedHandle, FloatHandle, TriggerConfig, ShowOnce } from '@perspective-ai/sdk';
2
+ export { AutoOpenConfig, BrandColors, EmbedConfig, EmbedError, EmbedHandle, FloatHandle, ShowOnce, ThemeValue, TriggerConfig } from '@perspective-ai/sdk';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { HTMLAttributes, RefObject } from 'react';
5
5
 
@@ -115,6 +115,18 @@ interface UseFloatBubbleReturn {
115
115
  */
116
116
  declare function useFloatBubble(options: UseFloatBubbleOptions): UseFloatBubbleReturn;
117
117
 
118
+ interface UseAutoOpenOptions extends Omit<EmbedConfig, "type" | "autoOpen"> {
119
+ trigger: TriggerConfig;
120
+ showOnce?: ShowOnce;
121
+ }
122
+ interface UseAutoOpenReturn {
123
+ /** Cancel the pending trigger */
124
+ cancel: () => void;
125
+ /** Whether the trigger has fired */
126
+ triggered: boolean;
127
+ }
128
+ declare function useAutoOpen(options: UseAutoOpenOptions): UseAutoOpenReturn;
129
+
118
130
  type Theme = "light" | "dark";
119
131
  type ThemeInput = "light" | "dark" | "system";
120
132
  /**
@@ -123,7 +135,7 @@ type ThemeInput = "light" | "dark" | "system";
123
135
  */
124
136
  declare function useThemeSync(theme?: ThemeInput): Theme;
125
137
 
126
- declare function useStableCallback<T extends (...args: any[]) => any>(callback: T | undefined): T;
138
+ declare function useStableCallback<T extends ((...args: any[]) => any) | undefined>(callback: T): T;
127
139
 
128
140
  interface WidgetProps extends Omit<EmbedConfig, "type">, Omit<HTMLAttributes<HTMLDivElement>, "onError" | "onSubmit"> {
129
141
  /** Ref to access the embed handle for programmatic control */
@@ -160,4 +172,4 @@ interface FloatBubbleProps extends Omit<EmbedConfig, "type"> {
160
172
  */
161
173
  declare function FloatBubble({ researchId, params, brand, theme, host, onReady, onSubmit, onNavigate, onClose, onError, embedRef, }: FloatBubbleProps): null;
162
174
 
163
- export { FloatBubble, type FloatBubbleProps, Fullpage, type FullpageProps, type UseFloatBubbleOptions, type UseFloatBubbleReturn, type UsePopupOptions, type UsePopupReturn, type UseSliderOptions, type UseSliderReturn, Widget, type WidgetProps, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
175
+ export { FloatBubble, type FloatBubbleProps, Fullpage, type FullpageProps, type UseAutoOpenOptions, type UseAutoOpenReturn, type UseFloatBubbleOptions, type UseFloatBubbleReturn, type UsePopupOptions, type UsePopupReturn, type UseSliderOptions, type UseSliderReturn, Widget, type WidgetProps, useAutoOpen, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useRef, useLayoutEffect, useEffect, useCallback, useState } from 'react';
2
- import { openPopup, openSlider, createFloatBubble, createWidget, createFullpage } from '@perspective-ai/sdk';
2
+ import { openPopup, openSlider, createFloatBubble, markShown, shouldShow, setupTrigger, createWidget, createFullpage } from '@perspective-ai/sdk';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
5
5
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
@@ -8,10 +8,11 @@ function useStableCallback(callback) {
8
8
  useIsomorphicLayoutEffect(() => {
9
9
  callbackRef.current = callback;
10
10
  });
11
- return useCallback(
11
+ const stable = useCallback(
12
12
  ((...args) => callbackRef.current?.(...args)),
13
13
  []
14
14
  );
15
+ return callback ? stable : callback;
15
16
  }
16
17
 
17
18
  // src/hooks/usePopup.ts
@@ -376,6 +377,35 @@ function useFloatBubble(options) {
376
377
  handle
377
378
  };
378
379
  }
380
+ function useAutoOpen(options) {
381
+ const { trigger, showOnce = "session", researchId, ...embedConfig } = options;
382
+ const cleanupRef = useRef(null);
383
+ const [triggered, setTriggered] = useState(false);
384
+ const triggerDelay = trigger.type === "timeout" ? trigger.delay : void 0;
385
+ const stableOnTrigger = useStableCallback(() => {
386
+ markShown(researchId, showOnce);
387
+ openPopup({ researchId, ...embedConfig });
388
+ });
389
+ useEffect(() => {
390
+ if (!shouldShow(researchId, showOnce)) return;
391
+ cleanupRef.current = setupTrigger(trigger, () => {
392
+ setTriggered((prev) => {
393
+ if (prev) return prev;
394
+ stableOnTrigger();
395
+ return true;
396
+ });
397
+ });
398
+ return () => {
399
+ cleanupRef.current?.();
400
+ cleanupRef.current = null;
401
+ };
402
+ }, [researchId, trigger.type, triggerDelay, showOnce]);
403
+ const cancel = useCallback(() => {
404
+ cleanupRef.current?.();
405
+ cleanupRef.current = null;
406
+ }, []);
407
+ return { cancel, triggered };
408
+ }
379
409
  function useThemeSync(theme = "system") {
380
410
  const [resolved, setResolved] = useState(
381
411
  theme !== "system" ? theme : "light"
@@ -562,6 +592,6 @@ function FloatBubble({
562
592
  return null;
563
593
  }
564
594
 
565
- export { FloatBubble, Fullpage, Widget, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
595
+ export { FloatBubble, Fullpage, Widget, useAutoOpen, useFloatBubble, usePopup, useSlider, useStableCallback, useThemeSync };
566
596
  //# sourceMappingURL=index.js.map
567
597
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useStableCallback.ts","../src/hooks/usePopup.ts","../src/hooks/useSlider.ts","../src/hooks/useFloatBubble.ts","../src/hooks/useThemeSync.ts","../src/Widget.tsx","../src/Fullpage.tsx","../src/FloatBubble.tsx"],"names":["useRef","useCallback","useEffect","useState"],"mappings":"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","file":"index.js","sourcesContent":["import { useRef, useCallback, useLayoutEffect, useEffect } from \"react\";\n\nconst useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStableCallback<T extends (...args: any[]) => any>(\n callback: T | undefined\n): T {\n const callbackRef = useRef(callback);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n return useCallback(\n ((...args: Parameters<T>) => callbackRef.current?.(...args)) as T,\n []\n );\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openPopup,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for usePopup hook */\nexport interface UsePopupOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for usePopup hook */\nexport interface UsePopupReturn {\n /** Open the popup */\n open: () => void;\n /** Close the popup */\n close: () => void;\n /** Toggle the popup */\n toggle: () => void;\n /** Whether the popup is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic popup control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * // Basic usage with custom trigger\n * const { open, isOpen } = usePopup({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Open Survey</MyCustomButton>\n *\n * // Controlled mode\n * const [isOpen, setIsOpen] = useState(false);\n * const popup = usePopup({\n * researchId: \"abc\",\n * open: isOpen,\n * onOpenChange: setIsOpen\n * });\n * ```\n */\nexport function usePopup(options: UsePopupOptions): UsePopupReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createPopup = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openPopup({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroyPopup = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createPopup();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createPopup]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroyPopup();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroyPopup]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createPopup();\n } else if (!controlledOpen && handleRef.current) {\n destroyPopup();\n }\n }, [controlledOpen, isControlled, createPopup, destroyPopup]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openSlider,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useSlider hook */\nexport interface UseSliderOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useSlider hook */\nexport interface UseSliderReturn {\n /** Open the slider */\n open: () => void;\n /** Close the slider */\n close: () => void;\n /** Toggle the slider */\n toggle: () => void;\n /** Whether the slider is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic slider control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * const { open, isOpen } = useSlider({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Give Feedback</MyCustomButton>\n * ```\n */\nexport function useSlider(options: UseSliderOptions): UseSliderReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createSlider = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openSlider({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroySlider = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createSlider();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createSlider]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroySlider();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroySlider]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createSlider();\n } else if (!controlledOpen && handleRef.current) {\n destroySlider();\n }\n }, [controlledOpen, isControlled, createSlider, destroySlider]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n createFloatBubble,\n type EmbedConfig,\n type FloatHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useFloatBubble hook */\nexport interface UseFloatBubbleOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useFloatBubble hook */\nexport interface UseFloatBubbleReturn {\n /** Open the float bubble window */\n open: () => void;\n /** Close the float bubble window */\n close: () => void;\n /** Toggle the float bubble window */\n toggle: () => void;\n /** Unmount the float bubble entirely */\n unmount: () => void;\n /** Whether the float bubble window is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null until mounted) */\n handle: FloatHandle | null;\n}\n\n/**\n * Headless hook for float bubble lifecycle management.\n * Creates a floating bubble button that expands into a chat window.\n * The bubble mounts on component mount and unmounts on component unmount.\n *\n * @example\n * ```tsx\n * // Basic usage - bubble mounts on component mount\n * useFloatBubble({ researchId: \"abc\" });\n *\n * // With programmatic control\n * const { open, close, isOpen } = useFloatBubble({ researchId: \"abc\" });\n * <button onClick={open}>Open Chat</button>\n * ```\n */\nexport function useFloatBubble(\n options: UseFloatBubbleOptions\n): UseFloatBubbleReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<FloatHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<FloatHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const handleClose = useCallback(() => {\n setInternalOpen(false);\n if (isControlled) {\n onOpenChange?.(false);\n }\n onClose?.();\n }, [isControlled, onOpenChange, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n useEffect(() => {\n const newHandle = createFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n\n return () => {\n if (handleRef.current === newHandle) {\n newHandle.unmount();\n handleRef.current = null;\n setHandle(null);\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n useEffect(() => {\n if (!isControlled || !handle) return;\n\n if (controlledOpen && !handle.isOpen) {\n handle.open();\n } else if (!controlledOpen && handle.isOpen) {\n handle.close();\n }\n }, [controlledOpen, isControlled, handle]);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n handleRef.current?.open();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n handleRef.current?.close();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange]);\n\n const toggleFn = useCallback(() => {\n const currentlyOpen = handleRef.current?.isOpen ?? internalOpen;\n if (currentlyOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [internalOpen, openFn, closeFn]);\n\n const unmountFn = useCallback(() => {\n handleRef.current?.unmount();\n handleRef.current = null;\n setHandle(null);\n setInternalOpen(false);\n }, []);\n\n const isOpen = isControlled\n ? controlledOpen\n : (handle?.isOpen ?? internalOpen);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n unmount: unmountFn,\n isOpen,\n handle,\n };\n}\n","import { useState, useEffect } from \"react\";\n\ntype Theme = \"light\" | \"dark\";\ntype ThemeInput = \"light\" | \"dark\" | \"system\";\n\n/**\n * Hook to resolve theme based on override and system preference.\n * Listens for system preference changes when theme is \"system\".\n */\nexport function useThemeSync(theme: ThemeInput = \"system\"): Theme {\n // Always start with a deterministic value for SSR hydration safety.\n // The actual system preference is synced in useEffect.\n const [resolved, setResolved] = useState<Theme>(\n theme !== \"system\" ? theme : \"light\"\n );\n\n useEffect(() => {\n if (theme !== \"system\") {\n setResolved(theme);\n return;\n }\n\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n // Set initial value\n setResolved(mq.matches ? \"dark\" : \"light\");\n\n const handler = (e: MediaQueryListEvent) =>\n setResolved(e.matches ? \"dark\" : \"light\");\n\n mq.addEventListener(\"change\", handler);\n return () => mq.removeEventListener(\"change\", handler);\n }, [theme]);\n\n return resolved;\n}\n","import { useRef, useEffect, type HTMLAttributes, type RefObject } from \"react\";\nimport {\n createWidget,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface WidgetProps\n extends\n Omit<EmbedConfig, \"type\">,\n Omit<HTMLAttributes<HTMLDivElement>, \"onError\" | \"onSubmit\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Inline widget embed component.\n * Renders the interview directly in a container.\n */\nexport function Widget({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n className,\n style,\n ...divProps\n}: WidgetProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks to avoid re-mounting on callback changes\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const handle = createWidget(container, {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ minHeight: 500, ...style }}\n data-testid=\"perspective-widget\"\n {...divProps}\n />\n );\n}\n","import { useRef, useEffect, type RefObject } from \"react\";\nimport {\n createFullpage,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface FullpageProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Full viewport embed component.\n * Takes over the entire screen with the interview.\n */\nexport function Fullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FullpageProps) {\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const handle = createFullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n // This component doesn't render anything - the fullpage overlay is added to document.body\n return null;\n}\n","import { useEffect, type RefObject } from \"react\";\nimport { type EmbedConfig, type FloatHandle } from \"@perspective-ai/sdk\";\nimport { useFloatBubble } from \"./hooks/useFloatBubble\";\n\nexport interface FloatBubbleProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the handle for programmatic control */\n embedRef?: RefObject<FloatHandle | null>;\n}\n\n/**\n * Floating bubble widget that expands into a chat window.\n * This is a convenience wrapper around useFloatBubble hook.\n *\n * @example\n * ```tsx\n * <FloatBubble researchId=\"abc\" onSubmit={handleSubmit} />\n * ```\n */\nexport function FloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FloatBubbleProps) {\n const { handle } = useFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n });\n\n useEffect(() => {\n if (embedRef) {\n embedRef.current = handle;\n }\n return () => {\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [embedRef, handle]);\n\n return null;\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useStableCallback.ts","../src/hooks/usePopup.ts","../src/hooks/useSlider.ts","../src/hooks/useFloatBubble.ts","../src/hooks/useAutoOpen.ts","../src/hooks/useThemeSync.ts","../src/Widget.tsx","../src/Fullpage.tsx","../src/FloatBubble.tsx"],"names":["useRef","useCallback","useEffect","useState","openPopup"],"mappings":"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","file":"index.js","sourcesContent":["import { useRef, useCallback, useLayoutEffect, useEffect } from \"react\";\n\nconst useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nexport function useStableCallback<\n T extends ((...args: any[]) => any) | undefined,\n>(callback: T): T {\n const callbackRef = useRef(callback);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n // Always create the stable wrapper (hooks can't be conditional),\n // but return undefined when no callback is provided to preserve\n // truthiness semantics for consumers that branch on it.\n const stable = useCallback(\n ((...args: any[]) => callbackRef.current?.(...args)) as NonNullable<T>,\n []\n );\n\n return callback ? stable : callback;\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openPopup,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for usePopup hook */\nexport interface UsePopupOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for usePopup hook */\nexport interface UsePopupReturn {\n /** Open the popup */\n open: () => void;\n /** Close the popup */\n close: () => void;\n /** Toggle the popup */\n toggle: () => void;\n /** Whether the popup is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic popup control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * // Basic usage with custom trigger\n * const { open, isOpen } = usePopup({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Open Survey</MyCustomButton>\n *\n * // Controlled mode\n * const [isOpen, setIsOpen] = useState(false);\n * const popup = usePopup({\n * researchId: \"abc\",\n * open: isOpen,\n * onOpenChange: setIsOpen\n * });\n * ```\n */\nexport function usePopup(options: UsePopupOptions): UsePopupReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createPopup = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openPopup({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroyPopup = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createPopup();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createPopup]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroyPopup();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroyPopup]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createPopup();\n } else if (!controlledOpen && handleRef.current) {\n destroyPopup();\n }\n }, [controlledOpen, isControlled, createPopup, destroyPopup]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n openSlider,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useSlider hook */\nexport interface UseSliderOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useSlider hook */\nexport interface UseSliderReturn {\n /** Open the slider */\n open: () => void;\n /** Close the slider */\n close: () => void;\n /** Toggle the slider */\n toggle: () => void;\n /** Whether the slider is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null when closed) */\n handle: EmbedHandle | null;\n}\n\n/**\n * Headless hook for programmatic slider control.\n * Use this when you need custom trigger elements or programmatic control.\n *\n * @example\n * ```tsx\n * const { open, isOpen } = useSlider({ researchId: \"abc\" });\n * <MyCustomButton onClick={open}>Give Feedback</MyCustomButton>\n * ```\n */\nexport function useSlider(options: UseSliderOptions): UseSliderReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<EmbedHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n const isOpen = isControlled ? controlledOpen : internalOpen;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const setOpen = useCallback(\n (value: boolean) => {\n if (isControlled) {\n onOpenChange?.(value);\n } else {\n setInternalOpen(value);\n }\n },\n [isControlled, onOpenChange]\n );\n\n const handleClose = useCallback(() => {\n handleRef.current = null;\n setHandle(null);\n setOpen(false);\n onClose?.();\n }, [setOpen, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n const createSlider = useCallback(() => {\n if (handleRef.current) return handleRef.current;\n\n const newHandle = openSlider({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n return newHandle;\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n const destroySlider = useCallback(() => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n setHandle(null);\n }\n }, []);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n createSlider();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange, createSlider]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n destroySlider();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange, destroySlider]);\n\n const toggleFn = useCallback(() => {\n if (isOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [isOpen, openFn, closeFn]);\n\n useEffect(() => {\n if (!isControlled) return;\n\n if (controlledOpen && !handleRef.current) {\n createSlider();\n } else if (!controlledOpen && handleRef.current) {\n destroySlider();\n }\n }, [controlledOpen, isControlled, createSlider, destroySlider]);\n\n useEffect(() => {\n return () => {\n if (handleRef.current) {\n handleRef.current.destroy();\n handleRef.current = null;\n }\n };\n }, []);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n isOpen,\n handle,\n };\n}\n","import { useCallback, useState, useEffect, useRef } from \"react\";\nimport {\n createFloatBubble,\n type EmbedConfig,\n type FloatHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\n/** Options for useFloatBubble hook */\nexport interface UseFloatBubbleOptions extends Omit<EmbedConfig, \"type\"> {\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** Return type for useFloatBubble hook */\nexport interface UseFloatBubbleReturn {\n /** Open the float bubble window */\n open: () => void;\n /** Close the float bubble window */\n close: () => void;\n /** Toggle the float bubble window */\n toggle: () => void;\n /** Unmount the float bubble entirely */\n unmount: () => void;\n /** Whether the float bubble window is currently open */\n isOpen: boolean;\n /** The underlying SDK handle (null until mounted) */\n handle: FloatHandle | null;\n}\n\n/**\n * Headless hook for float bubble lifecycle management.\n * Creates a floating bubble button that expands into a chat window.\n * The bubble mounts on component mount and unmounts on component unmount.\n *\n * @example\n * ```tsx\n * // Basic usage - bubble mounts on component mount\n * useFloatBubble({ researchId: \"abc\" });\n *\n * // With programmatic control\n * const { open, close, isOpen } = useFloatBubble({ researchId: \"abc\" });\n * <button onClick={open}>Open Chat</button>\n * ```\n */\nexport function useFloatBubble(\n options: UseFloatBubbleOptions\n): UseFloatBubbleReturn {\n const {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n open: controlledOpen,\n onOpenChange,\n } = options;\n\n const [handle, setHandle] = useState<FloatHandle | null>(null);\n const [internalOpen, setInternalOpen] = useState(false);\n const handleRef = useRef<FloatHandle | null>(null);\n\n const isControlled = controlledOpen !== undefined;\n\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnError = useStableCallback(onError);\n\n const handleClose = useCallback(() => {\n setInternalOpen(false);\n if (isControlled) {\n onOpenChange?.(false);\n }\n onClose?.();\n }, [isControlled, onOpenChange, onClose]);\n\n const stableOnClose = useStableCallback(handleClose);\n\n useEffect(() => {\n const newHandle = createFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = newHandle;\n setHandle(newHandle);\n\n return () => {\n if (handleRef.current === newHandle) {\n newHandle.unmount();\n handleRef.current = null;\n setHandle(null);\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n ]);\n\n useEffect(() => {\n if (!isControlled || !handle) return;\n\n if (controlledOpen && !handle.isOpen) {\n handle.open();\n } else if (!controlledOpen && handle.isOpen) {\n handle.close();\n }\n }, [controlledOpen, isControlled, handle]);\n\n const openFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(true);\n } else {\n handleRef.current?.open();\n setInternalOpen(true);\n }\n }, [isControlled, onOpenChange]);\n\n const closeFn = useCallback(() => {\n if (isControlled) {\n onOpenChange?.(false);\n } else {\n handleRef.current?.close();\n setInternalOpen(false);\n }\n }, [isControlled, onOpenChange]);\n\n const toggleFn = useCallback(() => {\n const currentlyOpen = handleRef.current?.isOpen ?? internalOpen;\n if (currentlyOpen) {\n closeFn();\n } else {\n openFn();\n }\n }, [internalOpen, openFn, closeFn]);\n\n const unmountFn = useCallback(() => {\n handleRef.current?.unmount();\n handleRef.current = null;\n setHandle(null);\n setInternalOpen(false);\n }, []);\n\n const isOpen = isControlled\n ? controlledOpen\n : (handle?.isOpen ?? internalOpen);\n\n return {\n open: openFn,\n close: closeFn,\n toggle: toggleFn,\n unmount: unmountFn,\n isOpen,\n handle,\n };\n}\n","import { useEffect, useRef, useState, useCallback } from \"react\";\nimport {\n openPopup,\n setupTrigger,\n shouldShow,\n markShown,\n} from \"@perspective-ai/sdk\";\nimport type { EmbedConfig, TriggerConfig, ShowOnce } from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./useStableCallback\";\n\nexport interface UseAutoOpenOptions extends Omit<\n EmbedConfig,\n \"type\" | \"autoOpen\"\n> {\n trigger: TriggerConfig;\n showOnce?: ShowOnce; // default: \"session\"\n}\n\nexport interface UseAutoOpenReturn {\n /** Cancel the pending trigger */\n cancel: () => void;\n /** Whether the trigger has fired */\n triggered: boolean;\n}\n\nexport function useAutoOpen(options: UseAutoOpenOptions): UseAutoOpenReturn {\n const { trigger, showOnce = \"session\", researchId, ...embedConfig } = options;\n const cleanupRef = useRef<(() => void) | null>(null);\n const [triggered, setTriggered] = useState(false);\n const triggerDelay = trigger.type === \"timeout\" ? trigger.delay : undefined;\n\n // Fix #5: useStableCallback so the trigger always calls with latest config\n const stableOnTrigger = useStableCallback(() => {\n markShown(researchId, showOnce);\n openPopup({ researchId, ...embedConfig });\n });\n\n useEffect(() => {\n if (!shouldShow(researchId, showOnce)) return;\n\n cleanupRef.current = setupTrigger(trigger, () => {\n setTriggered((prev) => {\n if (prev) return prev; // already fired\n stableOnTrigger();\n return true;\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n // Primitive deps only — avoids re-triggering on object identity changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [researchId, trigger.type, triggerDelay, showOnce]);\n\n const cancel = useCallback(() => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n }, []);\n\n return { cancel, triggered };\n}\n","import { useState, useEffect } from \"react\";\n\ntype Theme = \"light\" | \"dark\";\ntype ThemeInput = \"light\" | \"dark\" | \"system\";\n\n/**\n * Hook to resolve theme based on override and system preference.\n * Listens for system preference changes when theme is \"system\".\n */\nexport function useThemeSync(theme: ThemeInput = \"system\"): Theme {\n // Always start with a deterministic value for SSR hydration safety.\n // The actual system preference is synced in useEffect.\n const [resolved, setResolved] = useState<Theme>(\n theme !== \"system\" ? theme : \"light\"\n );\n\n useEffect(() => {\n if (theme !== \"system\") {\n setResolved(theme);\n return;\n }\n\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n // Set initial value\n setResolved(mq.matches ? \"dark\" : \"light\");\n\n const handler = (e: MediaQueryListEvent) =>\n setResolved(e.matches ? \"dark\" : \"light\");\n\n mq.addEventListener(\"change\", handler);\n return () => mq.removeEventListener(\"change\", handler);\n }, [theme]);\n\n return resolved;\n}\n","import { useRef, useEffect, type HTMLAttributes, type RefObject } from \"react\";\nimport {\n createWidget,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface WidgetProps\n extends\n Omit<EmbedConfig, \"type\">,\n Omit<HTMLAttributes<HTMLDivElement>, \"onError\" | \"onSubmit\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Inline widget embed component.\n * Renders the interview directly in a container.\n */\nexport function Widget({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n className,\n style,\n ...divProps\n}: WidgetProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks to avoid re-mounting on callback changes\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const handle = createWidget(container, {\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ minHeight: 500, ...style }}\n data-testid=\"perspective-widget\"\n {...divProps}\n />\n );\n}\n","import { useRef, useEffect, type RefObject } from \"react\";\nimport {\n createFullpage,\n type EmbedConfig,\n type EmbedHandle,\n} from \"@perspective-ai/sdk\";\nimport { useStableCallback } from \"./hooks/useStableCallback\";\n\nexport interface FullpageProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the embed handle for programmatic control */\n embedRef?: RefObject<EmbedHandle | null>;\n}\n\n/**\n * Full viewport embed component.\n * Takes over the entire screen with the interview.\n */\nexport function Fullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FullpageProps) {\n const handleRef = useRef<EmbedHandle | null>(null);\n\n // Stable callbacks\n const stableOnReady = useStableCallback(onReady);\n const stableOnSubmit = useStableCallback(onSubmit);\n const stableOnNavigate = useStableCallback(onNavigate);\n const stableOnClose = useStableCallback(onClose);\n const stableOnError = useStableCallback(onError);\n\n useEffect(() => {\n const handle = createFullpage({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady: stableOnReady,\n onSubmit: stableOnSubmit,\n onNavigate: stableOnNavigate,\n onClose: stableOnClose,\n onError: stableOnError,\n });\n\n handleRef.current = handle;\n\n if (embedRef) {\n embedRef.current = handle;\n }\n\n return () => {\n handle.unmount();\n handleRef.current = null;\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [\n researchId,\n params,\n brand,\n theme,\n host,\n stableOnReady,\n stableOnSubmit,\n stableOnNavigate,\n stableOnClose,\n stableOnError,\n embedRef,\n ]);\n\n // This component doesn't render anything - the fullpage overlay is added to document.body\n return null;\n}\n","import { useEffect, type RefObject } from \"react\";\nimport { type EmbedConfig, type FloatHandle } from \"@perspective-ai/sdk\";\nimport { useFloatBubble } from \"./hooks/useFloatBubble\";\n\nexport interface FloatBubbleProps extends Omit<EmbedConfig, \"type\"> {\n /** Ref to access the handle for programmatic control */\n embedRef?: RefObject<FloatHandle | null>;\n}\n\n/**\n * Floating bubble widget that expands into a chat window.\n * This is a convenience wrapper around useFloatBubble hook.\n *\n * @example\n * ```tsx\n * <FloatBubble researchId=\"abc\" onSubmit={handleSubmit} />\n * ```\n */\nexport function FloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n embedRef,\n}: FloatBubbleProps) {\n const { handle } = useFloatBubble({\n researchId,\n params,\n brand,\n theme,\n host,\n onReady,\n onSubmit,\n onNavigate,\n onClose,\n onError,\n });\n\n useEffect(() => {\n if (embedRef) {\n embedRef.current = handle;\n }\n return () => {\n if (embedRef) {\n embedRef.current = null;\n }\n };\n }, [embedRef, handle]);\n\n return null;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-ai/sdk-react",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "React components for Perspective AI embed SDK",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -56,7 +56,7 @@
56
56
  "react-dom": "^18.0.0 || ^19.0.0"
57
57
  },
58
58
  "dependencies": {
59
- "@perspective-ai/sdk": "^1.0.1"
59
+ "@perspective-ai/sdk": "^1.1.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@testing-library/dom": "^10.4.1",
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, cleanup, act } from "@testing-library/react";
3
+ import { useAutoOpen } from "./useAutoOpen";
4
+
5
+ vi.mock("@perspective-ai/sdk", () => ({
6
+ openPopup: vi.fn(() => ({
7
+ unmount: vi.fn(),
8
+ update: vi.fn(),
9
+ destroy: vi.fn(),
10
+ researchId: "test-id",
11
+ type: "popup",
12
+ iframe: null,
13
+ container: null,
14
+ })),
15
+ setupTrigger: vi.fn(() => vi.fn()),
16
+ shouldShow: vi.fn(() => true),
17
+ markShown: vi.fn(),
18
+ }));
19
+
20
+ // useStableCallback just passes through in tests
21
+ vi.mock("./useStableCallback", () => ({
22
+ useStableCallback: (cb: unknown) => cb,
23
+ }));
24
+
25
+ import {
26
+ openPopup,
27
+ setupTrigger,
28
+ shouldShow,
29
+ markShown,
30
+ } from "@perspective-ai/sdk";
31
+
32
+ const mockOpenPopup = vi.mocked(openPopup);
33
+ const mockSetupTrigger = vi.mocked(setupTrigger);
34
+ const mockShouldShow = vi.mocked(shouldShow);
35
+ const mockMarkShown = vi.mocked(markShown);
36
+
37
+ describe("useAutoOpen", () => {
38
+ beforeEach(() => {
39
+ vi.clearAllMocks();
40
+ // Restore defaults after clearAllMocks resets implementations
41
+ mockShouldShow.mockReturnValue(true);
42
+ mockSetupTrigger.mockReturnValue(vi.fn());
43
+ vi.useFakeTimers();
44
+ });
45
+
46
+ afterEach(() => {
47
+ vi.useRealTimers();
48
+ cleanup();
49
+ });
50
+
51
+ it("calls setupTrigger on mount when shouldShow returns true", () => {
52
+ renderHook(() =>
53
+ useAutoOpen({
54
+ researchId: "test-id",
55
+ trigger: { type: "timeout", delay: 5000 },
56
+ })
57
+ );
58
+
59
+ expect(mockShouldShow).toHaveBeenCalledWith("test-id", "session");
60
+ expect(mockSetupTrigger).toHaveBeenCalledWith(
61
+ { type: "timeout", delay: 5000 },
62
+ expect.any(Function)
63
+ );
64
+ });
65
+
66
+ it("does not call setupTrigger when shouldShow returns false", () => {
67
+ mockShouldShow.mockReturnValue(false);
68
+
69
+ renderHook(() =>
70
+ useAutoOpen({
71
+ researchId: "test-id",
72
+ trigger: { type: "timeout", delay: 5000 },
73
+ })
74
+ );
75
+
76
+ expect(mockSetupTrigger).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it("calls markShown and openPopup when trigger fires", () => {
80
+ let triggerCallback: () => void;
81
+ mockSetupTrigger.mockImplementation((_config, cb) => {
82
+ triggerCallback = cb;
83
+ return vi.fn();
84
+ });
85
+
86
+ const { result } = renderHook(() =>
87
+ useAutoOpen({
88
+ researchId: "test-id",
89
+ trigger: { type: "timeout", delay: 5000 },
90
+ showOnce: "visitor",
91
+ })
92
+ );
93
+
94
+ expect(result.current.triggered).toBe(false);
95
+
96
+ act(() => triggerCallback!());
97
+
98
+ expect(mockMarkShown).toHaveBeenCalledWith("test-id", "visitor");
99
+ expect(mockOpenPopup).toHaveBeenCalledWith(
100
+ expect.objectContaining({ researchId: "test-id" })
101
+ );
102
+ expect(result.current.triggered).toBe(true);
103
+ });
104
+
105
+ it("only fires once even if trigger callback called multiple times", () => {
106
+ let triggerCallback: () => void;
107
+ mockSetupTrigger.mockImplementation((_config, cb) => {
108
+ triggerCallback = cb;
109
+ return vi.fn();
110
+ });
111
+
112
+ const { result } = renderHook(() =>
113
+ useAutoOpen({
114
+ researchId: "test-id",
115
+ trigger: { type: "timeout", delay: 5000 },
116
+ })
117
+ );
118
+
119
+ act(() => triggerCallback!());
120
+ act(() => triggerCallback!());
121
+
122
+ expect(mockOpenPopup).toHaveBeenCalledTimes(1);
123
+ expect(result.current.triggered).toBe(true);
124
+ });
125
+
126
+ it("defaults showOnce to session", () => {
127
+ renderHook(() =>
128
+ useAutoOpen({
129
+ researchId: "test-id",
130
+ trigger: { type: "exit-intent" },
131
+ })
132
+ );
133
+
134
+ expect(mockShouldShow).toHaveBeenCalledWith("test-id", "session");
135
+ });
136
+
137
+ it("calls cleanup on unmount", () => {
138
+ const mockCleanup = vi.fn();
139
+ mockSetupTrigger.mockReturnValue(mockCleanup);
140
+
141
+ const { unmount } = renderHook(() =>
142
+ useAutoOpen({
143
+ researchId: "test-id",
144
+ trigger: { type: "timeout", delay: 5000 },
145
+ })
146
+ );
147
+
148
+ unmount();
149
+
150
+ expect(mockCleanup).toHaveBeenCalled();
151
+ });
152
+
153
+ it("cancel() stops the trigger", () => {
154
+ const mockCleanup = vi.fn();
155
+ mockSetupTrigger.mockReturnValue(mockCleanup);
156
+
157
+ const { result } = renderHook(() =>
158
+ useAutoOpen({
159
+ researchId: "test-id",
160
+ trigger: { type: "timeout", delay: 5000 },
161
+ })
162
+ );
163
+
164
+ result.current.cancel();
165
+
166
+ expect(mockCleanup).toHaveBeenCalled();
167
+ });
168
+ });
@@ -0,0 +1,63 @@
1
+ import { useEffect, useRef, useState, useCallback } from "react";
2
+ import {
3
+ openPopup,
4
+ setupTrigger,
5
+ shouldShow,
6
+ markShown,
7
+ } from "@perspective-ai/sdk";
8
+ import type { EmbedConfig, TriggerConfig, ShowOnce } from "@perspective-ai/sdk";
9
+ import { useStableCallback } from "./useStableCallback";
10
+
11
+ export interface UseAutoOpenOptions extends Omit<
12
+ EmbedConfig,
13
+ "type" | "autoOpen"
14
+ > {
15
+ trigger: TriggerConfig;
16
+ showOnce?: ShowOnce; // default: "session"
17
+ }
18
+
19
+ export interface UseAutoOpenReturn {
20
+ /** Cancel the pending trigger */
21
+ cancel: () => void;
22
+ /** Whether the trigger has fired */
23
+ triggered: boolean;
24
+ }
25
+
26
+ export function useAutoOpen(options: UseAutoOpenOptions): UseAutoOpenReturn {
27
+ const { trigger, showOnce = "session", researchId, ...embedConfig } = options;
28
+ const cleanupRef = useRef<(() => void) | null>(null);
29
+ const [triggered, setTriggered] = useState(false);
30
+ const triggerDelay = trigger.type === "timeout" ? trigger.delay : undefined;
31
+
32
+ // Fix #5: useStableCallback so the trigger always calls with latest config
33
+ const stableOnTrigger = useStableCallback(() => {
34
+ markShown(researchId, showOnce);
35
+ openPopup({ researchId, ...embedConfig });
36
+ });
37
+
38
+ useEffect(() => {
39
+ if (!shouldShow(researchId, showOnce)) return;
40
+
41
+ cleanupRef.current = setupTrigger(trigger, () => {
42
+ setTriggered((prev) => {
43
+ if (prev) return prev; // already fired
44
+ stableOnTrigger();
45
+ return true;
46
+ });
47
+ });
48
+
49
+ return () => {
50
+ cleanupRef.current?.();
51
+ cleanupRef.current = null;
52
+ };
53
+ // Primitive deps only — avoids re-triggering on object identity changes
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [researchId, trigger.type, triggerDelay, showOnce]);
56
+
57
+ const cancel = useCallback(() => {
58
+ cleanupRef.current?.();
59
+ cleanupRef.current = null;
60
+ }, []);
61
+
62
+ return { cancel, triggered };
63
+ }
@@ -47,15 +47,29 @@ describe("useStableCallback", () => {
47
47
  expect(callback1).toHaveBeenCalledTimes(1); // Still only called once
48
48
  });
49
49
 
50
- it("handles undefined callback", () => {
50
+ it("returns undefined when callback is undefined", () => {
51
51
  const { result } = renderHook(() => useStableCallback(undefined));
52
+ expect(result.current).toBeUndefined();
53
+ });
54
+
55
+ it("transitions between undefined and defined", () => {
56
+ const callback = vi.fn();
57
+ const { result, rerender } = renderHook(({ cb }) => useStableCallback(cb), {
58
+ initialProps: { cb: undefined as (() => void) | undefined },
59
+ });
60
+
61
+ expect(result.current).toBeUndefined();
62
+
63
+ rerender({ cb: callback });
64
+ expect(result.current).toBeDefined();
65
+ const stableRef = result.current;
66
+
67
+ // Remains stable across re-renders with different callback instances
68
+ rerender({ cb: vi.fn() });
69
+ expect(result.current).toBe(stableRef);
52
70
 
53
- // Should not throw when called
54
- expect(() => {
55
- act(() => {
56
- result.current("test");
57
- });
58
- }).not.toThrow();
71
+ rerender({ cb: undefined });
72
+ expect(result.current).toBeUndefined();
59
73
  });
60
74
 
61
75
  it("passes through all arguments", () => {
@@ -3,18 +3,22 @@ import { useRef, useCallback, useLayoutEffect, useEffect } from "react";
3
3
  const useIsomorphicLayoutEffect =
4
4
  typeof window !== "undefined" ? useLayoutEffect : useEffect;
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- export function useStableCallback<T extends (...args: any[]) => any>(
8
- callback: T | undefined
9
- ): T {
6
+ export function useStableCallback<
7
+ T extends ((...args: any[]) => any) | undefined,
8
+ >(callback: T): T {
10
9
  const callbackRef = useRef(callback);
11
10
 
12
11
  useIsomorphicLayoutEffect(() => {
13
12
  callbackRef.current = callback;
14
13
  });
15
14
 
16
- return useCallback(
17
- ((...args: Parameters<T>) => callbackRef.current?.(...args)) as T,
15
+ // Always create the stable wrapper (hooks can't be conditional),
16
+ // but return undefined when no callback is provided to preserve
17
+ // truthiness semantics for consumers that branch on it.
18
+ const stable = useCallback(
19
+ ((...args: any[]) => callbackRef.current?.(...args)) as NonNullable<T>,
18
20
  []
19
21
  );
22
+
23
+ return callback ? stable : callback;
20
24
  }
package/src/index.ts CHANGED
@@ -32,6 +32,11 @@ export {
32
32
  type UseFloatBubbleOptions,
33
33
  type UseFloatBubbleReturn,
34
34
  } from "./hooks/useFloatBubble";
35
+ export {
36
+ useAutoOpen,
37
+ type UseAutoOpenOptions,
38
+ type UseAutoOpenReturn,
39
+ } from "./hooks/useAutoOpen";
35
40
  export { useThemeSync } from "./hooks/useThemeSync";
36
41
  export { useStableCallback } from "./hooks/useStableCallback";
37
42
 
@@ -46,4 +51,7 @@ export type {
46
51
  BrandColors,
47
52
  ThemeValue,
48
53
  EmbedError,
54
+ TriggerConfig,
55
+ ShowOnce,
56
+ AutoOpenConfig,
49
57
  } from "@perspective-ai/sdk";