@yak-io/react 0.3.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import type React from "react";
2
2
  import { type Theme, type ChatConfigProvider, type ToolCallHandler, type GraphQLSchemaHandler, type RESTSchemaHandler } from "@yak-io/javascript";
3
3
  /**
4
4
  * Props for YakProvider
@@ -10,90 +10,19 @@ export type YakProviderProps = {
10
10
  * Provider function for chat configuration (routes + tools).
11
11
  * The consuming platform decides how to get the config (static, fetch, etc.)
12
12
  * Called when the widget is opened.
13
- *
14
- * @example Static config
15
- * ```tsx
16
- * getConfig={() => ({
17
- * routes: { routes: [...], generated_at: "..." },
18
- * tools: { tools: [...], generated_at: "..." },
19
- * })}
20
- * ```
21
- *
22
- * @example Fetch from server
23
- * ```tsx
24
- * getConfig={async () => {
25
- * const res = await fetch("/api/yak/config");
26
- * return res.json();
27
- * }}
28
- * ```
29
13
  */
30
14
  getConfig?: ChatConfigProvider;
31
15
  /**
32
16
  * Handler for tool calls from the chat widget.
33
17
  * The consuming platform decides how to execute (browser, server fetch, etc.)
34
- *
35
- * @example Browser-only execution
36
- * ```tsx
37
- * onToolCall={async (name, args) => {
38
- * if (name === "ui.scrollTo") {
39
- * document.getElementById((args as {id: string}).id)?.scrollIntoView();
40
- * return { success: true };
41
- * }
42
- * throw new Error(`Unknown tool: ${name}`);
43
- * }}
44
- * ```
45
- *
46
- * @example Server delegation
47
- * ```tsx
48
- * onToolCall={async (name, args) => {
49
- * const res = await fetch("/api/yak/tools", {
50
- * method: "POST",
51
- * headers: { "Content-Type": "application/json" },
52
- * body: JSON.stringify({ name, args }),
53
- * });
54
- * const data = await res.json();
55
- * if (!data.ok) throw new Error(data.error);
56
- * return data.result;
57
- * }}
58
- * ```
59
18
  */
60
19
  onToolCall?: ToolCallHandler;
61
20
  /**
62
21
  * Handler for GraphQL schema tool calls.
63
- * Called when the LLM generates a GraphQL operation based on provided schema context.
64
- *
65
- * @example
66
- * ```tsx
67
- * onGraphQLSchemaCall={async (schemaName, request) => {
68
- * const response = await fetch("/graphql", {
69
- * method: "POST",
70
- * headers: { "Content-Type": "application/json" },
71
- * body: JSON.stringify(request),
72
- * });
73
- * return response.json();
74
- * }}
75
- * ```
76
22
  */
77
23
  onGraphQLSchemaCall?: GraphQLSchemaHandler;
78
24
  /**
79
25
  * Handler for REST/OpenAPI schema tool calls.
80
- * Called when the LLM generates a REST request based on provided OpenAPI schema context.
81
- *
82
- * @example
83
- * ```tsx
84
- * onRESTSchemaCall={async (schemaName, request) => {
85
- * const url = new URL(request.path, "https://api.example.com");
86
- * if (request.query) {
87
- * Object.entries(request.query).forEach(([k, v]) => url.searchParams.set(k, v));
88
- * }
89
- * const response = await fetch(url, {
90
- * method: request.method,
91
- * headers: request.body ? { "Content-Type": "application/json" } : undefined,
92
- * body: request.body ? JSON.stringify(request.body) : undefined,
93
- * });
94
- * return response.json();
95
- * }}
96
- * ```
97
26
  */
98
27
  onRESTSchemaCall?: RESTSchemaHandler;
99
28
  /** Optional theme configuration */
@@ -106,7 +35,8 @@ export type YakProviderProps = {
106
35
  children: React.ReactNode;
107
36
  };
108
37
  /**
109
- * YakProvider sets up the context and message handling for the yak widget.
38
+ * YakProvider sets up the context, message handling, and renders the chat iframe.
39
+ * The iframe is rendered as a fixed-position panel that can be opened/closed.
110
40
  */
111
41
  export declare function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }: YakProviderProps): React.JSX.Element;
112
42
  //# sourceMappingURL=YakProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,EACL,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmOtC"}
1
+ {"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;OAEG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;OAEG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAmMF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,EACL,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAqVtC"}
@@ -1,11 +1,204 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useMemo, useEffect, useRef } from "react";
4
4
  import { YakContext } from "./context.js";
5
5
  import { YakClient, } from "@yak-io/javascript";
6
6
  import { logger } from "./internal/logger.js";
