@agentforge-io/chat-sdk 2.4.0-dev.5 → 2.4.0-dev.7

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,24 +1,61 @@
1
1
  /**
2
- * `<ChatDrawer>` — fullscreen mobile chat shell.
2
+ * `<ChatDrawer>` — standard bottom-sheet wrapper around `<ChatWidget>`.
3
3
  *
4
- * Plain `position: fixed` modal. No Vaul, no Radix Dialog, no focus
5
- * trap. Those libraries fought us repeatedly: Radix's `onPointerDownOutside`
6
- * mis-classified the Send tap as "interact outside" and closed the
7
- * drawer; Radix's focus trap moved focus to the wrapper when the send
8
- * button disabled, collapsing the mobile keyboard.
4
+ * The host gives us:
5
+ * - `open` / `onOpenChange`: controlled visibility (host owns the route /
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.
9
9
  *
10
- * What this gives us:
11
- * - 100dvh (dynamic viewport height): the surface shrinks automatically
12
- * when the on-screen keyboard appears, so the composer stays above
13
- * the keys without any visualViewport bookkeeping.
14
- * - No focus interception: the textarea owns its own focus. As long
15
- * as the host's Send button uses `onPointerDown preventDefault`,
16
- * focus never leaves the textarea and the keyboard never collapses.
17
- * - No drag-to-dismiss, no click-outside, no ESC handling. The host
18
- * closes via the back chevron in its header.
10
+ * What the drawer adds on top:
11
+ * - Mounts via React portal (`document.body`) so it overlays the page
12
+ * regardless of the host's stacking context. Lazy-mounted: the portal
13
+ * target is computed at first open so SSR stays clean.
14
+ * - Visually pinned to the bottom of the *visual viewport* (not the
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.
30
+ *
31
+ * What the drawer does NOT do (deliberately):
32
+ * - It doesn't sync with the URL. The host decides whether `?view=chat`,
33
+ * `/chat`, or any other route shape opens it.
34
+ * - It doesn't manage the chat session lifecycle. That's `<ChatWidget>`'s
35
+ * job. We just provide presentation.
36
+ * - It doesn't render a "fake composer" to trigger opening. The host
37
+ * decides what trigger UX makes sense (button, input pill, FAB, etc.)
38
+ * and calls `onOpenChange(true)`.
19
39
  */
20
40
  import { type CSSProperties, type ReactNode } from 'react';
21
41
  import { type ChatWidgetProps } from './react';
