@cornercue/react 0.1.0-beta.8 → 0.2.0-beta.0
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 +17 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -6,10 +6,24 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
// src/FeedbackButton.tsx
|
|
7
7
|
|
|
8
8
|
// src/cdn-loader.ts
|
|
9
|
-
var
|
|
9
|
+
var BUILD_TIME_HASH = "e397dcd4" ;
|
|
10
10
|
var CDN_BASE = "https://cdn.cornercue.com";
|
|
11
11
|
var coreModuleCache = null;
|
|
12
12
|
var loadPromise = null;
|
|
13
|
+
async function fetchCurrentHash() {
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(`${CDN_BASE}/widget/core/hash.txt`, {
|
|
16
|
+
cache: "no-store"
|
|
17
|
+
});
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
return BUILD_TIME_HASH;
|
|
20
|
+
}
|
|
21
|
+
const hash = (await response.text()).trim();
|
|
22
|
+
return hash || BUILD_TIME_HASH;
|
|
23
|
+
} catch {
|
|
24
|
+
return BUILD_TIME_HASH;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
13
27
|
async function loadCoreCDN() {
|
|
14
28
|
if (coreModuleCache) {
|
|
15
29
|
return coreModuleCache;
|
|
@@ -18,7 +32,8 @@ async function loadCoreCDN() {
|
|
|
18
32
|
return loadPromise;
|
|
19
33
|
}
|
|
20
34
|
loadPromise = (async () => {
|
|
21
|
-
const
|
|
35
|
+
const hash = await fetchCurrentHash();
|
|
36
|
+
const url = `${CDN_BASE}/widget/core/core.${hash}.js`;
|
|
22
37
|
try {
|
|
23
38
|
const module = await import(
|
|
24
39
|
/* @vite-ignore */
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cdn-loader.ts","../src/FeedbackButton.tsx"],"names":["useRef","useState","useEffect","useCallback","isValidElement","cloneElement"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * CDN Loader for CornerCue Widget Core\n *\n * This module dynamically loads the widget core from CDN, enabling\n * instant updates without requiring npm package updates.\n *\n * Cache strategy:\n * - react-widget.[hash].js: max-age=31536000, immutable (long-cached)\n * - Uses content hash for cache busting\n */\n\nimport type {\n CornerCueWidget as CornerCueWidgetType,\n WidgetConfig,\n} from \"@cornercue/core\";\n\n// Build-time injected hash - replaced by post-build script\ndeclare const __WIDGET_HASH__: string;\nconst WIDGET_HASH =\n typeof __WIDGET_HASH__ !== \"undefined\" ? __WIDGET_HASH__ : \"latest\";\n\n// Build-time flag for dev mode\ndeclare const __DEV__: boolean;\nconst IS_DEV = typeof __DEV__ !== \"undefined\" && __DEV__;\n\n// CDN base URL\nconst CDN_BASE = IS_DEV ? \"\" : \"https://cdn.cornercue.com\";\n\n// Cache for loaded module\nlet coreModuleCache: typeof import(\"@cornercue/core\") | null = null;\nlet loadPromise: Promise<typeof import(\"@cornercue/core\")> | null = null;\n\n/**\n * Load the core widget module from CDN\n */\nexport async function loadCoreCDN(): Promise<typeof import(\"@cornercue/core\")> {\n if (coreModuleCache) {\n return coreModuleCache;\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = (async () => {\n const url = IS_DEV\n ? `/widget/packages/core/dist/index.js`\n : `${CDN_BASE}/widget/core/core.${WIDGET_HASH}.js`;\n\n try {\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n } catch (error) {\n console.warn(\n `CornerCue: Failed to load core from \"${url}\", trying latest`,\n error,\n );\n // Fallback to latest\n const fallbackUrl = IS_DEV\n ? `/widget/packages/core/dist/index.js`\n : `${CDN_BASE}/widget/core/core.latest.js`;\n\n const module = await import(/* @vite-ignore */ fallbackUrl);\n coreModuleCache = module;\n return module;\n }\n })();\n\n return loadPromise;\n}\n\n// Shared widget instance (lazy loaded)\nlet sharedWidget: CornerCueWidgetType | null = null;\nlet widgetRefCount = 0;\n\n/**\n * Get or create a shared widget instance (lazy loaded from CDN)\n */\nexport async function getSharedWidgetAsync(\n config?: WidgetConfig,\n): Promise<CornerCueWidgetType> {\n if (!sharedWidget) {\n const { CornerCueWidget } = await loadCoreCDN();\n sharedWidget = new CornerCueWidget(config);\n }\n widgetRefCount++;\n return sharedWidget;\n}\n\n/**\n * Release reference to shared widget\n */\nexport function releaseSharedWidget(): void {\n widgetRefCount--;\n if (widgetRefCount <= 0 && sharedWidget) {\n sharedWidget.destroy();\n sharedWidget = null;\n widgetRefCount = 0;\n }\n}\n\n/**\n * Prefetch the core module during idle time\n */\nexport function prefetchCore(): void {\n if (typeof window === \"undefined\") return;\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => {\n loadCoreCDN().catch(() => {\n // Silently fail - will load on demand\n });\n });\n } else {\n // Fallback for Safari\n setTimeout(() => {\n loadCoreCDN().catch(() => {});\n }, 1000);\n }\n}\n","import React, {\n useRef,\n useEffect,\n useCallback,\n useState,\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type MouseEvent,\n} from \"react\";\nimport type {\n CornerCueWidget,\n WidgetConfig,\n UserInfo,\n Metadata,\n WidgetEvents,\n FeedbackType,\n} from \"@cornercue/core\";\nimport {\n getSharedWidgetAsync,\n releaseSharedWidget,\n prefetchCore,\n} from \"./cdn-loader\";\n\nexport interface FeedbackButtonProps {\n /**\n * Project ID for submissions\n */\n projectId: string;\n\n /**\n * User information to attach to feedback\n */\n user?: UserInfo | null;\n\n /**\n * Additional metadata to attach to feedback\n */\n metadata?: Metadata | null;\n\n /**\n * Default feedback type to open directly (skips menu)\n * Options: 'bug' | 'feature' | 'feedback'\n */\n defaultType?: FeedbackType;\n\n /**\n * Widget configuration overrides\n */\n config?: WidgetConfig;\n\n /**\n * Event callbacks\n */\n events?: WidgetEvents;\n\n /**\n * The trigger element (button, link, etc.)\n */\n children: ReactNode;\n\n /**\n * Additional class name for the wrapper\n */\n className?: string;\n}\n\n/**\n * React component that wraps a trigger element and opens the feedback widget on click.\n * Widget core is lazily loaded from CDN on first interaction.\n *\n * @example\n * ```tsx\n * <FeedbackButton\n * projectId=\"your-project-id\"\n * user={{ id: 'user123', email: 'user@example.com' }}\n * metadata={{ plan: 'pro' }}\n * >\n * <button>Give Feedback</button>\n * </FeedbackButton>\n * ```\n */\nexport function FeedbackButton({\n projectId,\n user,\n metadata,\n defaultType,\n config,\n // events - reserved for future use\n children,\n className,\n}: FeedbackButtonProps): ReactElement {\n const triggerRef = useRef<HTMLElement>(null);\n const widgetRef = useRef<CornerCueWidget | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n // Prefetch widget core on mount\n useEffect(() => {\n prefetchCore();\n\n return () => {\n if (widgetRef.current) {\n releaseSharedWidget();\n widgetRef.current = null;\n }\n };\n }, []);\n\n // Handle click on trigger\n const handleClick = useCallback(\n async (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n // Load widget if not already loaded\n if (!widgetRef.current) {\n if (isLoading) return; // Prevent double loading\n setIsLoading(true);\n try {\n widgetRef.current = await getSharedWidgetAsync(config);\n } catch (error) {\n console.error(\"CornerCue: Failed to load widget\", error);\n setIsLoading(false);\n return;\n }\n setIsLoading(false);\n }\n\n const widget = widgetRef.current;\n\n if (widget.isWidgetOpen()) {\n widget.close();\n } else {\n widget.open(trigger, {\n projectId,\n user: user ?? null,\n metadata: metadata ?? null,\n defaultType,\n });\n }\n },\n [projectId, user, metadata, defaultType, config, isLoading],\n );\n\n // Clone the child element to add ref and click handler\n if (!isValidElement(children)) {\n console.warn(\"FeedbackButton: children must be a valid React element\");\n return <>{children}</>;\n }\n\n const childElement = children as ReactElement<{\n ref?: React.Ref<HTMLElement>;\n onClick?: (e: MouseEvent) => void;\n className?: string;\n }>;\n\n return cloneElement(childElement, {\n ref: triggerRef,\n onClick: (e: MouseEvent) => {\n // Call original onClick if present\n childElement.props.onClick?.(e);\n handleClick(e);\n },\n className: className\n ? `${childElement.props.className || \"\"} ${className}`.trim()\n : childElement.props.className,\n });\n}\n\n// Re-export types\nexport type {\n WidgetConfig,\n UserInfo,\n Metadata,\n FeedbackType,\n WidgetEvents,\n OpenOptions,\n} from \"@cornercue/core\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cdn-loader.ts","../src/FeedbackButton.tsx"],"names":["useRef","useState","useEffect","useCallback","isValidElement","cloneElement"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * CDN Loader for CornerCue Widget Core\n *\n * This module dynamically loads the widget core from CDN, enabling\n * instant updates without requiring npm package updates.\n *\n * Cache strategy:\n * - hash.txt: no-cache (fetched on each page load, ~8 bytes)\n * - core.[hash].js: max-age=31536000, immutable (long-cached)\n * - Build-time hash used as fallback if hash.txt fetch fails\n */\n\nimport type {\n CornerCueWidget as CornerCueWidgetType,\n WidgetConfig,\n} from \"@cornercue/core\";\n\n// Build-time injected hash - used as fallback if runtime hash fetch fails\ndeclare const __WIDGET_HASH__: string;\nconst BUILD_TIME_HASH =\n typeof __WIDGET_HASH__ !== \"undefined\" ? __WIDGET_HASH__ : \"latest\";\n\n// Build-time flag for dev mode\ndeclare const __DEV__: boolean;\nconst IS_DEV = typeof __DEV__ !== \"undefined\" && __DEV__;\n\n// CDN base URL\nconst CDN_BASE = \"https://cdn.cornercue.com\";\n\n// Cache for loaded module\nlet coreModuleCache: typeof import(\"@cornercue/core\") | null = null;\nlet loadPromise: Promise<typeof import(\"@cornercue/core\")> | null = null;\n\n/**\n * Fetch the current hash from CDN\n * Returns build-time hash as fallback if fetch fails\n */\nasync function fetchCurrentHash(): Promise<string> {\n if (IS_DEV) {\n return BUILD_TIME_HASH;\n }\n\n try {\n const response = await fetch(`${CDN_BASE}/widget/core/hash.txt`, {\n cache: \"no-store\",\n });\n if (!response.ok) {\n return BUILD_TIME_HASH;\n }\n const hash = (await response.text()).trim();\n return hash || BUILD_TIME_HASH;\n } catch {\n // Network error - fall back to build-time hash\n return BUILD_TIME_HASH;\n }\n}\n\n/**\n * Load the core widget module from CDN\n */\nexport async function loadCoreCDN(): Promise<typeof import(\"@cornercue/core\")> {\n if (coreModuleCache) {\n return coreModuleCache;\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = (async () => {\n if (IS_DEV) {\n const url = `http://localhost:5500/widget/packages/core/dist/index.js`;\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n }\n\n // Fetch current hash from CDN\n const hash = await fetchCurrentHash();\n const url = `${CDN_BASE}/widget/core/core.${hash}.js`;\n\n try {\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n } catch (error) {\n console.warn(\n `CornerCue: Failed to load core from \"${url}\", trying latest`,\n error,\n );\n // Fallback to latest\n const fallbackUrl = `${CDN_BASE}/widget/core/core.latest.js`;\n const module = await import(/* @vite-ignore */ fallbackUrl);\n coreModuleCache = module;\n return module;\n }\n })();\n\n return loadPromise;\n}\n\n// Shared widget instance (lazy loaded)\nlet sharedWidget: CornerCueWidgetType | null = null;\nlet widgetRefCount = 0;\n\n/**\n * Get or create a shared widget instance (lazy loaded from CDN)\n */\nexport async function getSharedWidgetAsync(\n config?: WidgetConfig,\n): Promise<CornerCueWidgetType> {\n if (!sharedWidget) {\n const { CornerCueWidget } = await loadCoreCDN();\n sharedWidget = new CornerCueWidget(config);\n }\n widgetRefCount++;\n return sharedWidget;\n}\n\n/**\n * Release reference to shared widget\n */\nexport function releaseSharedWidget(): void {\n widgetRefCount--;\n if (widgetRefCount <= 0 && sharedWidget) {\n sharedWidget.destroy();\n sharedWidget = null;\n widgetRefCount = 0;\n }\n}\n\n/**\n * Prefetch the core module during idle time\n */\nexport function prefetchCore(): void {\n if (typeof window === \"undefined\") return;\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => {\n loadCoreCDN().catch(() => {\n // Silently fail - will load on demand\n });\n });\n } else {\n // Fallback for Safari\n setTimeout(() => {\n loadCoreCDN().catch(() => {});\n }, 1000);\n }\n}\n","import React, {\n useRef,\n useEffect,\n useCallback,\n useState,\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type MouseEvent,\n} from \"react\";\nimport type {\n CornerCueWidget,\n WidgetConfig,\n UserInfo,\n Metadata,\n WidgetEvents,\n FeedbackType,\n} from \"@cornercue/core\";\nimport {\n getSharedWidgetAsync,\n releaseSharedWidget,\n prefetchCore,\n} from \"./cdn-loader\";\n\nexport interface FeedbackButtonProps {\n /**\n * Project ID for submissions\n */\n projectId: string;\n\n /**\n * User information to attach to feedback\n */\n user?: UserInfo | null;\n\n /**\n * Additional metadata to attach to feedback\n */\n metadata?: Metadata | null;\n\n /**\n * Default feedback type to open directly (skips menu)\n * Options: 'bug' | 'feature' | 'feedback'\n */\n defaultType?: FeedbackType;\n\n /**\n * Widget configuration overrides\n */\n config?: WidgetConfig;\n\n /**\n * Event callbacks\n */\n events?: WidgetEvents;\n\n /**\n * The trigger element (button, link, etc.)\n */\n children: ReactNode;\n\n /**\n * Additional class name for the wrapper\n */\n className?: string;\n}\n\n/**\n * React component that wraps a trigger element and opens the feedback widget on click.\n * Widget core is lazily loaded from CDN on first interaction.\n *\n * @example\n * ```tsx\n * <FeedbackButton\n * projectId=\"your-project-id\"\n * user={{ id: 'user123', email: 'user@example.com' }}\n * metadata={{ plan: 'pro' }}\n * >\n * <button>Give Feedback</button>\n * </FeedbackButton>\n * ```\n */\nexport function FeedbackButton({\n projectId,\n user,\n metadata,\n defaultType,\n config,\n // events - reserved for future use\n children,\n className,\n}: FeedbackButtonProps): ReactElement {\n const triggerRef = useRef<HTMLElement>(null);\n const widgetRef = useRef<CornerCueWidget | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n // Prefetch widget core on mount\n useEffect(() => {\n prefetchCore();\n\n return () => {\n if (widgetRef.current) {\n releaseSharedWidget();\n widgetRef.current = null;\n }\n };\n }, []);\n\n // Handle click on trigger\n const handleClick = useCallback(\n async (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n // Load widget if not already loaded\n if (!widgetRef.current) {\n if (isLoading) return; // Prevent double loading\n setIsLoading(true);\n try {\n widgetRef.current = await getSharedWidgetAsync(config);\n } catch (error) {\n console.error(\"CornerCue: Failed to load widget\", error);\n setIsLoading(false);\n return;\n }\n setIsLoading(false);\n }\n\n const widget = widgetRef.current;\n\n if (widget.isWidgetOpen()) {\n widget.close();\n } else {\n widget.open(trigger, {\n projectId,\n user: user ?? null,\n metadata: metadata ?? null,\n defaultType,\n });\n }\n },\n [projectId, user, metadata, defaultType, config, isLoading],\n );\n\n // Clone the child element to add ref and click handler\n if (!isValidElement(children)) {\n console.warn(\"FeedbackButton: children must be a valid React element\");\n return <>{children}</>;\n }\n\n const childElement = children as ReactElement<{\n ref?: React.Ref<HTMLElement>;\n onClick?: (e: MouseEvent) => void;\n className?: string;\n }>;\n\n return cloneElement(childElement, {\n ref: triggerRef,\n onClick: (e: MouseEvent) => {\n // Call original onClick if present\n childElement.props.onClick?.(e);\n handleClick(e);\n },\n className: className\n ? `${childElement.props.className || \"\"} ${className}`.trim()\n : childElement.props.className,\n });\n}\n\n// Re-export types\nexport type {\n WidgetConfig,\n UserInfo,\n Metadata,\n FeedbackType,\n WidgetEvents,\n OpenOptions,\n} from \"@cornercue/core\";\n"]}
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,24 @@ import { jsx, Fragment } from 'react/jsx-runtime';
|
|
|
4
4
|
// src/FeedbackButton.tsx
|
|
5
5
|
|
|
6
6
|
// src/cdn-loader.ts
|
|
7
|
-
var
|
|
7
|
+
var BUILD_TIME_HASH = "e397dcd4" ;
|
|
8
8
|
var CDN_BASE = "https://cdn.cornercue.com";
|
|
9
9
|
var coreModuleCache = null;
|
|
10
10
|
var loadPromise = null;
|
|
11
|
+
async function fetchCurrentHash() {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${CDN_BASE}/widget/core/hash.txt`, {
|
|
14
|
+
cache: "no-store"
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
return BUILD_TIME_HASH;
|
|
18
|
+
}
|
|
19
|
+
const hash = (await response.text()).trim();
|
|
20
|
+
return hash || BUILD_TIME_HASH;
|
|
21
|
+
} catch {
|
|
22
|
+
return BUILD_TIME_HASH;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
11
25
|
async function loadCoreCDN() {
|
|
12
26
|
if (coreModuleCache) {
|
|
13
27
|
return coreModuleCache;
|
|
@@ -16,7 +30,8 @@ async function loadCoreCDN() {
|
|
|
16
30
|
return loadPromise;
|
|
17
31
|
}
|
|
18
32
|
loadPromise = (async () => {
|
|
19
|
-
const
|
|
33
|
+
const hash = await fetchCurrentHash();
|
|
34
|
+
const url = `${CDN_BASE}/widget/core/core.${hash}.js`;
|
|
20
35
|
try {
|
|
21
36
|
const module = await import(
|
|
22
37
|
/* @vite-ignore */
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cdn-loader.ts","../src/FeedbackButton.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * CDN Loader for CornerCue Widget Core\n *\n * This module dynamically loads the widget core from CDN, enabling\n * instant updates without requiring npm package updates.\n *\n * Cache strategy:\n * - react-widget.[hash].js: max-age=31536000, immutable (long-cached)\n * - Uses content hash for cache busting\n */\n\nimport type {\n CornerCueWidget as CornerCueWidgetType,\n WidgetConfig,\n} from \"@cornercue/core\";\n\n// Build-time injected hash - replaced by post-build script\ndeclare const __WIDGET_HASH__: string;\nconst WIDGET_HASH =\n typeof __WIDGET_HASH__ !== \"undefined\" ? __WIDGET_HASH__ : \"latest\";\n\n// Build-time flag for dev mode\ndeclare const __DEV__: boolean;\nconst IS_DEV = typeof __DEV__ !== \"undefined\" && __DEV__;\n\n// CDN base URL\nconst CDN_BASE = IS_DEV ? \"\" : \"https://cdn.cornercue.com\";\n\n// Cache for loaded module\nlet coreModuleCache: typeof import(\"@cornercue/core\") | null = null;\nlet loadPromise: Promise<typeof import(\"@cornercue/core\")> | null = null;\n\n/**\n * Load the core widget module from CDN\n */\nexport async function loadCoreCDN(): Promise<typeof import(\"@cornercue/core\")> {\n if (coreModuleCache) {\n return coreModuleCache;\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = (async () => {\n const url = IS_DEV\n ? `/widget/packages/core/dist/index.js`\n : `${CDN_BASE}/widget/core/core.${WIDGET_HASH}.js`;\n\n try {\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n } catch (error) {\n console.warn(\n `CornerCue: Failed to load core from \"${url}\", trying latest`,\n error,\n );\n // Fallback to latest\n const fallbackUrl = IS_DEV\n ? `/widget/packages/core/dist/index.js`\n : `${CDN_BASE}/widget/core/core.latest.js`;\n\n const module = await import(/* @vite-ignore */ fallbackUrl);\n coreModuleCache = module;\n return module;\n }\n })();\n\n return loadPromise;\n}\n\n// Shared widget instance (lazy loaded)\nlet sharedWidget: CornerCueWidgetType | null = null;\nlet widgetRefCount = 0;\n\n/**\n * Get or create a shared widget instance (lazy loaded from CDN)\n */\nexport async function getSharedWidgetAsync(\n config?: WidgetConfig,\n): Promise<CornerCueWidgetType> {\n if (!sharedWidget) {\n const { CornerCueWidget } = await loadCoreCDN();\n sharedWidget = new CornerCueWidget(config);\n }\n widgetRefCount++;\n return sharedWidget;\n}\n\n/**\n * Release reference to shared widget\n */\nexport function releaseSharedWidget(): void {\n widgetRefCount--;\n if (widgetRefCount <= 0 && sharedWidget) {\n sharedWidget.destroy();\n sharedWidget = null;\n widgetRefCount = 0;\n }\n}\n\n/**\n * Prefetch the core module during idle time\n */\nexport function prefetchCore(): void {\n if (typeof window === \"undefined\") return;\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => {\n loadCoreCDN().catch(() => {\n // Silently fail - will load on demand\n });\n });\n } else {\n // Fallback for Safari\n setTimeout(() => {\n loadCoreCDN().catch(() => {});\n }, 1000);\n }\n}\n","import React, {\n useRef,\n useEffect,\n useCallback,\n useState,\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type MouseEvent,\n} from \"react\";\nimport type {\n CornerCueWidget,\n WidgetConfig,\n UserInfo,\n Metadata,\n WidgetEvents,\n FeedbackType,\n} from \"@cornercue/core\";\nimport {\n getSharedWidgetAsync,\n releaseSharedWidget,\n prefetchCore,\n} from \"./cdn-loader\";\n\nexport interface FeedbackButtonProps {\n /**\n * Project ID for submissions\n */\n projectId: string;\n\n /**\n * User information to attach to feedback\n */\n user?: UserInfo | null;\n\n /**\n * Additional metadata to attach to feedback\n */\n metadata?: Metadata | null;\n\n /**\n * Default feedback type to open directly (skips menu)\n * Options: 'bug' | 'feature' | 'feedback'\n */\n defaultType?: FeedbackType;\n\n /**\n * Widget configuration overrides\n */\n config?: WidgetConfig;\n\n /**\n * Event callbacks\n */\n events?: WidgetEvents;\n\n /**\n * The trigger element (button, link, etc.)\n */\n children: ReactNode;\n\n /**\n * Additional class name for the wrapper\n */\n className?: string;\n}\n\n/**\n * React component that wraps a trigger element and opens the feedback widget on click.\n * Widget core is lazily loaded from CDN on first interaction.\n *\n * @example\n * ```tsx\n * <FeedbackButton\n * projectId=\"your-project-id\"\n * user={{ id: 'user123', email: 'user@example.com' }}\n * metadata={{ plan: 'pro' }}\n * >\n * <button>Give Feedback</button>\n * </FeedbackButton>\n * ```\n */\nexport function FeedbackButton({\n projectId,\n user,\n metadata,\n defaultType,\n config,\n // events - reserved for future use\n children,\n className,\n}: FeedbackButtonProps): ReactElement {\n const triggerRef = useRef<HTMLElement>(null);\n const widgetRef = useRef<CornerCueWidget | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n // Prefetch widget core on mount\n useEffect(() => {\n prefetchCore();\n\n return () => {\n if (widgetRef.current) {\n releaseSharedWidget();\n widgetRef.current = null;\n }\n };\n }, []);\n\n // Handle click on trigger\n const handleClick = useCallback(\n async (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n // Load widget if not already loaded\n if (!widgetRef.current) {\n if (isLoading) return; // Prevent double loading\n setIsLoading(true);\n try {\n widgetRef.current = await getSharedWidgetAsync(config);\n } catch (error) {\n console.error(\"CornerCue: Failed to load widget\", error);\n setIsLoading(false);\n return;\n }\n setIsLoading(false);\n }\n\n const widget = widgetRef.current;\n\n if (widget.isWidgetOpen()) {\n widget.close();\n } else {\n widget.open(trigger, {\n projectId,\n user: user ?? null,\n metadata: metadata ?? null,\n defaultType,\n });\n }\n },\n [projectId, user, metadata, defaultType, config, isLoading],\n );\n\n // Clone the child element to add ref and click handler\n if (!isValidElement(children)) {\n console.warn(\"FeedbackButton: children must be a valid React element\");\n return <>{children}</>;\n }\n\n const childElement = children as ReactElement<{\n ref?: React.Ref<HTMLElement>;\n onClick?: (e: MouseEvent) => void;\n className?: string;\n }>;\n\n return cloneElement(childElement, {\n ref: triggerRef,\n onClick: (e: MouseEvent) => {\n // Call original onClick if present\n childElement.props.onClick?.(e);\n handleClick(e);\n },\n className: className\n ? `${childElement.props.className || \"\"} ${className}`.trim()\n : childElement.props.className,\n });\n}\n\n// Re-export types\nexport type {\n WidgetConfig,\n UserInfo,\n Metadata,\n FeedbackType,\n WidgetEvents,\n OpenOptions,\n} from \"@cornercue/core\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cdn-loader.ts","../src/FeedbackButton.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * CDN Loader for CornerCue Widget Core\n *\n * This module dynamically loads the widget core from CDN, enabling\n * instant updates without requiring npm package updates.\n *\n * Cache strategy:\n * - hash.txt: no-cache (fetched on each page load, ~8 bytes)\n * - core.[hash].js: max-age=31536000, immutable (long-cached)\n * - Build-time hash used as fallback if hash.txt fetch fails\n */\n\nimport type {\n CornerCueWidget as CornerCueWidgetType,\n WidgetConfig,\n} from \"@cornercue/core\";\n\n// Build-time injected hash - used as fallback if runtime hash fetch fails\ndeclare const __WIDGET_HASH__: string;\nconst BUILD_TIME_HASH =\n typeof __WIDGET_HASH__ !== \"undefined\" ? __WIDGET_HASH__ : \"latest\";\n\n// Build-time flag for dev mode\ndeclare const __DEV__: boolean;\nconst IS_DEV = typeof __DEV__ !== \"undefined\" && __DEV__;\n\n// CDN base URL\nconst CDN_BASE = \"https://cdn.cornercue.com\";\n\n// Cache for loaded module\nlet coreModuleCache: typeof import(\"@cornercue/core\") | null = null;\nlet loadPromise: Promise<typeof import(\"@cornercue/core\")> | null = null;\n\n/**\n * Fetch the current hash from CDN\n * Returns build-time hash as fallback if fetch fails\n */\nasync function fetchCurrentHash(): Promise<string> {\n if (IS_DEV) {\n return BUILD_TIME_HASH;\n }\n\n try {\n const response = await fetch(`${CDN_BASE}/widget/core/hash.txt`, {\n cache: \"no-store\",\n });\n if (!response.ok) {\n return BUILD_TIME_HASH;\n }\n const hash = (await response.text()).trim();\n return hash || BUILD_TIME_HASH;\n } catch {\n // Network error - fall back to build-time hash\n return BUILD_TIME_HASH;\n }\n}\n\n/**\n * Load the core widget module from CDN\n */\nexport async function loadCoreCDN(): Promise<typeof import(\"@cornercue/core\")> {\n if (coreModuleCache) {\n return coreModuleCache;\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = (async () => {\n if (IS_DEV) {\n const url = `http://localhost:5500/widget/packages/core/dist/index.js`;\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n }\n\n // Fetch current hash from CDN\n const hash = await fetchCurrentHash();\n const url = `${CDN_BASE}/widget/core/core.${hash}.js`;\n\n try {\n const module = await import(/* @vite-ignore */ url);\n coreModuleCache = module;\n return module;\n } catch (error) {\n console.warn(\n `CornerCue: Failed to load core from \"${url}\", trying latest`,\n error,\n );\n // Fallback to latest\n const fallbackUrl = `${CDN_BASE}/widget/core/core.latest.js`;\n const module = await import(/* @vite-ignore */ fallbackUrl);\n coreModuleCache = module;\n return module;\n }\n })();\n\n return loadPromise;\n}\n\n// Shared widget instance (lazy loaded)\nlet sharedWidget: CornerCueWidgetType | null = null;\nlet widgetRefCount = 0;\n\n/**\n * Get or create a shared widget instance (lazy loaded from CDN)\n */\nexport async function getSharedWidgetAsync(\n config?: WidgetConfig,\n): Promise<CornerCueWidgetType> {\n if (!sharedWidget) {\n const { CornerCueWidget } = await loadCoreCDN();\n sharedWidget = new CornerCueWidget(config);\n }\n widgetRefCount++;\n return sharedWidget;\n}\n\n/**\n * Release reference to shared widget\n */\nexport function releaseSharedWidget(): void {\n widgetRefCount--;\n if (widgetRefCount <= 0 && sharedWidget) {\n sharedWidget.destroy();\n sharedWidget = null;\n widgetRefCount = 0;\n }\n}\n\n/**\n * Prefetch the core module during idle time\n */\nexport function prefetchCore(): void {\n if (typeof window === \"undefined\") return;\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => {\n loadCoreCDN().catch(() => {\n // Silently fail - will load on demand\n });\n });\n } else {\n // Fallback for Safari\n setTimeout(() => {\n loadCoreCDN().catch(() => {});\n }, 1000);\n }\n}\n","import React, {\n useRef,\n useEffect,\n useCallback,\n useState,\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type MouseEvent,\n} from \"react\";\nimport type {\n CornerCueWidget,\n WidgetConfig,\n UserInfo,\n Metadata,\n WidgetEvents,\n FeedbackType,\n} from \"@cornercue/core\";\nimport {\n getSharedWidgetAsync,\n releaseSharedWidget,\n prefetchCore,\n} from \"./cdn-loader\";\n\nexport interface FeedbackButtonProps {\n /**\n * Project ID for submissions\n */\n projectId: string;\n\n /**\n * User information to attach to feedback\n */\n user?: UserInfo | null;\n\n /**\n * Additional metadata to attach to feedback\n */\n metadata?: Metadata | null;\n\n /**\n * Default feedback type to open directly (skips menu)\n * Options: 'bug' | 'feature' | 'feedback'\n */\n defaultType?: FeedbackType;\n\n /**\n * Widget configuration overrides\n */\n config?: WidgetConfig;\n\n /**\n * Event callbacks\n */\n events?: WidgetEvents;\n\n /**\n * The trigger element (button, link, etc.)\n */\n children: ReactNode;\n\n /**\n * Additional class name for the wrapper\n */\n className?: string;\n}\n\n/**\n * React component that wraps a trigger element and opens the feedback widget on click.\n * Widget core is lazily loaded from CDN on first interaction.\n *\n * @example\n * ```tsx\n * <FeedbackButton\n * projectId=\"your-project-id\"\n * user={{ id: 'user123', email: 'user@example.com' }}\n * metadata={{ plan: 'pro' }}\n * >\n * <button>Give Feedback</button>\n * </FeedbackButton>\n * ```\n */\nexport function FeedbackButton({\n projectId,\n user,\n metadata,\n defaultType,\n config,\n // events - reserved for future use\n children,\n className,\n}: FeedbackButtonProps): ReactElement {\n const triggerRef = useRef<HTMLElement>(null);\n const widgetRef = useRef<CornerCueWidget | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n // Prefetch widget core on mount\n useEffect(() => {\n prefetchCore();\n\n return () => {\n if (widgetRef.current) {\n releaseSharedWidget();\n widgetRef.current = null;\n }\n };\n }, []);\n\n // Handle click on trigger\n const handleClick = useCallback(\n async (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n // Load widget if not already loaded\n if (!widgetRef.current) {\n if (isLoading) return; // Prevent double loading\n setIsLoading(true);\n try {\n widgetRef.current = await getSharedWidgetAsync(config);\n } catch (error) {\n console.error(\"CornerCue: Failed to load widget\", error);\n setIsLoading(false);\n return;\n }\n setIsLoading(false);\n }\n\n const widget = widgetRef.current;\n\n if (widget.isWidgetOpen()) {\n widget.close();\n } else {\n widget.open(trigger, {\n projectId,\n user: user ?? null,\n metadata: metadata ?? null,\n defaultType,\n });\n }\n },\n [projectId, user, metadata, defaultType, config, isLoading],\n );\n\n // Clone the child element to add ref and click handler\n if (!isValidElement(children)) {\n console.warn(\"FeedbackButton: children must be a valid React element\");\n return <>{children}</>;\n }\n\n const childElement = children as ReactElement<{\n ref?: React.Ref<HTMLElement>;\n onClick?: (e: MouseEvent) => void;\n className?: string;\n }>;\n\n return cloneElement(childElement, {\n ref: triggerRef,\n onClick: (e: MouseEvent) => {\n // Call original onClick if present\n childElement.props.onClick?.(e);\n handleClick(e);\n },\n className: className\n ? `${childElement.props.className || \"\"} ${className}`.trim()\n : childElement.props.className,\n });\n}\n\n// Re-export types\nexport type {\n WidgetConfig,\n UserInfo,\n Metadata,\n FeedbackType,\n WidgetEvents,\n OpenOptions,\n} from \"@cornercue/core\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornercue/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "CornerCue feedback widget - React component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typecheck": "tsc --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cornercue/core": "0.1.0-beta.
|
|
30
|
+
"@cornercue/core": "0.1.0-beta.7"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": ">=17.0.0",
|