7
7
  /**
8
- * YakProvider sets up the context and message handling for the yak widget.
8
+ * All iframe/panel styles consolidated in one place
9
+ */
10
+ function getIframeStyles() {
11
+ return `
12
+ /* ===========================================
13
+ YAK IFRAME STYLES (rendered by provider)
14
+ =========================================== */
15
+
16
+ /* ===========================================
17
+ ROOT LAYER (full viewport, pointer-events pass-through)
18
+ =========================================== */
19
+ .yak-panel-root {
20
+ position: fixed;
21
+ top: 0;
22
+ left: 0;
23
+ right: 0;
24
+ bottom: 0;
25
+ width: 100vw;
26
+ height: 100vh;
27
+ pointer-events: none;
28
+ z-index: 9998;
29
+ }
30
+
31
+ /* ===========================================
32
+ CONTAINER
33
+ =========================================== */
34
+ .yak-panel-container {
35
+ position: absolute;
36
+ width: 500px;
37
+ height: 600px;
38
+ max-width: calc(100vw - 40px);
39
+ max-height: calc(100vh - 120px);
40
+ border-radius: 15px;
41
+ overflow: hidden;
42
+ background-color: transparent;
43
+ pointer-events: auto;
44
+ }
45
+
46
+ /* Container position variants (chatbox mode) - all 9 positions */
47
+ .yak-panel-container[data-position="top-left"]:not(.yak-panel-drawer) {
48
+ top: 16px;
49
+ left: 16px;
50
+ }
51
+ .yak-panel-container[data-position="top-center"]:not(.yak-panel-drawer) {
52
+ top: 16px;
53
+ left: 50%;
54
+ transform: translateX(-50%);
55
+ }
56
+ .yak-panel-container[data-position="top-right"]:not(.yak-panel-drawer) {
57
+ top: 16px;
58
+ right: 16px;
59
+ }
60
+ .yak-panel-container[data-position="left-center"]:not(.yak-panel-drawer) {
61
+ top: 50%;
62
+ left: 16px;
63
+ transform: translateY(-50%);
64
+ }
65
+ .yak-panel-container[data-position="right-center"]:not(.yak-panel-drawer) {
66
+ top: 50%;
67
+ right: 16px;
68
+ transform: translateY(-50%);
69
+ }
70
+ .yak-panel-container[data-position="bottom-left"]:not(.yak-panel-drawer) {
71
+ bottom: 16px;
72
+ left: 16px;
73
+ }
74
+ .yak-panel-container[data-position="bottom-center"]:not(.yak-panel-drawer) {
75
+ bottom: 16px;
76
+ left: 50%;
77
+ transform: translateX(-50%);
78
+ }
79
+ .yak-panel-container[data-position="bottom-right"]:not(.yak-panel-drawer) {
80
+ bottom: 16px;
81
+ right: 16px;
82
+ }
83
+
84
+ /* Container visibility (chatbox mode) */
85
+ .yak-panel-container:not(.yak-panel-drawer) {
86
+ display: none;
87
+ }
88
+ .yak-panel-container:not(.yak-panel-drawer)[data-open="true"] {
89
+ display: block;
90
+ }
91
+
92
+ /* ===========================================
93
+ EXPANDED MODE (full viewport for data table)
94
+ =========================================== */
95
+ .yak-panel-container[data-expanded="true"] {
96
+ width: calc(100vw - 32px) !important;
97
+ height: calc(100vh - 32px) !important;
98
+ max-width: none !important;
99
+ max-height: none !important;
100
+ top: 16px !important;
101
+ left: 16px !important;
102
+ right: 16px !important;
103
+ bottom: 16px !important;
104
+ border-radius: 15px !important;
105
+ border: 1px solid rgba(0, 0, 0, 0.1) !important;
106
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15) !important;
107
+ }
108
+
109
+ @media (prefers-color-scheme: dark) {
110
+ .yak-panel-container[data-expanded="true"]:not(.yak-panel-light) {
111
+ border-color: rgba(255, 255, 255, 0.1) !important;
112
+ }
113
+ }
114
+
115
+ .yak-panel-container.yak-panel-dark[data-expanded="true"] {
116
+ border-color: rgba(255, 255, 255, 0.1) !important;
117
+ }
118
+
119
+ .yak-panel-container.yak-panel-light[data-expanded="true"] {
120
+ border-color: rgba(0, 0, 0, 0.1) !important;
121
+ }
122
+
123
+ /* ===========================================
124
+ DRAWER MODE
125
+ =========================================== */
126
+ .yak-panel-container.yak-panel-drawer {
127
+ height: calc(100% - 32px);
128
+ max-width: 100vw;
129
+ max-height: none;
130
+ border-radius: 15px;
131
+ transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
132
+ }
133
+
134
+ /* Drawer position - only left-center and right-center are relevant for drawer mode */
135
+ .yak-panel-container.yak-panel-drawer[data-position="left-center"],
136
+ .yak-panel-container.yak-panel-drawer[data-position="top-left"],
137
+ .yak-panel-container.yak-panel-drawer[data-position="bottom-left"] {
138
+ top: 16px;
139
+ left: 16px;
140
+ bottom: 16px;
141
+ transform: translateX(calc(-100% - 16px));
142
+ }
143
+ .yak-panel-container.yak-panel-drawer[data-position="right-center"],
144
+ .yak-panel-container.yak-panel-drawer[data-position="top-right"],
145
+ .yak-panel-container.yak-panel-drawer[data-position="bottom-right"] {
146
+ top: 16px;
147
+ right: 16px;
148
+ bottom: 16px;
149
+ transform: translateX(calc(100% + 16px));
150
+ }
151
+ /* Center positions default to right side for drawer */
152
+ .yak-panel-container.yak-panel-drawer[data-position="top-center"],
153
+ .yak-panel-container.yak-panel-drawer[data-position="bottom-center"] {
154
+ top: 16px;
155
+ right: 16px;
156
+ bottom: 16px;
157
+ transform: translateX(calc(100% + 16px));
158
+ }
159
+
160
+ /* Drawer open state */
161
+ .yak-panel-container.yak-panel-drawer[data-open="true"] {
162
+ transform: translateX(0);
163
+ }
164
+
165
+ /* ===========================================
166
+ IFRAME
167
+ =========================================== */
168
+ .yak-panel-iframe {
169
+ position: absolute;
170
+ inset: 0;
171
+ width: 100%;
172
+ height: 100%;
173
+ border: none;
174
+ }
175
+
176
+ /* ===========================================
177
+ MOBILE RESPONSIVE
178
+ =========================================== */
179
+ @media (max-width: 640px) {
180
+ .yak-panel-container:not(.yak-panel-drawer) {
181
+ width: 100% !important;
182
+ height: 100% !important;
183
+ height: 100dvh !important;
184
+ max-width: none !important;
185
+ max-height: none !important;
186
+ top: 0 !important;
187
+ left: 0 !important;
188
+ right: 0 !important;
189
+ bottom: 0 !important;
190
+ border-radius: 0 !important;
191
+ }
192
+ .yak-panel-container.yak-panel-drawer {
193
+ width: 100% !important;
194
+ max-width: none !important;
195
+ }
196
+ }
197
+ `;
198
+ }
199
+ /**
200
+ * YakProvider sets up the context, message handling, and renders the chat iframe.
201
+ * The iframe is rendered as a fixed-position panel that can be opened/closed.
9
202
  */
