@coxwave/tap-kit 4.4.3 → 4.6.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.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.cts +12 -1
- package/dist/react.d.ts +12 -1
- package/dist/react.js +10 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +10 -2
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loader.ts","../src/factory/createTapKit.ts","../src/kit.ts"],"names":["DEFAULT_CDN_LOADER_URL","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","extractHandlers","options","createSSRStub","applyOptionsToElement","element","createTapKit","normalizeCodingRowData","isMounted","isReady","isDestroyed","handlers","readyResolve","readyReject","readyPromise","pendingCalls","cleanupFns","cdnPromise","error","loadError","ensureElement","replayPendingCalls","e","setupEventCallbacks","unsubTimelineSeek","clipPlayHead","clipId","unsubAlarmFadeIn","messageInfo","call","queueCall","target","method","args","eventsProxy","params","callback","handler","videoProxy","config","container","el","course","id","data","contextType","payload","cleanup","_loading","_config","_loadError","_eventsProxy","_videoProxy","TapKit","__publicField","__privateAdd","__privateSet","__privateGet","err","unsubscribe","cancelled"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * CDN loader for TapKit\n * Dynamically loads the TapKit SDK from CDN\n *\n * For local testing, you can override the loader URL:\n * window.__TAP_KIT_LOADER_URL__ = '/tap-kit-core/loader.js';\n *\n * For local development (bypass loader, load IIFE directly):\n * window.__TAP_KIT_CORE_URL__ = '/packages/tap-kit-core/dist/index.global.js';\n */\n\n// Build-time constant injected by tsup define\ndeclare const __DEFAULT_CDN_LOADER_URL__: string;\n\nconst DEFAULT_CDN_LOADER_URL =\n typeof __DEFAULT_CDN_LOADER_URL__ !== \"undefined\" ? __DEFAULT_CDN_LOADER_URL__ : \"https://files.edutap.ai/tap-sdk/loader.js\";\nconst DEFAULT_TIMEOUT_MS = 4000; // 4 seconds total timeout\nconst IDLE_CALLBACK_TIMEOUT_MS = 500; // 500ms for requestIdleCallback\n\n/**\n * Get the loader URL from window override or default CDN\n */\nfunction getLoaderURL(): string {\n return window?.__TAP_KIT_LOADER_URL__ ? window.__TAP_KIT_LOADER_URL__ : DEFAULT_CDN_LOADER_URL;\n}\n\n/**\n * Check if local core mode is enabled\n * When __TAP_KIT_CORE_URL__ is set, directly load the IIFE bundle\n * This bypasses the loader.js and loads tap-kit-core directly\n */\nfunction isLocalCoreMode(): boolean {\n return typeof window !== \"undefined\" && !!window.__TAP_KIT_CORE_URL__;\n}\n\n/**\n * Get the local core URL\n */\nfunction getLocalCoreURL(): string {\n return window.__TAP_KIT_CORE_URL__ || \"\";\n}\n\n/**\n * Creates a SDK checker function with timeout and retry logic\n * Uses requestIdleCallback to avoid blocking browser rendering\n * @param resolve - Promise resolve function\n * @param reject - Promise reject function\n * @param timeoutMs - Maximum time to wait for SDK to load (milliseconds)\n * @returns Checker function to be called repeatedly\n */\nfunction createSDKChecker(\n resolve: (value: void | PromiseLike<void>) => void,\n reject: (reason?: unknown) => void,\n timeoutMs: number\n): () => void {\n const startTime = Date.now();\n\n const checkSDK = (): void => {\n // Check if real TapKit is loaded (not just stub)\n // Stub has TapKitLoaded flag set to true by loader.js after real SDK loads\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const elapsed = Date.now() - startTime;\n\n // Check if exceeded timeout\n if (elapsed > timeoutMs) {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`TapKit loader timeout: SDK not available after ${timeoutMs}ms`));\n return;\n }\n\n // Use requestIdleCallback for better performance\n // Falls back to setTimeout if not available\n if (typeof requestIdleCallback !== \"undefined\") {\n requestIdleCallback(checkSDK, { timeout: IDLE_CALLBACK_TIMEOUT_MS });\n } else {\n setTimeout(checkSDK, IDLE_CALLBACK_TIMEOUT_MS);\n }\n };\n\n return checkSDK;\n}\n\n/**\n * Loads the CDN loader script\n * The loader will then fetch versions.json and load the appropriate SDK version\n *\n * If __TAP_KIT_CORE_URL__ is set, bypasses loader and loads IIFE directly\n *\n * @param timeoutMs - Maximum time to wait for SDK to load (default: 4000ms)\n * @returns Promise that resolves when SDK is loaded\n * @throws {Error} If loader fails to load or times out\n */\nexport function loadCDNLoader(timeoutMs: number = DEFAULT_TIMEOUT_MS): Promise<void> {\n // If already loaded, return immediately\n if (window.__TAP_KIT_LOADER_LOADED__ && window.TapKit) {\n return Promise.resolve();\n }\n\n // If currently loading, return the existing promise\n if (window.__TAP_KIT_LOADER_LOADING__) {\n return window.__TAP_KIT_LOADER_LOADING__;\n }\n\n // Create loading promise\n const loadingPromise = new Promise<void>((resolve, reject) => {\n if (typeof document === \"undefined\") {\n reject(new Error(\"TapKit requires browser environment (document is undefined)\"));\n return;\n }\n\n // Local core mode: Load IIFE directly\n if (isLocalCoreMode()) {\n // Check if TapKit is already loaded (from other pages in playground)\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const coreURL = getLocalCoreURL();\n\n const script = document.createElement(\"script\");\n script.src = coreURL;\n script.async = true;\n\n script.onload = () => {\n // IIFE directly sets window.TapKit\n // Set the loaded flag manually since we bypass loader.js\n if (window.TapKit) {\n window.TapKitLoaded = true;\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n } else {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(\"TapKit not available after loading local core\"));\n }\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load local TapKit core: ${coreURL}`));\n };\n\n document.head.appendChild(script);\n return;\n }\n\n // CDN mode: Load loader.js\n const loaderURL = getLoaderURL();\n const script = document.createElement(\"script\");\n script.src = loaderURL;\n script.async = true;\n\n script.onload = () => {\n // The loader script will load the actual SDK\n // We need to wait a bit for the loader to fetch and load the SDK\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`));\n };\n\n // Check if script already exists\n const existingScript = document.querySelector(`script[src=\"${loaderURL}\"]`);\n\n if (existingScript) {\n // Script already added but not yet loaded\n existingScript.addEventListener(\"load\", () => {\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n });\n existingScript.addEventListener(\"error\", () => reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)));\n } else {\n document.head.appendChild(script);\n }\n });\n\n window.__TAP_KIT_LOADER_LOADING__ = loadingPromise;\n return loadingPromise;\n}\n","/**\n * createTapKit - Factory API for TapKit\n *\n * Non-React, framework-agnostic API with full configuration support.\n * Wraps the <tap-kit> Web Component with imperative control.\n *\n * @example\n * ```typescript\n * import { createTapKit } from '@coxwave/tap-kit';\n *\n * const tapkit = createTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * apiUrl: 'https://tapapistaging.coxwave.link',\n * onReady: () => console.log('Ready!'),\n * });\n *\n * tapkit.mount();\n * await tapkit.ready;\n * tapkit.show();\n * ```\n *\n * @see useTapKit for React projects (recommended)\n */\n\nimport type { Course, EventManager, TapKitElement, VideoController } from \"@coxwave/tap-kit-types\";\nimport { normalizeCodingRowData } from \"@coxwave/tap-kit-types\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { CreateTapKitOptions, FactoryEventHandlers, TapKitFactoryControl } from \"./types\";\n\n/**\n * Extract event handlers from options\n */\nfunction extractHandlers(options: CreateTapKitOptions): FactoryEventHandlers {\n return {\n onReady: options.onReady,\n onError: options.onError,\n onTimelineSeek: options.onTimelineSeek,\n onAlarmFadeIn: options.onAlarmFadeIn,\n };\n}\n\n/**\n * Create a noop stub for SSR environments\n */\nfunction createSSRStub(): TapKitFactoryControl {\n const noopPromise = Promise.resolve();\n\n return {\n ready: noopPromise,\n isReady: false,\n isOpen: false,\n isMounted: false,\n mount: () => {},\n unmount: () => {},\n show: () => {},\n hide: () => {},\n setCourse: () => {},\n setExternalReferenceId: () => {},\n setExternalReferenceData: () => {},\n events: {\n seekTimeline: () => {},\n onTimelineSeek: () => () => {},\n onAlarmFadeIn: () => () => {},\n },\n video: {\n bind: () => {},\n unbind: () => {},\n },\n sendContext: () => {},\n element: null,\n destroy: () => {},\n };\n}\n\n/**\n * Apply configuration options to TapKit element\n */\nfunction applyOptionsToElement(element: TapKitElement, options: CreateTapKitOptions): void {\n // Required\n element.apiKey = options.apiKey;\n\n // Course info\n if (options.userId !== undefined) element.userId = options.userId;\n if (options.courseId !== undefined) element.courseId = options.courseId;\n if (options.clipId !== undefined) element.clipId = options.clipId;\n if (options.sectionId !== undefined) element.sectionId = options.sectionId;\n if (options.clipPlayHead !== undefined) element.clipPlayHead = options.clipPlayHead;\n if (options.externalReferenceId !== undefined) element.externalReferenceId = options.externalReferenceId;\n if (options.userInputCode !== undefined) element.userInputCode = options.userInputCode;\n if (options.rowDataForCodeEditor !== undefined) element.rowDataForCodeEditor = options.rowDataForCodeEditor;\n\n // Display options\n if (options.mode !== undefined) element.mode = options.mode;\n if (options.chatMode !== undefined) element.chatMode = options.chatMode;\n if (options.language !== undefined) element.language = options.language;\n if (options.allowLayoutToggle !== undefined) element.allowLayoutToggle = options.allowLayoutToggle;\n if (options.buttonId !== undefined) element.buttonId = options.buttonId;\n\n // Environment options\n if (options.apiUrl !== undefined) element.apiUrl = options.apiUrl;\n if (options.tapUrl !== undefined) element.tapUrl = options.tapUrl;\n if (options.environment !== undefined) element.environment = options.environment;\n\n // Video target\n if (options.videoTarget !== undefined) element.videoTarget = options.videoTarget;\n\n // Debug\n if (options.debug !== undefined) element.debug = options.debug;\n}\n\n/**\n * Create TapKit factory instance\n *\n * Creates a <tap-kit> Web Component programmatically and returns\n * a control object for imperative management.\n *\n * **Important for React users:**\n * This API is designed for non-React environments. For React projects,\n * use `useTapKit` hook instead for proper lifecycle management.\n *\n * @param options - Configuration options including apiKey, course info, and event handlers\n * @returns Control object for managing TapKit instance\n *\n * @example\n * ```typescript\n * // Vanilla JavaScript\n * const tapkit = createTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * mode: 'floating',\n * onTimelineSeek: (time, clipId) => {\n * videoElement.currentTime = time;\n * },\n * });\n *\n * // Mount to DOM (required)\n * tapkit.mount();\n *\n * // Wait for ready and show\n * await tapkit.ready;\n * tapkit.show();\n *\n * // Later: cleanup\n * tapkit.destroy();\n * ```\n */\nexport function createTapKit(options: CreateTapKitOptions): TapKitFactoryControl {\n // SSR safety: return noop stub in non-browser environment\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return createSSRStub();\n }\n\n // Normalize rowDataForCodeEditor up front: accepts both `CodingRowData` and\n // JSON string. Throws synchronously on init failure (malformed JSON, missing\n // required fields, or empty values). The normalized object is propagated to\n // the element via `applyOptionsToElement`.\n if (options.rowDataForCodeEditor !== undefined) {\n options = {\n ...options,\n rowDataForCodeEditor: normalizeCodingRowData(options.rowDataForCodeEditor),\n };\n }\n\n // State (closure-based privacy)\n let element: TapKitElement | null = null;\n let isMounted = false;\n let isReady = false;\n let isDestroyed = false;\n\n // Store handlers in mutable reference for access in callbacks\n const handlers = extractHandlers(options);\n\n // Ready promise with resolver\n let readyResolve: () => void;\n let readyReject: (error: Error) => void;\n const readyPromise = new Promise<void>((resolve, reject) => {\n readyResolve = resolve;\n readyReject = reject;\n });\n\n // Queue for calls made before ready\n const pendingCalls: Array<{\n target: \"element\" | \"events\" | \"video\";\n method: string;\n args: unknown[];\n }> = [];\n\n // Cleanup functions for event subscriptions\n const cleanupFns: Array<() => void> = [];\n\n // CDN loading promise\n const cdnPromise = loadCDNLoader().catch((error) => {\n const loadError = error instanceof Error ? error : new Error(\"Failed to load TapKit CDN\");\n readyReject(loadError);\n handlers.onError?.(loadError);\n throw loadError;\n });\n\n /**\n * Create element after CDN is loaded\n */\n async function ensureElement(): Promise<TapKitElement> {\n if (isDestroyed) {\n throw new Error(\"[createTapKit] Instance has been destroyed\");\n }\n\n if (element) {\n return element;\n }\n\n await cdnPromise;\n\n // Create element\n element = document.createElement(\"tap-kit\");\n\n // Apply options\n applyOptionsToElement(element, options);\n\n // Set up event listeners\n element.addEventListener(\"tap-kit:ready\", () => {\n isReady = true;\n readyResolve();\n handlers.onReady?.();\n\n // Replay pending calls\n replayPendingCalls();\n });\n\n element.addEventListener(\"tap-kit:error\", (e) => {\n const customEvent = e as CustomEvent<{ error: Error }>;\n const error = customEvent.detail?.error ?? new Error(\"Unknown error\");\n readyReject(error);\n handlers.onError?.(error);\n });\n\n // Set up EventManager callbacks\n setupEventCallbacks();\n\n return element;\n }\n\n /**\n * Set up EventManager callbacks after element is ready\n */\n function setupEventCallbacks(): void {\n if (!element) return;\n\n // Use element.ready to ensure events API is available\n element.ready\n .then(() => {\n if (!element?.events) return;\n\n // Register stable wrappers and store cleanup functions\n const unsubTimelineSeek = element.events.onTimelineSeek((clipPlayHead, clipId) => {\n handlers.onTimelineSeek?.(clipPlayHead, clipId);\n });\n cleanupFns.push(unsubTimelineSeek);\n\n const unsubAlarmFadeIn = element.events.onAlarmFadeIn((messageInfo) => {\n handlers.onAlarmFadeIn?.(messageInfo);\n });\n cleanupFns.push(unsubAlarmFadeIn);\n })\n .catch(() => {\n // Ignore - error already handled by tap-kit:error event\n });\n }\n\n /**\n * Replay calls that were made before ready\n */\n function replayPendingCalls(): void {\n if (!element || !isReady) return;\n\n for (const call of pendingCalls) {\n try {\n if (call.target === \"element\") {\n (element as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n } else if (call.target === \"events\" && element.events) {\n (element.events as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n } else if (call.target === \"video\" && element.video) {\n (element.video as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n }\n } catch (e) {\n console.warn(`[createTapKit] Failed to replay ${call.method}:`, e);\n }\n }\n\n // Clear queue\n pendingCalls.length = 0;\n }\n\n /**\n * Queue a call to be executed when ready\n */\n function queueCall(target: \"element\" | \"events\" | \"video\", method: string, args: unknown[]): void {\n if (isReady && element) {\n // Execute immediately if ready\n try {\n if (target === \"element\") {\n (element as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n } else if (target === \"events\" && element.events) {\n (element.events as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n } else if (target === \"video\" && element.video) {\n (element.video as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n }\n } catch (e) {\n console.warn(`[createTapKit] Failed to call ${method}:`, e);\n }\n } else {\n // Queue for later\n pendingCalls.push({ target, method, args });\n }\n }\n\n // Create events proxy\n const eventsProxy: EventManager = {\n seekTimeline: (params) => queueCall(\"events\", \"seekTimeline\", [params]),\n onTimelineSeek: (callback) => {\n if (isReady && element?.events) {\n return element.events.onTimelineSeek(callback);\n }\n // Return noop unsubscribe for queued calls\n queueCall(\"events\", \"onTimelineSeek\", [callback]);\n return () => {};\n },\n onAlarmFadeIn: (handler) => {\n if (isReady && element?.events) {\n return element.events.onAlarmFadeIn(handler);\n }\n // Return noop unsubscribe for queued calls\n queueCall(\"events\", \"onAlarmFadeIn\", [handler]);\n return () => {};\n },\n };\n\n // Create video proxy\n const videoProxy: VideoController = {\n bind: (config, clipId) => queueCall(\"video\", \"bind\", [config, clipId]),\n unbind: () => queueCall(\"video\", \"unbind\", []),\n };\n\n // Start element creation (non-blocking)\n ensureElement().catch(() => {\n // Error already handled in ensureElement\n });\n\n // Return control object\n const control: TapKitFactoryControl = {\n get ready() {\n return readyPromise;\n },\n\n get isReady() {\n return isReady;\n },\n\n get isOpen() {\n return element?.isOpen ?? false;\n },\n\n get isMounted() {\n return isMounted;\n },\n\n mount(container: HTMLElement = document.body) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot mount: instance destroyed\");\n return;\n }\n\n // Defensive: already mounted to this container\n if (element && element.parentNode === container) {\n console.warn(\"[createTapKit] Already mounted to this container\");\n return;\n }\n\n // If mounted elsewhere, move to new container\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n }\n\n // Wait for element creation then mount\n ensureElement()\n .then((el) => {\n if (!isDestroyed) {\n container.appendChild(el);\n isMounted = true;\n }\n })\n .catch((error) => {\n console.error(\"[createTapKit] Failed to mount:\", error);\n });\n },\n\n unmount() {\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n isMounted = false;\n }\n },\n\n show() {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot show: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"show\", []);\n },\n\n hide() {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot hide: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"hide\", []);\n },\n\n setCourse(course: Partial<Course> & { courseId: string }) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setCourse: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"setCourse\", [course]);\n },\n\n setExternalReferenceId(id?: string) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setExternalReferenceId: instance destroyed\");\n return;\n }\n // Persist on options so deferred mount picks it up via applyOptionsToElement\n options.externalReferenceId = id;\n if (element) {\n element.externalReferenceId = id;\n }\n },\n\n setExternalReferenceData(data?: string) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setExternalReferenceData: instance destroyed\");\n return;\n }\n options.userInputCode = data;\n if (element) {\n element.userInputCode = data;\n }\n },\n\n sendContext(contextType: string, payload: Record<string, unknown>) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot sendContext: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"sendContext\", [contextType, payload]);\n },\n\n get events() {\n return eventsProxy;\n },\n\n get video() {\n return videoProxy;\n },\n\n get element() {\n return element;\n },\n\n destroy() {\n if (isDestroyed) return;\n\n isDestroyed = true;\n\n // Run cleanup functions (unsubscribe event handlers)\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch {\n // Ignore cleanup errors\n }\n }\n cleanupFns.length = 0;\n\n // Unmount if mounted\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n }\n\n // Clear state\n element = null;\n isMounted = false;\n isReady = false;\n pendingCalls.length = 0;\n },\n };\n\n return control;\n}\n","import type { TapKitConfig, TapKitInitParams, TapKitInstance } from \"@coxwave/tap-kit-types\";\nimport { loadCDNLoader } from \"./loader\";\n\n/**\n * TapKit - Official TapKit Web SDK\n */\nexport class TapKit implements TapKitInstance {\n private instance: TapKitInstance | null = null;\n #loading: Promise<void>;\n #config: TapKitConfig;\n #loadError: Error | null = null;\n #eventsProxy?: TapKitInstance[\"events\"]; // Cached proxy for events API\n #videoProxy?: TapKitInstance[\"video\"]; // Cached proxy for video API\n\n constructor(config: TapKitConfig) {\n this.#config = config;\n this.#loading = this.load();\n }\n\n private async load(): Promise<void> {\n try {\n await loadCDNLoader();\n\n if (!window.TapKit) {\n throw new Error(\"TapKit not available after loading CDN loader\");\n }\n\n this.instance = new window.TapKit(this.#config);\n } catch (err) {\n this.#loadError = err instanceof Error ? err : new Error(String(err));\n throw this.#loadError;\n }\n }\n\n /**\n * Promise that resolves when CDN is loaded and TapKit instance is available\n * (ready to call init())\n *\n * @example\n * ```typescript\n * const kit = new TapKit({ apiKey });\n * await kit.loaded; // CDN downloaded, window.TapKit available\n * await kit.init({...}); // Now safe to call init()\n * ```\n *\n * @see ready - For waiting until AFTER init() completes (iframe initialized)\n */\n get loaded(): Promise<void> {\n return this.#loading.then(() => {\n if (this.#loadError) {\n throw this.#loadError;\n }\n if (!this.instance) {\n throw new Error(\n `TapKit instance not initialized after loading. This may indicate a CDN loading failure for apiKey: ${this.#config.apiKey}`\n );\n }\n });\n }\n\n /**\n * Promise that resolves when SDK is fully initialized\n * (after init() is called and iframe is ready)\n *\n * @example\n * ```typescript\n * const kit = new TapKit({ apiKey });\n * await kit.init({...});\n * await kit.ready; // iframe is fully initialized\n * ```\n *\n * @see loaded - For waiting until CDN load completes (before init)\n */\n get ready(): Promise<void> {\n return this.#loading.then(() => {\n if (this.#loadError) {\n throw this.#loadError;\n }\n if (!this.instance) {\n throw new Error(\n `TapKit instance not initialized after loading. This may indicate a CDN loading failure for apiKey: ${this.#config.apiKey}`\n );\n }\n return this.instance.ready;\n });\n }\n\n /**\n * Create a proxy for video API that waits for ready before delegating\n */\n private createVideoProxy(): TapKitInstance[\"video\"] {\n return {\n bind: (config, clipId) => {\n // Queue the bind call - will execute after ready\n this.ready.then(() => {\n this.instance?.video.bind(config, clipId);\n });\n },\n unbind: () => {\n // Unbind can be called immediately if instance exists\n this.instance?.video?.unbind();\n },\n };\n }\n\n /**\n * Create events API that returns unsubscribe functions synchronously\n * Queues the actual subscription until ready, but returns unsubscribe immediately\n */\n private createEventsProxy(): TapKitInstance[\"events\"] {\n return {\n seekTimeline: (params) => {\n this.ready.then(() => {\n this.instance?.events.seekTimeline(params);\n });\n },\n onTimelineSeek: (callback) => {\n let unsubscribe: (() => void) | undefined;\n let cancelled = false;\n\n // Queue subscription\n this.ready.then(() => {\n if (cancelled) return;\n unsubscribe = this.instance?.events.onTimelineSeek(callback);\n });\n\n // Return synchronous unsubscribe function\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n },\n onAlarmFadeIn: (handler) => {\n let unsubscribe: (() => void) | undefined;\n let cancelled = false;\n\n this.ready.then(() => {\n if (cancelled) return;\n unsubscribe = this.instance?.events.onAlarmFadeIn(handler);\n });\n\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n },\n };\n }\n\n get events() {\n // Cache proxy to avoid creating new object on every access\n if (!this.#eventsProxy) {\n this.#eventsProxy = this.createEventsProxy();\n }\n return this.#eventsProxy;\n }\n\n get isOpen(): boolean {\n return this.instance?.isOpen ?? false;\n }\n\n get isInitialized(): boolean {\n return this.instance?.isInitialized ?? false;\n }\n\n get video() {\n // Cache proxy to avoid creating new object on every access\n if (!this.#videoProxy) {\n this.#videoProxy = this.createVideoProxy();\n }\n return this.#videoProxy;\n }\n\n async init(params: TapKitInitParams): Promise<() => void> {\n await this.#loading;\n if (!this.instance) {\n throw new Error(\"TapKit instance not available after loading\");\n }\n return await this.instance.init(params);\n }\n\n destroy(): void {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n // Clear proxy references to prevent memory leaks\n this.#eventsProxy = undefined;\n this.#videoProxy = undefined;\n }\n\n show(): void {\n if (this.instance) {\n this.instance.show();\n }\n }\n\n hide(): void {\n if (this.instance) {\n this.instance.hide();\n }\n }\n\n setCourse(course: Parameters<TapKitInstance[\"setCourse\"]>[0]): void {\n if (this.instance) {\n this.instance.setCourse(course);\n }\n }\n\n setExternalReferenceId(id?: string): void {\n if (this.instance) {\n this.instance.setExternalReferenceId(id);\n }\n }\n\n setExternalReferenceData(data?: string): void {\n if (this.instance) {\n this.instance.setExternalReferenceData(data);\n }\n }\n\n sendContext(contextType: string, payload: Record<string, unknown>): void {\n if (this.instance) {\n this.instance.sendContext(contextType, payload);\n }\n }\n\n get [Symbol.toStringTag]() {\n return \"TapKit\";\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/loader.ts","../src/factory/createTapKit.ts","../src/kit.ts"],"names":["DEFAULT_CDN_LOADER_URL","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","extractHandlers","options","createSSRStub","applyOptionsToElement","element","createTapKit","normalizeCodingRowData","isMounted","isReady","isDestroyed","handlers","readyResolve","readyReject","readyPromise","pendingCalls","cleanupFns","cdnPromise","error","loadError","ensureElement","replayPendingCalls","e","setupEventCallbacks","unsubTimelineSeek","clipPlayHead","clipId","unsubAlarmFadeIn","messageInfo","unsubExamQuestionNavigate","questionNo","call","queueCall","target","method","args","eventsProxy","params","callback","handler","videoProxy","config","container","el","course","id","data","contextType","payload","cleanup","_loading","_config","_loadError","_eventsProxy","_videoProxy","TapKit","__publicField","__privateAdd","__privateSet","__privateGet","err","unsubscribe","cancelled"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * CDN loader for TapKit\n * Dynamically loads the TapKit SDK from CDN\n *\n * For local testing, you can override the loader URL:\n * window.__TAP_KIT_LOADER_URL__ = '/tap-kit-core/loader.js';\n *\n * For local development (bypass loader, load IIFE directly):\n * window.__TAP_KIT_CORE_URL__ = '/packages/tap-kit-core/dist/index.global.js';\n */\n\n// Build-time constant injected by tsup define\ndeclare const __DEFAULT_CDN_LOADER_URL__: string;\n\nconst DEFAULT_CDN_LOADER_URL =\n typeof __DEFAULT_CDN_LOADER_URL__ !== \"undefined\" ? __DEFAULT_CDN_LOADER_URL__ : \"https://files.edutap.ai/tap-sdk/loader.js\";\nconst DEFAULT_TIMEOUT_MS = 4000; // 4 seconds total timeout\nconst IDLE_CALLBACK_TIMEOUT_MS = 500; // 500ms for requestIdleCallback\n\n/**\n * Get the loader URL from window override or default CDN\n */\nfunction getLoaderURL(): string {\n return window?.__TAP_KIT_LOADER_URL__ ? window.__TAP_KIT_LOADER_URL__ : DEFAULT_CDN_LOADER_URL;\n}\n\n/**\n * Check if local core mode is enabled\n * When __TAP_KIT_CORE_URL__ is set, directly load the IIFE bundle\n * This bypasses the loader.js and loads tap-kit-core directly\n */\nfunction isLocalCoreMode(): boolean {\n return typeof window !== \"undefined\" && !!window.__TAP_KIT_CORE_URL__;\n}\n\n/**\n * Get the local core URL\n */\nfunction getLocalCoreURL(): string {\n return window.__TAP_KIT_CORE_URL__ || \"\";\n}\n\n/**\n * Creates a SDK checker function with timeout and retry logic\n * Uses requestIdleCallback to avoid blocking browser rendering\n * @param resolve - Promise resolve function\n * @param reject - Promise reject function\n * @param timeoutMs - Maximum time to wait for SDK to load (milliseconds)\n * @returns Checker function to be called repeatedly\n */\nfunction createSDKChecker(\n resolve: (value: void | PromiseLike<void>) => void,\n reject: (reason?: unknown) => void,\n timeoutMs: number\n): () => void {\n const startTime = Date.now();\n\n const checkSDK = (): void => {\n // Check if real TapKit is loaded (not just stub)\n // Stub has TapKitLoaded flag set to true by loader.js after real SDK loads\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const elapsed = Date.now() - startTime;\n\n // Check if exceeded timeout\n if (elapsed > timeoutMs) {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`TapKit loader timeout: SDK not available after ${timeoutMs}ms`));\n return;\n }\n\n // Use requestIdleCallback for better performance\n // Falls back to setTimeout if not available\n if (typeof requestIdleCallback !== \"undefined\") {\n requestIdleCallback(checkSDK, { timeout: IDLE_CALLBACK_TIMEOUT_MS });\n } else {\n setTimeout(checkSDK, IDLE_CALLBACK_TIMEOUT_MS);\n }\n };\n\n return checkSDK;\n}\n\n/**\n * Loads the CDN loader script\n * The loader will then fetch versions.json and load the appropriate SDK version\n *\n * If __TAP_KIT_CORE_URL__ is set, bypasses loader and loads IIFE directly\n *\n * @param timeoutMs - Maximum time to wait for SDK to load (default: 4000ms)\n * @returns Promise that resolves when SDK is loaded\n * @throws {Error} If loader fails to load or times out\n */\nexport function loadCDNLoader(timeoutMs: number = DEFAULT_TIMEOUT_MS): Promise<void> {\n // If already loaded, return immediately\n if (window.__TAP_KIT_LOADER_LOADED__ && window.TapKit) {\n return Promise.resolve();\n }\n\n // If currently loading, return the existing promise\n if (window.__TAP_KIT_LOADER_LOADING__) {\n return window.__TAP_KIT_LOADER_LOADING__;\n }\n\n // Create loading promise\n const loadingPromise = new Promise<void>((resolve, reject) => {\n if (typeof document === \"undefined\") {\n reject(new Error(\"TapKit requires browser environment (document is undefined)\"));\n return;\n }\n\n // Local core mode: Load IIFE directly\n if (isLocalCoreMode()) {\n // Check if TapKit is already loaded (from other pages in playground)\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const coreURL = getLocalCoreURL();\n\n const script = document.createElement(\"script\");\n script.src = coreURL;\n script.async = true;\n\n script.onload = () => {\n // IIFE directly sets window.TapKit\n // Set the loaded flag manually since we bypass loader.js\n if (window.TapKit) {\n window.TapKitLoaded = true;\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n } else {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(\"TapKit not available after loading local core\"));\n }\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load local TapKit core: ${coreURL}`));\n };\n\n document.head.appendChild(script);\n return;\n }\n\n // CDN mode: Load loader.js\n const loaderURL = getLoaderURL();\n const script = document.createElement(\"script\");\n script.src = loaderURL;\n script.async = true;\n\n script.onload = () => {\n // The loader script will load the actual SDK\n // We need to wait a bit for the loader to fetch and load the SDK\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`));\n };\n\n // Check if script already exists\n const existingScript = document.querySelector(`script[src=\"${loaderURL}\"]`);\n\n if (existingScript) {\n // Script already added but not yet loaded\n existingScript.addEventListener(\"load\", () => {\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n });\n existingScript.addEventListener(\"error\", () => reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)));\n } else {\n document.head.appendChild(script);\n }\n });\n\n window.__TAP_KIT_LOADER_LOADING__ = loadingPromise;\n return loadingPromise;\n}\n","/**\n * createTapKit - Factory API for TapKit\n *\n * Non-React, framework-agnostic API with full configuration support.\n * Wraps the <tap-kit> Web Component with imperative control.\n *\n * @example\n * ```typescript\n * import { createTapKit } from '@coxwave/tap-kit';\n *\n * const tapkit = createTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * apiUrl: 'https://tapapistaging.coxwave.link',\n * onReady: () => console.log('Ready!'),\n * });\n *\n * tapkit.mount();\n * await tapkit.ready;\n * tapkit.show();\n * ```\n *\n * @see useTapKit for React projects (recommended)\n */\n\nimport type { Course, EventManager, TapKitElement, VideoController } from \"@coxwave/tap-kit-types\";\nimport { normalizeCodingRowData } from \"@coxwave/tap-kit-types\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { CreateTapKitOptions, FactoryEventHandlers, TapKitFactoryControl } from \"./types\";\n\n/**\n * Extract event handlers from options\n */\nfunction extractHandlers(options: CreateTapKitOptions): FactoryEventHandlers {\n return {\n onReady: options.onReady,\n onError: options.onError,\n onTimelineSeek: options.onTimelineSeek,\n onAlarmFadeIn: options.onAlarmFadeIn,\n onExamQuestionNavigate: options.onExamQuestionNavigate,\n };\n}\n\n/**\n * Create a noop stub for SSR environments\n */\nfunction createSSRStub(): TapKitFactoryControl {\n const noopPromise = Promise.resolve();\n\n return {\n ready: noopPromise,\n isReady: false,\n isOpen: false,\n isMounted: false,\n mount: () => {},\n unmount: () => {},\n show: () => {},\n hide: () => {},\n setCourse: () => {},\n setExternalReferenceId: () => {},\n setExternalReferenceData: () => {},\n events: {\n seekTimeline: () => {},\n onTimelineSeek: () => () => {},\n onAlarmFadeIn: () => () => {},\n onExamQuestionNavigate: () => () => {},\n },\n video: {\n bind: () => {},\n unbind: () => {},\n },\n sendContext: () => {},\n element: null,\n destroy: () => {},\n };\n}\n\n/**\n * Apply configuration options to TapKit element\n */\nfunction applyOptionsToElement(element: TapKitElement, options: CreateTapKitOptions): void {\n // Required\n element.apiKey = options.apiKey;\n\n // Course info\n if (options.userId !== undefined) element.userId = options.userId;\n if (options.courseId !== undefined) element.courseId = options.courseId;\n if (options.clipId !== undefined) element.clipId = options.clipId;\n if (options.sectionId !== undefined) element.sectionId = options.sectionId;\n if (options.clipPlayHead !== undefined) element.clipPlayHead = options.clipPlayHead;\n if (options.externalReferenceId !== undefined) element.externalReferenceId = options.externalReferenceId;\n if (options.userInputCode !== undefined) element.userInputCode = options.userInputCode;\n if (options.rowDataForCodeEditor !== undefined) element.rowDataForCodeEditor = options.rowDataForCodeEditor;\n\n // Display options\n if (options.mode !== undefined) element.mode = options.mode;\n if (options.chatMode !== undefined) element.chatMode = options.chatMode;\n if (options.examSkill !== undefined) element.examSkill = options.examSkill;\n if (options.language !== undefined) element.language = options.language;\n if (options.allowLayoutToggle !== undefined) element.allowLayoutToggle = options.allowLayoutToggle;\n if (options.buttonId !== undefined) element.buttonId = options.buttonId;\n\n // Environment options\n if (options.apiUrl !== undefined) element.apiUrl = options.apiUrl;\n if (options.tapUrl !== undefined) element.tapUrl = options.tapUrl;\n if (options.environment !== undefined) element.environment = options.environment;\n\n // Video target\n if (options.videoTarget !== undefined) element.videoTarget = options.videoTarget;\n\n // Debug\n if (options.debug !== undefined) element.debug = options.debug;\n}\n\n/**\n * Create TapKit factory instance\n *\n * Creates a <tap-kit> Web Component programmatically and returns\n * a control object for imperative management.\n *\n * **Important for React users:**\n * This API is designed for non-React environments. For React projects,\n * use `useTapKit` hook instead for proper lifecycle management.\n *\n * @param options - Configuration options including apiKey, course info, and event handlers\n * @returns Control object for managing TapKit instance\n *\n * @example\n * ```typescript\n * // Vanilla JavaScript\n * const tapkit = createTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * mode: 'floating',\n * onTimelineSeek: (time, clipId) => {\n * videoElement.currentTime = time;\n * },\n * });\n *\n * // Mount to DOM (required)\n * tapkit.mount();\n *\n * // Wait for ready and show\n * await tapkit.ready;\n * tapkit.show();\n *\n * // Later: cleanup\n * tapkit.destroy();\n * ```\n */\nexport function createTapKit(options: CreateTapKitOptions): TapKitFactoryControl {\n // SSR safety: return noop stub in non-browser environment\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return createSSRStub();\n }\n\n // Normalize rowDataForCodeEditor up front: accepts both `CodingRowData` and\n // JSON string. Throws synchronously on init failure (malformed JSON, missing\n // required fields, or empty values). The normalized object is propagated to\n // the element via `applyOptionsToElement`.\n if (options.rowDataForCodeEditor !== undefined) {\n options = {\n ...options,\n rowDataForCodeEditor: normalizeCodingRowData(options.rowDataForCodeEditor),\n };\n }\n\n // State (closure-based privacy)\n let element: TapKitElement | null = null;\n let isMounted = false;\n let isReady = false;\n let isDestroyed = false;\n\n // Store handlers in mutable reference for access in callbacks\n const handlers = extractHandlers(options);\n\n // Ready promise with resolver\n let readyResolve: () => void;\n let readyReject: (error: Error) => void;\n const readyPromise = new Promise<void>((resolve, reject) => {\n readyResolve = resolve;\n readyReject = reject;\n });\n\n // Queue for calls made before ready\n const pendingCalls: Array<{\n target: \"element\" | \"events\" | \"video\";\n method: string;\n args: unknown[];\n }> = [];\n\n // Cleanup functions for event subscriptions\n const cleanupFns: Array<() => void> = [];\n\n // CDN loading promise\n const cdnPromise = loadCDNLoader().catch((error) => {\n const loadError = error instanceof Error ? error : new Error(\"Failed to load TapKit CDN\");\n readyReject(loadError);\n handlers.onError?.(loadError);\n throw loadError;\n });\n\n /**\n * Create element after CDN is loaded\n */\n async function ensureElement(): Promise<TapKitElement> {\n if (isDestroyed) {\n throw new Error(\"[createTapKit] Instance has been destroyed\");\n }\n\n if (element) {\n return element;\n }\n\n await cdnPromise;\n\n // Create element\n element = document.createElement(\"tap-kit\");\n\n // Apply options\n applyOptionsToElement(element, options);\n\n // Set up event listeners\n element.addEventListener(\"tap-kit:ready\", () => {\n isReady = true;\n readyResolve();\n handlers.onReady?.();\n\n // Replay pending calls\n replayPendingCalls();\n });\n\n element.addEventListener(\"tap-kit:error\", (e) => {\n const customEvent = e as CustomEvent<{ error: Error }>;\n const error = customEvent.detail?.error ?? new Error(\"Unknown error\");\n readyReject(error);\n handlers.onError?.(error);\n });\n\n // Set up EventManager callbacks\n setupEventCallbacks();\n\n return element;\n }\n\n /**\n * Set up EventManager callbacks after element is ready\n */\n function setupEventCallbacks(): void {\n if (!element) return;\n\n // Use element.ready to ensure events API is available\n element.ready\n .then(() => {\n if (!element?.events) return;\n\n // Register stable wrappers and store cleanup functions\n const unsubTimelineSeek = element.events.onTimelineSeek((clipPlayHead, clipId) => {\n handlers.onTimelineSeek?.(clipPlayHead, clipId);\n });\n cleanupFns.push(unsubTimelineSeek);\n\n const unsubAlarmFadeIn = element.events.onAlarmFadeIn((messageInfo) => {\n handlers.onAlarmFadeIn?.(messageInfo);\n });\n cleanupFns.push(unsubAlarmFadeIn);\n\n const unsubExamQuestionNavigate = element.events.onExamQuestionNavigate((clipId, questionNo) => {\n handlers.onExamQuestionNavigate?.(clipId, questionNo);\n });\n cleanupFns.push(unsubExamQuestionNavigate);\n })\n .catch(() => {\n // Ignore - error already handled by tap-kit:error event\n });\n }\n\n /**\n * Replay calls that were made before ready\n */\n function replayPendingCalls(): void {\n if (!element || !isReady) return;\n\n for (const call of pendingCalls) {\n try {\n if (call.target === \"element\") {\n (element as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n } else if (call.target === \"events\" && element.events) {\n (element.events as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n } else if (call.target === \"video\" && element.video) {\n (element.video as unknown as Record<string, (...args: unknown[]) => void>)[call.method]?.(...call.args);\n }\n } catch (e) {\n console.warn(`[createTapKit] Failed to replay ${call.method}:`, e);\n }\n }\n\n // Clear queue\n pendingCalls.length = 0;\n }\n\n /**\n * Queue a call to be executed when ready\n */\n function queueCall(target: \"element\" | \"events\" | \"video\", method: string, args: unknown[]): void {\n if (isReady && element) {\n // Execute immediately if ready\n try {\n if (target === \"element\") {\n (element as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n } else if (target === \"events\" && element.events) {\n (element.events as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n } else if (target === \"video\" && element.video) {\n (element.video as unknown as Record<string, (...args: unknown[]) => void>)[method]?.(...args);\n }\n } catch (e) {\n console.warn(`[createTapKit] Failed to call ${method}:`, e);\n }\n } else {\n // Queue for later\n pendingCalls.push({ target, method, args });\n }\n }\n\n // Create events proxy\n const eventsProxy: EventManager = {\n seekTimeline: (params) => queueCall(\"events\", \"seekTimeline\", [params]),\n onTimelineSeek: (callback) => {\n if (isReady && element?.events) {\n return element.events.onTimelineSeek(callback);\n }\n // Return noop unsubscribe for queued calls\n queueCall(\"events\", \"onTimelineSeek\", [callback]);\n return () => {};\n },\n onAlarmFadeIn: (handler) => {\n if (isReady && element?.events) {\n return element.events.onAlarmFadeIn(handler);\n }\n // Return noop unsubscribe for queued calls\n queueCall(\"events\", \"onAlarmFadeIn\", [handler]);\n return () => {};\n },\n onExamQuestionNavigate: (handler) => {\n if (isReady && element?.events) {\n return element.events.onExamQuestionNavigate(handler);\n }\n // Return noop unsubscribe for queued calls\n queueCall(\"events\", \"onExamQuestionNavigate\", [handler]);\n return () => {};\n },\n };\n\n // Create video proxy\n const videoProxy: VideoController = {\n bind: (config, clipId) => queueCall(\"video\", \"bind\", [config, clipId]),\n unbind: () => queueCall(\"video\", \"unbind\", []),\n };\n\n // Start element creation (non-blocking)\n ensureElement().catch(() => {\n // Error already handled in ensureElement\n });\n\n // Return control object\n const control: TapKitFactoryControl = {\n get ready() {\n return readyPromise;\n },\n\n get isReady() {\n return isReady;\n },\n\n get isOpen() {\n return element?.isOpen ?? false;\n },\n\n get isMounted() {\n return isMounted;\n },\n\n mount(container: HTMLElement = document.body) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot mount: instance destroyed\");\n return;\n }\n\n // Defensive: already mounted to this container\n if (element && element.parentNode === container) {\n console.warn(\"[createTapKit] Already mounted to this container\");\n return;\n }\n\n // If mounted elsewhere, move to new container\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n }\n\n // Wait for element creation then mount\n ensureElement()\n .then((el) => {\n if (!isDestroyed) {\n container.appendChild(el);\n isMounted = true;\n }\n })\n .catch((error) => {\n console.error(\"[createTapKit] Failed to mount:\", error);\n });\n },\n\n unmount() {\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n isMounted = false;\n }\n },\n\n show() {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot show: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"show\", []);\n },\n\n hide() {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot hide: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"hide\", []);\n },\n\n setCourse(course: Partial<Course> & { courseId: string }) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setCourse: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"setCourse\", [course]);\n },\n\n setExternalReferenceId(id?: string) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setExternalReferenceId: instance destroyed\");\n return;\n }\n // Persist on options so deferred mount picks it up via applyOptionsToElement\n options.externalReferenceId = id;\n if (element) {\n element.externalReferenceId = id;\n }\n },\n\n setExternalReferenceData(data?: string) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot setExternalReferenceData: instance destroyed\");\n return;\n }\n options.userInputCode = data;\n if (element) {\n element.userInputCode = data;\n }\n },\n\n sendContext(contextType: string, payload: Record<string, unknown>) {\n if (isDestroyed) {\n console.warn(\"[createTapKit] Cannot sendContext: instance destroyed\");\n return;\n }\n queueCall(\"element\", \"sendContext\", [contextType, payload]);\n },\n\n get events() {\n return eventsProxy;\n },\n\n get video() {\n return videoProxy;\n },\n\n get element() {\n return element;\n },\n\n destroy() {\n if (isDestroyed) return;\n\n isDestroyed = true;\n\n // Run cleanup functions (unsubscribe event handlers)\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch {\n // Ignore cleanup errors\n }\n }\n cleanupFns.length = 0;\n\n // Unmount if mounted\n if (element?.parentNode) {\n element.parentNode.removeChild(element);\n }\n\n // Clear state\n element = null;\n isMounted = false;\n isReady = false;\n pendingCalls.length = 0;\n },\n };\n\n return control;\n}\n","import type { TapKitConfig, TapKitInitParams, TapKitInstance } from \"@coxwave/tap-kit-types\";\nimport { loadCDNLoader } from \"./loader\";\n\n/**\n * TapKit - Official TapKit Web SDK\n */\nexport class TapKit implements TapKitInstance {\n private instance: TapKitInstance | null = null;\n #loading: Promise<void>;\n #config: TapKitConfig;\n #loadError: Error | null = null;\n #eventsProxy?: TapKitInstance[\"events\"]; // Cached proxy for events API\n #videoProxy?: TapKitInstance[\"video\"]; // Cached proxy for video API\n\n constructor(config: TapKitConfig) {\n this.#config = config;\n this.#loading = this.load();\n }\n\n private async load(): Promise<void> {\n try {\n await loadCDNLoader();\n\n if (!window.TapKit) {\n throw new Error(\"TapKit not available after loading CDN loader\");\n }\n\n this.instance = new window.TapKit(this.#config);\n } catch (err) {\n this.#loadError = err instanceof Error ? err : new Error(String(err));\n throw this.#loadError;\n }\n }\n\n /**\n * Promise that resolves when CDN is loaded and TapKit instance is available\n * (ready to call init())\n *\n * @example\n * ```typescript\n * const kit = new TapKit({ apiKey });\n * await kit.loaded; // CDN downloaded, window.TapKit available\n * await kit.init({...}); // Now safe to call init()\n * ```\n *\n * @see ready - For waiting until AFTER init() completes (iframe initialized)\n */\n get loaded(): Promise<void> {\n return this.#loading.then(() => {\n if (this.#loadError) {\n throw this.#loadError;\n }\n if (!this.instance) {\n throw new Error(\n `TapKit instance not initialized after loading. This may indicate a CDN loading failure for apiKey: ${this.#config.apiKey}`\n );\n }\n });\n }\n\n /**\n * Promise that resolves when SDK is fully initialized\n * (after init() is called and iframe is ready)\n *\n * @example\n * ```typescript\n * const kit = new TapKit({ apiKey });\n * await kit.init({...});\n * await kit.ready; // iframe is fully initialized\n * ```\n *\n * @see loaded - For waiting until CDN load completes (before init)\n */\n get ready(): Promise<void> {\n return this.#loading.then(() => {\n if (this.#loadError) {\n throw this.#loadError;\n }\n if (!this.instance) {\n throw new Error(\n `TapKit instance not initialized after loading. This may indicate a CDN loading failure for apiKey: ${this.#config.apiKey}`\n );\n }\n return this.instance.ready;\n });\n }\n\n /**\n * Create a proxy for video API that waits for ready before delegating\n */\n private createVideoProxy(): TapKitInstance[\"video\"] {\n return {\n bind: (config, clipId) => {\n // Queue the bind call - will execute after ready\n this.ready.then(() => {\n this.instance?.video.bind(config, clipId);\n });\n },\n unbind: () => {\n // Unbind can be called immediately if instance exists\n this.instance?.video?.unbind();\n },\n };\n }\n\n /**\n * Create events API that returns unsubscribe functions synchronously\n * Queues the actual subscription until ready, but returns unsubscribe immediately\n */\n private createEventsProxy(): TapKitInstance[\"events\"] {\n return {\n seekTimeline: (params) => {\n this.ready.then(() => {\n this.instance?.events.seekTimeline(params);\n });\n },\n onTimelineSeek: (callback) => {\n let unsubscribe: (() => void) | undefined;\n let cancelled = false;\n\n // Queue subscription\n this.ready.then(() => {\n if (cancelled) return;\n unsubscribe = this.instance?.events.onTimelineSeek(callback);\n });\n\n // Return synchronous unsubscribe function\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n },\n onAlarmFadeIn: (handler) => {\n let unsubscribe: (() => void) | undefined;\n let cancelled = false;\n\n this.ready.then(() => {\n if (cancelled) return;\n unsubscribe = this.instance?.events.onAlarmFadeIn(handler);\n });\n\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n },\n onExamQuestionNavigate: (handler) => {\n let unsubscribe: (() => void) | undefined;\n let cancelled = false;\n\n this.ready.then(() => {\n if (cancelled) return;\n unsubscribe = this.instance?.events.onExamQuestionNavigate(handler);\n });\n\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n },\n };\n }\n\n get events() {\n // Cache proxy to avoid creating new object on every access\n if (!this.#eventsProxy) {\n this.#eventsProxy = this.createEventsProxy();\n }\n return this.#eventsProxy;\n }\n\n get isOpen(): boolean {\n return this.instance?.isOpen ?? false;\n }\n\n get isInitialized(): boolean {\n return this.instance?.isInitialized ?? false;\n }\n\n get video() {\n // Cache proxy to avoid creating new object on every access\n if (!this.#videoProxy) {\n this.#videoProxy = this.createVideoProxy();\n }\n return this.#videoProxy;\n }\n\n async init(params: TapKitInitParams): Promise<() => void> {\n await this.#loading;\n if (!this.instance) {\n throw new Error(\"TapKit instance not available after loading\");\n }\n return await this.instance.init(params);\n }\n\n destroy(): void {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n // Clear proxy references to prevent memory leaks\n this.#eventsProxy = undefined;\n this.#videoProxy = undefined;\n }\n\n show(): void {\n if (this.instance) {\n this.instance.show();\n }\n }\n\n hide(): void {\n if (this.instance) {\n this.instance.hide();\n }\n }\n\n setCourse(course: Parameters<TapKitInstance[\"setCourse\"]>[0]): void {\n if (this.instance) {\n this.instance.setCourse(course);\n }\n }\n\n setExternalReferenceId(id?: string): void {\n if (this.instance) {\n this.instance.setExternalReferenceId(id);\n }\n }\n\n setExternalReferenceData(data?: string): void {\n if (this.instance) {\n this.instance.setExternalReferenceData(data);\n }\n }\n\n sendContext(contextType: string, payload: Record<string, unknown>): void {\n if (this.instance) {\n this.instance.sendContext(contextType, payload);\n }\n }\n\n get [Symbol.toStringTag]() {\n return \"TapKit\";\n }\n}\n"]}
|
package/dist/react.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TapKitElement, AlarmMessageInstanceType, CodingRowDataInput, ChatMode, VideoPlayerConfig, UserInputCodeAdapter, GradingOption } from '@coxwave/tap-kit-types';
|
|
1
|
+
import { TapKitElement, AlarmMessageInstanceType, CodingRowDataInput, ChatMode, TapExamSkill, VideoPlayerConfig, UserInputCodeAdapter, GradingOption } from '@coxwave/tap-kit-types';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -44,6 +44,12 @@ interface TapKitEventHandlers {
|
|
|
44
44
|
* @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}
|
|
45
45
|
*/
|
|
46
46
|
onAlarmFadeIn?: (messageInfo: AlarmMessageInstanceType) => void;
|
|
47
|
+
/**
|
|
48
|
+
* 학습자가 취약점 진단의 근거 문항을 눌렀을 때 (EXAM 모드, 디지틀조선 TOEFL 첨삭).
|
|
49
|
+
* 호스트 시험 플레이어를 `clipId`(= 채점 API의 `problem_id`) 문항으로 옮기면 된다.
|
|
50
|
+
* @example onExamQuestionNavigate={(clipId) => goToQuestion(clipId)}
|
|
51
|
+
*/
|
|
52
|
+
onExamQuestionNavigate?: (clipId: string, questionNo?: number) => void;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Control object pattern for managing TapKit instance
|
|
@@ -171,6 +177,11 @@ interface UseTapKitOptions extends TapKitEventHandlers {
|
|
|
171
177
|
mode?: "inline" | "floating" | "sidebar";
|
|
172
178
|
/** Chat interaction mode (staging feature: per-message override) */
|
|
173
179
|
chatMode?: ChatMode;
|
|
180
|
+
/**
|
|
181
|
+
* 호스트 시험 플레이어의 현재 섹션 (Writing / Speaking).
|
|
182
|
+
* 취약점 진단 결과의 탭이 이 값에 따라 초기값·동기화된다 (ClickUp z90uw3mm72).
|
|
183
|
+
*/
|
|
184
|
+
examSkill?: TapExamSkill | null;
|
|
174
185
|
/**
|
|
175
186
|
* Allow user to toggle between floating and sidebar layouts
|
|
176
187
|
* Only applies when mode is "floating" or "sidebar"
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TapKitElement, AlarmMessageInstanceType, CodingRowDataInput, ChatMode, VideoPlayerConfig, UserInputCodeAdapter, GradingOption } from '@coxwave/tap-kit-types';
|
|
1
|
+
import { TapKitElement, AlarmMessageInstanceType, CodingRowDataInput, ChatMode, TapExamSkill, VideoPlayerConfig, UserInputCodeAdapter, GradingOption } from '@coxwave/tap-kit-types';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -44,6 +44,12 @@ interface TapKitEventHandlers {
|
|
|
44
44
|
* @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}
|
|
45
45
|
*/
|
|
46
46
|
onAlarmFadeIn?: (messageInfo: AlarmMessageInstanceType) => void;
|
|
47
|
+
/**
|
|
48
|
+
* 학습자가 취약점 진단의 근거 문항을 눌렀을 때 (EXAM 모드, 디지틀조선 TOEFL 첨삭).
|
|
49
|
+
* 호스트 시험 플레이어를 `clipId`(= 채점 API의 `problem_id`) 문항으로 옮기면 된다.
|
|
50
|
+
* @example onExamQuestionNavigate={(clipId) => goToQuestion(clipId)}
|
|
51
|
+
*/
|
|
52
|
+
onExamQuestionNavigate?: (clipId: string, questionNo?: number) => void;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Control object pattern for managing TapKit instance
|
|
@@ -171,6 +177,11 @@ interface UseTapKitOptions extends TapKitEventHandlers {
|
|
|
171
177
|
mode?: "inline" | "floating" | "sidebar";
|
|
172
178
|
/** Chat interaction mode (staging feature: per-message override) */
|
|
173
179
|
chatMode?: ChatMode;
|
|
180
|
+
/**
|
|
181
|
+
* 호스트 시험 플레이어의 현재 섹션 (Writing / Speaking).
|
|
182
|
+
* 취약점 진단 결과의 탭이 이 값에 따라 초기값·동기화된다 (ClickUp z90uw3mm72).
|
|
183
|
+
*/
|
|
184
|
+
examSkill?: TapExamSkill | null;
|
|
174
185
|
/**
|
|
175
186
|
* Allow user to toggle between floating and sidebar layouts
|
|
176
187
|
* Only applies when mode is "floating" or "sidebar"
|
package/dist/react.js
CHANGED
|
@@ -125,6 +125,7 @@ var TapKit = React__default.default.forwardRef(function TapKit2({ control, ...ht
|
|
|
125
125
|
"button-id": options.buttonId,
|
|
126
126
|
mode: options.mode,
|
|
127
127
|
"chat-mode": options.chatMode,
|
|
128
|
+
"exam-skill": options.examSkill ?? void 0,
|
|
128
129
|
debug: options.debug,
|
|
129
130
|
"tap-url": options.tapUrl,
|
|
130
131
|
"api-url": options.apiUrl,
|
|
@@ -244,7 +245,8 @@ function extractHandlers(options) {
|
|
|
244
245
|
onReady: options.onReady,
|
|
245
246
|
onError: options.onError,
|
|
246
247
|
onTimelineSeek: options.onTimelineSeek,
|
|
247
|
-
onAlarmFadeIn: options.onAlarmFadeIn
|
|
248
|
+
onAlarmFadeIn: options.onAlarmFadeIn,
|
|
249
|
+
onExamQuestionNavigate: options.onExamQuestionNavigate
|
|
248
250
|
};
|
|
249
251
|
}
|
|
250
252
|
function useTapKit(options) {
|
|
@@ -297,6 +299,9 @@ function useTapKit(options) {
|
|
|
297
299
|
instance.events.onAlarmFadeIn((messageInfo) => {
|
|
298
300
|
handlersRef.current.onAlarmFadeIn?.(messageInfo);
|
|
299
301
|
});
|
|
302
|
+
instance.events.onExamQuestionNavigate((clipId, questionNo) => {
|
|
303
|
+
handlersRef.current.onExamQuestionNavigate?.(clipId, questionNo);
|
|
304
|
+
});
|
|
300
305
|
}
|
|
301
306
|
if (pendingVideoBind.current && instance.video) {
|
|
302
307
|
instance.video.bind(pendingVideoBind.current.adapter, pendingVideoBind.current.clipId);
|
|
@@ -329,6 +334,7 @@ function useTapKit(options) {
|
|
|
329
334
|
buttonId: options.buttonId,
|
|
330
335
|
mode: options.mode,
|
|
331
336
|
chatMode: options.chatMode,
|
|
337
|
+
examSkill: options.examSkill,
|
|
332
338
|
allowLayoutToggle: options.allowLayoutToggle,
|
|
333
339
|
videoTarget: options.videoTarget,
|
|
334
340
|
userInputCodeTarget: options.userInputCodeTarget,
|
|
@@ -353,6 +359,7 @@ function useTapKit(options) {
|
|
|
353
359
|
options.buttonId,
|
|
354
360
|
options.mode,
|
|
355
361
|
options.chatMode,
|
|
362
|
+
options.examSkill,
|
|
356
363
|
options.allowLayoutToggle,
|
|
357
364
|
options.videoTarget,
|
|
358
365
|
options.userInputCodeTarget,
|
|
@@ -368,7 +375,8 @@ function useTapKit(options) {
|
|
|
368
375
|
onReady: handlersRef.current.onReady,
|
|
369
376
|
onError: handlersRef.current.onError,
|
|
370
377
|
onTimelineSeek: handlersRef.current.onTimelineSeek,
|
|
371
|
-
onAlarmFadeIn: handlersRef.current.onAlarmFadeIn
|
|
378
|
+
onAlarmFadeIn: handlersRef.current.onAlarmFadeIn,
|
|
379
|
+
onExamQuestionNavigate: handlersRef.current.onExamQuestionNavigate
|
|
372
380
|
}),
|
|
373
381
|
[]
|
|
374
382
|
);
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/types.ts","../src/react/TapKit.tsx","../src/loader.ts","../src/react/useTapKit.ts"],"names":["React","TapKit","useRef","useImperativeHandle","useCallback","useEffect","jsx","script","useMemo","normalizeCodingRowData","useState"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React-specific type definitions for TapKit\n *\n * Defines event handler types and control patterns for React integration.\n */\n\nimport type { AlarmMessageInstanceType, TapKitElement } from \"@coxwave/tap-kit-types\";\n\n/**\n * Video player adapter interface for timeline synchronization\n *\n * Supports custom video players (YouTube, Vimeo, etc.) that don't use HTMLVideoElement.\n */\nexport interface VideoAdapter {\n /** Get current playback time in seconds */\n getCurrentTime: () => number;\n /** Set playback time in seconds */\n setCurrentTime: (time: number, clipId?: string) => void;\n}\n\n/**\n * Event handler type definitions for TapKit Web Component\n *\n * Maps CustomEvent types to React callback signatures.\n */\nexport interface TapKitEventHandlers {\n /**\n * Called when TapKit SDK is ready\n * @example onReady={() => console.log('TapKit ready!')}\n */\n onReady?: () => void;\n\n /**\n * Called when initialization or runtime error occurs\n * @example onError={(error) => console.error('TapKit error:', error)}\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when timeline seek event occurs\n * @example onTimelineSeek={(clipPlayHead, clipId) => console.log('Seek:', clipPlayHead)}\n */\n onTimelineSeek?: (clipPlayHead: number, clipId: string) => void;\n\n /**\n * Called when alarm fade-in occurs\n * @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}\n */\n onAlarmFadeIn?: (messageInfo: AlarmMessageInstanceType) => void;\n}\n\n/**\n * Control object pattern for managing TapKit instance\n *\n * Separates instance management (setInstance) from configuration (options)\n * and event handling (handlers). Inspired by ChatKit's control pattern.\n *\n * @example\n * ```tsx\n * const tapkit = useTapKit({ apiKey: 'key', onReady: () => {} });\n * <TapKit control={tapkit.control} />\n * ```\n */\nexport interface TapKitControl<T> {\n /**\n * Set the TapKit element instance reference\n *\n * Called by TapKit component to register the element instance.\n * Enables imperative control via useTapKit methods.\n */\n setInstance: (instance: TapKitElement | null) => void;\n\n /**\n * Configuration options (non-function values)\n *\n * All options except event handlers.\n */\n options: T;\n\n /**\n * Event handler callbacks (function values)\n *\n * Separated from options for easier event listener management.\n */\n handlers: TapKitEventHandlers;\n\n /**\n * Whether CDN is loaded and Web Component is available\n *\n * @internal Used by TapKit component to delay rendering\n */\n isCdnLoaded: boolean;\n}\n\n/**\n * Map of TapKit event names to their handler keys\n *\n * Used for automatic event listener binding in TapKit component.\n *\n * @internal\n */\nexport const EVENT_HANDLER_MAP = {\n \"tap-kit:ready\": \"onReady\",\n \"tap-kit:error\": \"onError\",\n} as const;\n\n/**\n * List of TapKit event names\n *\n * @internal\n */\nexport const EVENT_NAMES = Object.keys(EVENT_HANDLER_MAP) as (keyof typeof EVENT_HANDLER_MAP)[];\n","/**\n * TapKit React Component\n *\n * Declarative React wrapper for <tap-kit> Web Component.\n * Use this with useTapKit hook for a clean separation of control and rendering.\n *\n * @example\n * ```tsx\n * 'use client';\n *\n * import { TapKit, useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyApp() {\n * const tapkit = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * onReady: () => console.log('TapKit ready!'),\n * onError: (error) => console.error('Error:', error),\n * });\n *\n * return (\n * <div>\n * <button onClick={tapkit.show} disabled={!tapkit.isReady}>\n * Show Chat\n * </button>\n * <TapKit control={tapkit.control} style={{ height: '600px' }} />\n * </div>\n * );\n * }\n * ```\n *\n * **Note for Next.js App Router users:**\n * Make sure to add 'use client' at the top of your component file.\n *\n * @see https://edutap-ai-docs.vercel.app/docs/guides/react\n */\n\nimport type { TapKitElement } from \"@coxwave/tap-kit-types\";\nimport React, { useCallback, useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { TapKitControl } from \"./types\";\nimport { EVENT_HANDLER_MAP, EVENT_NAMES } from \"./types\";\nimport type { TapKitOptions } from \"./useTapKit\";\n\n/**\n * Props for TapKit React component\n */\nexport interface TapKitProps extends Omit<React.HTMLAttributes<TapKitElement>, \"children\" | \"dangerouslySetInnerHTML\"> {\n /**\n * Control object from useTapKit hook\n *\n * Provides instance management, configuration, and event handlers.\n */\n control: TapKitControl<TapKitOptions>;\n}\n\n// Note: JSX IntrinsicElements augmentation is provided by @coxwave/tap-kit-types\n// See: packages/tap-kit-types/src/tap-kit.d.ts\n\n/**\n * TapKit React Component\n *\n * Renders <tap-kit> Web Component with React integration.\n * Automatically manages instance lifecycle and event listeners.\n *\n * **Ref Access**: The forwarded ref provides direct access to the TapKitElement:\n * ```tsx\n * const tapkitRef = useRef<TapKitElement>(null);\n * <TapKit ref={tapkitRef} control={...} />\n * // tapkitRef.current?.show()\n * ```\n */\nexport const TapKit = React.forwardRef<TapKitElement, TapKitProps>(function TapKit({ control, ...htmlProps }, forwardedRef) {\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Forward TapKitElement to parent\n useImperativeHandle(forwardedRef, () => elementRef.current as TapKitElement, []);\n\n // Callback ref to register element when mounted\n const setElementRef = useCallback(\n (element: TapKitElement | null) => {\n elementRef.current = element;\n\n if (element) {\n // Set apiKey as property (not attribute) - required for LitElement\n // apiKey has { attribute: false } so it won't work via HTML attribute\n if (control.options.apiKey) {\n element.apiKey = control.options.apiKey;\n }\n\n // Set videoTarget as property (not attribute) - object type\n // videoTarget has { attribute: false } for lifecycle-safe video binding\n if (control.options.videoTarget) {\n element.videoTarget = control.options.videoTarget;\n }\n\n // Set userInputCodeTarget as property (not attribute) - object type\n // Same pattern as videoTarget: declarative adapter for host code editor sync\n if (control.options.userInputCodeTarget) {\n element.userInputCodeTarget = control.options.userInputCodeTarget;\n }\n\n // Set rowDataForCodeEditor as property (not attribute) - object type\n // Auto-extracted 5 fields are attached to chat stream requests\n if (control.options.rowDataForCodeEditor) {\n element.rowDataForCodeEditor = control.options.rowDataForCodeEditor;\n }\n\n // Set allowLayoutToggle as property (not attribute) - Boolean type\n // HTML boolean attributes treat any string value (including \"false\") as true\n // Must be set via property to correctly handle false values\n if (control.options.allowLayoutToggle !== undefined) {\n element.allowLayoutToggle = control.options.allowLayoutToggle;\n }\n\n // Set grading as property (not attribute) - SPEAK contains a File\n // Delivered to iframe via dedicated grading:setup message\n if (control.options.grading) {\n element.grading = control.options.grading;\n }\n\n // Note: root is NOT set here - declarative API uses auto-detected parent\n // The element will detect declarative mode in connectedCallback\n }\n\n control.setInstance(element);\n },\n [\n control.setInstance,\n control.options.apiKey,\n control.options.videoTarget,\n control.options.userInputCodeTarget,\n control.options.rowDataForCodeEditor,\n control.options.allowLayoutToggle,\n control.options.grading,\n ]\n );\n\n // Store handlers in ref to avoid effect re-runs on handler reference changes\n // Handlers are stable via useMemo in useTapKit, but use ref for extra safety\n const handlersRef = useRef(control.handlers);\n handlersRef.current = control.handlers;\n\n // Register event listeners (only once per element mount)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n const listeners: Array<{\n event: string;\n handler: EventListener;\n }> = [];\n\n // Bind CustomEvent listeners (tap-kit:ready, tap-kit:error)\n for (const eventName of EVENT_NAMES) {\n const handlerKey = EVENT_HANDLER_MAP[eventName as keyof typeof EVENT_HANDLER_MAP];\n\n // Use closure over handlersRef to always get latest handler\n const listener = (e: Event) => {\n const handler = handlersRef.current[handlerKey];\n if (!handler) return;\n\n const customEvent = e as CustomEvent;\n if (handlerKey === \"onError\" && customEvent.detail?.error) {\n // onError receives error directly\n // biome-ignore lint/suspicious/noExplicitAny: Handler type is validated at runtime\n (handler as any)(customEvent.detail.error);\n } else if (handlerKey === \"onReady\") {\n // onReady receives no arguments\n // biome-ignore lint/suspicious/noExplicitAny: Handler type is validated at runtime\n (handler as any)();\n }\n };\n\n element.addEventListener(eventName, listener);\n listeners.push({ event: eventName, handler: listener });\n }\n\n return () => {\n // Cleanup all event listeners\n for (const { event, handler } of listeners) {\n element.removeEventListener(event, handler);\n }\n };\n }, []); // Empty deps - listeners use ref for handlers\n\n // Sync videoTarget property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.videoTarget = control.options.videoTarget;\n }, [control.options.videoTarget]);\n\n // Sync userInputCodeTarget property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.userInputCodeTarget = control.options.userInputCodeTarget;\n }, [control.options.userInputCodeTarget]);\n\n // Sync rowDataForCodeEditor property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.rowDataForCodeEditor = control.options.rowDataForCodeEditor;\n }, [control.options.rowDataForCodeEditor]);\n\n // Sync allowLayoutToggle property when it changes\n // (Boolean attributes don't work correctly with \"false\" string)\n useEffect(() => {\n const element = elementRef.current;\n if (!element || control.options.allowLayoutToggle === undefined) return;\n\n element.allowLayoutToggle = control.options.allowLayoutToggle;\n }, [control.options.allowLayoutToggle]);\n\n // Sync grading property when it changes\n // (property-only, SPEAK contains a File - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !control.options.grading) return;\n\n element.grading = control.options.grading;\n }, [control.options.grading]);\n\n // Note: root sync removed - declarative API uses auto-detected parent\n // The element stays in its declared position\n\n const { options, isCdnLoaded } = control;\n\n // Wait for CDN to load before rendering Web Component\n // Without this, <tap-kit> is just an HTMLUnknownElement without methods\n if (!isCdnLoaded) {\n return null;\n }\n\n // Render <tap-kit> Web Component directly\n // Note: apiKey, videoTarget, allowLayoutToggle are set via ref callback (property-only)\n return (\n <tap-kit\n ref={setElementRef}\n user-id={options.userId}\n course-id={options.courseId}\n clip-id={options.clipId}\n section-id={options.sectionId}\n clip-play-head={options.clipPlayHead}\n external-reference-id={options.externalReferenceId}\n user-input-code={options.userInputCode}\n language={options.language}\n button-id={options.buttonId}\n mode={options.mode}\n chat-mode={options.chatMode}\n debug={options.debug}\n tap-url={options.tapUrl}\n api-url={options.apiUrl}\n environment={options.environment}\n {...htmlProps}\n />\n );\n});\n","/**\n * CDN loader for TapKit\n * Dynamically loads the TapKit SDK from CDN\n *\n * For local testing, you can override the loader URL:\n * window.__TAP_KIT_LOADER_URL__ = '/tap-kit-core/loader.js';\n *\n * For local development (bypass loader, load IIFE directly):\n * window.__TAP_KIT_CORE_URL__ = '/packages/tap-kit-core/dist/index.global.js';\n */\n\n// Build-time constant injected by tsup define\ndeclare const __DEFAULT_CDN_LOADER_URL__: string;\n\nconst DEFAULT_CDN_LOADER_URL =\n typeof __DEFAULT_CDN_LOADER_URL__ !== \"undefined\" ? __DEFAULT_CDN_LOADER_URL__ : \"https://files.edutap.ai/tap-sdk/loader.js\";\nconst DEFAULT_TIMEOUT_MS = 4000; // 4 seconds total timeout\nconst IDLE_CALLBACK_TIMEOUT_MS = 500; // 500ms for requestIdleCallback\n\n/**\n * Get the loader URL from window override or default CDN\n */\nfunction getLoaderURL(): string {\n return window?.__TAP_KIT_LOADER_URL__ ? window.__TAP_KIT_LOADER_URL__ : DEFAULT_CDN_LOADER_URL;\n}\n\n/**\n * Check if local core mode is enabled\n * When __TAP_KIT_CORE_URL__ is set, directly load the IIFE bundle\n * This bypasses the loader.js and loads tap-kit-core directly\n */\nfunction isLocalCoreMode(): boolean {\n return typeof window !== \"undefined\" && !!window.__TAP_KIT_CORE_URL__;\n}\n\n/**\n * Get the local core URL\n */\nfunction getLocalCoreURL(): string {\n return window.__TAP_KIT_CORE_URL__ || \"\";\n}\n\n/**\n * Creates a SDK checker function with timeout and retry logic\n * Uses requestIdleCallback to avoid blocking browser rendering\n * @param resolve - Promise resolve function\n * @param reject - Promise reject function\n * @param timeoutMs - Maximum time to wait for SDK to load (milliseconds)\n * @returns Checker function to be called repeatedly\n */\nfunction createSDKChecker(\n resolve: (value: void | PromiseLike<void>) => void,\n reject: (reason?: unknown) => void,\n timeoutMs: number\n): () => void {\n const startTime = Date.now();\n\n const checkSDK = (): void => {\n // Check if real TapKit is loaded (not just stub)\n // Stub has TapKitLoaded flag set to true by loader.js after real SDK loads\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const elapsed = Date.now() - startTime;\n\n // Check if exceeded timeout\n if (elapsed > timeoutMs) {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`TapKit loader timeout: SDK not available after ${timeoutMs}ms`));\n return;\n }\n\n // Use requestIdleCallback for better performance\n // Falls back to setTimeout if not available\n if (typeof requestIdleCallback !== \"undefined\") {\n requestIdleCallback(checkSDK, { timeout: IDLE_CALLBACK_TIMEOUT_MS });\n } else {\n setTimeout(checkSDK, IDLE_CALLBACK_TIMEOUT_MS);\n }\n };\n\n return checkSDK;\n}\n\n/**\n * Loads the CDN loader script\n * The loader will then fetch versions.json and load the appropriate SDK version\n *\n * If __TAP_KIT_CORE_URL__ is set, bypasses loader and loads IIFE directly\n *\n * @param timeoutMs - Maximum time to wait for SDK to load (default: 4000ms)\n * @returns Promise that resolves when SDK is loaded\n * @throws {Error} If loader fails to load or times out\n */\nexport function loadCDNLoader(timeoutMs: number = DEFAULT_TIMEOUT_MS): Promise<void> {\n // If already loaded, return immediately\n if (window.__TAP_KIT_LOADER_LOADED__ && window.TapKit) {\n return Promise.resolve();\n }\n\n // If currently loading, return the existing promise\n if (window.__TAP_KIT_LOADER_LOADING__) {\n return window.__TAP_KIT_LOADER_LOADING__;\n }\n\n // Create loading promise\n const loadingPromise = new Promise<void>((resolve, reject) => {\n if (typeof document === \"undefined\") {\n reject(new Error(\"TapKit requires browser environment (document is undefined)\"));\n return;\n }\n\n // Local core mode: Load IIFE directly\n if (isLocalCoreMode()) {\n // Check if TapKit is already loaded (from other pages in playground)\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const coreURL = getLocalCoreURL();\n\n const script = document.createElement(\"script\");\n script.src = coreURL;\n script.async = true;\n\n script.onload = () => {\n // IIFE directly sets window.TapKit\n // Set the loaded flag manually since we bypass loader.js\n if (window.TapKit) {\n window.TapKitLoaded = true;\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n } else {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(\"TapKit not available after loading local core\"));\n }\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load local TapKit core: ${coreURL}`));\n };\n\n document.head.appendChild(script);\n return;\n }\n\n // CDN mode: Load loader.js\n const loaderURL = getLoaderURL();\n const script = document.createElement(\"script\");\n script.src = loaderURL;\n script.async = true;\n\n script.onload = () => {\n // The loader script will load the actual SDK\n // We need to wait a bit for the loader to fetch and load the SDK\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`));\n };\n\n // Check if script already exists\n const existingScript = document.querySelector(`script[src=\"${loaderURL}\"]`);\n\n if (existingScript) {\n // Script already added but not yet loaded\n existingScript.addEventListener(\"load\", () => {\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n });\n existingScript.addEventListener(\"error\", () => reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)));\n } else {\n document.head.appendChild(script);\n }\n });\n\n window.__TAP_KIT_LOADER_LOADING__ = loadingPromise;\n return loadingPromise;\n}\n","/**\n * useTapKit Hook - ChatKit-style control for TapKit Web Component\n *\n * This hook provides a control object to manage TapKit through the\n * <TapKit /> component. Inspired by OpenAI's ChatKit pattern.\n *\n * @example\n * ```tsx\n * 'use client';\n *\n * import { TapKit, useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyApp() {\n * const tapkit = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * onReady: () => console.log('Ready!'),\n * onError: (error) => console.error(error),\n * });\n *\n * return (\n * <div>\n * <button onClick={tapkit.show} disabled={!tapkit.isReady}>\n * Ask AI Tutor\n * </button>\n * <TapKit control={tapkit.control} />\n * </div>\n * );\n * }\n * ```\n */\n\nimport type {\n ChatMode,\n CodingRowDataInput,\n GradingOption,\n TapKitElement,\n UserInputCodeAdapter,\n VideoPlayerConfig,\n} from \"@coxwave/tap-kit-types\";\nimport { normalizeCodingRowData } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { TapKitControl, TapKitEventHandlers, VideoAdapter } from \"./types\";\n\nexport interface UseTapKitOptions extends TapKitEventHandlers {\n /** API Key (required) */\n apiKey: string;\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Section ID (course 내 섹션 식별자) */\n sectionId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** External reference ID (외부 시스템에서 부여한 참조 식별자) */\n externalReferenceId?: string;\n /** External reference data (외부 시스템에서 부여한 참조 데이터) */\n userInputCode?: string;\n /**\n * Raw coding exercise metadata from host LMS/platform.\n *\n * 5 required string fields are auto-extracted and attached to chat stream\n * requests as `coding_language`, `coding_testcase`, `coding_content`,\n * `coding_example`, `coding_restriction`. Additional fields on the object\n * are ignored.\n *\n * If provided, all 5 fields must be non-empty strings — otherwise the hook\n * throws on render (treated as init failure).\n *\n * **Tip**: Wrap in `useMemo` to keep object reference stable across renders.\n *\n * @example\n * ```tsx\n * const rowData = useMemo(() => ({\n * language_id_nm: \"C++ (GCC 9.2.0)\",\n * d_test_case: \"<p>...</p>\",\n * content: \"...\",\n * io_example: \"...\",\n * restriction: \"...\",\n * }), []);\n *\n * const tapkit = useTapKit({ apiKey, userId, courseId, rowDataForCodeEditor: rowData });\n * ```\n */\n rowDataForCodeEditor?: CodingRowDataInput;\n /** Language */\n language?: \"ko\" | \"en\" | \"ja\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\n /** Chat interaction mode (staging feature: per-message override) */\n chatMode?: ChatMode;\n /**\n * Allow user to toggle between floating and sidebar layouts\n * Only applies when mode is \"floating\" or \"sidebar\"\n * @default true\n */\n allowLayoutToggle?: boolean;\n /**\n * Video player target for timeline synchronization (recommended)\n *\n * Supports HTMLVideoElement or VideoPlayerAdapter.\n * Survives lifecycle changes (mode change, reconnect).\n *\n * @example\n * ```tsx\n * // HTMLVideoElement\n * const tapkit = useTapKit({ videoTarget: videoRef.current, ... });\n *\n * // VideoPlayerAdapter\n * const tapkit = useTapKit({\n * videoTarget: {\n * getCurrentTime: () => player.currentTime,\n * setCurrentTime: (t) => player.currentTime = t\n * },\n * ...\n * });\n * ```\n */\n videoTarget?: VideoPlayerConfig;\n /**\n * User input code adapter for bidirectional sync with a host code editor.\n *\n * 호스트 페이지의 코드 에디터(Monaco, CodeMirror, iframe IDE 등)와 SDK를\n * 양방향 동기화하는 어댑터. videoTarget과 동일한 패턴.\n *\n * - SDK가 폴링 주기로 `getCode()` 호출 → 채팅 컨텍스트에 최신 코드 반영\n * - 채팅의 \"에디터에 적용\" 모달 → SDK가 `setCode(code)` 호출 (Optional)\n *\n * @example\n * ```tsx\n * const tapkit = useTapKit({\n * userInputCodeTarget: {\n * getCode: () => monacoEditor.getValue(),\n * setCode: (code) => monacoEditor.setValue(code),\n * },\n * ...\n * });\n * ```\n */\n userInputCodeTarget?: UserInputCodeAdapter;\n /**\n * Grading session — single grading problem (Speaking 또는 Writing).\n *\n * 호스트가 SDK에 넘기는 채점 셋업. `gradeType`으로 판별한다. config:update가 아닌\n * 전용 `grading:setup` 메시지로 iframe에 전달된다. SPEAK은 edutap이 음성 파일을\n * 업로드한 뒤 홈에서 프리셋 질문 버튼을 노출하고, WRITE는 학습자가 채팅 입력창에\n * 답안을 작성한다. (SPEAK은) File을 포함하므로 객체 참조가 바뀔 때마다 재전송된다\n * — 안정적인 참조를 위해 `useMemo`로 감싸는 것을 권장.\n *\n * @example\n * ```tsx\n * // Speaking\n * const grading = useMemo(() => ({\n * gradeType: \"SPEAK\" as const,\n * audioFile,\n * problemText: \"Describe your hometown\",\n * problemType: \"INTERVIEW\" as const,\n * referenceAnswer: \"...\",\n * hint: \"장소 묘사\",\n * }), [audioFile]);\n *\n * // Writing (음성 없음, 학습자가 채팅 입력창에 답안 작성)\n * const grading = { gradeType: \"WRITE\" as const, problemText, problemType: \"MSOM\" as const };\n *\n * const tapkit = useTapKit({ apiKey, userId, courseId, grading });\n * ```\n */\n grading?: GradingOption;\n /** Debug mode */\n debug?: boolean;\n /** TAP Frontend URL */\n tapUrl?: string;\n /** API Backend URL */\n apiUrl?: string;\n /** Environment */\n environment?: \"dev\" | \"prod\" | \"staging\";\n // Note: `root` option removed - declarative API (<TapKit />) stays in declared position\n // For imperative control over position, use createTapKit() with root option\n}\n\n/**\n * Configuration options type (non-function values)\n */\nexport type TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;\n\n/**\n * Extract event handlers from options (pure function)\n */\nfunction extractHandlers(options: UseTapKitOptions): TapKitEventHandlers {\n return {\n onReady: options.onReady,\n onError: options.onError,\n onTimelineSeek: options.onTimelineSeek,\n onAlarmFadeIn: options.onAlarmFadeIn,\n };\n}\n\nexport interface UseTapKitReturn {\n /** Control object for <TapKit /> component */\n control: TapKitControl<TapKitOptions>;\n /** Whether TapKit is ready */\n isReady: boolean;\n /** Error during initialization */\n error: Error | null;\n /** Show chat interface */\n show: () => void;\n /** Hide chat interface */\n hide: () => void;\n /** Set course information */\n setCourse: (course: { courseId: string; clipId?: string; userId?: string; clipPlayHead?: number }) => void;\n /** Update external reference ID at runtime (pass undefined to clear) */\n setExternalReferenceId: (id?: string) => void;\n /** Update external reference data at runtime (pass undefined to clear) */\n setExternalReferenceData: (data?: string) => void;\n /** Send contextual data from host to iframe (AI tutor) */\n sendContext: (contextType: string, payload: Record<string, unknown>) => void;\n /**\n * Video adapter control\n *\n * @deprecated Use `videoTarget` option in useTapKit instead for declarative binding.\n *\n * ```tsx\n * // ✅ Recommended (declarative)\n * const tapkit = useTapKit({ videoTarget: adapter, ... });\n *\n * // ❌ Deprecated (imperative)\n * tapkit.video.bind(adapter);\n * ```\n */\n video: {\n bind: (adapter: VideoAdapter, clipId?: string) => void;\n unbind: () => void;\n };\n}\n\n/**\n * Hook for managing TapKit Web Component (ChatKit Pattern)\n *\n * Returns a control object to pass to <TapKit /> component.\n * The component handles rendering, this hook handles state and methods.\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n // Normalize rowDataForCodeEditor — accepts both `CodingRowData` and JSON string.\n // Throws synchronously on render if partial/invalid so the caller's React error\n // boundary catches config errors before the SDK is initialized.\n const normalizedRowData = useMemo(\n () => (options.rowDataForCodeEditor === undefined ? undefined : normalizeCodingRowData(options.rowDataForCodeEditor)),\n [options.rowDataForCodeEditor]\n );\n\n // Element ref - set by <TapKit /> component via control.setInstance\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Pending video bind request (queued until ready)\n const pendingVideoBind = useRef<{\n adapter: VideoAdapter;\n clipId?: string;\n } | null>(null);\n\n // State\n const [isCdnLoaded, setIsCdnLoaded] = useState(false);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Store handlers in refs to avoid stale closures\n const handlersRef = useRef(extractHandlers(options));\n handlersRef.current = extractHandlers(options);\n\n // Load CDN on mount (defines <tap-kit> Web Component)\n useEffect(() => {\n loadCDNLoader()\n .then(() => {\n setIsCdnLoaded(true);\n })\n .catch((err) => {\n const loadError = err instanceof Error ? err : new Error(\"Failed to load TapKit CDN\");\n setError(loadError);\n handlersRef.current.onError?.(loadError);\n });\n }, []);\n\n // Set instance callback - called by <TapKit /> component\n const setInstance = useCallback((instance: TapKitElement | null) => {\n elementRef.current = instance;\n\n if (!instance) {\n setIsReady(false);\n return;\n }\n\n // Listen for ready event\n const handleReady = () => {\n setIsReady(true);\n handlersRef.current.onReady?.();\n };\n\n const handleError = (e: Event) => {\n const customEvent = e as CustomEvent<{ error: Error }>;\n const err = customEvent.detail?.error ?? new Error(\"Unknown error\");\n setError(err);\n handlersRef.current.onError?.(err);\n };\n\n instance.addEventListener(\"tap-kit:ready\", handleReady);\n instance.addEventListener(\"tap-kit:error\", handleError);\n\n // Check if already ready\n if (instance.isInitialized) {\n setIsReady(true);\n }\n\n // Setup EventManager callbacks when ready\n // Use stable wrappers that call handlersRef.current to always get latest handlers\n const setupEventHandlers = () => {\n if (instance.events && typeof instance.events.onTimelineSeek === \"function\") {\n // Register stable wrappers instead of direct refs\n // This ensures handler updates are reflected without re-registration\n instance.events.onTimelineSeek((clipPlayHead, clipId) => {\n handlersRef.current.onTimelineSeek?.(clipPlayHead, clipId);\n });\n instance.events.onAlarmFadeIn((messageInfo) => {\n handlersRef.current.onAlarmFadeIn?.(messageInfo);\n });\n }\n\n // Execute pending video bind if queued\n if (pendingVideoBind.current && instance.video) {\n instance.video.bind(pendingVideoBind.current.adapter, pendingVideoBind.current.clipId);\n pendingVideoBind.current = null;\n }\n };\n\n // Check if ready property exists (may not be available immediately)\n if (instance.ready && typeof instance.ready.then === \"function\") {\n instance.ready.then(setupEventHandlers).catch(() => {\n // Ignore - error already handled by tap-kit:error event\n });\n } else if (instance.isInitialized) {\n // Already initialized, setup handlers immediately\n setupEventHandlers();\n } else {\n // Fallback: wait for ready event then setup handlers\n instance.addEventListener(\"tap-kit:ready\", setupEventHandlers, {\n once: true,\n });\n }\n }, []);\n\n // Memoize config options using individual values to prevent infinite re-renders\n // (options object is recreated on every render, so we must depend on primitives)\n // Note: videoTarget is object reference - changes trigger re-render intentionally\n const configOptions = useMemo(\n () => ({\n apiKey: options.apiKey,\n userId: options.userId,\n courseId: options.courseId,\n clipId: options.clipId,\n sectionId: options.sectionId,\n clipPlayHead: options.clipPlayHead,\n externalReferenceId: options.externalReferenceId,\n userInputCode: options.userInputCode,\n rowDataForCodeEditor: normalizedRowData,\n language: options.language,\n buttonId: options.buttonId,\n mode: options.mode,\n chatMode: options.chatMode,\n allowLayoutToggle: options.allowLayoutToggle,\n videoTarget: options.videoTarget,\n userInputCodeTarget: options.userInputCodeTarget,\n grading: options.grading,\n debug: options.debug,\n tapUrl: options.tapUrl,\n apiUrl: options.apiUrl,\n environment: options.environment,\n // Note: root removed - declarative API stays in declared position\n }),\n [\n options.apiKey,\n options.userId,\n options.courseId,\n options.clipId,\n options.sectionId,\n options.clipPlayHead,\n options.externalReferenceId,\n options.userInputCode,\n normalizedRowData,\n options.language,\n options.buttonId,\n options.mode,\n options.chatMode,\n options.allowLayoutToggle,\n options.videoTarget,\n options.userInputCodeTarget,\n options.grading,\n options.debug,\n options.tapUrl,\n options.apiUrl,\n options.environment,\n ]\n );\n\n // Create stable handlers object for control\n // NOTE: This object is intentionally created once (empty deps) and appears to capture\n // stale values. However, the actual handler invocations in TapKit.tsx use handlersRef.current\n // which always has the latest handlers. This object is just a stable reference marker\n // that tells TapKit.tsx which handlers exist - not the actual handlers to call.\n const handlers = useMemo<TapKitEventHandlers>(\n () => ({\n onReady: handlersRef.current.onReady,\n onError: handlersRef.current.onError,\n onTimelineSeek: handlersRef.current.onTimelineSeek,\n onAlarmFadeIn: handlersRef.current.onAlarmFadeIn,\n }),\n []\n );\n\n // Create control object\n const control = useMemo<TapKitControl<TapKitOptions>>(\n () => ({\n setInstance,\n options: configOptions,\n handlers,\n isCdnLoaded,\n }),\n [setInstance, configOptions, handlers, isCdnLoaded]\n );\n\n // Methods\n const show = useCallback(() => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot show: element not mounted\");\n return;\n }\n elementRef.current.show();\n }, []);\n\n const hide = useCallback(() => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot hide: element not mounted\");\n return;\n }\n elementRef.current.hide();\n }, []);\n\n const setCourse = useCallback((course: { courseId: string; clipId?: string; userId?: string; clipPlayHead?: number }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n }, []);\n\n const setExternalReferenceId = useCallback((id?: string) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setExternalReferenceId: element not mounted\");\n return;\n }\n elementRef.current.externalReferenceId = id;\n }, []);\n\n const setExternalReferenceData = useCallback((data?: string) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setExternalReferenceData: element not mounted\");\n return;\n }\n elementRef.current.userInputCode = data;\n }, []);\n\n const sendContext = useCallback((contextType: string, payload: Record<string, unknown>) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot sendContext: element not mounted\");\n return;\n }\n elementRef.current.sendContext(contextType, payload);\n }, []);\n\n // Video adapter control\n const video = useMemo(\n () => ({\n bind: (adapter: VideoAdapter, clipId?: string) => {\n // If video controller is ready, bind immediately\n if (elementRef.current?.video) {\n elementRef.current.video.bind(adapter, clipId);\n return;\n }\n\n // Queue for later execution when ready\n pendingVideoBind.current = { adapter, clipId };\n },\n unbind: () => {\n // Clear pending request\n pendingVideoBind.current = null;\n elementRef.current?.video?.unbind();\n },\n }),\n []\n );\n\n return {\n control,\n isReady,\n error,\n show,\n hide,\n setCourse,\n setExternalReferenceId,\n setExternalReferenceData,\n sendContext,\n video,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/types.ts","../src/react/TapKit.tsx","../src/loader.ts","../src/react/useTapKit.ts"],"names":["React","TapKit","useRef","useImperativeHandle","useCallback","useEffect","jsx","script","useMemo","normalizeCodingRowData","useState"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React-specific type definitions for TapKit\n *\n * Defines event handler types and control patterns for React integration.\n */\n\nimport type { AlarmMessageInstanceType, TapKitElement } from \"@coxwave/tap-kit-types\";\n\n/**\n * Video player adapter interface for timeline synchronization\n *\n * Supports custom video players (YouTube, Vimeo, etc.) that don't use HTMLVideoElement.\n */\nexport interface VideoAdapter {\n /** Get current playback time in seconds */\n getCurrentTime: () => number;\n /** Set playback time in seconds */\n setCurrentTime: (time: number, clipId?: string) => void;\n}\n\n/**\n * Event handler type definitions for TapKit Web Component\n *\n * Maps CustomEvent types to React callback signatures.\n */\nexport interface TapKitEventHandlers {\n /**\n * Called when TapKit SDK is ready\n * @example onReady={() => console.log('TapKit ready!')}\n */\n onReady?: () => void;\n\n /**\n * Called when initialization or runtime error occurs\n * @example onError={(error) => console.error('TapKit error:', error)}\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when timeline seek event occurs\n * @example onTimelineSeek={(clipPlayHead, clipId) => console.log('Seek:', clipPlayHead)}\n */\n onTimelineSeek?: (clipPlayHead: number, clipId: string) => void;\n\n /**\n * Called when alarm fade-in occurs\n * @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}\n */\n onAlarmFadeIn?: (messageInfo: AlarmMessageInstanceType) => void;\n\n /**\n * 학습자가 취약점 진단의 근거 문항을 눌렀을 때 (EXAM 모드, 디지틀조선 TOEFL 첨삭).\n * 호스트 시험 플레이어를 `clipId`(= 채점 API의 `problem_id`) 문항으로 옮기면 된다.\n * @example onExamQuestionNavigate={(clipId) => goToQuestion(clipId)}\n */\n onExamQuestionNavigate?: (clipId: string, questionNo?: number) => void;\n}\n\n/**\n * Control object pattern for managing TapKit instance\n *\n * Separates instance management (setInstance) from configuration (options)\n * and event handling (handlers). Inspired by ChatKit's control pattern.\n *\n * @example\n * ```tsx\n * const tapkit = useTapKit({ apiKey: 'key', onReady: () => {} });\n * <TapKit control={tapkit.control} />\n * ```\n */\nexport interface TapKitControl<T> {\n /**\n * Set the TapKit element instance reference\n *\n * Called by TapKit component to register the element instance.\n * Enables imperative control via useTapKit methods.\n */\n setInstance: (instance: TapKitElement | null) => void;\n\n /**\n * Configuration options (non-function values)\n *\n * All options except event handlers.\n */\n options: T;\n\n /**\n * Event handler callbacks (function values)\n *\n * Separated from options for easier event listener management.\n */\n handlers: TapKitEventHandlers;\n\n /**\n * Whether CDN is loaded and Web Component is available\n *\n * @internal Used by TapKit component to delay rendering\n */\n isCdnLoaded: boolean;\n}\n\n/**\n * Map of TapKit event names to their handler keys\n *\n * Used for automatic event listener binding in TapKit component.\n *\n * @internal\n */\nexport const EVENT_HANDLER_MAP = {\n \"tap-kit:ready\": \"onReady\",\n \"tap-kit:error\": \"onError\",\n} as const;\n\n/**\n * List of TapKit event names\n *\n * @internal\n */\nexport const EVENT_NAMES = Object.keys(EVENT_HANDLER_MAP) as (keyof typeof EVENT_HANDLER_MAP)[];\n","/**\n * TapKit React Component\n *\n * Declarative React wrapper for <tap-kit> Web Component.\n * Use this with useTapKit hook for a clean separation of control and rendering.\n *\n * @example\n * ```tsx\n * 'use client';\n *\n * import { TapKit, useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyApp() {\n * const tapkit = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * onReady: () => console.log('TapKit ready!'),\n * onError: (error) => console.error('Error:', error),\n * });\n *\n * return (\n * <div>\n * <button onClick={tapkit.show} disabled={!tapkit.isReady}>\n * Show Chat\n * </button>\n * <TapKit control={tapkit.control} style={{ height: '600px' }} />\n * </div>\n * );\n * }\n * ```\n *\n * **Note for Next.js App Router users:**\n * Make sure to add 'use client' at the top of your component file.\n *\n * @see https://edutap-ai-docs.vercel.app/docs/guides/react\n */\n\nimport type { TapKitElement } from \"@coxwave/tap-kit-types\";\nimport React, { useCallback, useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { TapKitControl } from \"./types\";\nimport { EVENT_HANDLER_MAP, EVENT_NAMES } from \"./types\";\nimport type { TapKitOptions } from \"./useTapKit\";\n\n/**\n * Props for TapKit React component\n */\nexport interface TapKitProps extends Omit<React.HTMLAttributes<TapKitElement>, \"children\" | \"dangerouslySetInnerHTML\"> {\n /**\n * Control object from useTapKit hook\n *\n * Provides instance management, configuration, and event handlers.\n */\n control: TapKitControl<TapKitOptions>;\n}\n\n// Note: JSX IntrinsicElements augmentation is provided by @coxwave/tap-kit-types\n// See: packages/tap-kit-types/src/tap-kit.d.ts\n\n/**\n * TapKit React Component\n *\n * Renders <tap-kit> Web Component with React integration.\n * Automatically manages instance lifecycle and event listeners.\n *\n * **Ref Access**: The forwarded ref provides direct access to the TapKitElement:\n * ```tsx\n * const tapkitRef = useRef<TapKitElement>(null);\n * <TapKit ref={tapkitRef} control={...} />\n * // tapkitRef.current?.show()\n * ```\n */\nexport const TapKit = React.forwardRef<TapKitElement, TapKitProps>(function TapKit({ control, ...htmlProps }, forwardedRef) {\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Forward TapKitElement to parent\n useImperativeHandle(forwardedRef, () => elementRef.current as TapKitElement, []);\n\n // Callback ref to register element when mounted\n const setElementRef = useCallback(\n (element: TapKitElement | null) => {\n elementRef.current = element;\n\n if (element) {\n // Set apiKey as property (not attribute) - required for LitElement\n // apiKey has { attribute: false } so it won't work via HTML attribute\n if (control.options.apiKey) {\n element.apiKey = control.options.apiKey;\n }\n\n // Set videoTarget as property (not attribute) - object type\n // videoTarget has { attribute: false } for lifecycle-safe video binding\n if (control.options.videoTarget) {\n element.videoTarget = control.options.videoTarget;\n }\n\n // Set userInputCodeTarget as property (not attribute) - object type\n // Same pattern as videoTarget: declarative adapter for host code editor sync\n if (control.options.userInputCodeTarget) {\n element.userInputCodeTarget = control.options.userInputCodeTarget;\n }\n\n // Set rowDataForCodeEditor as property (not attribute) - object type\n // Auto-extracted 5 fields are attached to chat stream requests\n if (control.options.rowDataForCodeEditor) {\n element.rowDataForCodeEditor = control.options.rowDataForCodeEditor;\n }\n\n // Set allowLayoutToggle as property (not attribute) - Boolean type\n // HTML boolean attributes treat any string value (including \"false\") as true\n // Must be set via property to correctly handle false values\n if (control.options.allowLayoutToggle !== undefined) {\n element.allowLayoutToggle = control.options.allowLayoutToggle;\n }\n\n // Set grading as property (not attribute) - SPEAK contains a File\n // Delivered to iframe via dedicated grading:setup message\n if (control.options.grading) {\n element.grading = control.options.grading;\n }\n\n // Note: root is NOT set here - declarative API uses auto-detected parent\n // The element will detect declarative mode in connectedCallback\n }\n\n control.setInstance(element);\n },\n [\n control.setInstance,\n control.options.apiKey,\n control.options.videoTarget,\n control.options.userInputCodeTarget,\n control.options.rowDataForCodeEditor,\n control.options.allowLayoutToggle,\n control.options.grading,\n ]\n );\n\n // Store handlers in ref to avoid effect re-runs on handler reference changes\n // Handlers are stable via useMemo in useTapKit, but use ref for extra safety\n const handlersRef = useRef(control.handlers);\n handlersRef.current = control.handlers;\n\n // Register event listeners (only once per element mount)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n const listeners: Array<{\n event: string;\n handler: EventListener;\n }> = [];\n\n // Bind CustomEvent listeners (tap-kit:ready, tap-kit:error)\n for (const eventName of EVENT_NAMES) {\n const handlerKey = EVENT_HANDLER_MAP[eventName as keyof typeof EVENT_HANDLER_MAP];\n\n // Use closure over handlersRef to always get latest handler\n const listener = (e: Event) => {\n const handler = handlersRef.current[handlerKey];\n if (!handler) return;\n\n const customEvent = e as CustomEvent;\n if (handlerKey === \"onError\" && customEvent.detail?.error) {\n // onError receives error directly\n // biome-ignore lint/suspicious/noExplicitAny: Handler type is validated at runtime\n (handler as any)(customEvent.detail.error);\n } else if (handlerKey === \"onReady\") {\n // onReady receives no arguments\n // biome-ignore lint/suspicious/noExplicitAny: Handler type is validated at runtime\n (handler as any)();\n }\n };\n\n element.addEventListener(eventName, listener);\n listeners.push({ event: eventName, handler: listener });\n }\n\n return () => {\n // Cleanup all event listeners\n for (const { event, handler } of listeners) {\n element.removeEventListener(event, handler);\n }\n };\n }, []); // Empty deps - listeners use ref for handlers\n\n // Sync videoTarget property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.videoTarget = control.options.videoTarget;\n }, [control.options.videoTarget]);\n\n // Sync userInputCodeTarget property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.userInputCodeTarget = control.options.userInputCodeTarget;\n }, [control.options.userInputCodeTarget]);\n\n // Sync rowDataForCodeEditor property when it changes\n // (property-only, not attribute - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n element.rowDataForCodeEditor = control.options.rowDataForCodeEditor;\n }, [control.options.rowDataForCodeEditor]);\n\n // Sync allowLayoutToggle property when it changes\n // (Boolean attributes don't work correctly with \"false\" string)\n useEffect(() => {\n const element = elementRef.current;\n if (!element || control.options.allowLayoutToggle === undefined) return;\n\n element.allowLayoutToggle = control.options.allowLayoutToggle;\n }, [control.options.allowLayoutToggle]);\n\n // Sync grading property when it changes\n // (property-only, SPEAK contains a File - must be set imperatively)\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !control.options.grading) return;\n\n element.grading = control.options.grading;\n }, [control.options.grading]);\n\n // Note: root sync removed - declarative API uses auto-detected parent\n // The element stays in its declared position\n\n const { options, isCdnLoaded } = control;\n\n // Wait for CDN to load before rendering Web Component\n // Without this, <tap-kit> is just an HTMLUnknownElement without methods\n if (!isCdnLoaded) {\n return null;\n }\n\n // Render <tap-kit> Web Component directly\n // Note: apiKey, videoTarget, allowLayoutToggle are set via ref callback (property-only)\n return (\n <tap-kit\n ref={setElementRef}\n user-id={options.userId}\n course-id={options.courseId}\n clip-id={options.clipId}\n section-id={options.sectionId}\n clip-play-head={options.clipPlayHead}\n external-reference-id={options.externalReferenceId}\n user-input-code={options.userInputCode}\n language={options.language}\n button-id={options.buttonId}\n mode={options.mode}\n chat-mode={options.chatMode}\n exam-skill={options.examSkill ?? undefined}\n debug={options.debug}\n tap-url={options.tapUrl}\n api-url={options.apiUrl}\n environment={options.environment}\n {...htmlProps}\n />\n );\n});\n","/**\n * CDN loader for TapKit\n * Dynamically loads the TapKit SDK from CDN\n *\n * For local testing, you can override the loader URL:\n * window.__TAP_KIT_LOADER_URL__ = '/tap-kit-core/loader.js';\n *\n * For local development (bypass loader, load IIFE directly):\n * window.__TAP_KIT_CORE_URL__ = '/packages/tap-kit-core/dist/index.global.js';\n */\n\n// Build-time constant injected by tsup define\ndeclare const __DEFAULT_CDN_LOADER_URL__: string;\n\nconst DEFAULT_CDN_LOADER_URL =\n typeof __DEFAULT_CDN_LOADER_URL__ !== \"undefined\" ? __DEFAULT_CDN_LOADER_URL__ : \"https://files.edutap.ai/tap-sdk/loader.js\";\nconst DEFAULT_TIMEOUT_MS = 4000; // 4 seconds total timeout\nconst IDLE_CALLBACK_TIMEOUT_MS = 500; // 500ms for requestIdleCallback\n\n/**\n * Get the loader URL from window override or default CDN\n */\nfunction getLoaderURL(): string {\n return window?.__TAP_KIT_LOADER_URL__ ? window.__TAP_KIT_LOADER_URL__ : DEFAULT_CDN_LOADER_URL;\n}\n\n/**\n * Check if local core mode is enabled\n * When __TAP_KIT_CORE_URL__ is set, directly load the IIFE bundle\n * This bypasses the loader.js and loads tap-kit-core directly\n */\nfunction isLocalCoreMode(): boolean {\n return typeof window !== \"undefined\" && !!window.__TAP_KIT_CORE_URL__;\n}\n\n/**\n * Get the local core URL\n */\nfunction getLocalCoreURL(): string {\n return window.__TAP_KIT_CORE_URL__ || \"\";\n}\n\n/**\n * Creates a SDK checker function with timeout and retry logic\n * Uses requestIdleCallback to avoid blocking browser rendering\n * @param resolve - Promise resolve function\n * @param reject - Promise reject function\n * @param timeoutMs - Maximum time to wait for SDK to load (milliseconds)\n * @returns Checker function to be called repeatedly\n */\nfunction createSDKChecker(\n resolve: (value: void | PromiseLike<void>) => void,\n reject: (reason?: unknown) => void,\n timeoutMs: number\n): () => void {\n const startTime = Date.now();\n\n const checkSDK = (): void => {\n // Check if real TapKit is loaded (not just stub)\n // Stub has TapKitLoaded flag set to true by loader.js after real SDK loads\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const elapsed = Date.now() - startTime;\n\n // Check if exceeded timeout\n if (elapsed > timeoutMs) {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`TapKit loader timeout: SDK not available after ${timeoutMs}ms`));\n return;\n }\n\n // Use requestIdleCallback for better performance\n // Falls back to setTimeout if not available\n if (typeof requestIdleCallback !== \"undefined\") {\n requestIdleCallback(checkSDK, { timeout: IDLE_CALLBACK_TIMEOUT_MS });\n } else {\n setTimeout(checkSDK, IDLE_CALLBACK_TIMEOUT_MS);\n }\n };\n\n return checkSDK;\n}\n\n/**\n * Loads the CDN loader script\n * The loader will then fetch versions.json and load the appropriate SDK version\n *\n * If __TAP_KIT_CORE_URL__ is set, bypasses loader and loads IIFE directly\n *\n * @param timeoutMs - Maximum time to wait for SDK to load (default: 4000ms)\n * @returns Promise that resolves when SDK is loaded\n * @throws {Error} If loader fails to load or times out\n */\nexport function loadCDNLoader(timeoutMs: number = DEFAULT_TIMEOUT_MS): Promise<void> {\n // If already loaded, return immediately\n if (window.__TAP_KIT_LOADER_LOADED__ && window.TapKit) {\n return Promise.resolve();\n }\n\n // If currently loading, return the existing promise\n if (window.__TAP_KIT_LOADER_LOADING__) {\n return window.__TAP_KIT_LOADER_LOADING__;\n }\n\n // Create loading promise\n const loadingPromise = new Promise<void>((resolve, reject) => {\n if (typeof document === \"undefined\") {\n reject(new Error(\"TapKit requires browser environment (document is undefined)\"));\n return;\n }\n\n // Local core mode: Load IIFE directly\n if (isLocalCoreMode()) {\n // Check if TapKit is already loaded (from other pages in playground)\n if (window.TapKit && window.TapKitLoaded === true) {\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n return;\n }\n\n const coreURL = getLocalCoreURL();\n\n const script = document.createElement(\"script\");\n script.src = coreURL;\n script.async = true;\n\n script.onload = () => {\n // IIFE directly sets window.TapKit\n // Set the loaded flag manually since we bypass loader.js\n if (window.TapKit) {\n window.TapKitLoaded = true;\n window.__TAP_KIT_LOADER_LOADED__ = true;\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n resolve();\n } else {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(\"TapKit not available after loading local core\"));\n }\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load local TapKit core: ${coreURL}`));\n };\n\n document.head.appendChild(script);\n return;\n }\n\n // CDN mode: Load loader.js\n const loaderURL = getLoaderURL();\n const script = document.createElement(\"script\");\n script.src = loaderURL;\n script.async = true;\n\n script.onload = () => {\n // The loader script will load the actual SDK\n // We need to wait a bit for the loader to fetch and load the SDK\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n };\n\n script.onerror = () => {\n window.__TAP_KIT_LOADER_LOADING__ = undefined;\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`));\n };\n\n // Check if script already exists\n const existingScript = document.querySelector(`script[src=\"${loaderURL}\"]`);\n\n if (existingScript) {\n // Script already added but not yet loaded\n existingScript.addEventListener(\"load\", () => {\n const checkSDK = createSDKChecker(resolve, reject, timeoutMs);\n checkSDK();\n });\n existingScript.addEventListener(\"error\", () => reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)));\n } else {\n document.head.appendChild(script);\n }\n });\n\n window.__TAP_KIT_LOADER_LOADING__ = loadingPromise;\n return loadingPromise;\n}\n","/**\n * useTapKit Hook - ChatKit-style control for TapKit Web Component\n *\n * This hook provides a control object to manage TapKit through the\n * <TapKit /> component. Inspired by OpenAI's ChatKit pattern.\n *\n * @example\n * ```tsx\n * 'use client';\n *\n * import { TapKit, useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyApp() {\n * const tapkit = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * onReady: () => console.log('Ready!'),\n * onError: (error) => console.error(error),\n * });\n *\n * return (\n * <div>\n * <button onClick={tapkit.show} disabled={!tapkit.isReady}>\n * Ask AI Tutor\n * </button>\n * <TapKit control={tapkit.control} />\n * </div>\n * );\n * }\n * ```\n */\n\nimport type {\n ChatMode,\n TapExamSkill,\n CodingRowDataInput,\n GradingOption,\n TapKitElement,\n UserInputCodeAdapter,\n VideoPlayerConfig,\n} from \"@coxwave/tap-kit-types\";\nimport { normalizeCodingRowData } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { TapKitControl, TapKitEventHandlers, VideoAdapter } from \"./types\";\n\nexport interface UseTapKitOptions extends TapKitEventHandlers {\n /** API Key (required) */\n apiKey: string;\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Section ID (course 내 섹션 식별자) */\n sectionId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** External reference ID (외부 시스템에서 부여한 참조 식별자) */\n externalReferenceId?: string;\n /** External reference data (외부 시스템에서 부여한 참조 데이터) */\n userInputCode?: string;\n /**\n * Raw coding exercise metadata from host LMS/platform.\n *\n * 5 required string fields are auto-extracted and attached to chat stream\n * requests as `coding_language`, `coding_testcase`, `coding_content`,\n * `coding_example`, `coding_restriction`. Additional fields on the object\n * are ignored.\n *\n * If provided, all 5 fields must be non-empty strings — otherwise the hook\n * throws on render (treated as init failure).\n *\n * **Tip**: Wrap in `useMemo` to keep object reference stable across renders.\n *\n * @example\n * ```tsx\n * const rowData = useMemo(() => ({\n * language_id_nm: \"C++ (GCC 9.2.0)\",\n * d_test_case: \"<p>...</p>\",\n * content: \"...\",\n * io_example: \"...\",\n * restriction: \"...\",\n * }), []);\n *\n * const tapkit = useTapKit({ apiKey, userId, courseId, rowDataForCodeEditor: rowData });\n * ```\n */\n rowDataForCodeEditor?: CodingRowDataInput;\n /** Language */\n language?: \"ko\" | \"en\" | \"ja\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\n /** Chat interaction mode (staging feature: per-message override) */\n chatMode?: ChatMode;\n /**\n * 호스트 시험 플레이어의 현재 섹션 (Writing / Speaking).\n * 취약점 진단 결과의 탭이 이 값에 따라 초기값·동기화된다 (ClickUp z90uw3mm72).\n */\n examSkill?: TapExamSkill | null;\n /**\n * Allow user to toggle between floating and sidebar layouts\n * Only applies when mode is \"floating\" or \"sidebar\"\n * @default true\n */\n allowLayoutToggle?: boolean;\n /**\n * Video player target for timeline synchronization (recommended)\n *\n * Supports HTMLVideoElement or VideoPlayerAdapter.\n * Survives lifecycle changes (mode change, reconnect).\n *\n * @example\n * ```tsx\n * // HTMLVideoElement\n * const tapkit = useTapKit({ videoTarget: videoRef.current, ... });\n *\n * // VideoPlayerAdapter\n * const tapkit = useTapKit({\n * videoTarget: {\n * getCurrentTime: () => player.currentTime,\n * setCurrentTime: (t) => player.currentTime = t\n * },\n * ...\n * });\n * ```\n */\n videoTarget?: VideoPlayerConfig;\n /**\n * User input code adapter for bidirectional sync with a host code editor.\n *\n * 호스트 페이지의 코드 에디터(Monaco, CodeMirror, iframe IDE 등)와 SDK를\n * 양방향 동기화하는 어댑터. videoTarget과 동일한 패턴.\n *\n * - SDK가 폴링 주기로 `getCode()` 호출 → 채팅 컨텍스트에 최신 코드 반영\n * - 채팅의 \"에디터에 적용\" 모달 → SDK가 `setCode(code)` 호출 (Optional)\n *\n * @example\n * ```tsx\n * const tapkit = useTapKit({\n * userInputCodeTarget: {\n * getCode: () => monacoEditor.getValue(),\n * setCode: (code) => monacoEditor.setValue(code),\n * },\n * ...\n * });\n * ```\n */\n userInputCodeTarget?: UserInputCodeAdapter;\n /**\n * Grading session — single grading problem (Speaking 또는 Writing).\n *\n * 호스트가 SDK에 넘기는 채점 셋업. `gradeType`으로 판별한다. config:update가 아닌\n * 전용 `grading:setup` 메시지로 iframe에 전달된다. SPEAK은 edutap이 음성 파일을\n * 업로드한 뒤 홈에서 프리셋 질문 버튼을 노출하고, WRITE는 학습자가 채팅 입력창에\n * 답안을 작성한다. (SPEAK은) File을 포함하므로 객체 참조가 바뀔 때마다 재전송된다\n * — 안정적인 참조를 위해 `useMemo`로 감싸는 것을 권장.\n *\n * @example\n * ```tsx\n * // Speaking\n * const grading = useMemo(() => ({\n * gradeType: \"SPEAK\" as const,\n * audioFile,\n * problemText: \"Describe your hometown\",\n * problemType: \"INTERVIEW\" as const,\n * referenceAnswer: \"...\",\n * hint: \"장소 묘사\",\n * }), [audioFile]);\n *\n * // Writing (음성 없음, 학습자가 채팅 입력창에 답안 작성)\n * const grading = { gradeType: \"WRITE\" as const, problemText, problemType: \"MSOM\" as const };\n *\n * const tapkit = useTapKit({ apiKey, userId, courseId, grading });\n * ```\n */\n grading?: GradingOption;\n /** Debug mode */\n debug?: boolean;\n /** TAP Frontend URL */\n tapUrl?: string;\n /** API Backend URL */\n apiUrl?: string;\n /** Environment */\n environment?: \"dev\" | \"prod\" | \"staging\";\n // Note: `root` option removed - declarative API (<TapKit />) stays in declared position\n // For imperative control over position, use createTapKit() with root option\n}\n\n/**\n * Configuration options type (non-function values)\n */\nexport type TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;\n\n/**\n * Extract event handlers from options (pure function)\n */\nfunction extractHandlers(options: UseTapKitOptions): TapKitEventHandlers {\n return {\n onReady: options.onReady,\n onError: options.onError,\n onTimelineSeek: options.onTimelineSeek,\n onAlarmFadeIn: options.onAlarmFadeIn,\n onExamQuestionNavigate: options.onExamQuestionNavigate,\n };\n}\n\nexport interface UseTapKitReturn {\n /** Control object for <TapKit /> component */\n control: TapKitControl<TapKitOptions>;\n /** Whether TapKit is ready */\n isReady: boolean;\n /** Error during initialization */\n error: Error | null;\n /** Show chat interface */\n show: () => void;\n /** Hide chat interface */\n hide: () => void;\n /** Set course information */\n setCourse: (course: { courseId: string; clipId?: string; userId?: string; clipPlayHead?: number }) => void;\n /** Update external reference ID at runtime (pass undefined to clear) */\n setExternalReferenceId: (id?: string) => void;\n /** Update external reference data at runtime (pass undefined to clear) */\n setExternalReferenceData: (data?: string) => void;\n /** Send contextual data from host to iframe (AI tutor) */\n sendContext: (contextType: string, payload: Record<string, unknown>) => void;\n /**\n * Video adapter control\n *\n * @deprecated Use `videoTarget` option in useTapKit instead for declarative binding.\n *\n * ```tsx\n * // ✅ Recommended (declarative)\n * const tapkit = useTapKit({ videoTarget: adapter, ... });\n *\n * // ❌ Deprecated (imperative)\n * tapkit.video.bind(adapter);\n * ```\n */\n video: {\n bind: (adapter: VideoAdapter, clipId?: string) => void;\n unbind: () => void;\n };\n}\n\n/**\n * Hook for managing TapKit Web Component (ChatKit Pattern)\n *\n * Returns a control object to pass to <TapKit /> component.\n * The component handles rendering, this hook handles state and methods.\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n // Normalize rowDataForCodeEditor — accepts both `CodingRowData` and JSON string.\n // Throws synchronously on render if partial/invalid so the caller's React error\n // boundary catches config errors before the SDK is initialized.\n const normalizedRowData = useMemo(\n () => (options.rowDataForCodeEditor === undefined ? undefined : normalizeCodingRowData(options.rowDataForCodeEditor)),\n [options.rowDataForCodeEditor]\n );\n\n // Element ref - set by <TapKit /> component via control.setInstance\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Pending video bind request (queued until ready)\n const pendingVideoBind = useRef<{\n adapter: VideoAdapter;\n clipId?: string;\n } | null>(null);\n\n // State\n const [isCdnLoaded, setIsCdnLoaded] = useState(false);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Store handlers in refs to avoid stale closures\n const handlersRef = useRef(extractHandlers(options));\n handlersRef.current = extractHandlers(options);\n\n // Load CDN on mount (defines <tap-kit> Web Component)\n useEffect(() => {\n loadCDNLoader()\n .then(() => {\n setIsCdnLoaded(true);\n })\n .catch((err) => {\n const loadError = err instanceof Error ? err : new Error(\"Failed to load TapKit CDN\");\n setError(loadError);\n handlersRef.current.onError?.(loadError);\n });\n }, []);\n\n // Set instance callback - called by <TapKit /> component\n const setInstance = useCallback((instance: TapKitElement | null) => {\n elementRef.current = instance;\n\n if (!instance) {\n setIsReady(false);\n return;\n }\n\n // Listen for ready event\n const handleReady = () => {\n setIsReady(true);\n handlersRef.current.onReady?.();\n };\n\n const handleError = (e: Event) => {\n const customEvent = e as CustomEvent<{ error: Error }>;\n const err = customEvent.detail?.error ?? new Error(\"Unknown error\");\n setError(err);\n handlersRef.current.onError?.(err);\n };\n\n instance.addEventListener(\"tap-kit:ready\", handleReady);\n instance.addEventListener(\"tap-kit:error\", handleError);\n\n // Check if already ready\n if (instance.isInitialized) {\n setIsReady(true);\n }\n\n // Setup EventManager callbacks when ready\n // Use stable wrappers that call handlersRef.current to always get latest handlers\n const setupEventHandlers = () => {\n if (instance.events && typeof instance.events.onTimelineSeek === \"function\") {\n // Register stable wrappers instead of direct refs\n // This ensures handler updates are reflected without re-registration\n instance.events.onTimelineSeek((clipPlayHead, clipId) => {\n handlersRef.current.onTimelineSeek?.(clipPlayHead, clipId);\n });\n instance.events.onAlarmFadeIn((messageInfo) => {\n handlersRef.current.onAlarmFadeIn?.(messageInfo);\n });\n instance.events.onExamQuestionNavigate((clipId, questionNo) => {\n handlersRef.current.onExamQuestionNavigate?.(clipId, questionNo);\n });\n }\n\n // Execute pending video bind if queued\n if (pendingVideoBind.current && instance.video) {\n instance.video.bind(pendingVideoBind.current.adapter, pendingVideoBind.current.clipId);\n pendingVideoBind.current = null;\n }\n };\n\n // Check if ready property exists (may not be available immediately)\n if (instance.ready && typeof instance.ready.then === \"function\") {\n instance.ready.then(setupEventHandlers).catch(() => {\n // Ignore - error already handled by tap-kit:error event\n });\n } else if (instance.isInitialized) {\n // Already initialized, setup handlers immediately\n setupEventHandlers();\n } else {\n // Fallback: wait for ready event then setup handlers\n instance.addEventListener(\"tap-kit:ready\", setupEventHandlers, {\n once: true,\n });\n }\n }, []);\n\n // Memoize config options using individual values to prevent infinite re-renders\n // (options object is recreated on every render, so we must depend on primitives)\n // Note: videoTarget is object reference - changes trigger re-render intentionally\n const configOptions = useMemo(\n () => ({\n apiKey: options.apiKey,\n userId: options.userId,\n courseId: options.courseId,\n clipId: options.clipId,\n sectionId: options.sectionId,\n clipPlayHead: options.clipPlayHead,\n externalReferenceId: options.externalReferenceId,\n userInputCode: options.userInputCode,\n rowDataForCodeEditor: normalizedRowData,\n language: options.language,\n buttonId: options.buttonId,\n mode: options.mode,\n chatMode: options.chatMode,\n examSkill: options.examSkill,\n allowLayoutToggle: options.allowLayoutToggle,\n videoTarget: options.videoTarget,\n userInputCodeTarget: options.userInputCodeTarget,\n grading: options.grading,\n debug: options.debug,\n tapUrl: options.tapUrl,\n apiUrl: options.apiUrl,\n environment: options.environment,\n // Note: root removed - declarative API stays in declared position\n }),\n [\n options.apiKey,\n options.userId,\n options.courseId,\n options.clipId,\n options.sectionId,\n options.clipPlayHead,\n options.externalReferenceId,\n options.userInputCode,\n normalizedRowData,\n options.language,\n options.buttonId,\n options.mode,\n options.chatMode,\n options.examSkill,\n options.allowLayoutToggle,\n options.videoTarget,\n options.userInputCodeTarget,\n options.grading,\n options.debug,\n options.tapUrl,\n options.apiUrl,\n options.environment,\n ]\n );\n\n // Create stable handlers object for control\n // NOTE: This object is intentionally created once (empty deps) and appears to capture\n // stale values. However, the actual handler invocations in TapKit.tsx use handlersRef.current\n // which always has the latest handlers. This object is just a stable reference marker\n // that tells TapKit.tsx which handlers exist - not the actual handlers to call.\n const handlers = useMemo<TapKitEventHandlers>(\n () => ({\n onReady: handlersRef.current.onReady,\n onError: handlersRef.current.onError,\n onTimelineSeek: handlersRef.current.onTimelineSeek,\n onAlarmFadeIn: handlersRef.current.onAlarmFadeIn,\n onExamQuestionNavigate: handlersRef.current.onExamQuestionNavigate,\n }),\n []\n );\n\n // Create control object\n const control = useMemo<TapKitControl<TapKitOptions>>(\n () => ({\n setInstance,\n options: configOptions,\n handlers,\n isCdnLoaded,\n }),\n [setInstance, configOptions, handlers, isCdnLoaded]\n );\n\n // Methods\n const show = useCallback(() => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot show: element not mounted\");\n return;\n }\n elementRef.current.show();\n }, []);\n\n const hide = useCallback(() => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot hide: element not mounted\");\n return;\n }\n elementRef.current.hide();\n }, []);\n\n const setCourse = useCallback((course: { courseId: string; clipId?: string; userId?: string; clipPlayHead?: number }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n }, []);\n\n const setExternalReferenceId = useCallback((id?: string) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setExternalReferenceId: element not mounted\");\n return;\n }\n elementRef.current.externalReferenceId = id;\n }, []);\n\n const setExternalReferenceData = useCallback((data?: string) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setExternalReferenceData: element not mounted\");\n return;\n }\n elementRef.current.userInputCode = data;\n }, []);\n\n const sendContext = useCallback((contextType: string, payload: Record<string, unknown>) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot sendContext: element not mounted\");\n return;\n }\n elementRef.current.sendContext(contextType, payload);\n }, []);\n\n // Video adapter control\n const video = useMemo(\n () => ({\n bind: (adapter: VideoAdapter, clipId?: string) => {\n // If video controller is ready, bind immediately\n if (elementRef.current?.video) {\n elementRef.current.video.bind(adapter, clipId);\n return;\n }\n\n // Queue for later execution when ready\n pendingVideoBind.current = { adapter, clipId };\n },\n unbind: () => {\n // Clear pending request\n pendingVideoBind.current = null;\n elementRef.current?.video?.unbind();\n },\n }),\n []\n );\n\n return {\n control,\n isReady,\n error,\n show,\n hide,\n setCourse,\n setExternalReferenceId,\n setExternalReferenceData,\n sendContext,\n video,\n };\n}\n"]}
|