@agentforge-io/chat-sdk 2.3.1 → 2.4.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ChatDrawer.d.ts +43 -78
- package/dist/ChatDrawer.js +131 -219
- package/package.json +9 -3
package/dist/ChatDrawer.d.ts
CHANGED
|
@@ -1,60 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `<ChatDrawer>` — standard
|
|
2
|
+
* `<ChatDrawer>` — standard mobile chat shell.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* URL sync, the SDK doesn't touch the URL).
|
|
7
|
-
* - All the `<ChatWidget>` props (token, apiBaseUrl, etc.): forwarded
|
|
8
|
-
* verbatim. The widget mounts INSIDE the drawer.
|
|
4
|
+
* Mobile-first fullscreen modal that wraps `<ChatWidget>` and gets the
|
|
5
|
+
* three pieces of mobile UX every embed needs right:
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* layout viewport). On iOS Safari and Android Chrome the on-screen
|
|
16
|
-
* keyboard shrinks `window.visualViewport.height`; we listen to
|
|
17
|
-
* `resize` / `scroll` on visualViewport and reflow the drawer's
|
|
18
|
-
* `height` + `bottom` so the composer never gets clipped by the
|
|
19
|
-
* keyboard.
|
|
20
|
-
* - Opens at a configurable snap fraction (default 0.98 = 98% of the
|
|
21
|
-
* visible viewport). The visitor sees a thin sliver of the page
|
|
22
|
-
* underneath, which keeps context and lets a tap-outside dismiss.
|
|
23
|
-
* - Drag-to-dismiss with vanilla touch events: drag the handle down
|
|
24
|
-
* past 30% of the panel height and the drawer closes. No external
|
|
25
|
-
* deps — the SDK stays portable.
|
|
26
|
-
* - Survives close+reopen: the widget inside is rendered once and kept
|
|
27
|
-
* alive (CSS `display:none` toggle when closed, NOT unmounted), so
|
|
28
|
-
* the chat session, transcript, and any in-flight tool calls stay
|
|
29
|
-
* intact when the visitor closes and reopens the drawer.
|
|
7
|
+
* 1. **Sticky header** at the top (back button + agent identity).
|
|
8
|
+
* 2. **Scrollable transcript** in the middle (the SDK panel +
|
|
9
|
+
* its own scroller).
|
|
10
|
+
* 3. **Sticky composer** at the bottom that NEVER hides under
|
|
11
|
+
* the on-screen keyboard.
|
|
30
12
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
13
|
+
* Why Vaul: implementing the keyboard-aware sticky composer with
|
|
14
|
+
* vanilla `visualViewport` listeners is doable in 50 lines but the
|
|
15
|
+
* real-world iOS Safari / Chrome Android edge cases (rubber-band
|
|
16
|
+
* scroll, address-bar transitions, autocorrect bar over the keyboard,
|
|
17
|
+
* focus loss on send button disable) are not. Vaul handles all of
|
|
18
|
+
* them (used by Linear, Vercel, etc.). It's declared as an OPTIONAL
|
|
19
|
+
* peerDependency so the SDK stays portable — if a host doesn't install
|
|
20
|
+
* Vaul, `<ChatDrawer>` falls back to a render that asks the host to
|
|
21
|
+
* install it. Most hosts already have Vaul (Radix design system).
|
|
22
|
+
*
|
|
23
|
+
* Why fullscreen (not snap points): the operator decision was that a
|
|
24
|
+
* conversation should occupy the whole device. No drag-to-dismiss,
|
|
25
|
+
* no peek of the page behind. Close via the explicit back button in
|
|
26
|
+
* the header. This is the "modal" feel — predictable, no accidental
|
|
27
|
+
* dismissals from a fast scroll.
|
|
28
|
+
*
|
|
29
|
+
* Why we still don't manage the chat session: the SDK's `ChatWidget`
|
|
30
|
+
* owns that, this component is purely the surround.
|
|
39
31
|
*/
|
|
40
32
|
import { type CSSProperties, type ReactNode } from 'react';
|
|
41
33
|
import { type ChatWidgetProps } from './react';
|
|
42
34
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* creates the widget internally. The standard / forward-looking
|
|
47
|
-
* API — most consumers should use this.
|
|
48
|
-
*
|
|
49
|
-
* - `chatSlot`: pass a pre-rendered React node (typically a
|
|
50
|
-
* `<ChatWidget>` instance the host wired up itself). Useful when
|
|
51
|
-
* the host already orchestrates the widget (multiple chat slots,
|
|
52
|
-
* custom decorators, legacy code) and just wants the drawer's
|
|
53
|
-
* positioning + drag UX on top.
|
|
54
|
-
*
|
|
55
|
-
* Exactly one of the two MUST be passed. Mutual exclusivity isn't
|
|
56
|
-
* encoded at the type level (TS unions with optional props get noisy)
|
|
57
|
-
* — the runtime asserts gracefully if neither is present.
|
|
35
|
+
* Two intake shapes — either drop in a pre-built `<ChatWidget>` as
|
|
36
|
+
* `chatSlot` (most hosts) or hand us the widget props and the drawer
|
|
37
|
+
* mounts the widget itself.
|
|
58
38
|
*/
|
|
59
39
|
type ChatSurface = {
|
|
60
40
|
widgetProps: ChatWidgetProps;
|
|
@@ -64,39 +44,24 @@ type ChatSurface = {
|
|
|
64
44
|
widgetProps?: never;
|
|
65
45
|
};
|
|
66
46
|
export type ChatDrawerProps = ChatSurface & {
|
|
67
|
-
/**
|
|
47
|
+
/** Controlled visibility. */
|
|
68
48
|
open: boolean;
|
|
69
|
-
/** Fired when the drawer wants to close (
|
|
70
|
-
*
|
|
49
|
+
/** Fired when the drawer wants to close (back button, ESC). The
|
|
50
|
+
* host is responsible for setting `open=false`. */
|
|
71
51
|
onOpenChange: (open: boolean) => void;
|
|
72
|
-
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
snap?: number;
|
|
76
|
-
/** Display in the drawer header above the chat. Templates pass the
|
|
77
|
-
* agent / team identity here. When omitted, the drawer renders a
|
|
78
|
-
* bare drag handle only — useful for hosts that want a borderless
|
|
79
|
-
* panel. */
|
|
52
|
+
/** Sticky header above the chat panel. Templates pass the agent /
|
|
53
|
+
* team identity card here. Optional — when omitted the drawer
|
|
54
|
+
* renders just the close button. */
|
|
80
55
|
header?: ReactNode;
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/** Extra class on the drawer's root surface (the white card). Use it to
|
|
86
|
-
* add a custom shadow / border colour. The CSS vars on `--af-*` already
|
|
87
|
-
* let you re-theme the chat widget itself; this is for the SURROUND. */
|
|
56
|
+
/** Custom close button. Defaults to a chevron-left back button at
|
|
57
|
+
* the left edge of the header. */
|
|
58
|
+
closeButton?: ReactNode;
|
|
59
|
+
/** Extra class on the drawer surface (the full-height card). */
|
|
88
60
|
drawerClassName?: string;
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* host re-declares them here so the chat surface inside the portal
|
|
94
|
-
* picks up the same theme.
|
|
95
|
-
*
|
|
96
|
-
* We default the SURFACE BG to `var(--af-bg, …)` so passing
|
|
97
|
-
* `--af-bg` in `surfaceStyle` themes the drawer's background.
|
|
98
|
-
* When omitted the drawer falls back to white in light mode and
|
|
99
|
-
* the system default elsewhere — passable but not theme-perfect. */
|
|
61
|
+
/** CSS variables (`--af-bg`, `--af-fg`, `--af-bubble-*`, etc.) for
|
|
62
|
+
* the drawer surface. Because the drawer renders into a portal,
|
|
63
|
+
* the host's page-wrapper vars don't cascade in — re-declare them
|
|
64
|
+
* here so the chat surface theme matches. */
|
|
100
65
|
surfaceStyle?: CSSProperties & Record<string, string>;
|
|
101
66
|
};
|
|
102
67
|
export declare function ChatDrawer(props: ChatDrawerProps): JSX.Element | null;
|
package/dist/ChatDrawer.js
CHANGED
|
@@ -3,136 +3,59 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ChatDrawer = ChatDrawer;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
/**
|
|
6
|
-
* `<ChatDrawer>` — standard
|
|
6
|
+
* `<ChatDrawer>` — standard mobile chat shell.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* URL sync, the SDK doesn't touch the URL).
|
|
11
|
-
* - All the `<ChatWidget>` props (token, apiBaseUrl, etc.): forwarded
|
|
12
|
-
* verbatim. The widget mounts INSIDE the drawer.
|
|
8
|
+
* Mobile-first fullscreen modal that wraps `<ChatWidget>` and gets the
|
|
9
|
+
* three pieces of mobile UX every embed needs right:
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* layout viewport). On iOS Safari and Android Chrome the on-screen
|
|
20
|
-
* keyboard shrinks `window.visualViewport.height`; we listen to
|
|
21
|
-
* `resize` / `scroll` on visualViewport and reflow the drawer's
|
|
22
|
-
* `height` + `bottom` so the composer never gets clipped by the
|
|
23
|
-
* keyboard.
|
|
24
|
-
* - Opens at a configurable snap fraction (default 0.98 = 98% of the
|
|
25
|
-
* visible viewport). The visitor sees a thin sliver of the page
|
|
26
|
-
* underneath, which keeps context and lets a tap-outside dismiss.
|
|
27
|
-
* - Drag-to-dismiss with vanilla touch events: drag the handle down
|
|
28
|
-
* past 30% of the panel height and the drawer closes. No external
|
|
29
|
-
* deps — the SDK stays portable.
|
|
30
|
-
* - Survives close+reopen: the widget inside is rendered once and kept
|
|
31
|
-
* alive (CSS `display:none` toggle when closed, NOT unmounted), so
|
|
32
|
-
* the chat session, transcript, and any in-flight tool calls stay
|
|
33
|
-
* intact when the visitor closes and reopens the drawer.
|
|
11
|
+
* 1. **Sticky header** at the top (back button + agent identity).
|
|
12
|
+
* 2. **Scrollable transcript** in the middle (the SDK panel +
|
|
13
|
+
* its own scroller).
|
|
14
|
+
* 3. **Sticky composer** at the bottom that NEVER hides under
|
|
15
|
+
* the on-screen keyboard.
|
|
34
16
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
17
|
+
* Why Vaul: implementing the keyboard-aware sticky composer with
|
|
18
|
+
* vanilla `visualViewport` listeners is doable in 50 lines but the
|
|
19
|
+
* real-world iOS Safari / Chrome Android edge cases (rubber-band
|
|
20
|
+
* scroll, address-bar transitions, autocorrect bar over the keyboard,
|
|
21
|
+
* focus loss on send button disable) are not. Vaul handles all of
|
|
22
|
+
* them (used by Linear, Vercel, etc.). It's declared as an OPTIONAL
|
|
23
|
+
* peerDependency so the SDK stays portable — if a host doesn't install
|
|
24
|
+
* Vaul, `<ChatDrawer>` falls back to a render that asks the host to
|
|
25
|
+
* install it. Most hosts already have Vaul (Radix design system).
|
|
26
|
+
*
|
|
27
|
+
* Why fullscreen (not snap points): the operator decision was that a
|
|
28
|
+
* conversation should occupy the whole device. No drag-to-dismiss,
|
|
29
|
+
* no peek of the page behind. Close via the explicit back button in
|
|
30
|
+
* the header. This is the "modal" feel — predictable, no accidental
|
|
31
|
+
* dismissals from a fast scroll.
|
|
32
|
+
*
|
|
33
|
+
* Why we still don't manage the chat session: the SDK's `ChatWidget`
|
|
34
|
+
* owns that, this component is purely the surround.
|
|
43
35
|
*/
|
|
44
36
|
const react_1 = require("react");
|
|
45
|
-
const react_dom_1 = require("react-dom");
|
|
46
37
|
const react_2 = require("./react");
|
|
38
|
+
// Lazy import of Vaul. We can't `import { Drawer } from 'vaul'` at
|
|
39
|
+
// the top of the file because vaul is an OPTIONAL peerDependency
|
|
40
|
+
// and the package shouldn't crash at import-time when the host
|
|
41
|
+
// hasn't installed it. We resolve at module evaluation but inside a
|
|
42
|
+
// try/catch so the missing-vaul case is just a runtime "please
|
|
43
|
+
// install vaul" warning instead of a build break.
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
let VaulDrawer = null;
|
|
46
|
+
try {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
|
+
VaulDrawer = require('vaul').Drawer;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// vaul not installed — render a fallback in the component.
|
|
52
|
+
}
|
|
47
53
|
function ChatDrawer(props) {
|
|
48
|
-
const { open, onOpenChange,
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
const [hasOpened, setHasOpened] = (0, react_1.useState)(open);
|
|
54
|
-
(0, react_1.useEffect)(() => {
|
|
55
|
-
if (open)
|
|
56
|
-
setHasOpened(true);
|
|
57
|
-
}, [open]);
|
|
58
|
-
// Visible viewport tracking. iOS Safari + Android Chrome shrink
|
|
59
|
-
// `visualViewport.height` when the on-screen keyboard pops up; the
|
|
60
|
-
// layout viewport stays the same. We pin the drawer's height + bottom
|
|
61
|
-
// to the visual viewport so the composer is always above the keyboard.
|
|
62
|
-
const [vv, setVv] = (0, react_1.useState)(() => ({
|
|
63
|
-
h: typeof window === 'undefined' ? 0 : window.innerHeight,
|
|
64
|
-
offsetTop: 0,
|
|
65
|
-
}));
|
|
66
|
-
(0, react_1.useEffect)(() => {
|
|
67
|
-
if (typeof window === 'undefined')
|
|
68
|
-
return;
|
|
69
|
-
const view = window.visualViewport;
|
|
70
|
-
if (!view)
|
|
71
|
-
return;
|
|
72
|
-
const update = () => setVv({ h: view.height, offsetTop: view.offsetTop });
|
|
73
|
-
update();
|
|
74
|
-
view.addEventListener('resize', update);
|
|
75
|
-
view.addEventListener('scroll', update);
|
|
76
|
-
return () => {
|
|
77
|
-
view.removeEventListener('resize', update);
|
|
78
|
-
view.removeEventListener('scroll', update);
|
|
79
|
-
};
|
|
80
|
-
}, []);
|
|
81
|
-
// Snap-point height: % of the visible viewport. Recomputed when vv
|
|
82
|
-
// changes so a keyboard popup keeps the drawer aligned.
|
|
83
|
-
const drawerHeight = Math.max(0, Math.round(vv.h * Math.min(Math.max(snap, 0.1), 1)));
|
|
84
|
-
// Drag-to-dismiss. Vanilla touch handlers — no library. We track
|
|
85
|
-
// pointerdown on the handle, follow movement on pointermove, and
|
|
86
|
-
// decide on pointerup whether the drag crossed the "dismiss" threshold
|
|
87
|
-
// (30% of the panel height).
|
|
88
|
-
const surfaceRef = (0, react_1.useRef)(null);
|
|
89
|
-
const dragStateRef = (0, react_1.useRef)(null);
|
|
90
|
-
const [dragOffset, setDragOffset] = (0, react_1.useState)(0);
|
|
91
|
-
const onHandlePointerDown = (0, react_1.useCallback)((e) => {
|
|
92
|
-
if (e.button !== 0 && e.pointerType !== 'touch')
|
|
93
|
-
return;
|
|
94
|
-
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
95
|
-
dragStateRef.current = {
|
|
96
|
-
startY: e.clientY,
|
|
97
|
-
startTime: Date.now(),
|
|
98
|
-
dragging: true,
|
|
99
|
-
};
|
|
100
|
-
}, []);
|
|
101
|
-
const onHandlePointerMove = (0, react_1.useCallback)((e) => {
|
|
102
|
-
const s = dragStateRef.current;
|
|
103
|
-
if (!s?.dragging)
|
|
104
|
-
return;
|
|
105
|
-
const delta = Math.max(0, e.clientY - s.startY);
|
|
106
|
-
setDragOffset(delta);
|
|
107
|
-
}, []);
|
|
108
|
-
const onHandlePointerUp = (0, react_1.useCallback)((e) => {
|
|
109
|
-
const s = dragStateRef.current;
|
|
110
|
-
if (!s?.dragging)
|
|
111
|
-
return;
|
|
112
|
-
dragStateRef.current = null;
|
|
113
|
-
try {
|
|
114
|
-
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Pointer was already released by some other code path; nothing
|
|
118
|
-
// to do here. Releasing a non-captured pointer throws — swallow.
|
|
119
|
-
}
|
|
120
|
-
const delta = Math.max(0, e.clientY - s.startY);
|
|
121
|
-
const dismissThreshold = drawerHeight * 0.3;
|
|
122
|
-
const elapsed = Date.now() - s.startTime;
|
|
123
|
-
// Dismiss on EITHER a long-drag (past the threshold) or a quick
|
|
124
|
-
// flick (small distance but high velocity). Velocity unit is
|
|
125
|
-
// px/ms; >0.5 is roughly the threshold iOS sheets use.
|
|
126
|
-
const velocity = elapsed > 0 ? delta / elapsed : 0;
|
|
127
|
-
if (delta > dismissThreshold || velocity > 0.5) {
|
|
128
|
-
onOpenChange(false);
|
|
129
|
-
}
|
|
130
|
-
setDragOffset(0);
|
|
131
|
-
}, [drawerHeight, onOpenChange]);
|
|
132
|
-
// Lock body scroll while the drawer is open so the page underneath
|
|
133
|
-
// doesn't move when the visitor scrolls inside the chat. Restored on
|
|
134
|
-
// close. Necessary on iOS Safari where the rubber-band scroll bleeds
|
|
135
|
-
// through to the body even with overflow:hidden on a child.
|
|
54
|
+
const { open, onOpenChange, header, closeButton, drawerClassName, surfaceStyle, widgetProps, chatSlot, } = props;
|
|
55
|
+
// Body scroll lock — Vaul does this internally on open, but it
|
|
56
|
+
// also restores on unmount, which interacts badly with a portal
|
|
57
|
+
// re-mount when the drawer re-renders during a heavy parent
|
|
58
|
+
// update. Belt-and-braces.
|
|
136
59
|
(0, react_1.useEffect)(() => {
|
|
137
60
|
if (typeof document === 'undefined')
|
|
138
61
|
return;
|
|
@@ -144,101 +67,90 @@ function ChatDrawer(props) {
|
|
|
144
67
|
document.body.style.overflow = prev;
|
|
145
68
|
};
|
|
146
69
|
}, [open]);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (e.key === 'Escape')
|
|
157
|
-
onOpenChange(false);
|
|
158
|
-
};
|
|
159
|
-
window.addEventListener('keydown', onKey, true);
|
|
160
|
-
return () => window.removeEventListener('keydown', onKey, true);
|
|
161
|
-
}, [open, onOpenChange]);
|
|
162
|
-
// Lazy portal target. We render to `document.body` so the drawer
|
|
163
|
-
// overlays everything regardless of the consumer's stacking context.
|
|
164
|
-
const [portalEl, setPortalEl] = (0, react_1.useState)(null);
|
|
165
|
-
(0, react_1.useLayoutEffect)(() => {
|
|
166
|
-
if (typeof document === 'undefined')
|
|
167
|
-
return;
|
|
168
|
-
setPortalEl(document.body);
|
|
169
|
-
}, []);
|
|
170
|
-
// Stable id so the drag handle's `aria-controls` can point at the
|
|
171
|
-
// panel. Required for screen readers to announce the relationship.
|
|
172
|
-
const panelId = (0, react_1.useId)();
|
|
173
|
-
if (!portalEl)
|
|
174
|
-
return null;
|
|
175
|
-
if (!hasOpened)
|
|
70
|
+
if (!VaulDrawer) {
|
|
71
|
+
// Host hasn't installed vaul. We log a one-time warning and
|
|
72
|
+
// render nothing. Most hosts that hit this never wanted the
|
|
73
|
+
// drawer in the first place (they're using ChatWidget inline).
|
|
74
|
+
if (open && typeof console !== 'undefined') {
|
|
75
|
+
console.warn('[@agentforge-io/chat-sdk] <ChatDrawer> requires `vaul` to render. ' +
|
|
76
|
+
'Install it with `npm install vaul` or `yarn add vaul`. ' +
|
|
77
|
+
'The drawer is a no-op until vaul is available.');
|
|
78
|
+
}
|
|
176
79
|
return null;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
80
|
+
}
|
|
81
|
+
const chatNode = chatSlot ?? (widgetProps ? ((0, jsx_runtime_1.jsx)(react_2.ChatWidget, { ...widgetProps, inline: true, variant: widgetProps.variant ?? 'bare' })) : null);
|
|
82
|
+
return ((0, jsx_runtime_1.jsx)(VaulDrawer.Root, { open: open, onOpenChange: onOpenChange,
|
|
83
|
+
// `direction="bottom"` is the standard bottom-sheet origin.
|
|
84
|
+
direction: "bottom",
|
|
85
|
+
// No drag-to-dismiss. The visitor closes with the explicit
|
|
86
|
+
// back button in the header (or ESC). Prevents accidental
|
|
87
|
+
// dismissals when the visitor scrolls fast in the transcript.
|
|
88
|
+
dismissible: false,
|
|
89
|
+
// No native snap points — we WANT fullscreen.
|
|
90
|
+
shouldScaleBackground: false, children: (0, jsx_runtime_1.jsxs)(VaulDrawer.Portal, { children: [(0, jsx_runtime_1.jsx)(VaulDrawer.Overlay, { className: "af-drawer-overlay", style: {
|
|
91
|
+
position: 'fixed',
|
|
92
|
+
inset: 0,
|
|
93
|
+
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
94
|
+
zIndex: 2147483600,
|
|
95
|
+
} }), (0, jsx_runtime_1.jsxs)(VaulDrawer.Content, { className: `af-drawer-surface ${drawerClassName ?? ''}`, style: {
|
|
96
|
+
// Fullscreen: 100% of the visual viewport height. Vaul
|
|
97
|
+
// tracks visualViewport internally so this height
|
|
98
|
+
// shrinks when the keyboard pops up, keeping the
|
|
99
|
+
// composer always above the keys.
|
|
100
|
+
position: 'fixed',
|
|
101
|
+
inset: 0,
|
|
102
|
+
zIndex: 2147483600,
|
|
103
|
+
display: 'flex',
|
|
104
|
+
flexDirection: 'column',
|
|
105
|
+
outline: 'none',
|
|
106
|
+
backgroundColor: 'var(--af-bg, #ffffff)',
|
|
107
|
+
color: 'var(--af-fg, inherit)',
|
|
108
|
+
// Re-apply the host's theme vars on the portalled
|
|
109
|
+
// surface so the chat widget below picks them up. The
|
|
110
|
+
// `--af-bg` declared here is what the chat panel reads
|
|
111
|
+
// for its message background; without this the drawer
|
|
112
|
+
// would strobe white over a dark themed page.
|
|
113
|
+
...surfaceStyle,
|
|
114
|
+
}, children: [(0, jsx_runtime_1.jsx)(VaulDrawer.Title, { style: {
|
|
115
|
+
position: 'absolute',
|
|
116
|
+
width: '1px',
|
|
117
|
+
height: '1px',
|
|
118
|
+
padding: 0,
|
|
119
|
+
margin: '-1px',
|
|
120
|
+
overflow: 'hidden',
|
|
121
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
122
|
+
whiteSpace: 'nowrap',
|
|
123
|
+
border: 0,
|
|
124
|
+
}, children: "Chat" }), (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
125
|
+
flexShrink: 0,
|
|
126
|
+
position: 'sticky',
|
|
127
|
+
top: 0,
|
|
128
|
+
zIndex: 1,
|
|
129
|
+
backgroundColor: 'var(--af-bg, #ffffff)',
|
|
130
|
+
borderBottom: '1px solid var(--af-border, rgba(15, 23, 42, 0.08))',
|
|
131
|
+
}, children: [closeButton ?? (0, jsx_runtime_1.jsx)(DefaultCloseButton, { onClose: () => onOpenChange(false) }), header] }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
132
|
+
flex: 1,
|
|
133
|
+
minHeight: 0,
|
|
134
|
+
display: 'flex',
|
|
135
|
+
flexDirection: 'column',
|
|
136
|
+
}, "data-af-drawer-body": true, children: chatNode })] })] }) }));
|
|
137
|
+
}
|
|
138
|
+
function DefaultCloseButton({ onClose }) {
|
|
139
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
140
|
+
display: 'flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
padding: '8px 12px',
|
|
143
|
+
}, children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, "aria-label": "Close chat", style: {
|
|
144
|
+
appearance: 'none',
|
|
145
|
+
background: 'transparent',
|
|
146
|
+
border: 'none',
|
|
147
|
+
padding: '8px',
|
|
148
|
+
margin: 0,
|
|
149
|
+
cursor: 'pointer',
|
|
150
|
+
color: 'var(--af-fg, inherit)',
|
|
151
|
+
display: 'inline-flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
borderRadius: '8px',
|
|
155
|
+
}, children: (0, jsx_runtime_1.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: (0, jsx_runtime_1.jsx)("polyline", { points: "15 18 9 12 15 6" }) }) }) }));
|
|
244
156
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0-dev.0",
|
|
4
4
|
"description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,17 +24,23 @@
|
|
|
24
24
|
"clean": "rm -rf dist *.tgz"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"react": ">=18.0.0"
|
|
27
|
+
"react": ">=18.0.0",
|
|
28
|
+
"vaul": ">=1.0.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependenciesMeta": {
|
|
30
31
|
"react": {
|
|
31
32
|
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"vaul": {
|
|
35
|
+
"optional": true
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
35
39
|
"@types/node": "^20.0.0",
|
|
36
40
|
"@types/react": "^18.3.28",
|
|
37
41
|
"react": "^18.3.1",
|
|
38
|
-
"
|
|
42
|
+
"react-dom": "^18.3.1",
|
|
43
|
+
"typescript": "^5.0.0",
|
|
44
|
+
"vaul": "^1.1.2"
|
|
39
45
|
}
|
|
40
46
|
}
|