10
203
  export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, children, }) {
11
204
  const [iframeWindow, setIframeWindow] = useState(null);
@@ -13,6 +206,9 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
13
206
  const [isWidgetOpen, setIsWidgetOpen] = useState(false);
14
207
  const [pendingPrompt, setPendingPrompt] = useState(null);
15
208
  const [isIframeReady, setIsIframeReady] = useState(false);
209
+ const [hasBeenOpened, setHasBeenOpened] = useState(false);
210
+ const [isExpanded, setIsExpanded] = useState(false);
211
+ const iframeRef = useRef(null);
16
212
  // Store event subscribers for tool call events
17
213
  const toolEventSubscribersRef = useRef(new Set());
18
214
  // Handler that notifies all subscribers when a tool call completes
@@ -50,6 +246,7 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
50
246
  const client = clientRef.current;
51
247
  // Get the iframe origin from the client (computed based on environment)
52
248
  const iframeOrigin = client.getIframeOrigin();
249
+ const iframeSrc = client.getEmbedUrl();
53
250
  // Update client config when props change
54
251
  useEffect(() => {
55
252
  client.updateConfig({
@@ -63,7 +260,18 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
63
260
  chatConfig: chatConfig ?? undefined,
64
261
  onToolCallComplete: handleToolCallComplete,
65
262
  });
66
- }, [appId, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, chatConfig, client, handleToolCallComplete]);
263
+ }, [
264
+ appId,
265
+ onToolCall,
266
+ onGraphQLSchemaCall,
267
+ onRESTSchemaCall,
268
+ theme,
269
+ onRedirect,
270
+ disableRestartButton,
271
+ chatConfig,
272
+ client,
273
+ handleToolCallComplete,
274
+ ]);
67
275
  // Update client iframe window
68
276
  useEffect(() => {
69
277
  client.setIframeWindow(iframeWindow);
@@ -77,6 +285,67 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
77
285
  client.mount();
78
286
  return () => client.unmount();
79
287
  }, [client]);
288
+ // Track when widget is first opened
289
+ useEffect(() => {
290
+ if (isWidgetOpen && !hasBeenOpened) {
291
+ setHasBeenOpened(true);
292
+ }
293
+ }, [isWidgetOpen, hasBeenOpened]);
294
+ // Register iframe window when loaded
295
+ useEffect(() => {
296
+ if (!hasBeenOpened)
297
+ return;
298
+ const iframe = iframeRef.current;
299
+ if (!iframe)
300
+ return;
301
+ const handleLoad = () => {
302
+ if (iframe.contentWindow) {
303
+ logger.debug("Iframe window registered");
304
+ setIframeWindow(iframe.contentWindow);
305
+ }
306
+ };
307
+ iframe.addEventListener("load", handleLoad);
308
+ return () => {
309
+ iframe.removeEventListener("load", handleLoad);
310
+ logger.debug("Iframe window unregistered");
311
+ setIframeWindow(null);
312
+ };
313
+ }, [hasBeenOpened]);
314
+ // Listen for expansion messages from iframe
315
+ useEffect(() => {
316
+ const handleMessage = (event) => {
317
+ const iframe = iframeRef.current;
318
+ if (!iframe)
319
+ return;
320
+ // Check for expansion control messages
321
+ if (event.data?.type === "YAK_SET_EXPANDED") {
322
+ setIsExpanded(Boolean(event.data.expanded));
323
+ }
324
+ };
325
+ window.addEventListener("message", handleMessage);
326
+ return () => window.removeEventListener("message", handleMessage);
327
+ }, []);
328
+ // Detect mobile/fullscreen and notify iframe
329
+ useEffect(() => {
330
+ if (!isIframeReady)
331
+ return;
332
+ const mobileQuery = window.matchMedia("(max-width: 640px)");
333
+ const notifyFullscreen = (isFullscreen) => {
334
+ const msg = {
335
+ type: "yak:viewport",
336
+ payload: { fullscreen: isFullscreen },
337
+ };
338
+ iframeWindow?.postMessage(msg, iframeOrigin);
339
+ };
340
+ // Send initial state
341
+ notifyFullscreen(mobileQuery.matches);
342
+ // Listen for changes
343
+ const handleChange = (e) => {
344
+ notifyFullscreen(e.matches);
345
+ };
346
+ mobileQuery.addEventListener("change", handleChange);
347
+ return () => mobileQuery.removeEventListener("change", handleChange);
348
+ }, [isIframeReady, iframeWindow, iframeOrigin]);
80
349
  // Get chat config when widget is opened
