@coxwave/tap-kit 2.0.1 → 2.0.2

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/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { TapKitElement, TapKitConfig } from '@coxwave/tap-kit-types';
2
- import React$1 from 'react';
1
+ import { TapKitElement } from '@coxwave/tap-kit-types';
2
+ import React from 'react';
3
3
 
4
4
  /**
5
5
  * React-specific type definitions for TapKit
@@ -66,6 +66,12 @@ interface TapKitControl<T> {
66
66
  * Separated from options for easier event listener management.
67
67
  */
68
68
  handlers: TapKitEventHandlers;
69
+ /**
70
+ * Whether CDN is loaded and Web Component is available
71
+ *
72
+ * @internal Used by TapKit component to delay rendering
73
+ */
74
+ isCdnLoaded: boolean;
69
75
  }
70
76
 
71
77
  /**
@@ -110,24 +116,18 @@ interface TapKitControl<T> {
110
116
  /**
111
117
  * Props for TapKit React component
112
118
  */
113
- interface TapKitProps extends Omit<React$1.HTMLAttributes<TapKitElement>, "children" | "dangerouslySetInnerHTML"> {
119
+ interface TapKitProps extends Omit<React.HTMLAttributes<TapKitElement>, "children" | "dangerouslySetInnerHTML"> {
114
120
  /**
115
121
  * Control object from useTapKit hook
116
122
  *
117
123
  * Provides instance management, configuration, and event handlers.
118
124
  */
119
125
  control: TapKitControl<any>;
120
- /**
121
- * Custom container element ID (optional)
122
- *
123
- * If provided, TapKit will use this element as container.
124
- */
125
- containerId?: string;
126
126
  }
127
127
  declare global {
128
128
  namespace JSX {
129
129
  interface IntrinsicElements {
130
- "tap-kit": React$1.DetailedHTMLProps<React$1.HTMLAttributes<TapKitElement>, TapKitElement> & {
130
+ "tap-kit": React.DetailedHTMLProps<React.HTMLAttributes<TapKitElement>, TapKitElement> & {
131
131
  "api-key"?: string;
132
132
  "user-id"?: string;
133
133
  "course-id"?: string;
@@ -135,7 +135,6 @@ declare global {
135
135
  "clip-play-head"?: number;
136
136
  language?: "ko" | "en";
137
137
  "button-id"?: string;
138
- "container-id"?: string;
139
138
  mode?: "inline" | "floating" | "sidebar";
140
139
  debug?: boolean;
141
140
  "tap-url"?: string;
@@ -158,60 +157,45 @@ declare global {
158
157
  * // tapkitRef.current?.show()
159
158
  * ```
160
159
  */
161
- declare const TapKit: React$1.ForwardRefExoticComponent<TapKitProps & React$1.RefAttributes<TapKitElement>>;
160
+ declare const TapKit: React.ForwardRefExoticComponent<TapKitProps & React.RefAttributes<TapKitElement>>;
162
161
 
163
162
  /**
164
- * useTapKit Hook - Advanced imperative control of TapKit Web Component
165
- *
166
- * This hook provides direct access to the TapKitElement instance and full
167
- * control over its lifecycle. Use this when you need:
168
- * - Direct element manipulation
169
- * - Custom rendering logic
170
- * - Imperative control over Web Component behavior
163
+ * useTapKit Hook - ChatKit-style control for TapKit Web Component
171
164
  *
172
- * For most use cases, prefer the `<TapKit />` component which provides a
173
- * simpler declarative API.
165
+ * This hook provides a control object to manage TapKit through the
166
+ * <TapKit /> component. Inspired by OpenAI's ChatKit pattern.
174
167
  *
175
- * @param options - TapKit configuration
176
- * @returns Object with element reference, state, and control methods
177
- *
178
- * @example Advanced control with custom rendering
168
+ * @example
179
169
  * ```tsx
180
170
  * 'use client';
181
171
  *
182
- * import { useTapKit } from '@coxwave/tap-kit/react';
172
+ * import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
183
173
  *
184
- * function MyComponent() {
185
- * const { element, elementRef, show, hide, isReady, error } = useTapKit({
174
+ * function MyApp() {
175
+ * const tapkit = useTapKit({
186
176
  * apiKey: 'your-key',
187
177
  * userId: 'user-123',
188
178
  * courseId: 'course-456',
189
179
  * clipId: 'clip-789',
180
+ * onReady: () => console.log('Ready!'),
181
+ * onError: (error) => console.error(error),
190
182
  * });
191
183
  *
192
- * // Direct element access for advanced operations
193
- * useEffect(() => {
194
- * if (element) {
195
- * // Direct manipulation of TapKitElement
196
- * console.log('Element mounted:', element);
197
- * }
198
- * }, [element]);
199
- *
200
184
  * return (
201
185
  * <div>
202
- * <button onClick={show} disabled={!isReady}>Show Chat</button>
203
- * <button onClick={hide}>Hide Chat</button>
204
- * {error && <p>Error: {error.message}</p>}
205
- * <div ref={elementRef} /> // Container for Web Component
186
+ * <button onClick={tapkit.show} disabled={!tapkit.isReady}>
187
+ * Ask AI Tutor
188
+ * </button>
189
+ * <TapKit control={tapkit.control} />
206
190
  * </div>
207
191
  * );
208
192
  * }
209
193
  * ```
210
- *
211
- * @see TapKit - Use this component for simpler declarative API
212
194
  */
213
195
 
214
- interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {
196
+ interface UseTapKitOptions extends TapKitEventHandlers {
197
+ /** API Key (required) */
198
+ apiKey: string;
215
199
  /** User ID */
216
200
  userId?: string;
217
201
  /** Course ID */
@@ -240,13 +224,7 @@ interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {
240
224
  */
241
225
  type TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;
242
226
  interface UseTapKitReturn {
243
- /** Web Component element reference */
244
- element: TapKitElement | null;
245
- /** Ref object for direct element access */
246
- ref: React.RefObject<TapKitElement | null>;
247
- /** Container ref to attach element */
248
- elementRef: React.RefCallback<HTMLDivElement>;
249
- /** Control object for TapKit component */
227
+ /** Control object for <TapKit /> component */
250
228
  control: TapKitControl<TapKitOptions>;
251
229
  /** Whether TapKit is ready */
252
230
  isReady: boolean;
@@ -263,14 +241,22 @@ interface UseTapKitReturn {
263
241
  userId?: string;
264
242
  clipPlayHead?: number;
265
243
  }) => void;
244
+ /** Video adapter control */
245
+ video: {
246
+ /** Bind video player for timeline synchronization */
247
+ bind: (adapter: {
248
+ getCurrentTime: () => number;
249
+ setCurrentTime: (time: number) => void;
250
+ }, clipId: string) => void;
251
+ /** Unbind current video player */
252
+ unbind: () => void;
253
+ };
266
254
  }
267
255
  /**
268
- * Hook for managing TapKit Web Component
269
- *
270
- * Automatically loads CDN, creates Web Component, and provides control methods.
256
+ * Hook for managing TapKit Web Component (ChatKit Pattern)
271
257
  *
272
- * @param options - TapKit configuration with event handlers
273
- * @returns Methods to control TapKit instance and control object
258
+ * Returns a control object to pass to <TapKit /> component.
259
+ * The component handles rendering, this hook handles state and methods.
274
260
  */
275
261
  declare function useTapKit(options: UseTapKitOptions): UseTapKitReturn;
276
262
 
package/dist/react.js CHANGED
@@ -1,3 +1,374 @@
1
- 'use strict';var B=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var B__default=/*#__PURE__*/_interopDefault(B);var R={"tap-kit:ready":"onReady","tap-kit:error":"onError"},k=Object.keys(R);var X=B__default.default.forwardRef(function({control:t,containerId:i,...s},a){let o=B.useRef(null);B.useImperativeHandle(a,()=>o.current,[]),B.useEffect(()=>{let d=o.current;if(d)return t.setInstance(d),()=>{t.setInstance(null);}},[t.setInstance]),B.useEffect(()=>{let d=o.current;if(!d)return;let l=[];for(let c of k){let p=R[c],_=t.handlers[p];if(_){let f=m=>{let T=m;p==="onError"&&T.detail?.error?_(T.detail.error):p==="onReady"&&_();};d.addEventListener(c,f),l.push({event:c,handler:f});}}return ()=>{for(let{event:c,handler:p}of l)d.removeEventListener(c,p);}},[t]);let{options:r}=t;return jsxRuntime.jsx("tap-kit",{ref:o,"api-key":r.apiKey,"user-id":r.userId,"course-id":r.courseId,"clip-id":r.clipId,"clip-play-head":r.clipPlayHead,language:r.language,"button-id":r.buttonId,"container-id":i,mode:r.mode,debug:r.debug,"tap-url":r.tapUrl,"api-url":r.apiUrl,environment:r.environment,...s})});var W=typeof __DEFAULT_CDN_LOADER_URL__<"u"?__DEFAULT_CDN_LOADER_URL__:"https://files.edutap.ai/tap-sdk/loader.js",Y=4e3,h=500;function Z(){return window?.__TAP_KIT_LOADER_URL__?window.__TAP_KIT_LOADER_URL__:W}function j(){return typeof window<"u"&&!!window.__TAP_KIT_CORE_URL__}function ee(){return window.__TAP_KIT_CORE_URL__||""}function N(n,t,i){let s=Date.now(),a=()=>{if(window.TapKit&&window.TapKitLoaded===true){window.__TAP_KIT_LOADER_LOADED__=true,window.__TAP_KIT_LOADER_LOADING__=void 0,n();return}if(Date.now()-s>i){window.__TAP_KIT_LOADER_LOADING__=void 0,t(new Error(`TapKit loader timeout: SDK not available after ${i}ms`));return}typeof requestIdleCallback<"u"?requestIdleCallback(a,{timeout:h}):setTimeout(a,h);};return a}function H(n=Y){if(window.__TAP_KIT_LOADER_LOADED__&&window.TapKit)return Promise.resolve();if(window.__TAP_KIT_LOADER_LOADING__)return window.__TAP_KIT_LOADER_LOADING__;let t=new Promise((i,s)=>{if(typeof document>"u"){s(new Error("TapKit requires browser environment (document is undefined)"));return}if(j()){if(window.TapKit&&window.TapKitLoaded===true){window.__TAP_KIT_LOADER_LOADED__=true,window.__TAP_KIT_LOADER_LOADING__=void 0,i();return}let d=ee(),l=document.createElement("script");l.src=d,l.async=true,l.onload=()=>{window.TapKit?(window.TapKitLoaded=true,window.__TAP_KIT_LOADER_LOADED__=true,window.__TAP_KIT_LOADER_LOADING__=void 0,i()):(window.__TAP_KIT_LOADER_LOADING__=void 0,s(new Error("TapKit not available after loading local core")));},l.onerror=()=>{window.__TAP_KIT_LOADER_LOADING__=void 0,s(new Error(`Failed to load local TapKit core: ${d}`));},document.head.appendChild(l);return}let a=Z(),o=document.createElement("script");o.src=a,o.async=true,o.onload=()=>{N(i,s,n)();},o.onerror=()=>{window.__TAP_KIT_LOADER_LOADING__=void 0,s(new Error(`Failed to load TapKit CDN loader: ${a}`));};let r=document.querySelector(`script[src="${a}"]`);r?(r.addEventListener("load",()=>{N(i,s,n)();}),r.addEventListener("error",()=>s(new Error(`Failed to load TapKit CDN loader: ${a}`)))):document.head.appendChild(o);});return window.__TAP_KIT_LOADER_LOADING__=t,t}function ne(n){let{onReady:t,onError:i,onTimelineSeek:s,onAlarmFadeIn:a,...o}=n;return o}function te(n){return {onReady:n.onReady,onError:n.onError,onTimelineSeek:n.onTimelineSeek,onAlarmFadeIn:n.onAlarmFadeIn}}function re(n,t){let i=[];return t.onTimelineSeek&&i.push(n.events.onTimelineSeek(t.onTimelineSeek)),t.onAlarmFadeIn&&i.push(n.events.onAlarmFadeIn(t.onAlarmFadeIn)),i}function ie(n){let{onReady:t,onError:i,apiKey:s,userId:a,courseId:o,clipId:r,clipPlayHead:d,language:l,buttonId:c,mode:p="floating",debug:_,tapUrl:f,apiUrl:m,environment:T}=n,u=B.useRef(null),I=B.useRef(null),[E,O]=B.useState(false),[x,M]=B.useState(null),K=B.useRef(true),F=B.useCallback(e=>{I.current=e;},[]),b=B.useCallback(e=>{u.current=e;},[]);B.useEffect(()=>{K.current=true;let e=null;async function D(){try{if(await H(),await customElements.whenDefined("tap-kit"),!K.current||!I.current)return;if(!window.createTapKit)throw new Error("createTapKit not available after loading CDN");if(e=window.createTapKit({apiKey:s,userId:a,courseId:o,clipId:r,clipPlayHead:d,language:l,buttonId:c,mode:p,debug:_,tapUrl:f,apiUrl:m,environment:T}),I.current.appendChild(e),u.current=e,await e.ready,!K.current)return;O(!0),t&&t();}catch(w){let P=w instanceof Error?w:new Error(String(w));K.current&&(M(P),i&&i(P));}}return D(),()=>{K.current=false,e&&(e.remove(),u.current=null),O(false);}},[s,t,i]),B.useEffect(()=>{let e=u.current;!e||!E||(a!==void 0&&(e.userId=a),o!==void 0&&(e.courseId=o),r!==void 0&&(e.clipId=r),d!==void 0&&(e.clipPlayHead=d),l!==void 0&&(e.language=l),c!==void 0&&(e.buttonId=c),p!==void 0&&(e.mode=p),_!==void 0&&(e.debug=_),f!==void 0&&(e.tapUrl=f),m!==void 0&&(e.apiUrl=m),T!==void 0&&(e.environment=T));},[E,a,o,r,d,l,c,p,_,f,m,T]);let C=B.useMemo(()=>ne(n),[n]),L=B.useMemo(()=>te(n),[n]);B.useEffect(()=>{let e=u.current;if(!e||!E)return;let D=re(e,L);return ()=>{for(let w of D)w();}},[E,L]);let G=B.useMemo(()=>({setInstance:b,options:C,handlers:L}),[b,C,L]),V=B.useCallback(()=>{u.current&&u.current.show();},[]),$=B.useCallback(()=>{u.current&&u.current.hide();},[]),q=B.useCallback(e=>{u.current&&u.current.setCourse(e);},[]);return {element:u.current,ref:u,elementRef:F,control:G,isReady:E,error:x,show:V,hide:$,setCourse:q}}
2
- exports.TapKit=X;exports.useTapKit=ie;//# sourceMappingURL=react.js.map
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var React__default = /*#__PURE__*/_interopDefault(React);
9
+
10
+ // src/react/TapKit.tsx
11
+
12
+ // src/react/types.ts
13
+ var EVENT_HANDLER_MAP = {
14
+ "tap-kit:ready": "onReady",
15
+ "tap-kit:error": "onError"
16
+ };
17
+ var EVENT_NAMES = Object.keys(
18
+ EVENT_HANDLER_MAP
19
+ );
20
+ var TapKit = React__default.default.forwardRef(
21
+ function TapKit2({ control, ...htmlProps }, forwardedRef) {
22
+ const elementRef = React.useRef(null);
23
+ React.useImperativeHandle(
24
+ forwardedRef,
25
+ () => elementRef.current,
26
+ []
27
+ );
28
+ const setElementRef = React.useCallback(
29
+ (element) => {
30
+ elementRef.current = element;
31
+ if (element && control.options.apiKey) {
32
+ element.apiKey = control.options.apiKey;
33
+ }
34
+ control.setInstance(element);
35
+ },
36
+ [control.setInstance, control.options.apiKey]
37
+ );
38
+ const handlersRef = React.useRef(control.handlers);
39
+ handlersRef.current = control.handlers;
40
+ React.useEffect(() => {
41
+ const element = elementRef.current;
42
+ if (!element) return;
43
+ const listeners = [];
44
+ for (const eventName of EVENT_NAMES) {
45
+ const handlerKey = EVENT_HANDLER_MAP[eventName];
46
+ const listener = (e) => {
47
+ const handler = handlersRef.current[handlerKey];
48
+ if (!handler) return;
49
+ const customEvent = e;
50
+ if (handlerKey === "onError" && customEvent.detail?.error) {
51
+ handler(customEvent.detail.error);
52
+ } else if (handlerKey === "onReady") {
53
+ handler();
54
+ }
55
+ };
56
+ element.addEventListener(eventName, listener);
57
+ listeners.push({ event: eventName, handler: listener });
58
+ }
59
+ return () => {
60
+ for (const { event, handler } of listeners) {
61
+ element.removeEventListener(event, handler);
62
+ }
63
+ };
64
+ }, []);
65
+ const { options, isCdnLoaded } = control;
66
+ if (!isCdnLoaded) {
67
+ return null;
68
+ }
69
+ return /* @__PURE__ */ jsxRuntime.jsx(
70
+ "tap-kit",
71
+ {
72
+ ref: setElementRef,
73
+ "user-id": options.userId,
74
+ "course-id": options.courseId,
75
+ "clip-id": options.clipId,
76
+ "clip-play-head": options.clipPlayHead,
77
+ language: options.language,
78
+ "button-id": options.buttonId,
79
+ mode: options.mode,
80
+ debug: options.debug,
81
+ "tap-url": options.tapUrl,
82
+ "api-url": options.apiUrl,
83
+ environment: options.environment,
84
+ ...htmlProps
85
+ }
86
+ );
87
+ }
88
+ );
89
+
90
+ // src/loader.ts
91
+ var DEFAULT_CDN_LOADER_URL = typeof __DEFAULT_CDN_LOADER_URL__ !== "undefined" ? __DEFAULT_CDN_LOADER_URL__ : "https://files.edutap.ai/tap-sdk/loader.js";
92
+ var DEFAULT_TIMEOUT_MS = 4e3;
93
+ var IDLE_CALLBACK_TIMEOUT_MS = 500;
94
+ function getLoaderURL() {
95
+ return window?.__TAP_KIT_LOADER_URL__ ? window.__TAP_KIT_LOADER_URL__ : DEFAULT_CDN_LOADER_URL;
96
+ }
97
+ function isLocalCoreMode() {
98
+ return typeof window !== "undefined" && !!window.__TAP_KIT_CORE_URL__;
99
+ }
100
+ function getLocalCoreURL() {
101
+ return window.__TAP_KIT_CORE_URL__ || "";
102
+ }
103
+ function createSDKChecker(resolve, reject, timeoutMs) {
104
+ const startTime = Date.now();
105
+ const checkSDK = () => {
106
+ if (window.TapKit && window.TapKitLoaded === true) {
107
+ window.__TAP_KIT_LOADER_LOADED__ = true;
108
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
109
+ resolve();
110
+ return;
111
+ }
112
+ const elapsed = Date.now() - startTime;
113
+ if (elapsed > timeoutMs) {
114
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
115
+ reject(
116
+ new Error(
117
+ `TapKit loader timeout: SDK not available after ${timeoutMs}ms`
118
+ )
119
+ );
120
+ return;
121
+ }
122
+ if (typeof requestIdleCallback !== "undefined") {
123
+ requestIdleCallback(checkSDK, { timeout: IDLE_CALLBACK_TIMEOUT_MS });
124
+ } else {
125
+ setTimeout(checkSDK, IDLE_CALLBACK_TIMEOUT_MS);
126
+ }
127
+ };
128
+ return checkSDK;
129
+ }
130
+ function loadCDNLoader(timeoutMs = DEFAULT_TIMEOUT_MS) {
131
+ if (window.__TAP_KIT_LOADER_LOADED__ && window.TapKit) {
132
+ return Promise.resolve();
133
+ }
134
+ if (window.__TAP_KIT_LOADER_LOADING__) {
135
+ return window.__TAP_KIT_LOADER_LOADING__;
136
+ }
137
+ const loadingPromise = new Promise((resolve, reject) => {
138
+ if (typeof document === "undefined") {
139
+ reject(
140
+ new Error(
141
+ "TapKit requires browser environment (document is undefined)"
142
+ )
143
+ );
144
+ return;
145
+ }
146
+ if (isLocalCoreMode()) {
147
+ if (window.TapKit && window.TapKitLoaded === true) {
148
+ window.__TAP_KIT_LOADER_LOADED__ = true;
149
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
150
+ resolve();
151
+ return;
152
+ }
153
+ const coreURL = getLocalCoreURL();
154
+ const script2 = document.createElement("script");
155
+ script2.src = coreURL;
156
+ script2.async = true;
157
+ script2.onload = () => {
158
+ if (window.TapKit) {
159
+ window.TapKitLoaded = true;
160
+ window.__TAP_KIT_LOADER_LOADED__ = true;
161
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
162
+ resolve();
163
+ } else {
164
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
165
+ reject(new Error("TapKit not available after loading local core"));
166
+ }
167
+ };
168
+ script2.onerror = () => {
169
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
170
+ reject(new Error(`Failed to load local TapKit core: ${coreURL}`));
171
+ };
172
+ document.head.appendChild(script2);
173
+ return;
174
+ }
175
+ const loaderURL = getLoaderURL();
176
+ const script = document.createElement("script");
177
+ script.src = loaderURL;
178
+ script.async = true;
179
+ script.onload = () => {
180
+ const checkSDK = createSDKChecker(resolve, reject, timeoutMs);
181
+ checkSDK();
182
+ };
183
+ script.onerror = () => {
184
+ window.__TAP_KIT_LOADER_LOADING__ = void 0;
185
+ reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`));
186
+ };
187
+ const existingScript = document.querySelector(`script[src="${loaderURL}"]`);
188
+ if (existingScript) {
189
+ existingScript.addEventListener("load", () => {
190
+ const checkSDK = createSDKChecker(resolve, reject, timeoutMs);
191
+ checkSDK();
192
+ });
193
+ existingScript.addEventListener(
194
+ "error",
195
+ () => reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`))
196
+ );
197
+ } else {
198
+ document.head.appendChild(script);
199
+ }
200
+ });
201
+ window.__TAP_KIT_LOADER_LOADING__ = loadingPromise;
202
+ return loadingPromise;
203
+ }
204
+
205
+ // src/react/useTapKit.ts
206
+ function extractHandlers(options) {
207
+ return {
208
+ onReady: options.onReady,
209
+ onError: options.onError,
210
+ onTimelineSeek: options.onTimelineSeek,
211
+ onAlarmFadeIn: options.onAlarmFadeIn
212
+ };
213
+ }
214
+ function useTapKit(options) {
215
+ const elementRef = React.useRef(null);
216
+ const [isCdnLoaded, setIsCdnLoaded] = React.useState(false);
217
+ const [isReady, setIsReady] = React.useState(false);
218
+ const [error, setError] = React.useState(null);
219
+ const handlersRef = React.useRef(extractHandlers(options));
220
+ handlersRef.current = extractHandlers(options);
221
+ React.useEffect(() => {
222
+ loadCDNLoader().then(() => {
223
+ setIsCdnLoaded(true);
224
+ }).catch((err) => {
225
+ const loadError = err instanceof Error ? err : new Error("Failed to load TapKit CDN");
226
+ setError(loadError);
227
+ handlersRef.current.onError?.(loadError);
228
+ });
229
+ }, []);
230
+ const setInstance = React.useCallback((instance) => {
231
+ elementRef.current = instance;
232
+ if (!instance) {
233
+ setIsReady(false);
234
+ return;
235
+ }
236
+ const handleReady = () => {
237
+ setIsReady(true);
238
+ handlersRef.current.onReady?.();
239
+ };
240
+ const handleError = (e) => {
241
+ const customEvent = e;
242
+ const err = customEvent.detail?.error ?? new Error("Unknown error");
243
+ setError(err);
244
+ handlersRef.current.onError?.(err);
245
+ };
246
+ instance.addEventListener("tap-kit:ready", handleReady);
247
+ instance.addEventListener("tap-kit:error", handleError);
248
+ if (instance.isInitialized) {
249
+ setIsReady(true);
250
+ }
251
+ const setupEventHandlers = () => {
252
+ if (instance.events && typeof instance.events.onTimelineSeek === "function") {
253
+ if (handlersRef.current.onTimelineSeek) {
254
+ instance.events.onTimelineSeek(handlersRef.current.onTimelineSeek);
255
+ }
256
+ if (handlersRef.current.onAlarmFadeIn) {
257
+ instance.events.onAlarmFadeIn(handlersRef.current.onAlarmFadeIn);
258
+ }
259
+ }
260
+ };
261
+ if (instance.ready && typeof instance.ready.then === "function") {
262
+ instance.ready.then(setupEventHandlers).catch(() => {
263
+ });
264
+ } else if (instance.isInitialized) {
265
+ setupEventHandlers();
266
+ } else {
267
+ instance.addEventListener("tap-kit:ready", setupEventHandlers, {
268
+ once: true
269
+ });
270
+ }
271
+ }, []);
272
+ const configOptions = React.useMemo(
273
+ () => ({
274
+ apiKey: options.apiKey,
275
+ userId: options.userId,
276
+ courseId: options.courseId,
277
+ clipId: options.clipId,
278
+ clipPlayHead: options.clipPlayHead,
279
+ language: options.language,
280
+ buttonId: options.buttonId,
281
+ mode: options.mode,
282
+ debug: options.debug,
283
+ tapUrl: options.tapUrl,
284
+ apiUrl: options.apiUrl,
285
+ environment: options.environment
286
+ }),
287
+ [
288
+ options.apiKey,
289
+ options.userId,
290
+ options.courseId,
291
+ options.clipId,
292
+ options.clipPlayHead,
293
+ options.language,
294
+ options.buttonId,
295
+ options.mode,
296
+ options.debug,
297
+ options.tapUrl,
298
+ options.apiUrl,
299
+ options.environment
300
+ ]
301
+ );
302
+ const handlers = React.useMemo(
303
+ () => ({
304
+ onReady: handlersRef.current.onReady,
305
+ onError: handlersRef.current.onError,
306
+ onTimelineSeek: handlersRef.current.onTimelineSeek,
307
+ onAlarmFadeIn: handlersRef.current.onAlarmFadeIn
308
+ }),
309
+ // Empty deps - handlers are accessed via ref which is always up-to-date
310
+ []
311
+ );
312
+ const control = React.useMemo(
313
+ () => ({
314
+ setInstance,
315
+ options: configOptions,
316
+ handlers,
317
+ isCdnLoaded
318
+ }),
319
+ [setInstance, configOptions, handlers, isCdnLoaded]
320
+ );
321
+ const show = React.useCallback(() => {
322
+ if (!elementRef.current) {
323
+ console.warn("[useTapKit] Cannot show: element not mounted");
324
+ return;
325
+ }
326
+ elementRef.current.show();
327
+ }, []);
328
+ const hide = React.useCallback(() => {
329
+ if (!elementRef.current) {
330
+ console.warn("[useTapKit] Cannot hide: element not mounted");
331
+ return;
332
+ }
333
+ elementRef.current.hide();
334
+ }, []);
335
+ const setCourse = React.useCallback(
336
+ (course) => {
337
+ if (!elementRef.current) {
338
+ console.warn("[useTapKit] Cannot setCourse: element not mounted");
339
+ return;
340
+ }
341
+ elementRef.current.setCourse(course);
342
+ },
343
+ []
344
+ );
345
+ const video = React.useMemo(
346
+ () => ({
347
+ bind: (adapter, clipId) => {
348
+ if (!elementRef.current) {
349
+ console.warn("[useTapKit] Cannot bind video: element not mounted");
350
+ return;
351
+ }
352
+ elementRef.current.video.bind(adapter, clipId);
353
+ },
354
+ unbind: () => {
355
+ elementRef.current?.video?.unbind();
356
+ }
357
+ }),
358
+ []
359
+ );
360
+ return {
361
+ control,
362
+ isReady,
363
+ error,
364
+ show,
365
+ hide,
366
+ setCourse,
367
+ video
368
+ };
369
+ }
370
+
371
+ exports.TapKit = TapKit;
372
+ exports.useTapKit = useTapKit;
373
+ //# sourceMappingURL=react.js.map
3
374
  //# sourceMappingURL=react.js.map