42
+ /**
43
+ * Drawer accepts the chat surface in two shapes:
44
+ *
45
+ * - `widgetProps`: pass the ChatWidget configuration and the drawer
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.
58
+ */
22
59
  type ChatSurface = {
23
60
  widgetProps: ChatWidgetProps;
24
61
  chatSlot?: never;
@@ -27,23 +64,39 @@ type ChatSurface = {
27
64
  widgetProps?: never;
28
65
  };
29
66
  export type ChatDrawerProps = ChatSurface & {
30
- /** Controlled visibility. */
67
+ /** Whether the drawer is visible. Controlled. */
31
68
  open: boolean;
32
- /** Fired when the drawer wants to close. Today only the host's back
33
- * chevron emits this no auto-dismiss paths exist. Kept in the
34
- * shape so the host doesn't have to change call sites. */
69
+ /** Fired when the drawer wants to close (drag-to-dismiss, tap on the
70
+ * backdrop, close button). Host is responsible for setting `open=false`. */
35
71
  onOpenChange: (open: boolean) => void;
36
- /** Sticky header above the chat panel. */
72
+ /** Snap fraction of the visible viewport, 0–1. Defaults to 0.98 (98% =
73
+ * the standard "near-fullscreen drawer that still hints at the page
74
+ * below" pattern). Lower numbers leave more of the page visible. */
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. */
37
80
  header?: ReactNode;
38
- /** Custom close button rendered ABOVE `header`. Default is the SDK's
39
- * chevron-left. Pass `null` to opt out (when the host renders its
40
- * own close affordance inline within `header`). */
41
- closeButton?: ReactNode;
42
- /** Extra class on the drawer surface. */
81
+ /** Backdrop click closes the drawer. Default true — set false if the
82
+ * host wants a "modal" feel where the only escape is the close button
83
+ * or the drag-down gesture. */
84
+ closeOnBackdropClick?: boolean;
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. */
43
88
  drawerClassName?: string;
44
- /** CSS vars (`--af-bg`, `--af-fg`, `--af-bubble-*`, …) for the
45
- * surface. The drawer renders into a portal, so the host's
46
- * page-wrapper vars don't cascade in re-declare them here. */
89
+ /** Inline style applied to the drawer surface — typically the host's
90
+ * bundle of `--af-bg`, `--af-fg`, `--af-bubble-*`, etc. CSS vars.
91
+ * Because the drawer renders into a portal (`document.body`), any
92
+ * vars declared on the host's page wrapper DON'T cascade in. The
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. */
47
100
  surfaceStyle?: CSSProperties & Record<string, string>;
48
101
  };
49
102
  export declare function ChatDrawer(props: ChatDrawerProps): JSX.Element | null;
@@ -3,36 +3,143 @@ 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>` — fullscreen mobile chat shell.
6
+ * `<ChatDrawer>` — standard bottom-sheet wrapper around `<ChatWidget>`.
7
7
  *
8
- * Plain `position: fixed` modal. No Vaul, no Radix Dialog, no focus
9
- * trap. Those libraries fought us repeatedly: Radix's `onPointerDownOutside`
10
- * mis-classified the Send tap as "interact outside" and closed the
11
- * drawer; Radix's focus trap moved focus to the wrapper when the send
12
- * button disabled, collapsing the mobile keyboard.
8
+ * The host gives us:
9
+ * - `open` / `onOpenChange`: controlled visibility (host owns the route /
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.
13
13
  *
14
- * What this gives us:
15
- * - 100dvh (dynamic viewport height): the surface shrinks automatically
16
- * when the on-screen keyboard appears, so the composer stays above
17
- * the keys without any visualViewport bookkeeping.
18
- * - No focus interception: the textarea owns its own focus. As long
19
- * as the host's Send button uses `onPointerDown preventDefault`,
20
- * focus never leaves the textarea and the keyboard never collapses.
21
- * - No drag-to-dismiss, no click-outside, no ESC handling. The host
22
- * closes via the back chevron in its header.
14
+ * What the drawer adds on top:
15
+ * - Mounts via React portal (`document.body`) so it overlays the page
16
+ * regardless of the host's stacking context. Lazy-mounted: the portal
17
+ * target is computed at first open so SSR stays clean.
18
+ * - Visually pinned to the bottom of the *visual viewport* (not the
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.
34
+ *
35
+ * What the drawer does NOT do (deliberately):
36
+ * - It doesn't sync with the URL. The host decides whether `?view=chat`,
37
+ * `/chat`, or any other route shape opens it.
38
+ * - It doesn't manage the chat session lifecycle. That's `<ChatWidget>`'s
39
+ * job. We just provide presentation.
40
+ * - It doesn't render a "fake composer" to trigger opening. The host
41
+ * decides what trigger UX makes sense (button, input pill, FAB, etc.)
42
+ * and calls `onOpenChange(true)`.
23
43
  */
24
44
  const react_1 = require("react");
45
+ const react_dom_1 = require("react-dom");
25
46
  const react_2 = require("./react");
26
47
  function ChatDrawer(props) {
27
- const { open, onOpenChange, header, closeButton, drawerClassName, surfaceStyle, widgetProps, chatSlot, } = props;
28
- // Defer the first paint until after mount so SSR doesn't try to
29
- // render a portal target that doesn't exist yet.
30
- const [mounted, setMounted] = (0, react_1.useState)(false);
48
+ const { open, onOpenChange, snap = 1, header, closeOnBackdropClick = true, drawerClassName, surfaceStyle: surfaceStyleProp, widgetProps, chatSlot, } = props;
49
+ // `fullscreen` mode: snap=1 (the default). The drawer occupies the
50
+ // entire visible viewport. We disable drag-to-dismiss, the backdrop,
51
+ // the rounded top corners, and the drag handle in this mode — they
52
+ // exist purely for the bottom-sheet form factor (snap<1) where the
53
+ // page peeks behind the surface. With snap=1 there's nothing behind
54
+ // and no affordance to drag against.
55
+ const fullscreen = snap >= 1;
56
+ // We mount once and keep alive across close→reopen so the chat session
57
+ // doesn't get destroyed. After the FIRST open the panel stays in the
58
+ // DOM forever (toggled by `display:none`) — `hasOpened` gates the
59
+ // initial mount so SSR doesn't render an empty portal.
60
+ const [hasOpened, setHasOpened] = (0, react_1.useState)(open);
61
+ (0, react_1.useEffect)(() => {
62
+ if (open)
63
+ setHasOpened(true);
64
+ }, [open]);
65
+ // Visible viewport tracking. iOS Safari + Android Chrome shrink
66
+ // `visualViewport.height` when the on-screen keyboard pops up; the
67
+ // layout viewport stays the same. We pin the drawer's height + bottom
68
+ // to the visual viewport so the composer is always above the keyboard.
69
+ const [vv, setVv] = (0, react_1.useState)(() => ({
70
+ h: typeof window === 'undefined' ? 0 : window.innerHeight,
71
+ offsetTop: 0,
72
+ }));
31
73
  (0, react_1.useEffect)(() => {
32
- setMounted(true);
74
+ if (typeof window === 'undefined')
75
+ return;
76
+ const view = window.visualViewport;
77
+ if (!view)
78
+ return;
79
+ const update = () => setVv({ h: view.height, offsetTop: view.offsetTop });
80
+ update();
81
+ view.addEventListener('resize', update);
82
+ view.addEventListener('scroll', update);
83
+ return () => {
84
+ view.removeEventListener('resize', update);
85
+ view.removeEventListener('scroll', update);
86
+ };
87
+ }, []);
88
+ // Snap-point height: % of the visible viewport. Recomputed when vv
89
+ // changes so a keyboard popup keeps the drawer aligned.
90
+ const drawerHeight = Math.max(0, Math.round(vv.h * Math.min(Math.max(snap, 0.1), 1)));
91
+ // Drag-to-dismiss. Vanilla touch handlers — no library. We track
92
+ // pointerdown on the handle, follow movement on pointermove, and
93
+ // decide on pointerup whether the drag crossed the "dismiss" threshold
94
+ // (30% of the panel height).
95
+ const surfaceRef = (0, react_1.useRef)(null);
96
+ const dragStateRef = (0, react_1.useRef)(null);
97
+ const [dragOffset, setDragOffset] = (0, react_1.useState)(0);
98
+ const onHandlePointerDown = (0, react_1.useCallback)((e) => {
99
+ if (e.button !== 0 && e.pointerType !== 'touch')
100
+ return;
101
+ e.currentTarget.setPointerCapture?.(e.pointerId);
102
+ dragStateRef.current = {
103
+ startY: e.clientY,
104
+ startTime: Date.now(),
105
+ dragging: true,
106
+ };
107
+ }, []);
108
+ const onHandlePointerMove = (0, react_1.useCallback)((e) => {
109
+ const s = dragStateRef.current;
110
+ if (!s?.dragging)
111
+ return;
112
+ const delta = Math.max(0, e.clientY - s.startY);
113
+ setDragOffset(delta);
33
114
  }, []);
34
- // Lock body scroll while the drawer is open. Standard pattern: save
35
- // the previous overflow, set hidden, restore on close/unmount.
115
+ const onHandlePointerUp = (0, react_1.useCallback)((e) => {
116
+ const s = dragStateRef.current;
117
+ if (!s?.dragging)
118
+ return;
119
+ dragStateRef.current = null;
120
+ try {
121
+ e.currentTarget.releasePointerCapture?.(e.pointerId);
122
+ }
123
+ catch {
124
+ // Pointer was already released by some other code path; nothing
125
+ // to do here. Releasing a non-captured pointer throws — swallow.
126
+ }
127
+ const delta = Math.max(0, e.clientY - s.startY);
128
+ const dismissThreshold = drawerHeight * 0.3;
129
+ const elapsed = Date.now() - s.startTime;
130
+ // Dismiss on EITHER a long-drag (past the threshold) or a quick
131
+ // flick (small distance but high velocity). Velocity unit is
132
+ // px/ms; >0.5 is roughly the threshold iOS sheets use.
133
+ const velocity = elapsed > 0 ? delta / elapsed : 0;
134
+ if (delta > dismissThreshold || velocity > 0.5) {
135
+ onOpenChange(false);
136
+ }
137
+ setDragOffset(0);
138
+ }, [drawerHeight, onOpenChange]);
139
+ // Lock body scroll while the drawer is open so the page underneath
140
+ // doesn't move when the visitor scrolls inside the chat. Restored on
141
+ // close. Necessary on iOS Safari where the rubber-band scroll bleeds
142
+ // through to the body even with overflow:hidden on a child.
36
143
  (0, react_1.useEffect)(() => {
37
144
  if (typeof document === 'undefined')
38
145
  return;
@@ -44,48 +151,102 @@ function ChatDrawer(props) {
44
151
  document.body.style.overflow = prev;
45
152
  };
46
153
  }, [open]);
47
- if (!mounted || !open)
154
+ // Escape closes the drawer. Keyboard-friendly even when focus is in
155
+ // the textarea — preventDefault on the input itself swallows Escape
156
+ // before it bubbles, so we use capture phase.
157
+ (0, react_1.useEffect)(() => {
158
+ if (!open)
159
+ return;
160
+ if (typeof window === 'undefined')
161
+ return;
162
+ const onKey = (e) => {
163
+ if (e.key === 'Escape')
164
+ onOpenChange(false);
165
+ };
166
+ window.addEventListener('keydown', onKey, true);
167
+ return () => window.removeEventListener('keydown', onKey, true);
168
+ }, [open, onOpenChange]);
169
+ // Lazy portal target. We render to `document.body` so the drawer
170
+ // overlays everything regardless of the consumer's stacking context.
171
+ const [portalEl, setPortalEl] = (0, react_1.useState)(null);
172
+ (0, react_1.useLayoutEffect)(() => {
173
+ if (typeof document === 'undefined')
174
+ return;
175
+ setPortalEl(document.body);
176
+ }, []);
177
+ // Stable id so the drag handle's `aria-controls` can point at the
178
+ // panel. Required for screen readers to announce the relationship.
179
+ const panelId = (0, react_1.useId)();
180
+ if (!portalEl)
48
181
  return null;
49
- const chatNode = chatSlot ?? (widgetProps ? ((0, jsx_runtime_1.jsx)(react_2.ChatWidget, { ...widgetProps, inline: true, variant: widgetProps.variant ?? 'bare' })) : null);
50
- return ((0, jsx_runtime_1.jsxs)("div", { role: "dialog", "aria-modal": "true", "aria-label": "Chat", className: `af-drawer-surface ${drawerClassName ?? ''}`, style: {
51
- // 100dvh shrinks when the on-screen keyboard appears.
52
- // `inset: 0` + `position: fixed` covers the viewport.
182
+ if (!hasOpened)
183
+ return null;
184
+ // The translate3d ensures the drawer animates from below on first
185
+ // open (initial transform = 100%, becomes 0% after the open prop
186
+ // flips). When dragging we add the manual drag offset on top.
187
+ const translateY = open ? `${dragOffset}px` : `${drawerHeight}px`;
188
+ // Transition disabled WHILE dragging so the surface follows the
189
+ // finger 1:1. Re-enabled at the end of the drag (delta back to 0
190
+ // smoothly when below threshold).
191
+ const isDragging = dragStateRef.current?.dragging === true;
192
+ // Motion / sizing portion of the surface style, kept separate from
193
+ // the host-supplied theming so a re-render driven by drag offset
194
+ // doesn't smash the host's CSS vars.
195
+ const surfaceMotionStyle = {
196
+ height: `${drawerHeight}px`,
197
+ transform: `translate3d(0, ${translateY}, 0)`,
198
+ bottom: `${vv.offsetTop}px`,
199
+ transition: isDragging ? 'none' : 'transform 240ms cubic-bezier(.32,.72,0,1)',
200
+ willChange: 'transform',
201
+ };
202
+ return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { className: "af-drawer-root", "data-state": open ? 'open' : 'closed', style: {
53
203
  position: 'fixed',
54
204
  inset: 0,
55
- height: '100dvh',
56
205
  zIndex: 2147483600,
57
- display: 'flex',
58
- flexDirection: 'column',
59
- backgroundColor: 'var(--af-bg, #ffffff)',
60
- color: 'var(--af-fg, inherit)',
61
- ...surfaceStyle,
62
- }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
63
- flexShrink: 0,
206
+ pointerEvents: open ? 'auto' : 'none',
207
+ }, children: [!fullscreen && ((0, jsx_runtime_1.jsx)("div", { className: "af-drawer-backdrop", onClick: closeOnBackdropClick ? () => onOpenChange(false) : undefined, style: {
208
+ position: 'absolute',
209
+ inset: 0,
210
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
211
+ opacity: open ? 1 : 0,
212
+ transition: 'opacity 200ms ease-out',
213
+ }, "aria-hidden": true })), (0, jsx_runtime_1.jsxs)("div", { ref: surfaceRef, id: panelId, role: "dialog", "aria-modal": "true", className: `af-drawer-surface ${drawerClassName ?? ''}`, style: {
214
+ position: 'absolute',
215
+ left: 0,
216
+ right: 0,
217
+ width: '100%',
218
+ // Host supplies the CSS vars via `surfaceStyle`; the
219
+ // backgroundColor resolves from `--af-bg` (which the host
220
+ // declares in that same prop). When `surfaceStyle` is
221
+ // omitted we fall back to white — passable for an unthemed
222
+ // demo, wrong for a themed embed; passing surfaceStyle is
223
+ // the correct path.
64
224
  backgroundColor: 'var(--af-bg, #ffffff)',
65
- borderBottom: '1px solid var(--af-border, rgba(15, 23, 42, 0.08))',
66
- }, children: [closeButton === undefined ? ((0, jsx_runtime_1.jsx)(DefaultCloseButton, { onClose: () => onOpenChange(false) })) : (closeButton), header] }), (0, jsx_runtime_1.jsx)("div", { style: {
67
- flex: 1,
68
- minHeight: 0,
225
+ color: 'var(--af-fg, inherit)',
226
+ // Bottom-sheet visuals only fullscreen mode is flat.
227
+ borderTopLeftRadius: fullscreen ? 0 : '16px',
228
+ borderTopRightRadius: fullscreen ? 0 : '16px',
229
+ boxShadow: fullscreen ? 'none' : '0 -8px 24px rgba(15, 23, 42, 0.25)',
69
230
  display: 'flex',
70
231
  flexDirection: 'column',
71
- }, "data-af-drawer-body": true, children: chatNode })] }));
72
- }
73
- function DefaultCloseButton({ onClose }) {
74
- return ((0, jsx_runtime_1.jsx)("div", { style: {
75
- display: 'flex',
76
- alignItems: 'center',
77
- padding: '8px 12px',
78
- }, children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, "aria-label": "Close chat", style: {
79
- appearance: 'none',
80
- background: 'transparent',
81
- border: 'none',
82
- padding: '8px',
83
- margin: 0,
84
- cursor: 'pointer',
85
- color: 'var(--af-fg, inherit)',
86
- display: 'inline-flex',
87
- alignItems: 'center',
88
- justifyContent: 'center',
89
- borderRadius: '8px',
90
- }, 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" }) }) }) }));
232
+ overflow: 'hidden',
233
+ // Host-supplied theme vars come FIRST so the motion
234
+ // properties below override any conflicting `transform` /
235
+ // `height` declarations a careless host might pass.
236
+ ...surfaceStyleProp,
237
+ ...surfaceMotionStyle,
238
+ }, children: [!fullscreen && ((0, jsx_runtime_1.jsx)("div", { onPointerDown: onHandlePointerDown, onPointerMove: onHandlePointerMove, onPointerUp: onHandlePointerUp, onPointerCancel: onHandlePointerUp, style: {
239
+ padding: '10px 0',
240
+ cursor: 'grab',
241
+ touchAction: 'none',
242
+ display: 'flex',
243
+ justifyContent: 'center',
244
+ flexShrink: 0,
245
+ }, "aria-label": "Drag to close", children: (0, jsx_runtime_1.jsx)("span", { style: {
246
+ width: '40px',
247
+ height: '4px',
248
+ borderRadius: '999px',
249
+ backgroundColor: 'var(--af-muted, rgba(100, 116, 139, 0.45))',
250
+ display: 'block',
251
+ } }) })), header, (0, jsx_runtime_1.jsx)("div", { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }, children: chatSlot ?? (widgetProps ? ((0, jsx_runtime_1.jsx)(react_2.ChatWidget, { ...widgetProps, inline: true, variant: widgetProps.variant ?? 'bare' })) : null) })] })] }), portalEl);
91
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.4.0-dev.5",
3
+ "version": "2.4.0-dev.7",
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",
@@ -35,7 +35,6 @@
35
35
  "@types/node": "^20.0.0",
36
36
  "@types/react": "^18.3.28",
37
37
  "react": "^18.3.1",
38
- "react-dom": "^18.3.1",
39
38
  "typescript": "^5.0.0"
40
39
  }
41
40
  }