81
350
  useEffect(() => {
82
351
  if (typeof window === "undefined" || !isWidgetOpen || !getConfig)
@@ -117,14 +386,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
117
386
  // Methods to get URLs from the client
118
387
  const getIframeOrigin = useCallback(() => client.getIframeOrigin(), [client]);
119
388
  const getEmbedUrl = useCallback(() => client.getEmbedUrl(), [client]);
120
- const registerIframeWindow = useCallback((win) => {
121
- logger.debug("Iframe window registered");
122
- setIframeWindow(win);
123
- }, []);
124
- const unregisterIframeWindow = useCallback(() => {
125
- logger.debug("Iframe window unregistered");
126
- setIframeWindow(null);
127
- }, []);
128
389
  const sendMessage = useCallback((message) => {
129
390
  iframeWindow?.postMessage(message, iframeOrigin);
130
391
  }, [iframeWindow, iframeOrigin]);
@@ -175,8 +436,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
175
436
  config,
176
437
  getIframeOrigin,
177
438
  getEmbedUrl,
178
- registerIframeWindow,
179
- unregisterIframeWindow,
180
439
  sendMessage,
181
440
  isOpen: isWidgetOpen,
182
441
  isIframeReady,
@@ -189,8 +448,6 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
189
448
  config,
190
449
  getIframeOrigin,
191
450
  getEmbedUrl,
192
- registerIframeWindow,
193
- unregisterIframeWindow,
194
451
  sendMessage,
195
452
  isWidgetOpen,
196
453
  isIframeReady,
@@ -199,5 +456,16 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
199
456
  openWithPrompt,
200
457
  subscribeToToolEvents,
201
458
  ]);
202
- return _jsx(YakContext.Provider, { value: contextValue, children: children });
459
+ // Determine position styles based on theme
460
+ const position = theme?.position ?? "bottom-right";
461
+ const colorMode = theme?.colorMode;
462
+ const displayMode = theme?.displayMode ?? "chatbox";
463
+ const isDrawer = displayMode === "drawer";
464
+ // Determine color mode class for the container
465
+ const colorModeClass = colorMode === "light" ? "yak-panel-light" : colorMode === "dark" ? "yak-panel-dark" : "";
466
+ // Build container class names
467
+ const containerClasses = ["yak-panel-container", isDrawer && "yak-panel-drawer", colorModeClass]
468
+ .filter(Boolean)
469
+ .join(" ");
470
+ return (_jsxs(YakContext.Provider, { value: contextValue, children: [children, _jsx("style", { children: getIframeStyles() }), hasBeenOpened && (_jsx("div", { className: "yak-panel-root", "data-expanded": isExpanded, children: _jsx("div", { className: containerClasses, "data-position": position, "data-open": isWidgetOpen && isIframeReady, "data-expanded": isExpanded, children: _jsx("iframe", { ref: iframeRef, src: iframeSrc, className: "yak-panel-iframe", title: "yak-chat-host" }) }) }))] }));
203
471
  }
@@ -1,16 +1,31 @@
1
- import React from "react";
1
+ import type React from "react";
2
+ import type { WidgetPosition } from "@yak-io/javascript";
2
3
  /**
3
4
  * Props for YakWidget
4
5
  */
5
6
  export type YakWidgetProps = {
6
- /** Optional CSS class name for the iframe */
7
- iframeClassName?: string;
8
7
  /** Text to display next to the logo */
9
8
  triggerLabel?: string;
9
+ /** Position of the button (defaults to theme position or "bottom-right") */
10
+ position?: WidgetPosition;
11
+ /** Color mode override */
12
+ colorMode?: "light" | "dark" | "system";
13
+ /** Custom button colors for light mode */
14
+ lightButton?: {
15
+ background?: string;
16
+ color?: string;
17
+ border?: string;
18
+ };
19
+ /** Custom button colors for dark mode */
20
+ darkButton?: {
21
+ background?: string;
22
+ color?: string;
23
+ border?: string;
24
+ };
10
25
  };
11
26
  /**
12
- * YakWidget renders a fixed-position launcher button and iframe panel.
13
- * The iframe loads immediately and shows its own skeleton while waiting for config.
27
+ * YakWidget renders a fixed-position launcher button.
28
+ * The chat iframe is rendered by YakProvider - this is just the trigger button.
14
29
  */
15
- export declare function YakWidget({ iframeClassName, triggerLabel, }?: YakWidgetProps): React.JSX.Element;
30
+ export declare function YakWidget({ triggerLabel, position, colorMode, lightButton, darkButton, }?: YakWidgetProps): React.JSX.Element;
16
31
  //# sourceMappingURL=YakWidget.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAqU3D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,eAAe,EACf,YAA4B,GAC7B,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAwJzC"}
1
+ {"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAwPzD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxC,0CAA0C;IAC1C,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,yCAAyC;IACzC,UAAU,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,YAA4B,EAC5B,QAAyB,EACzB,SAAS,EACT,WAAW,EACX,UAAU,GACX,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAuCzC"}
package/dist/YakWidget.js CHANGED
@@ -1,14 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useRef, useEffect, useState } from "react";
4
3
  import { useYak } from "./context.js";
5
4
  /**
6
- * All widget styles consolidated in one place
5
+ * All button styles consolidated in one place
7
6
  */
