@coxwave/tap-kit 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -72
- package/dist/react.d.cts +173 -5
- package/dist/react.d.ts +173 -5
- package/dist/react.js +2 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +2 -2
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,120 +17,223 @@ npm install @coxwave/tap-kit
|
|
|
17
17
|
|
|
18
18
|
## React에서 사용하기 (권장)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
### 방법 1: TapKit 컴포넌트 (선언적)
|
|
21
|
+
|
|
22
|
+
가장 간단한 방법은 `<TapKit />` 컴포넌트를 사용하는 것입니다:
|
|
21
23
|
|
|
22
24
|
```tsx
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
'use client'; // Next.js App Router 사용 시
|
|
26
|
+
|
|
27
|
+
import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
|
|
25
28
|
|
|
26
29
|
function App() {
|
|
27
|
-
const
|
|
30
|
+
const tapkit = useTapKit({
|
|
28
31
|
apiKey: 'your-api-key',
|
|
32
|
+
userId: 'user-123',
|
|
33
|
+
courseId: 'course-456',
|
|
34
|
+
clipId: 'clip-789',
|
|
35
|
+
mode: 'floating',
|
|
36
|
+
onReady: () => console.log('TapKit 준비 완료!'),
|
|
37
|
+
onError: (error) => console.error('에러:', error),
|
|
29
38
|
});
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<button onClick={tapkit.show} disabled={!tapkit.isReady}>
|
|
43
|
+
AI 튜터에게 질문하기
|
|
44
|
+
</button>
|
|
45
|
+
<TapKit control={tapkit.control} style={{ height: '600px' }} />
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 방법 2: useTapKit Hook (고급)
|
|
52
|
+
|
|
53
|
+
직접 Web Component를 제어하려면 hook만 사용할 수 있습니다:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
'use client'; // Next.js App Router 사용 시
|
|
57
|
+
|
|
58
|
+
import { useTapKit } from '@coxwave/tap-kit/react';
|
|
59
|
+
|
|
60
|
+
function App() {
|
|
61
|
+
const { elementRef, show, hide, isReady, error } = useTapKit({
|
|
62
|
+
apiKey: 'your-api-key',
|
|
63
|
+
userId: 'user-123',
|
|
64
|
+
courseId: 'course-456',
|
|
65
|
+
clipId: 'clip-789',
|
|
66
|
+
});
|
|
42
67
|
|
|
43
68
|
if (error) {
|
|
44
69
|
return <div>에러 발생: {error.message}</div>;
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<button onClick={show} disabled={!isReady}>AI 튜터 열기</button>
|
|
75
|
+
<button onClick={hide}>AI 튜터 닫기</button>
|
|
76
|
+
<div ref={elementRef} />
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
52
79
|
}
|
|
53
80
|
```
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
>
|
|
57
|
-
> ```tsx
|
|
58
|
-
> <tap-button position="bottom-left" size="large" />
|
|
59
|
-
> ```
|
|
60
|
-
|
|
61
|
-
## Vanilla JS에서 사용하기
|
|
62
|
-
|
|
63
|
-
Vanilla JavaScript로 사용하려면 [TapKit 시작하기](https://edutap-ai-docs.vercel.app/docs/sdk/npm)를 참고하세요.
|
|
64
|
-
|
|
65
|
-
## CDN에서 사용하기
|
|
82
|
+
### 이벤트 핸들러
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
TapKit은 다양한 이벤트 핸들러를 지원합니다:
|
|
68
85
|
|
|
69
|
-
|
|
86
|
+
```tsx
|
|
87
|
+
const tapkit = useTapKit({
|
|
88
|
+
apiKey: 'your-api-key',
|
|
89
|
+
|
|
90
|
+
// SDK 준비 완료
|
|
91
|
+
onReady: () => {
|
|
92
|
+
console.log('TapKit이 준비되었습니다!');
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// 에러 발생
|
|
96
|
+
onError: (error) => {
|
|
97
|
+
console.error('TapKit 에러:', error);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// 타임라인 탐색 (사용자가 영상 특정 구간으로 이동 요청)
|
|
101
|
+
onTimelineSeek: (clipPlayHead, clipId) => {
|
|
102
|
+
console.log(`${clipId}의 ${clipPlayHead}초로 이동`);
|
|
103
|
+
// 여기서 비디오 플레이어를 제어할 수 있습니다
|
|
104
|
+
videoPlayer.seekTo(clipPlayHead);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// 알람 표시 (AI가 중요 포인트 강조)
|
|
108
|
+
onAlarmFadeIn: (messageInfo) => {
|
|
109
|
+
console.log('알람 표시:', messageInfo);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
70
113
|
|
|
71
|
-
|
|
114
|
+
### 동적 코스 변경
|
|
72
115
|
|
|
73
116
|
```tsx
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
function VideoPlayer({ videoId }) {
|
|
118
|
+
const tapkit = useTapKit({
|
|
119
|
+
apiKey: 'your-api-key',
|
|
120
|
+
userId: 'user-123',
|
|
121
|
+
});
|
|
77
122
|
|
|
123
|
+
// 비디오가 변경될 때 코스 업데이트
|
|
78
124
|
useEffect(() => {
|
|
79
|
-
if (isReady) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
if (tapkit.isReady) {
|
|
126
|
+
tapkit.setCourse({
|
|
127
|
+
courseId: 'course-456',
|
|
128
|
+
clipId: videoId,
|
|
129
|
+
clipPlayHead: 0,
|
|
83
130
|
});
|
|
84
131
|
}
|
|
85
|
-
}, [
|
|
132
|
+
}, [videoId, tapkit]);
|
|
86
133
|
|
|
87
|
-
return <
|
|
134
|
+
return <TapKit control={tapkit.control} />;
|
|
88
135
|
}
|
|
89
136
|
```
|
|
90
137
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
### Ref로 직접 제어
|
|
139
|
+
|
|
140
|
+
TapKit 컴포넌트와 useTapKit hook 모두 ref를 통해 Web Component에 직접 접근할 수 있습니다:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
function App() {
|
|
144
|
+
const tapkit = useTapKit({ apiKey: 'your-api-key' });
|
|
145
|
+
const tapkitRef = useRef<TapKitElement>(null);
|
|
146
|
+
|
|
147
|
+
const handleCustomAction = () => {
|
|
148
|
+
// 방법 1: hook의 메서드 사용
|
|
149
|
+
tapkit.show();
|
|
150
|
+
|
|
151
|
+
// 방법 2: 컴포넌트 ref 사용
|
|
152
|
+
tapkitRef.current?.show();
|
|
153
|
+
|
|
154
|
+
// 방법 3: hook의 ref 사용
|
|
155
|
+
tapkit.ref.current?.show();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<button onClick={handleCustomAction}>열기</button>
|
|
161
|
+
<TapKit ref={tapkitRef} control={tapkit.control} />
|
|
162
|
+
</>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
97
165
|
```
|
|
98
166
|
|
|
167
|
+
## Vanilla JS에서 사용하기
|
|
168
|
+
|
|
169
|
+
Vanilla JavaScript로 사용하려면 [TapKit 시작하기](https://edutap-ai-docs.vercel.app/docs/sdk/npm)를 참고하세요.
|
|
170
|
+
|
|
171
|
+
## CDN에서 사용하기
|
|
172
|
+
|
|
173
|
+
npm 없이 CDN으로 직접 사용하려면 [CDN 사용 가이드](https://edutap-ai-docs.vercel.app/docs/sdk/cdn)를 참고하세요.
|
|
174
|
+
|
|
99
175
|
## API Reference
|
|
100
176
|
|
|
101
|
-
###
|
|
177
|
+
### `<TapKit />` Component
|
|
178
|
+
|
|
179
|
+
`<tap-kit>` Web Component를 React에서 선언적으로 사용할 수 있는 컴포넌트입니다.
|
|
180
|
+
|
|
181
|
+
**Props:**
|
|
182
|
+
- `control: TapKitControl` - useTapKit hook에서 반환된 control 객체 (필수)
|
|
183
|
+
- `containerId?: string` - 커스텀 컨테이너 엘리먼트 ID (선택)
|
|
184
|
+
- `style?: CSSProperties` - 인라인 스타일 (선택)
|
|
185
|
+
- `className?: string` - CSS 클래스 (선택)
|
|
186
|
+
- `ref?: Ref<TapKitElement>` - Web Component에 대한 ref (선택)
|
|
187
|
+
|
|
188
|
+
**Ref 사용 예시:**
|
|
189
|
+
```tsx
|
|
190
|
+
const tapkitRef = useRef<TapKitElement>(null);
|
|
191
|
+
|
|
192
|
+
<TapKit ref={tapkitRef} control={tapkit.control} />
|
|
193
|
+
|
|
194
|
+
// ref로 Web Component 직접 제어
|
|
195
|
+
tapkitRef.current?.show();
|
|
196
|
+
tapkitRef.current?.hide();
|
|
197
|
+
tapkitRef.current?.setCourse({ courseId: 'new', clipId: 'new-1' });
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `useTapKit(options)`
|
|
102
201
|
|
|
103
202
|
TapKit 인스턴스를 생성하고 관리하는 React Hook입니다.
|
|
104
203
|
|
|
105
204
|
**Parameters:**
|
|
106
|
-
- `
|
|
205
|
+
- `options.apiKey: string` - API 키 (필수)
|
|
206
|
+
- `options.userId?: string` - 사용자 ID
|
|
207
|
+
- `options.courseId?: string` - 코스 ID
|
|
208
|
+
- `options.clipId?: string` - 클립 ID
|
|
209
|
+
- `options.clipPlayHead?: number` - 재생 위치 (초)
|
|
210
|
+
- `options.language?: 'ko' | 'en'` - 언어 설정
|
|
211
|
+
- `options.mode?: 'inline' | 'floating' | 'sidebar'` - 표시 모드
|
|
212
|
+
- `options.buttonId?: string` - 커스텀 버튼 엘리먼트 ID
|
|
213
|
+
- `options.debug?: boolean` - 디버그 모드
|
|
214
|
+
- `options.onReady?: () => void` - SDK 준비 완료 이벤트
|
|
215
|
+
- `options.onError?: (error: Error) => void` - 에러 발생 이벤트
|
|
216
|
+
- `options.onTimelineSeek?: (clipPlayHead: number, clipId: string) => void` - 타임라인 탐색 이벤트
|
|
217
|
+
- `options.onAlarmFadeIn?: (messageInfo: unknown) => void` - 알람 표시 이벤트
|
|
107
218
|
|
|
108
219
|
**Returns:**
|
|
109
|
-
- `kit: TapKitInstance | null` - TapKit 인스턴스
|
|
110
|
-
- `isReady: boolean` - 로딩 완료 여부
|
|
111
|
-
- `error: Error | null` - 에러 객체
|
|
112
|
-
- `init: (params) => Promise<() => void>` - 초기화 함수
|
|
113
220
|
|
|
114
|
-
|
|
221
|
+
**인스턴스 접근:**
|
|
222
|
+
- `element: TapKitElement | null` - Web Component 엘리먼트 (직접 접근용)
|
|
223
|
+
- `ref: RefObject<TapKitElement | null>` - 엘리먼트 ref 객체
|
|
224
|
+
- `elementRef: RefCallback<HTMLDivElement>` - 컨테이너 ref (Hook 패턴 전용)
|
|
115
225
|
|
|
116
|
-
|
|
226
|
+
**제어 객체:**
|
|
227
|
+
- `control: TapKitControl` - TapKit 컴포넌트에 전달할 control 객체
|
|
117
228
|
|
|
118
|
-
|
|
119
|
-
|
|
229
|
+
**상태:**
|
|
230
|
+
- `isReady: boolean` - TapKit 준비 완료 여부
|
|
231
|
+
- `error: Error | null` - 초기화 에러
|
|
120
232
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
</TapKitProvider>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function ChildComponent() {
|
|
130
|
-
const { kit, isReady } = useTapKitContext();
|
|
131
|
-
// ...
|
|
132
|
-
}
|
|
133
|
-
```
|
|
233
|
+
**메서드:**
|
|
234
|
+
- `show: () => void` - 채팅 인터페이스 표시
|
|
235
|
+
- `hide: () => void` - 채팅 인터페이스 숨김
|
|
236
|
+
- `setCourse: (course) => void` - 코스 정보 업데이트
|
|
134
237
|
|
|
135
238
|
## 문서
|
|
136
239
|
|
package/dist/react.d.cts
CHANGED
|
@@ -1,4 +1,164 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TapKitElement, TapKitConfig } from '@coxwave/tap-kit-types';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React-specific type definitions for TapKit
|
|
6
|
+
*
|
|
7
|
+
* Defines event handler types and control patterns for React integration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Event handler type definitions for TapKit Web Component
|
|
12
|
+
*
|
|
13
|
+
* Maps CustomEvent types to React callback signatures.
|
|
14
|
+
*/
|
|
15
|
+
interface TapKitEventHandlers {
|
|
16
|
+
/**
|
|
17
|
+
* Called when TapKit SDK is ready
|
|
18
|
+
* @example onReady={() => console.log('TapKit ready!')}
|
|
19
|
+
*/
|
|
20
|
+
onReady?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Called when initialization or runtime error occurs
|
|
23
|
+
* @example onError={(error) => console.error('TapKit error:', error)}
|
|
24
|
+
*/
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Called when timeline seek event occurs
|
|
28
|
+
* @example onTimelineSeek={(clipPlayHead, clipId) => console.log('Seek:', clipPlayHead)}
|
|
29
|
+
*/
|
|
30
|
+
onTimelineSeek?: (clipPlayHead: number, clipId: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Called when alarm fade-in occurs
|
|
33
|
+
* @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}
|
|
34
|
+
*/
|
|
35
|
+
onAlarmFadeIn?: (messageInfo: unknown) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Control object pattern for managing TapKit instance
|
|
39
|
+
*
|
|
40
|
+
* Separates instance management (setInstance) from configuration (options)
|
|
41
|
+
* and event handling (handlers). Inspired by ChatKit's control pattern.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const tapkit = useTapKit({ apiKey: 'key', onReady: () => {} });
|
|
46
|
+
* <TapKit control={tapkit.control} />
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
interface TapKitControl<T> {
|
|
50
|
+
/**
|
|
51
|
+
* Set the TapKit element instance reference
|
|
52
|
+
*
|
|
53
|
+
* Called by TapKit component to register the element instance.
|
|
54
|
+
* Enables imperative control via useTapKit methods.
|
|
55
|
+
*/
|
|
56
|
+
setInstance: (instance: TapKitElement | null) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Configuration options (non-function values)
|
|
59
|
+
*
|
|
60
|
+
* All options except event handlers.
|
|
61
|
+
*/
|
|
62
|
+
options: T;
|
|
63
|
+
/**
|
|
64
|
+
* Event handler callbacks (function values)
|
|
65
|
+
*
|
|
66
|
+
* Separated from options for easier event listener management.
|
|
67
|
+
*/
|
|
68
|
+
handlers: TapKitEventHandlers;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* TapKit React Component
|
|
73
|
+
*
|
|
74
|
+
* Declarative React wrapper for <tap-kit> Web Component.
|
|
75
|
+
* Use this with useTapKit hook for a clean separation of control and rendering.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* 'use client';
|
|
80
|
+
*
|
|
81
|
+
* import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
|
|
82
|
+
*
|
|
83
|
+
* function MyApp() {
|
|
84
|
+
* const tapkit = useTapKit({
|
|
85
|
+
* apiKey: 'your-key',
|
|
86
|
+
* userId: 'user-123',
|
|
87
|
+
* courseId: 'course-456',
|
|
88
|
+
* clipId: 'clip-789',
|
|
89
|
+
* onReady: () => console.log('TapKit ready!'),
|
|
90
|
+
* onError: (error) => console.error('Error:', error),
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* return (
|
|
94
|
+
* <div>
|
|
95
|
+
* <button onClick={tapkit.show} disabled={!tapkit.isReady}>
|
|
96
|
+
* Show Chat
|
|
97
|
+
* </button>
|
|
98
|
+
* <TapKit control={tapkit.control} style={{ height: '600px' }} />
|
|
99
|
+
* </div>
|
|
100
|
+
* );
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* **Note for Next.js App Router users:**
|
|
105
|
+
* Make sure to add 'use client' at the top of your component file.
|
|
106
|
+
*
|
|
107
|
+
* @see https://edutap-ai-docs.vercel.app/docs/guides/react
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Props for TapKit React component
|
|
112
|
+
*/
|
|
113
|
+
interface TapKitProps extends Omit<React$1.HTMLAttributes<TapKitElement>, "children" | "dangerouslySetInnerHTML"> {
|
|
114
|
+
/**
|
|
115
|
+
* Control object from useTapKit hook
|
|
116
|
+
*
|
|
117
|
+
* Provides instance management, configuration, and event handlers.
|
|
118
|
+
*/
|
|
119
|
+
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
|
+
}
|
|
127
|
+
declare global {
|
|
128
|
+
namespace JSX {
|
|
129
|
+
interface IntrinsicElements {
|
|
130
|
+
"tap-kit": React$1.DetailedHTMLProps<React$1.HTMLAttributes<TapKitElement>, TapKitElement> & {
|
|
131
|
+
"api-key"?: string;
|
|
132
|
+
"user-id"?: string;
|
|
133
|
+
"course-id"?: string;
|
|
134
|
+
"clip-id"?: string;
|
|
135
|
+
"clip-play-head"?: number;
|
|
136
|
+
language?: "ko" | "en";
|
|
137
|
+
"button-id"?: string;
|
|
138
|
+
"container-id"?: string;
|
|
139
|
+
mode?: "inline" | "floating" | "sidebar";
|
|
140
|
+
debug?: boolean;
|
|
141
|
+
"tap-url"?: string;
|
|
142
|
+
"api-url"?: string;
|
|
143
|
+
environment?: "dev" | "prod" | "staging" | "demo";
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* TapKit React Component
|
|
150
|
+
*
|
|
151
|
+
* Renders <tap-kit> Web Component with React integration.
|
|
152
|
+
* Automatically manages instance lifecycle and event listeners.
|
|
153
|
+
*
|
|
154
|
+
* **Ref Access**: The forwarded ref provides direct access to the TapKitElement:
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const tapkitRef = useRef<TapKitElement>(null);
|
|
157
|
+
* <TapKit ref={tapkitRef} control={...} />
|
|
158
|
+
* // tapkitRef.current?.show()
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare const TapKit: React$1.ForwardRefExoticComponent<TapKitProps & React$1.RefAttributes<TapKitElement>>;
|
|
2
162
|
|
|
3
163
|
/**
|
|
4
164
|
* useTapKit Hook - Advanced imperative control of TapKit Web Component
|
|
@@ -51,7 +211,7 @@ import { TapKitConfig, TapKitElement } from '@coxwave/tap-kit-types';
|
|
|
51
211
|
* @see TapKit - Use this component for simpler declarative API
|
|
52
212
|
*/
|
|
53
213
|
|
|
54
|
-
interface UseTapKitOptions extends TapKitConfig {
|
|
214
|
+
interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {
|
|
55
215
|
/** User ID */
|
|
56
216
|
userId?: string;
|
|
57
217
|
/** Course ID */
|
|
@@ -75,11 +235,19 @@ interface UseTapKitOptions extends TapKitConfig {
|
|
|
75
235
|
/** Environment */
|
|
76
236
|
environment?: "dev" | "prod" | "staging" | "demo";
|
|
77
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Configuration options type (non-function values)
|
|
240
|
+
*/
|
|
241
|
+
type TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;
|
|
78
242
|
interface UseTapKitReturn {
|
|
79
243
|
/** Web Component element reference */
|
|
80
244
|
element: TapKitElement | null;
|
|
245
|
+
/** Ref object for direct element access */
|
|
246
|
+
ref: React.RefObject<TapKitElement | null>;
|
|
81
247
|
/** Container ref to attach element */
|
|
82
248
|
elementRef: React.RefCallback<HTMLDivElement>;
|
|
249
|
+
/** Control object for TapKit component */
|
|
250
|
+
control: TapKitControl<TapKitOptions>;
|
|
83
251
|
/** Whether TapKit is ready */
|
|
84
252
|
isReady: boolean;
|
|
85
253
|
/** Error during initialization */
|
|
@@ -101,9 +269,9 @@ interface UseTapKitReturn {
|
|
|
101
269
|
*
|
|
102
270
|
* Automatically loads CDN, creates Web Component, and provides control methods.
|
|
103
271
|
*
|
|
104
|
-
* @param options - TapKit configuration
|
|
105
|
-
* @returns Methods to control TapKit instance
|
|
272
|
+
* @param options - TapKit configuration with event handlers
|
|
273
|
+
* @returns Methods to control TapKit instance and control object
|
|
106
274
|
*/
|
|
107
275
|
declare function useTapKit(options: UseTapKitOptions): UseTapKitReturn;
|
|
108
276
|
|
|
109
|
-
export { type UseTapKitOptions, type UseTapKitReturn, useTapKit };
|
|
277
|
+
export { TapKit, type TapKitControl, type TapKitEventHandlers, type TapKitProps, type UseTapKitOptions, type UseTapKitReturn, useTapKit };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,164 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TapKitElement, TapKitConfig } from '@coxwave/tap-kit-types';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React-specific type definitions for TapKit
|
|
6
|
+
*
|
|
7
|
+
* Defines event handler types and control patterns for React integration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Event handler type definitions for TapKit Web Component
|
|
12
|
+
*
|
|
13
|
+
* Maps CustomEvent types to React callback signatures.
|
|
14
|
+
*/
|
|
15
|
+
interface TapKitEventHandlers {
|
|
16
|
+
/**
|
|
17
|
+
* Called when TapKit SDK is ready
|
|
18
|
+
* @example onReady={() => console.log('TapKit ready!')}
|
|
19
|
+
*/
|
|
20
|
+
onReady?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Called when initialization or runtime error occurs
|
|
23
|
+
* @example onError={(error) => console.error('TapKit error:', error)}
|
|
24
|
+
*/
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Called when timeline seek event occurs
|
|
28
|
+
* @example onTimelineSeek={(clipPlayHead, clipId) => console.log('Seek:', clipPlayHead)}
|
|
29
|
+
*/
|
|
30
|
+
onTimelineSeek?: (clipPlayHead: number, clipId: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Called when alarm fade-in occurs
|
|
33
|
+
* @example onAlarmFadeIn={(messageInfo) => console.log('Alarm:', messageInfo)}
|
|
34
|
+
*/
|
|
35
|
+
onAlarmFadeIn?: (messageInfo: unknown) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Control object pattern for managing TapKit instance
|
|
39
|
+
*
|
|
40
|
+
* Separates instance management (setInstance) from configuration (options)
|
|
41
|
+
* and event handling (handlers). Inspired by ChatKit's control pattern.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const tapkit = useTapKit({ apiKey: 'key', onReady: () => {} });
|
|
46
|
+
* <TapKit control={tapkit.control} />
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
interface TapKitControl<T> {
|
|
50
|
+
/**
|
|
51
|
+
* Set the TapKit element instance reference
|
|
52
|
+
*
|
|
53
|
+
* Called by TapKit component to register the element instance.
|
|
54
|
+
* Enables imperative control via useTapKit methods.
|
|
55
|
+
*/
|
|
56
|
+
setInstance: (instance: TapKitElement | null) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Configuration options (non-function values)
|
|
59
|
+
*
|
|
60
|
+
* All options except event handlers.
|
|
61
|
+
*/
|
|
62
|
+
options: T;
|
|
63
|
+
/**
|
|
64
|
+
* Event handler callbacks (function values)
|
|
65
|
+
*
|
|
66
|
+
* Separated from options for easier event listener management.
|
|
67
|
+
*/
|
|
68
|
+
handlers: TapKitEventHandlers;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* TapKit React Component
|
|
73
|
+
*
|
|
74
|
+
* Declarative React wrapper for <tap-kit> Web Component.
|
|
75
|
+
* Use this with useTapKit hook for a clean separation of control and rendering.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* 'use client';
|
|
80
|
+
*
|
|
81
|
+
* import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
|
|
82
|
+
*
|
|
83
|
+
* function MyApp() {
|
|
84
|
+
* const tapkit = useTapKit({
|
|
85
|
+
* apiKey: 'your-key',
|
|
86
|
+
* userId: 'user-123',
|
|
87
|
+
* courseId: 'course-456',
|
|
88
|
+
* clipId: 'clip-789',
|
|
89
|
+
* onReady: () => console.log('TapKit ready!'),
|
|
90
|
+
* onError: (error) => console.error('Error:', error),
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* return (
|
|
94
|
+
* <div>
|
|
95
|
+
* <button onClick={tapkit.show} disabled={!tapkit.isReady}>
|
|
96
|
+
* Show Chat
|
|
97
|
+
* </button>
|
|
98
|
+
* <TapKit control={tapkit.control} style={{ height: '600px' }} />
|
|
99
|
+
* </div>
|
|
100
|
+
* );
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* **Note for Next.js App Router users:**
|
|
105
|
+
* Make sure to add 'use client' at the top of your component file.
|
|
106
|
+
*
|
|
107
|
+
* @see https://edutap-ai-docs.vercel.app/docs/guides/react
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Props for TapKit React component
|
|
112
|
+
*/
|
|
113
|
+
interface TapKitProps extends Omit<React$1.HTMLAttributes<TapKitElement>, "children" | "dangerouslySetInnerHTML"> {
|
|
114
|
+
/**
|
|
115
|
+
* Control object from useTapKit hook
|
|
116
|
+
*
|
|
117
|
+
* Provides instance management, configuration, and event handlers.
|
|
118
|
+
*/
|
|
119
|
+
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
|
+
}
|
|
127
|
+
declare global {
|
|
128
|
+
namespace JSX {
|
|
129
|
+
interface IntrinsicElements {
|
|
130
|
+
"tap-kit": React$1.DetailedHTMLProps<React$1.HTMLAttributes<TapKitElement>, TapKitElement> & {
|
|
131
|
+
"api-key"?: string;
|
|
132
|
+
"user-id"?: string;
|
|
133
|
+
"course-id"?: string;
|
|
134
|
+
"clip-id"?: string;
|
|
135
|
+
"clip-play-head"?: number;
|
|
136
|
+
language?: "ko" | "en";
|
|
137
|
+
"button-id"?: string;
|
|
138
|
+
"container-id"?: string;
|
|
139
|
+
mode?: "inline" | "floating" | "sidebar";
|
|
140
|
+
debug?: boolean;
|
|
141
|
+
"tap-url"?: string;
|
|
142
|
+
"api-url"?: string;
|
|
143
|
+
environment?: "dev" | "prod" | "staging" | "demo";
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* TapKit React Component
|
|
150
|
+
*
|
|
151
|
+
* Renders <tap-kit> Web Component with React integration.
|
|
152
|
+
* Automatically manages instance lifecycle and event listeners.
|
|
153
|
+
*
|
|
154
|
+
* **Ref Access**: The forwarded ref provides direct access to the TapKitElement:
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const tapkitRef = useRef<TapKitElement>(null);
|
|
157
|
+
* <TapKit ref={tapkitRef} control={...} />
|
|
158
|
+
* // tapkitRef.current?.show()
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare const TapKit: React$1.ForwardRefExoticComponent<TapKitProps & React$1.RefAttributes<TapKitElement>>;
|
|
2
162
|
|
|
3
163
|
/**
|
|
4
164
|
* useTapKit Hook - Advanced imperative control of TapKit Web Component
|
|
@@ -51,7 +211,7 @@ import { TapKitConfig, TapKitElement } from '@coxwave/tap-kit-types';
|
|
|
51
211
|
* @see TapKit - Use this component for simpler declarative API
|
|
52
212
|
*/
|
|
53
213
|
|
|
54
|
-
interface UseTapKitOptions extends TapKitConfig {
|
|
214
|
+
interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {
|
|
55
215
|
/** User ID */
|
|
56
216
|
userId?: string;
|
|
57
217
|
/** Course ID */
|
|
@@ -75,11 +235,19 @@ interface UseTapKitOptions extends TapKitConfig {
|
|
|
75
235
|
/** Environment */
|
|
76
236
|
environment?: "dev" | "prod" | "staging" | "demo";
|
|
77
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Configuration options type (non-function values)
|
|
240
|
+
*/
|
|
241
|
+
type TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;
|
|
78
242
|
interface UseTapKitReturn {
|
|
79
243
|
/** Web Component element reference */
|
|
80
244
|
element: TapKitElement | null;
|
|
245
|
+
/** Ref object for direct element access */
|
|
246
|
+
ref: React.RefObject<TapKitElement | null>;
|
|
81
247
|
/** Container ref to attach element */
|
|
82
248
|
elementRef: React.RefCallback<HTMLDivElement>;
|
|
249
|
+
/** Control object for TapKit component */
|
|
250
|
+
control: TapKitControl<TapKitOptions>;
|
|
83
251
|
/** Whether TapKit is ready */
|
|
84
252
|
isReady: boolean;
|
|
85
253
|
/** Error during initialization */
|
|
@@ -101,9 +269,9 @@ interface UseTapKitReturn {
|
|
|
101
269
|
*
|
|
102
270
|
* Automatically loads CDN, creates Web Component, and provides control methods.
|
|
103
271
|
*
|
|
104
|
-
* @param options - TapKit configuration
|
|
105
|
-
* @returns Methods to control TapKit instance
|
|
272
|
+
* @param options - TapKit configuration with event handlers
|
|
273
|
+
* @returns Methods to control TapKit instance and control object
|
|
106
274
|
*/
|
|
107
275
|
declare function useTapKit(options: UseTapKitOptions): UseTapKitReturn;
|
|
108
276
|
|
|
109
|
-
export { type UseTapKitOptions, type UseTapKitReturn, useTapKit };
|
|
277
|
+
export { TapKit, type TapKitControl, type TapKitEventHandlers, type TapKitProps, type UseTapKitOptions, type UseTapKitReturn, useTapKit };
|
package/dist/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var react=require('react');var k=typeof __DEFAULT_CDN_LOADER_URL__<"u"?__DEFAULT_CDN_LOADER_URL__:"https://files.edutap.ai/tap-sdk/loader.js",
|
|
2
|
-
exports.useTapKit=
|
|
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
|
|
3
3
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loader.ts","../src/react/useTapKit.ts"],"names":["DEFAULT_CDN_LOADER_URL","DEFAULT_TIMEOUT_MS","IDLE_CALLBACK_TIMEOUT_MS","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","useTapKit","options","apiKey","userId","courseId","clipId","clipPlayHead","language","buttonId","mode","debug","tapUrl","apiUrl","environment","elementRef","useRef","containerRef","isReady","setIsReady","useState","error","setError","isMountedRef","setContainerRef","useCallback","node","useEffect","element","init","err","show","hide","setCourse","course"],"mappings":"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","file":"react.js","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\"\n ? __DEFAULT_CDN_LOADER_URL__\n : \"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__\n ? window.__TAP_KIT_LOADER_URL__\n : 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(\n new Error(\n `TapKit loader timeout: SDK not available after ${timeoutMs}ms`,\n ),\n );\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(\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): 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(\n new Error(\n \"TapKit requires browser environment (document is undefined)\",\n ),\n );\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\", () =>\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)),\n );\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 - Advanced imperative control of TapKit Web Component\n *\n * This hook provides direct access to the TapKitElement instance and full\n * control over its lifecycle. Use this when you need:\n * - Direct element manipulation\n * - Custom rendering logic\n * - Imperative control over Web Component behavior\n *\n * For most use cases, prefer the `<TapKit />` component which provides a\n * simpler declarative API.\n *\n * @param options - TapKit configuration\n * @returns Object with element reference, state, and control methods\n *\n * @example Advanced control with custom rendering\n * ```tsx\n * 'use client';\n *\n * import { useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyComponent() {\n * const { element, elementRef, show, hide, isReady, error } = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * });\n *\n * // Direct element access for advanced operations\n * useEffect(() => {\n * if (element) {\n * // Direct manipulation of TapKitElement\n * console.log('Element mounted:', element);\n * }\n * }, [element]);\n *\n * return (\n * <div>\n * <button onClick={show} disabled={!isReady}>Show Chat</button>\n * <button onClick={hide}>Hide Chat</button>\n * {error && <p>Error: {error.message}</p>}\n * <div ref={elementRef} /> // Container for Web Component\n * </div>\n * );\n * }\n * ```\n *\n * @see TapKit - Use this component for simpler declarative API\n */\n\nimport type { TapKitConfig, TapKitElement } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\n\nexport interface UseTapKitOptions extends TapKitConfig {\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** Language */\n language?: \"ko\" | \"en\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\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\" | \"demo\";\n}\n\nexport interface UseTapKitReturn {\n /** Web Component element reference */\n element: TapKitElement | null;\n /** Container ref to attach element */\n elementRef: React.RefCallback<HTMLDivElement>;\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: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => void;\n}\n\n/**\n * Hook for managing TapKit Web Component\n *\n * Automatically loads CDN, creates Web Component, and provides control methods.\n *\n * @param options - TapKit configuration\n * @returns Methods to control TapKit instance\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n const {\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode = \"floating\",\n debug,\n tapUrl,\n apiUrl,\n environment,\n } = options;\n\n const elementRef = useRef<TapKitElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const isMountedRef = useRef(true);\n\n // Ref callback to attach container\n const setContainerRef = useCallback((node: HTMLDivElement | null) => {\n containerRef.current = node;\n }, []);\n\n // Initialize Web Component\n // biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally only re-initialize when apiKey changes. Other props are updated dynamically via the second useEffect.\n useEffect(() => {\n isMountedRef.current = true;\n let element: TapKitElement | null = null;\n\n async function init() {\n try {\n // Load CDN\n await loadCDNLoader();\n\n // Wait for custom element definition\n await customElements.whenDefined(\"tap-kit\");\n\n if (!isMountedRef.current || !containerRef.current) return;\n\n // Create Web Component\n if (!window.createTapKit) {\n throw new Error(\"createTapKit not available after loading CDN\");\n }\n\n element = window.createTapKit({\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n });\n\n containerRef.current.appendChild(element);\n elementRef.current = element;\n\n // Wait for ready\n await element.ready;\n\n if (!isMountedRef.current) return;\n setIsReady(true);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error(\"[useTapKit] Initialization failed:\", error);\n if (isMountedRef.current) {\n setError(error);\n }\n }\n }\n\n init();\n\n return () => {\n isMountedRef.current = false;\n if (element) {\n element.remove();\n elementRef.current = null;\n }\n setIsReady(false);\n };\n }, [apiKey]); // Re-initialize on apiKey change\n\n // Update properties when options change\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n if (userId !== undefined) element.userId = userId;\n if (courseId !== undefined) element.courseId = courseId;\n if (clipId !== undefined) element.clipId = clipId;\n if (clipPlayHead !== undefined) element.clipPlayHead = clipPlayHead;\n if (language !== undefined) element.language = language;\n if (buttonId !== undefined) element.buttonId = buttonId;\n if (mode !== undefined) element.mode = mode;\n if (debug !== undefined) element.debug = debug;\n if (tapUrl !== undefined) element.tapUrl = tapUrl;\n if (apiUrl !== undefined) element.apiUrl = apiUrl;\n if (environment !== undefined) element.environment = environment;\n }, [\n isReady,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\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(\n (course: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n },\n [],\n );\n\n return {\n element: elementRef.current,\n elementRef: setContainerRef,\n isReady,\n error,\n show,\n hide,\n setCourse,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/types.ts","../src/react/TapKit.tsx","../src/loader.ts","../src/react/useTapKit.ts"],"names":["EVENT_HANDLER_MAP","EVENT_NAMES","TapKit","React","control","containerId","htmlProps","forwardedRef","elementRef","useRef","useImperativeHandle","useEffect","element","listeners","eventName","handlerKey","handler","listener","e","customEvent","event","options","jsx","DEFAULT_CDN_LOADER_URL","DEFAULT_TIMEOUT_MS","IDLE_CALLBACK_TIMEOUT_MS","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","extractConfig","onReady","onError","onTimelineSeek","onAlarmFadeIn","config","extractHandlers","createEventBindings","handlers","unsubscribers","useTapKit","apiKey","userId","courseId","clipId","clipPlayHead","language","buttonId","mode","debug","tapUrl","apiUrl","environment","containerRef","isReady","setIsReady","useState","error","setError","isMountedRef","setContainerRef","useCallback","node","setInstance","instance","init","err","configOptions","useMemo","unsubscribe","show","hide","setCourse","course"],"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 { TapKitElement } from \"@coxwave/tap-kit-types\";\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: unknown) => 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/**\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(\n EVENT_HANDLER_MAP,\n) 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, { useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { TapKitControl } from \"./types\";\nimport { EVENT_HANDLER_MAP, EVENT_NAMES } from \"./types\";\n\n/**\n * Props for TapKit React component\n */\nexport interface TapKitProps\n extends Omit<\n React.HTMLAttributes<TapKitElement>,\n \"children\" | \"dangerouslySetInnerHTML\"\n > {\n /**\n * Control object from useTapKit hook\n *\n * Provides instance management, configuration, and event handlers.\n */\n control: TapKitControl<any>;\n\n /**\n * Custom container element ID (optional)\n *\n * If provided, TapKit will use this element as container.\n */\n containerId?: string;\n}\n\n// Augment JSX namespace to support <tap-kit> custom element\ndeclare global {\n namespace JSX {\n interface IntrinsicElements {\n \"tap-kit\": React.DetailedHTMLProps<\n React.HTMLAttributes<TapKitElement>,\n TapKitElement\n > & {\n \"api-key\"?: string;\n \"user-id\"?: string;\n \"course-id\"?: string;\n \"clip-id\"?: string;\n \"clip-play-head\"?: number;\n language?: \"ko\" | \"en\";\n \"button-id\"?: string;\n \"container-id\"?: string;\n mode?: \"inline\" | \"floating\" | \"sidebar\";\n debug?: boolean;\n \"tap-url\"?: string;\n \"api-url\"?: string;\n environment?: \"dev\" | \"prod\" | \"staging\" | \"demo\";\n };\n }\n }\n}\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>(\n function TapKit({ control, containerId, ...htmlProps }, forwardedRef) {\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Forward TapKitElement to parent\n useImperativeHandle(\n forwardedRef,\n () => elementRef.current as TapKitElement,\n [],\n );\n\n // Register element instance with control\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n control.setInstance(element);\n\n return () => {\n control.setInstance(null);\n };\n }, [control.setInstance]);\n\n // Register event listeners\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 =\n EVENT_HANDLER_MAP[eventName as keyof typeof EVENT_HANDLER_MAP];\n const handler = control.handlers[handlerKey];\n\n if (handler) {\n const listener = (e: Event) => {\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\n return () => {\n // Cleanup all event listeners\n for (const { event, handler } of listeners) {\n element.removeEventListener(event, handler);\n }\n };\n }, [control]);\n\n const { options } = control;\n\n // Render <tap-kit> Web Component directly\n return (\n <tap-kit\n ref={elementRef}\n api-key={options.apiKey}\n user-id={options.userId}\n course-id={options.courseId}\n clip-id={options.clipId}\n clip-play-head={options.clipPlayHead}\n language={options.language}\n button-id={options.buttonId}\n container-id={containerId}\n mode={options.mode}\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","/**\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\"\n ? __DEFAULT_CDN_LOADER_URL__\n : \"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__\n ? window.__TAP_KIT_LOADER_URL__\n : 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(\n new Error(\n `TapKit loader timeout: SDK not available after ${timeoutMs}ms`,\n ),\n );\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(\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): 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(\n new Error(\n \"TapKit requires browser environment (document is undefined)\",\n ),\n );\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\", () =>\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)),\n );\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 - Advanced imperative control of TapKit Web Component\n *\n * This hook provides direct access to the TapKitElement instance and full\n * control over its lifecycle. Use this when you need:\n * - Direct element manipulation\n * - Custom rendering logic\n * - Imperative control over Web Component behavior\n *\n * For most use cases, prefer the `<TapKit />` component which provides a\n * simpler declarative API.\n *\n * @param options - TapKit configuration\n * @returns Object with element reference, state, and control methods\n *\n * @example Advanced control with custom rendering\n * ```tsx\n * 'use client';\n *\n * import { useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyComponent() {\n * const { element, elementRef, show, hide, isReady, error } = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * });\n *\n * // Direct element access for advanced operations\n * useEffect(() => {\n * if (element) {\n * // Direct manipulation of TapKitElement\n * console.log('Element mounted:', element);\n * }\n * }, [element]);\n *\n * return (\n * <div>\n * <button onClick={show} disabled={!isReady}>Show Chat</button>\n * <button onClick={hide}>Hide Chat</button>\n * {error && <p>Error: {error.message}</p>}\n * <div ref={elementRef} /> // Container for Web Component\n * </div>\n * );\n * }\n * ```\n *\n * @see TapKit - Use this component for simpler declarative API\n */\n\nimport type { TapKitConfig, TapKitElement } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { TapKitControl, TapKitEventHandlers } from \"./types\";\n\nexport interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** Language */\n language?: \"ko\" | \"en\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\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\" | \"demo\";\n}\n\n/**\n * Configuration options type (non-function values)\n */\ntype TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;\n\n/**\n * Extract configuration from options (pure function)\n */\nfunction extractConfig(options: UseTapKitOptions): TapKitOptions {\n const { onReady, onError, onTimelineSeek, onAlarmFadeIn, ...config } =\n options;\n return config;\n}\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\n/**\n * Create event bindings for TapKitElement (pure function)\n */\nfunction createEventBindings(\n element: TapKitElement,\n handlers: TapKitEventHandlers,\n): Array<() => void> {\n const unsubscribers: Array<() => void> = [];\n\n if (handlers.onTimelineSeek) {\n unsubscribers.push(element.events.onTimelineSeek(handlers.onTimelineSeek));\n }\n\n if (handlers.onAlarmFadeIn) {\n unsubscribers.push(element.events.onAlarmFadeIn(handlers.onAlarmFadeIn));\n }\n\n return unsubscribers;\n}\n\nexport interface UseTapKitReturn {\n /** Web Component element reference */\n element: TapKitElement | null;\n /** Ref object for direct element access */\n ref: React.RefObject<TapKitElement | null>;\n /** Container ref to attach element */\n elementRef: React.RefCallback<HTMLDivElement>;\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: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => void;\n}\n\n/**\n * Hook for managing TapKit Web Component\n *\n * Automatically loads CDN, creates Web Component, and provides control methods.\n *\n * @param options - TapKit configuration with event handlers\n * @returns Methods to control TapKit instance and control object\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n const {\n // Event handlers (used in init effect)\n onReady,\n onError,\n // Configuration\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode = \"floating\",\n debug,\n tapUrl,\n apiUrl,\n environment,\n } = options;\n\n const elementRef = useRef<TapKitElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const isMountedRef = useRef(true);\n\n // Ref callback to attach container\n const setContainerRef = useCallback((node: HTMLDivElement | null) => {\n containerRef.current = node;\n }, []);\n\n // Set instance callback for control pattern\n const setInstance = useCallback((instance: TapKitElement | null) => {\n elementRef.current = instance;\n }, []);\n\n // Initialize Web Component\n // biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally only re-initialize when apiKey changes. Other props are updated dynamically via the second useEffect.\n useEffect(() => {\n isMountedRef.current = true;\n let element: TapKitElement | null = null;\n\n async function init() {\n try {\n // Load CDN\n await loadCDNLoader();\n\n // Wait for custom element definition\n await customElements.whenDefined(\"tap-kit\");\n\n if (!isMountedRef.current || !containerRef.current) return;\n\n // Create Web Component\n if (!window.createTapKit) {\n throw new Error(\"createTapKit not available after loading CDN\");\n }\n\n element = window.createTapKit({\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n });\n\n containerRef.current.appendChild(element);\n elementRef.current = element;\n\n // Wait for ready\n await element.ready;\n\n if (!isMountedRef.current) return;\n setIsReady(true);\n\n // Call onReady handler if provided\n if (onReady) {\n onReady();\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error(\"[useTapKit] Initialization failed:\", error);\n if (isMountedRef.current) {\n setError(error);\n // Call onError handler if provided\n if (onError) {\n onError(error);\n }\n }\n }\n }\n\n init();\n\n return () => {\n isMountedRef.current = false;\n if (element) {\n element.remove();\n elementRef.current = null;\n }\n setIsReady(false);\n };\n }, [apiKey, onReady, onError]); // Re-initialize on apiKey or handlers change\n\n // Update properties when options change\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n if (userId !== undefined) element.userId = userId;\n if (courseId !== undefined) element.courseId = courseId;\n if (clipId !== undefined) element.clipId = clipId;\n if (clipPlayHead !== undefined) element.clipPlayHead = clipPlayHead;\n if (language !== undefined) element.language = language;\n if (buttonId !== undefined) element.buttonId = buttonId;\n if (mode !== undefined) element.mode = mode;\n if (debug !== undefined) element.debug = debug;\n if (tapUrl !== undefined) element.tapUrl = tapUrl;\n if (apiUrl !== undefined) element.apiUrl = apiUrl;\n if (environment !== undefined) element.environment = environment;\n }, [\n isReady,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n ]);\n\n // Separate configuration options from event handlers (memoized)\n const configOptions = useMemo(() => extractConfig(options), [options]);\n\n const handlers = useMemo(() => extractHandlers(options), [options]);\n\n // Register event listeners for EventManager callbacks\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n const unsubscribers = createEventBindings(element, handlers);\n\n return () => {\n for (const unsubscribe of unsubscribers) {\n unsubscribe();\n }\n };\n }, [isReady, handlers]);\n\n // Create control object\n const control = useMemo<TapKitControl<TapKitOptions>>(\n () => ({\n setInstance,\n options: configOptions,\n handlers,\n }),\n [setInstance, configOptions, handlers],\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(\n (course: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n },\n [],\n );\n\n return {\n element: elementRef.current,\n ref: elementRef,\n elementRef: setContainerRef,\n control,\n isReady,\n error,\n show,\n hide,\n setCourse,\n };\n}\n"]}
|
package/dist/react.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {useRef,useState,useCallback,
|
|
2
|
-
export{
|
|
1
|
+
import B,{useRef,useImperativeHandle,useEffect,useState,useCallback,useMemo}from'react';import {jsx}from'react/jsx-runtime';var R={"tap-kit:ready":"onReady","tap-kit:error":"onError"},k=Object.keys(R);var X=B.forwardRef(function({control:t,containerId:i,...s},a){let o=useRef(null);useImperativeHandle(a,()=>o.current,[]),useEffect(()=>{let d=o.current;if(d)return t.setInstance(d),()=>{t.setInstance(null);}},[t.setInstance]),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 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=useRef(null),I=useRef(null),[E,O]=useState(false),[x,M]=useState(null),K=useRef(true),F=useCallback(e=>{I.current=e;},[]),b=useCallback(e=>{u.current=e;},[]);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]),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=useMemo(()=>ne(n),[n]),L=useMemo(()=>te(n),[n]);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=useMemo(()=>({setInstance:b,options:C,handlers:L}),[b,C,L]),V=useCallback(()=>{u.current&&u.current.show();},[]),$=useCallback(()=>{u.current&&u.current.hide();},[]),q=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
|
+
export{X as TapKit,ie as useTapKit};//# sourceMappingURL=react.mjs.map
|
|
3
3
|
//# sourceMappingURL=react.mjs.map
|
package/dist/react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loader.ts","../src/react/useTapKit.ts"],"names":["DEFAULT_CDN_LOADER_URL","DEFAULT_TIMEOUT_MS","IDLE_CALLBACK_TIMEOUT_MS","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","useTapKit","options","apiKey","userId","courseId","clipId","clipPlayHead","language","buttonId","mode","debug","tapUrl","apiUrl","environment","elementRef","useRef","containerRef","isReady","setIsReady","useState","error","setError","isMountedRef","setContainerRef","useCallback","node","useEffect","element","init","err","show","hide","setCourse","course"],"mappings":"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","file":"react.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\"\n ? __DEFAULT_CDN_LOADER_URL__\n : \"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__\n ? window.__TAP_KIT_LOADER_URL__\n : 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(\n new Error(\n `TapKit loader timeout: SDK not available after ${timeoutMs}ms`,\n ),\n );\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(\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): 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(\n new Error(\n \"TapKit requires browser environment (document is undefined)\",\n ),\n );\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\", () =>\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)),\n );\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 - Advanced imperative control of TapKit Web Component\n *\n * This hook provides direct access to the TapKitElement instance and full\n * control over its lifecycle. Use this when you need:\n * - Direct element manipulation\n * - Custom rendering logic\n * - Imperative control over Web Component behavior\n *\n * For most use cases, prefer the `<TapKit />` component which provides a\n * simpler declarative API.\n *\n * @param options - TapKit configuration\n * @returns Object with element reference, state, and control methods\n *\n * @example Advanced control with custom rendering\n * ```tsx\n * 'use client';\n *\n * import { useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyComponent() {\n * const { element, elementRef, show, hide, isReady, error } = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * });\n *\n * // Direct element access for advanced operations\n * useEffect(() => {\n * if (element) {\n * // Direct manipulation of TapKitElement\n * console.log('Element mounted:', element);\n * }\n * }, [element]);\n *\n * return (\n * <div>\n * <button onClick={show} disabled={!isReady}>Show Chat</button>\n * <button onClick={hide}>Hide Chat</button>\n * {error && <p>Error: {error.message}</p>}\n * <div ref={elementRef} /> // Container for Web Component\n * </div>\n * );\n * }\n * ```\n *\n * @see TapKit - Use this component for simpler declarative API\n */\n\nimport type { TapKitConfig, TapKitElement } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\n\nexport interface UseTapKitOptions extends TapKitConfig {\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** Language */\n language?: \"ko\" | \"en\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\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\" | \"demo\";\n}\n\nexport interface UseTapKitReturn {\n /** Web Component element reference */\n element: TapKitElement | null;\n /** Container ref to attach element */\n elementRef: React.RefCallback<HTMLDivElement>;\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: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => void;\n}\n\n/**\n * Hook for managing TapKit Web Component\n *\n * Automatically loads CDN, creates Web Component, and provides control methods.\n *\n * @param options - TapKit configuration\n * @returns Methods to control TapKit instance\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n const {\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode = \"floating\",\n debug,\n tapUrl,\n apiUrl,\n environment,\n } = options;\n\n const elementRef = useRef<TapKitElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const isMountedRef = useRef(true);\n\n // Ref callback to attach container\n const setContainerRef = useCallback((node: HTMLDivElement | null) => {\n containerRef.current = node;\n }, []);\n\n // Initialize Web Component\n // biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally only re-initialize when apiKey changes. Other props are updated dynamically via the second useEffect.\n useEffect(() => {\n isMountedRef.current = true;\n let element: TapKitElement | null = null;\n\n async function init() {\n try {\n // Load CDN\n await loadCDNLoader();\n\n // Wait for custom element definition\n await customElements.whenDefined(\"tap-kit\");\n\n if (!isMountedRef.current || !containerRef.current) return;\n\n // Create Web Component\n if (!window.createTapKit) {\n throw new Error(\"createTapKit not available after loading CDN\");\n }\n\n element = window.createTapKit({\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n });\n\n containerRef.current.appendChild(element);\n elementRef.current = element;\n\n // Wait for ready\n await element.ready;\n\n if (!isMountedRef.current) return;\n setIsReady(true);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error(\"[useTapKit] Initialization failed:\", error);\n if (isMountedRef.current) {\n setError(error);\n }\n }\n }\n\n init();\n\n return () => {\n isMountedRef.current = false;\n if (element) {\n element.remove();\n elementRef.current = null;\n }\n setIsReady(false);\n };\n }, [apiKey]); // Re-initialize on apiKey change\n\n // Update properties when options change\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n if (userId !== undefined) element.userId = userId;\n if (courseId !== undefined) element.courseId = courseId;\n if (clipId !== undefined) element.clipId = clipId;\n if (clipPlayHead !== undefined) element.clipPlayHead = clipPlayHead;\n if (language !== undefined) element.language = language;\n if (buttonId !== undefined) element.buttonId = buttonId;\n if (mode !== undefined) element.mode = mode;\n if (debug !== undefined) element.debug = debug;\n if (tapUrl !== undefined) element.tapUrl = tapUrl;\n if (apiUrl !== undefined) element.apiUrl = apiUrl;\n if (environment !== undefined) element.environment = environment;\n }, [\n isReady,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\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(\n (course: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n },\n [],\n );\n\n return {\n element: elementRef.current,\n elementRef: setContainerRef,\n isReady,\n error,\n show,\n hide,\n setCourse,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/types.ts","../src/react/TapKit.tsx","../src/loader.ts","../src/react/useTapKit.ts"],"names":["EVENT_HANDLER_MAP","EVENT_NAMES","TapKit","React","control","containerId","htmlProps","forwardedRef","elementRef","useRef","useImperativeHandle","useEffect","element","listeners","eventName","handlerKey","handler","listener","e","customEvent","event","options","jsx","DEFAULT_CDN_LOADER_URL","DEFAULT_TIMEOUT_MS","IDLE_CALLBACK_TIMEOUT_MS","getLoaderURL","isLocalCoreMode","getLocalCoreURL","createSDKChecker","resolve","reject","timeoutMs","startTime","checkSDK","loadCDNLoader","loadingPromise","coreURL","script","loaderURL","existingScript","extractConfig","onReady","onError","onTimelineSeek","onAlarmFadeIn","config","extractHandlers","createEventBindings","handlers","unsubscribers","useTapKit","apiKey","userId","courseId","clipId","clipPlayHead","language","buttonId","mode","debug","tapUrl","apiUrl","environment","containerRef","isReady","setIsReady","useState","error","setError","isMountedRef","setContainerRef","useCallback","node","setInstance","instance","init","err","configOptions","useMemo","unsubscribe","show","hide","setCourse","course"],"mappings":"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","file":"react.mjs","sourcesContent":["/**\n * React-specific type definitions for TapKit\n *\n * Defines event handler types and control patterns for React integration.\n */\n\nimport type { TapKitElement } from \"@coxwave/tap-kit-types\";\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: unknown) => 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/**\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(\n EVENT_HANDLER_MAP,\n) 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, { useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { TapKitControl } from \"./types\";\nimport { EVENT_HANDLER_MAP, EVENT_NAMES } from \"./types\";\n\n/**\n * Props for TapKit React component\n */\nexport interface TapKitProps\n extends Omit<\n React.HTMLAttributes<TapKitElement>,\n \"children\" | \"dangerouslySetInnerHTML\"\n > {\n /**\n * Control object from useTapKit hook\n *\n * Provides instance management, configuration, and event handlers.\n */\n control: TapKitControl<any>;\n\n /**\n * Custom container element ID (optional)\n *\n * If provided, TapKit will use this element as container.\n */\n containerId?: string;\n}\n\n// Augment JSX namespace to support <tap-kit> custom element\ndeclare global {\n namespace JSX {\n interface IntrinsicElements {\n \"tap-kit\": React.DetailedHTMLProps<\n React.HTMLAttributes<TapKitElement>,\n TapKitElement\n > & {\n \"api-key\"?: string;\n \"user-id\"?: string;\n \"course-id\"?: string;\n \"clip-id\"?: string;\n \"clip-play-head\"?: number;\n language?: \"ko\" | \"en\";\n \"button-id\"?: string;\n \"container-id\"?: string;\n mode?: \"inline\" | \"floating\" | \"sidebar\";\n debug?: boolean;\n \"tap-url\"?: string;\n \"api-url\"?: string;\n environment?: \"dev\" | \"prod\" | \"staging\" | \"demo\";\n };\n }\n }\n}\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>(\n function TapKit({ control, containerId, ...htmlProps }, forwardedRef) {\n const elementRef = useRef<TapKitElement | null>(null);\n\n // Forward TapKitElement to parent\n useImperativeHandle(\n forwardedRef,\n () => elementRef.current as TapKitElement,\n [],\n );\n\n // Register element instance with control\n useEffect(() => {\n const element = elementRef.current;\n if (!element) return;\n\n control.setInstance(element);\n\n return () => {\n control.setInstance(null);\n };\n }, [control.setInstance]);\n\n // Register event listeners\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 =\n EVENT_HANDLER_MAP[eventName as keyof typeof EVENT_HANDLER_MAP];\n const handler = control.handlers[handlerKey];\n\n if (handler) {\n const listener = (e: Event) => {\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\n return () => {\n // Cleanup all event listeners\n for (const { event, handler } of listeners) {\n element.removeEventListener(event, handler);\n }\n };\n }, [control]);\n\n const { options } = control;\n\n // Render <tap-kit> Web Component directly\n return (\n <tap-kit\n ref={elementRef}\n api-key={options.apiKey}\n user-id={options.userId}\n course-id={options.courseId}\n clip-id={options.clipId}\n clip-play-head={options.clipPlayHead}\n language={options.language}\n button-id={options.buttonId}\n container-id={containerId}\n mode={options.mode}\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","/**\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\"\n ? __DEFAULT_CDN_LOADER_URL__\n : \"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__\n ? window.__TAP_KIT_LOADER_URL__\n : 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(\n new Error(\n `TapKit loader timeout: SDK not available after ${timeoutMs}ms`,\n ),\n );\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(\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): 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(\n new Error(\n \"TapKit requires browser environment (document is undefined)\",\n ),\n );\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\", () =>\n reject(new Error(`Failed to load TapKit CDN loader: ${loaderURL}`)),\n );\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 - Advanced imperative control of TapKit Web Component\n *\n * This hook provides direct access to the TapKitElement instance and full\n * control over its lifecycle. Use this when you need:\n * - Direct element manipulation\n * - Custom rendering logic\n * - Imperative control over Web Component behavior\n *\n * For most use cases, prefer the `<TapKit />` component which provides a\n * simpler declarative API.\n *\n * @param options - TapKit configuration\n * @returns Object with element reference, state, and control methods\n *\n * @example Advanced control with custom rendering\n * ```tsx\n * 'use client';\n *\n * import { useTapKit } from '@coxwave/tap-kit/react';\n *\n * function MyComponent() {\n * const { element, elementRef, show, hide, isReady, error } = useTapKit({\n * apiKey: 'your-key',\n * userId: 'user-123',\n * courseId: 'course-456',\n * clipId: 'clip-789',\n * });\n *\n * // Direct element access for advanced operations\n * useEffect(() => {\n * if (element) {\n * // Direct manipulation of TapKitElement\n * console.log('Element mounted:', element);\n * }\n * }, [element]);\n *\n * return (\n * <div>\n * <button onClick={show} disabled={!isReady}>Show Chat</button>\n * <button onClick={hide}>Hide Chat</button>\n * {error && <p>Error: {error.message}</p>}\n * <div ref={elementRef} /> // Container for Web Component\n * </div>\n * );\n * }\n * ```\n *\n * @see TapKit - Use this component for simpler declarative API\n */\n\nimport type { TapKitConfig, TapKitElement } from \"@coxwave/tap-kit-types\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { loadCDNLoader } from \"../loader\";\nimport type { TapKitControl, TapKitEventHandlers } from \"./types\";\n\nexport interface UseTapKitOptions extends TapKitConfig, TapKitEventHandlers {\n /** User ID */\n userId?: string;\n /** Course ID */\n courseId?: string;\n /** Clip ID */\n clipId?: string;\n /** Clip playhead position */\n clipPlayHead?: number;\n /** Language */\n language?: \"ko\" | \"en\";\n /** Custom button element ID */\n buttonId?: string;\n /** Display mode */\n mode?: \"inline\" | \"floating\" | \"sidebar\";\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\" | \"demo\";\n}\n\n/**\n * Configuration options type (non-function values)\n */\ntype TapKitOptions = Omit<UseTapKitOptions, keyof TapKitEventHandlers>;\n\n/**\n * Extract configuration from options (pure function)\n */\nfunction extractConfig(options: UseTapKitOptions): TapKitOptions {\n const { onReady, onError, onTimelineSeek, onAlarmFadeIn, ...config } =\n options;\n return config;\n}\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\n/**\n * Create event bindings for TapKitElement (pure function)\n */\nfunction createEventBindings(\n element: TapKitElement,\n handlers: TapKitEventHandlers,\n): Array<() => void> {\n const unsubscribers: Array<() => void> = [];\n\n if (handlers.onTimelineSeek) {\n unsubscribers.push(element.events.onTimelineSeek(handlers.onTimelineSeek));\n }\n\n if (handlers.onAlarmFadeIn) {\n unsubscribers.push(element.events.onAlarmFadeIn(handlers.onAlarmFadeIn));\n }\n\n return unsubscribers;\n}\n\nexport interface UseTapKitReturn {\n /** Web Component element reference */\n element: TapKitElement | null;\n /** Ref object for direct element access */\n ref: React.RefObject<TapKitElement | null>;\n /** Container ref to attach element */\n elementRef: React.RefCallback<HTMLDivElement>;\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: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => void;\n}\n\n/**\n * Hook for managing TapKit Web Component\n *\n * Automatically loads CDN, creates Web Component, and provides control methods.\n *\n * @param options - TapKit configuration with event handlers\n * @returns Methods to control TapKit instance and control object\n */\nexport function useTapKit(options: UseTapKitOptions): UseTapKitReturn {\n const {\n // Event handlers (used in init effect)\n onReady,\n onError,\n // Configuration\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode = \"floating\",\n debug,\n tapUrl,\n apiUrl,\n environment,\n } = options;\n\n const elementRef = useRef<TapKitElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const isMountedRef = useRef(true);\n\n // Ref callback to attach container\n const setContainerRef = useCallback((node: HTMLDivElement | null) => {\n containerRef.current = node;\n }, []);\n\n // Set instance callback for control pattern\n const setInstance = useCallback((instance: TapKitElement | null) => {\n elementRef.current = instance;\n }, []);\n\n // Initialize Web Component\n // biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally only re-initialize when apiKey changes. Other props are updated dynamically via the second useEffect.\n useEffect(() => {\n isMountedRef.current = true;\n let element: TapKitElement | null = null;\n\n async function init() {\n try {\n // Load CDN\n await loadCDNLoader();\n\n // Wait for custom element definition\n await customElements.whenDefined(\"tap-kit\");\n\n if (!isMountedRef.current || !containerRef.current) return;\n\n // Create Web Component\n if (!window.createTapKit) {\n throw new Error(\"createTapKit not available after loading CDN\");\n }\n\n element = window.createTapKit({\n apiKey,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n });\n\n containerRef.current.appendChild(element);\n elementRef.current = element;\n\n // Wait for ready\n await element.ready;\n\n if (!isMountedRef.current) return;\n setIsReady(true);\n\n // Call onReady handler if provided\n if (onReady) {\n onReady();\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error(\"[useTapKit] Initialization failed:\", error);\n if (isMountedRef.current) {\n setError(error);\n // Call onError handler if provided\n if (onError) {\n onError(error);\n }\n }\n }\n }\n\n init();\n\n return () => {\n isMountedRef.current = false;\n if (element) {\n element.remove();\n elementRef.current = null;\n }\n setIsReady(false);\n };\n }, [apiKey, onReady, onError]); // Re-initialize on apiKey or handlers change\n\n // Update properties when options change\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n if (userId !== undefined) element.userId = userId;\n if (courseId !== undefined) element.courseId = courseId;\n if (clipId !== undefined) element.clipId = clipId;\n if (clipPlayHead !== undefined) element.clipPlayHead = clipPlayHead;\n if (language !== undefined) element.language = language;\n if (buttonId !== undefined) element.buttonId = buttonId;\n if (mode !== undefined) element.mode = mode;\n if (debug !== undefined) element.debug = debug;\n if (tapUrl !== undefined) element.tapUrl = tapUrl;\n if (apiUrl !== undefined) element.apiUrl = apiUrl;\n if (environment !== undefined) element.environment = environment;\n }, [\n isReady,\n userId,\n courseId,\n clipId,\n clipPlayHead,\n language,\n buttonId,\n mode,\n debug,\n tapUrl,\n apiUrl,\n environment,\n ]);\n\n // Separate configuration options from event handlers (memoized)\n const configOptions = useMemo(() => extractConfig(options), [options]);\n\n const handlers = useMemo(() => extractHandlers(options), [options]);\n\n // Register event listeners for EventManager callbacks\n useEffect(() => {\n const element = elementRef.current;\n if (!element || !isReady) return;\n\n const unsubscribers = createEventBindings(element, handlers);\n\n return () => {\n for (const unsubscribe of unsubscribers) {\n unsubscribe();\n }\n };\n }, [isReady, handlers]);\n\n // Create control object\n const control = useMemo<TapKitControl<TapKitOptions>>(\n () => ({\n setInstance,\n options: configOptions,\n handlers,\n }),\n [setInstance, configOptions, handlers],\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(\n (course: {\n courseId: string;\n clipId: string;\n userId?: string;\n clipPlayHead?: number;\n }) => {\n if (!elementRef.current) {\n console.warn(\"[useTapKit] Cannot setCourse: element not mounted\");\n return;\n }\n elementRef.current.setCourse(course);\n },\n [],\n );\n\n return {\n element: elementRef.current,\n ref: elementRef,\n elementRef: setContainerRef,\n control,\n isReady,\n error,\n show,\n hide,\n setCourse,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coxwave/tap-kit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "EduTAP SDK with React support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"README.md"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@coxwave/tap-kit-types": "2.0.
|
|
26
|
+
"@coxwave/tap-kit-types": "2.0.1"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"react": "^18.0.0 || ^19.0.0"
|