8
- function getWidgetStyles() {
7
+ function getButtonStyles() {
9
8
  return `
10
9
  /* ===========================================
11
- YAK WIDGET STYLES
10
+ YAK WIDGET BUTTON STYLES
12
11
  =========================================== */
13
12
 
14
13
  /* Trigger Button Base */
@@ -32,13 +31,47 @@ function getWidgetStyles() {
32
31
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
33
32
  }
34
33
 
35
- /* Trigger position variants */
36
- .yak-widget-trigger[data-position="left"] {
34
+ /* Trigger position variants - all 9 positions */
35
+ .yak-widget-trigger[data-position="top-left"] {
36
+ top: 28px;
37
+ left: 28px;
38
+ flex-direction: row-reverse;
39
+ }
40
+ .yak-widget-trigger[data-position="top-center"] {
41
+ top: 28px;
42
+ left: 50%;
43
+ transform: translateX(-50%);
44
+ flex-direction: row;
45
+ }
46
+ .yak-widget-trigger[data-position="top-right"] {
47
+ top: 28px;
48
+ right: 28px;
49
+ flex-direction: row;
50
+ }
51
+ .yak-widget-trigger[data-position="left-center"] {
52
+ top: 50%;
53
+ left: 28px;
54
+ transform: translateY(-50%);
55
+ flex-direction: row-reverse;
56
+ }
57
+ .yak-widget-trigger[data-position="right-center"] {
58
+ top: 50%;
59
+ right: 28px;
60
+ transform: translateY(-50%);
61
+ flex-direction: row;
62
+ }
63
+ .yak-widget-trigger[data-position="bottom-left"] {
37
64
  bottom: 28px;
38
65
  left: 28px;
39
66
  flex-direction: row-reverse;
40
67
  }
41
- .yak-widget-trigger[data-position="right"] {
68
+ .yak-widget-trigger[data-position="bottom-center"] {
69
+ bottom: 28px;
70
+ left: 50%;
71
+ transform: translateX(-50%);
72
+ flex-direction: row;
73
+ }
74
+ .yak-widget-trigger[data-position="bottom-right"] {
42
75
  bottom: 28px;
43
76
  right: 28px;
44
77
  flex-direction: row;
@@ -85,7 +118,6 @@ function getWidgetStyles() {
85
118
  /* Loading/disabled state for trigger button */
86
119
  .yak-widget-trigger:disabled {
87
120
  cursor: wait;
88
- opacity: 0.8;
89
121
  }
90
122
 
91
123
  /* Custom button styles for forced light mode */
@@ -171,179 +203,38 @@ function getWidgetStyles() {
171
203
  .yak-widget-trigger.yak-widget-dark:not(.yak-widget-custom-dark) .yak-widget-icon-bg {
172
204
  background-color: rgba(255, 255, 255, 0.1);
173
205
  }
174
-
175
- /* ===========================================
176
- BACKDROP
177
- =========================================== */
178
- .yak-widget-backdrop {
179
- position: fixed;
180
- top: 0;
181
- left: 0;
182
- right: 0;
183
- bottom: 0;
184
- background-color: rgba(0, 0, 0, 0.5);
185
- z-index: 9997;
186
- opacity: 0;
187
- visibility: hidden;
188
- transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
189
- }
190
- .yak-widget-backdrop[data-open="true"] {
191
- opacity: 1;
192
- visibility: visible;
193
- }
194
-
195
- /* ===========================================
196
- CONTAINER
197
- =========================================== */
198
- .yak-widget-container {
199
- position: fixed;
200
- width: 500px;
201
- height: 600px;
202
- max-width: calc(100vw - 40px);
203
- max-height: calc(100vh - 120px);
204
- border-radius: 15px;
205
- overflow: hidden;
206
- z-index: 9998;
207
- background-color: transparent;
208
- }
209
-
210
- /* Container position variants (chatbox mode) */
211
- .yak-widget-container[data-position="left"]:not(.yak-widget-drawer) {
212
- bottom: 16px;
213
- left: 16px;
214
- }
215
- .yak-widget-container[data-position="right"]:not(.yak-widget-drawer) {
216
- bottom: 16px;
217
- right: 16px;
218
- }
219
-
220
- /* Container visibility (chatbox mode) */
221
- .yak-widget-container:not(.yak-widget-drawer) {
222
- display: none;
223
- }
224
- .yak-widget-container:not(.yak-widget-drawer)[data-open="true"] {
225
- display: block;
226
- }
227
-
228
- /* ===========================================
229
- DRAWER MODE
230
- =========================================== */
231
- .yak-widget-container.yak-widget-drawer {
232
- height: calc(100% - 32px);
233
- max-width: 100vw;
234
- max-height: none;
235
- border-radius: 15px;
236
- transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
237
- }
238
-
239
- /* Drawer position */
240
- .yak-widget-container.yak-widget-drawer[data-position="left"] {
241
- top: 16px;
242
- left: 16px;
243
- bottom: 16px;
244
- transform: translateX(calc(-100% - 16px));
245
- }
246
- .yak-widget-container.yak-widget-drawer[data-position="right"] {
247
- top: 16px;
248
- right: 16px;
249
- bottom: 16px;
250
- transform: translateX(calc(100% + 16px));
251
- }
252
-
253
- /* Drawer open state */
254
- .yak-widget-container.yak-widget-drawer[data-open="true"] {
255
- transform: translateX(0);
256
- }
257
-
258
- /* ===========================================
259
- IFRAME
260
- =========================================== */
261
- .yak-widget-iframe {
262
- position: absolute;
263
- inset: 0;
264
- width: 100%;
265
- height: 100%;
266
- border: none;
267
- }
268
-
269
- /* ===========================================
270
- MOBILE RESPONSIVE
271
- =========================================== */
272
- @media (max-width: 640px) {
273
- .yak-widget-container:not(.yak-widget-drawer) {
274
- width: 100% !important;
275
- height: 100% !important;
276
- height: 100dvh !important;
277
- max-width: none !important;
278
- max-height: none !important;
279
- top: 0 !important;
280
- left: 0 !important;
281
- right: 0 !important;
282
- bottom: 0 !important;
283
- border-radius: 0 !important;
284
- }
285
- .yak-widget-container.yak-widget-drawer {
286
- width: 100% !important;
287
- max-width: none !important;
288
- }
289
- }
290
206
  `;
291
207
  }
292
208
  /**
293
209
  * Inline SVG for brain/circuit fallback icon
294
210
  */
295
- function BrainCircuitIcon({ size = 20, className }) {
296
- return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [_jsx("path", { d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" }), _jsx("path", { d: "M9 13a4.5 4.5 0 0 0 3-4" }), _jsx("path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5" }), _jsx("path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396" }), _jsx("path", { d: "M6 18a4 4 0 0 1-1.967-.516" }), _jsx("path", { d: "M12 13h4" }), _jsx("path", { d: "M12 18h6a2 2 0 0 1 2 2v1" }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M16 8V5a2 2 0 0 1 2-2" }), _jsx("circle", { cx: "16", cy: "13", r: ".5" }), _jsx("circle", { cx: "18", cy: "3", r: ".5" }), _jsx("circle", { cx: "20", cy: "21", r: ".5" }), _jsx("circle", { cx: "20", cy: "8", r: ".5" })] }));
211
+ function BrainCircuitIcon({ size = 20, className, }) {
212
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [_jsx("path", { d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" }), _jsx("path", { d: "M9 13a4.5 4.5 0 0 0 3-4" }), _jsx("path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5" }), _jsx("path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396" }), _jsx("path", { d: "M6 18a4 4 0 0 1-1.967-.516" }), _jsx("path", { d: "M12 13h4" }), _jsx("path", { d: "M12 18h6a2 2 0 0 1 2 2v1" }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M16 8V5a2 2 0 0 1 2-2" }), _jsx("circle", { cx: "16", cy: "13", r: ".5" }), _jsx("circle", { cx: "18", cy: "3", r: ".5" }), _jsx("circle", { cx: "20", cy: "21", r: ".5" }), _jsx("circle", { cx: "20", cy: "8", r: ".5" })] }));
297
213
  }
298
214
  /**
299
- * YakWidget renders a fixed-position launcher button and iframe panel.
300
- * The iframe loads immediately and shows its own skeleton while waiting for config.
215
+ * Compute button CSS variables from custom button color props
301
216
  */
302
- export function YakWidget({ iframeClassName, triggerLabel = "Ask with AI", } = {}) {
303
- const { config, getEmbedUrl, registerIframeWindow, unregisterIframeWindow, isOpen, isIframeReady, open, close, } = useYak();
304
- const iframeRef = useRef(null);
305
- const [hasBeenOpened, setHasBeenOpened] = useState(false);
306
- // Track if we're in a loading state (open but iframe not ready)
307
- const isLoading = isOpen && hasBeenOpened && !isIframeReady;
308
- // Get URLs from the client (computed based on environment)
309
- const iframeSrc = getEmbedUrl();
310
- // Track when widget is first opened
311
- useEffect(() => {
312
- if (isOpen && !hasBeenOpened) {
313
- setHasBeenOpened(true);
314
- }
315
- }, [isOpen, hasBeenOpened]);
316
- // Register iframe window when loaded
317
- useEffect(() => {
318
- if (!hasBeenOpened)
319
- return;
320
- const iframe = iframeRef.current;
321
- if (!iframe)
322
- return;
323
- const handleLoad = () => {
324
- if (iframe.contentWindow) {
325
- registerIframeWindow(iframe.contentWindow);
326
- }
327
- };
328
- iframe.addEventListener("load", handleLoad);
329
- return () => {
330
- iframe.removeEventListener("load", handleLoad);
331
- unregisterIframeWindow();
332
- };
333
- }, [registerIframeWindow, unregisterIframeWindow, hasBeenOpened]);
334
- // Determine position styles based on theme
335
- const position = config.theme?.position ?? "right";
336
- const colorMode = config.theme?.colorMode;
337
- const displayMode = config.theme?.displayMode ?? "chatbox";
338
- const isDrawer = displayMode === "drawer";
339
- // Determine color mode class for the widget
217
+ function buildButtonStyle(lightButton, darkButton) {
218
+ const style = {};
219
+ if (lightButton?.background)
220
+ style["--yak-btn-light-bg"] = lightButton.background;
221
+ if (lightButton?.color)
222
+ style["--yak-btn-light-color"] = lightButton.color;
223
+ if (lightButton?.border)
224
+ style["--yak-btn-light-border"] = lightButton.border;
225
+ if (darkButton?.background)
226
+ style["--yak-btn-dark-bg"] = darkButton.background;
227
+ if (darkButton?.color)
228
+ style["--yak-btn-dark-color"] = darkButton.color;
229
+ if (darkButton?.border)
230
+ style["--yak-btn-dark-border"] = darkButton.border;
231
+ return style;
232
+ }
233
+ /**
234
+ * Compute button class names from color mode and custom theme flags
235
+ */
236
+ function buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom) {
340
237
  const colorModeClass = colorMode === "light" ? "yak-widget-light" : colorMode === "dark" ? "yak-widget-dark" : "";
341
- // Check if custom button theme is provided (now nested in light/dark)
342
- const lightButton = config.theme?.light?.button;
343
- const darkButton = config.theme?.dark?.button;
344
- const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
345
- const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
346
- // Determine which custom class to apply based on color mode
347
238
  let customButtonClass = "";
348
239
  if (colorMode === "light" && hasLightCustom) {
349
240
  customButtonClass = "yak-widget-custom-light";
@@ -351,35 +242,18 @@ export function YakWidget({ iframeClassName, triggerLabel = "Ask with AI", } = {
351
242
  else if (colorMode === "dark" && hasDarkCustom) {
352
243
  customButtonClass = "yak-widget-custom-dark";
353
244
  }
354
- else if (colorMode === "system" || colorMode === undefined) {
355
- // For system mode, we need to handle both via media queries
356
- // We'll use a data attribute approach instead
357
- }
358
- // Build inline style for button CSS variables
359
- const buttonStyle = {};
360
- if (lightButton?.background)
361
- buttonStyle["--yak-btn-light-bg"] = lightButton.background;
362
- if (lightButton?.color)
363
- buttonStyle["--yak-btn-light-color"] = lightButton.color;
364
- if (lightButton?.border)
365
- buttonStyle["--yak-btn-light-border"] = lightButton.border;
366
- if (darkButton?.background)
367
- buttonStyle["--yak-btn-dark-bg"] = darkButton.background;
368
- if (darkButton?.color)
369
- buttonStyle["--yak-btn-dark-color"] = darkButton.color;
370
- if (darkButton?.border)
371
- buttonStyle["--yak-btn-dark-border"] = darkButton.border;
372
- // Build container class names
373
- const containerClasses = [
374
- "yak-widget-container",
375
- isDrawer && "yak-widget-drawer",
376
- colorModeClass,
377
- ].filter(Boolean).join(" ");
378
- // Build button class names
379
- const buttonClasses = [
380
- "yak-widget-trigger",
381
- colorModeClass,
382
- customButtonClass,
383
- ].filter(Boolean).join(" ");
384
- return (_jsxs(_Fragment, { children: [_jsx("style", { children: getWidgetStyles() }), _jsxs("button", { onClick: open, className: buttonClasses, style: Object.keys(buttonStyle).length > 0 ? buttonStyle : undefined, "data-position": position, "data-has-light-custom": hasLightCustom || undefined, "data-has-dark-custom": hasDarkCustom || undefined, "aria-label": isLoading ? "Loading chat" : "Open chat", disabled: isLoading, children: [_jsx("span", { className: "yak-widget-trigger-label", children: triggerLabel }), _jsx("div", { className: "yak-widget-icon-bg", children: isLoading ? (_jsx("div", { className: "yak-widget-spinner", "aria-hidden": "true" })) : (_jsx(BrainCircuitIcon, { size: 20, className: "yak-widget-icon" })) })] }), isDrawer && hasBeenOpened && (_jsx("div", { className: "yak-widget-backdrop", "data-open": isOpen && isIframeReady, onClick: close, "aria-hidden": "true" })), hasBeenOpened && (_jsx("div", { className: containerClasses, "data-position": position, "data-open": isOpen && isIframeReady, children: _jsx("iframe", { ref: iframeRef, src: iframeSrc, className: `yak-widget-iframe ${iframeClassName ?? ""}`.trim(), title: "yak-chat-host" }) }))] }));
245
+ return ["yak-widget-trigger", colorModeClass, customButtonClass].filter(Boolean).join(" ");
246
+ }
247
+ /**
248
+ * YakWidget renders a fixed-position launcher button.
249
+ * The chat iframe is rendered by YakProvider - this is just the trigger button.
250
+ */
251
+ export function YakWidget({ triggerLabel = "Ask with AI", position = "bottom-right", colorMode, lightButton, darkButton, } = {}) {
252
+ const { open, isOpen, isIframeReady } = useYak();
253
+ const isLoading = isOpen && !isIframeReady;
254
+ const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
255
+ const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
256
+ const buttonStyle = buildButtonStyle(lightButton, darkButton);
257
+ const buttonClasses = buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom);
258
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: getButtonStyles() }), _jsxs("button", { type: "button", onClick: open, className: buttonClasses, style: Object.keys(buttonStyle).length > 0 ? buttonStyle : undefined, "data-position": position, "data-has-light-custom": hasLightCustom || undefined, "data-has-dark-custom": hasDarkCustom || undefined, "aria-label": isLoading ? "Loading chat" : "Open chat", disabled: isLoading, children: [_jsx("span", { className: "yak-widget-trigger-label", children: triggerLabel }), _jsx("div", { className: "yak-widget-icon-bg", children: isLoading ? (_jsx("div", { className: "yak-widget-spinner", "aria-hidden": "true" })) : (_jsx(BrainCircuitIcon, { size: 20, className: "yak-widget-icon" })) })] })] }));
385
259
  }
package/dist/context.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Theme, IframeMessageFromHost, ChatConfig, ToolCallEvent } from "@yak-io/javascript";
1
+ import type { Theme, ChatConfig, ToolCallEvent, IframeMessageFromHost } from "@yak-io/javascript";
2
2
  /**
3
3
  * Configuration for the yak provider
4
4
  */
@@ -13,17 +13,10 @@ export type YakConfig = {
13
13
  */
14
14
  export type ToolCallEventHandler = (event: ToolCallEvent) => void;
15
15
  /**
16
- * Context value with config and iframe management
16
+ * Public API for controlling the Yak chat widget.
17
+ * This is what consumers get from useYak().
17
18
  */
18
19
  export type YakContextValue = {
19
- config: YakConfig;
20
- /** Get the iframe origin URL (determined by environment) */
21
- getIframeOrigin: () => string;
22
- /** Get the full iframe embed URL */
23
- getEmbedUrl: () => string;
24
- registerIframeWindow: (win: Window) => void;
25
- unregisterIframeWindow: () => void;
26
- sendMessage: (message: IframeMessageFromHost) => void;
27
20
  /** Whether the chat widget is currently open */
28
21
  isOpen: boolean;
29
22
  /** Whether the iframe is ready to receive messages */
@@ -34,11 +27,25 @@ export type YakContextValue = {
34
27
  close: () => void;
35
28
  /** Open the chat widget and trigger a specific prompt */
36
29
  openWithPrompt: (prompt: string) => void;
37
- setIsIframeReady: (ready: boolean) => void;
38
30
  /** Subscribe to tool call completion events */
39
31
  subscribeToToolEvents: (handler: ToolCallEventHandler) => () => void;
40
32
  };
41
- export declare const YakContext: import("react").Context<YakContextValue | null>;
33
+ /**
34
+ * Internal context with additional methods for iframe management.
35
+ * Not exposed to consumers - only used by provider internals.
36
+ */
37
+ export type YakInternalContextValue = YakContextValue & {
38
+ config: YakConfig;
39
+ /** Get the iframe origin URL (determined by environment) */
40
+ getIframeOrigin: () => string;
41
+ /** Get the full iframe embed URL */
42
+ getEmbedUrl: () => string;
43
+ /** Send a message to the iframe */
44
+ sendMessage: (message: IframeMessageFromHost) => void;
45
+ /** Set iframe ready state (called by provider) */
46
+ setIsIframeReady: (ready: boolean) => void;
47
+ };
48
+ export declare const YakContext: import("react").Context<YakInternalContextValue | null>;
42
49
  /**
43
50
  * Hook to access the Yak chat widget API.
44
51
  * Provides methods for opening, closing, and triggering prompts.
@@ -60,6 +67,11 @@ export declare const YakContext: import("react").Context<YakContextValue | null>
60
67
  * @throws {Error} if used outside YakProvider
61
68
  */
62
69
  export declare function useYak(): YakContextValue;
70
+ /**
71
+ * Internal hook for components that need full context access.
72
+ * Not exported publicly.
73
+ */
74
+ export declare function useYakInternal(): YakInternalContextValue;
63
75
  /**
64
76
  * Hook to subscribe to tool call completion events.
65
77
  * Useful for page-level cache invalidation when chatbot tools modify data.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAElG;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,gDAAgD;IAChD,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,aAAa,EAAE,OAAO,CAAC;IACvB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,+CAA+C;IAC/C,qBAAqB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAC;CACtE,CAAC;AAEF,eAAO,MAAM,UAAU,iDAA8C,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,IAAI,eAAe,CAMxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAkBnE"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAElG;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gDAAgD;IAChD,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,aAAa,EAAE,OAAO,CAAC;IACvB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,+CAA+C;IAC/C,qBAAqB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAC;CACtE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,eAAe,GAAG;IACtD,MAAM,EAAE,SAAS,CAAC;IAClB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,mCAAmC;IACnC,WAAW,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,kDAAkD;IAClD,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,eAAO,MAAM,UAAU,yDAAsD,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,IAAI,eAAe,CAcxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,uBAAuB,CAMxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAkBnE"}
package/dist/context.js CHANGED
@@ -26,6 +26,25 @@ export function useYak() {
26
26
  if (!context) {
27
27
  throw new Error("useYak must be used within YakProvider");
28
28
  }
29
+ // Return only the public API
30
+ return {
31
+ isOpen: context.isOpen,
32
+ isIframeReady: context.isIframeReady,
33
+ open: context.open,
34
+ close: context.close,
35
+ openWithPrompt: context.openWithPrompt,
36
+ subscribeToToolEvents: context.subscribeToToolEvents,
37
+ };
38
+ }
39
+ /**
40
+ * Internal hook for components that need full context access.
41
+ * Not exported publicly.
42
+ */
43
+ export function useYakInternal() {
44
+ const context = useContext(YakContext);
45
+ if (!context) {
46
+ throw new Error("useYakInternal must be used within YakProvider");
47
+ }
29
48
  return context;
30
49
  }
31
50
  /**
package/dist/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { YakProvider } from "./YakProvider.js";
4
4
  export type { YakProviderProps } from "./YakProvider.js";
5
5
  export { YakWidget } from "./YakWidget.js";
6
6
  export type { YakWidgetProps } from "./YakWidget.js";
7
- export { type GraphQLSchemaHandler, type RESTSchemaHandler, type GraphQLRequest, type RESTRequest, type ToolCallHandler, type ToolCallEvent, type SchemaSource, type GraphQLSchemaSource, type OpenAPISchemaSource, type Theme, type ThemeColors, type ButtonColors, } from "@yak-io/javascript";
7
+ export type { GraphQLSchemaHandler, RESTSchemaHandler, GraphQLRequest, RESTRequest, ToolCallHandler, ToolCallEvent, SchemaSource, GraphQLSchemaSource, OpenAPISchemaSource, Theme, ThemeColors, ButtonColors, WidgetPosition, } from "@yak-io/javascript";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@yak-io/react",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "React SDK for embedding yak chatbot",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "author": "Yak <support@yak.io>",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/9f-au/yak.git",
10
+ "url": "https://github.com/388-labs/yak.git",
11
11
  "directory": "packages/react"
12
12
  },
13
13
  "publishConfig": {
@@ -41,21 +41,23 @@
41
41
  "./package.json": "./package.json"
42
42
  },
43
43
  "dependencies": {
44
- "@yak-io/javascript": "0.3.0"
44
+ "@yak-io/javascript": "0.4.1"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^18.0.0 || ^19.0.0",
48
48
  "react-dom": "^18.0.0 || ^19.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@types/node": "^24.10.2",
52
- "@types/react": "^19.2.7",
51
+ "@types/node": "^24.10.4",
52
+ "@types/react": "^19.2.14",
53
53
  "@types/react-dom": "^19.2.0",
54
54
  "typescript": "^5.3.0",
55
55
  "@repo/typescript-config": "0.0.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc",
59
- "check-types": "tsc --noEmit"
59
+ "check-types": "tsc --noEmit",
60
+ "lint": "biome lint ./src --fix",
61
+ "format": "biome format ./src --write"
60
62
  }
61
63
  }