kyd-shared-badge 0.3.5 → 0.3.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
4
  import { FiMessageSquare, FiX, FiChevronRight } from 'react-icons/fi';
5
5
  import { useChatStreaming } from './useChatStreaming';
6
6
  import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
@@ -49,6 +49,7 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
49
49
  const { messages, input, setInput, sending, sendMessage, cancel } = useChatStreaming({ api, badgeId });
50
50
  const containerRef = useRef<HTMLDivElement>(null);
51
51
  const [autoScroll, setAutoScroll] = useState(true);
52
+ const prevScrollHeightRef = useRef<number>(0);
52
53
 
53
54
  // Detect user scroll position to toggle auto-scroll
54
55
  useEffect(() => {
@@ -65,14 +66,24 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
65
66
  return () => scrollEl.removeEventListener('scroll', onScroll);
66
67
  }, [open]);
67
68
 
68
- // Keep scrolled to bottom only when autoScroll is enabled
69
- useEffect(() => {
69
+ // Preserve viewport when new content streams. If autoScroll is off, compensate for height growth.
70
+ useLayoutEffect(() => {
70
71
  if (!open) return;
71
- if (!autoScroll) return;
72
72
  const root = containerRef.current;
73
73
  if (!root) return;
74
74
  const scrollEl = root.querySelector('.cs-message-list') as HTMLElement | null;
75
- if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;
75
+ if (!scrollEl) return;
76
+ const newHeight = scrollEl.scrollHeight;
77
+ const prevHeight = prevScrollHeightRef.current || newHeight;
78
+ if (autoScroll) {
79
+ scrollEl.scrollTop = newHeight;
80
+ } else {
81
+ const delta = newHeight - prevHeight;
82
+ if (delta !== 0) {
83
+ scrollEl.scrollTop = scrollEl.scrollTop + delta;
84
+ }
85
+ }
86
+ prevScrollHeightRef.current = newHeight;
76
87
  }, [messages, open, autoScroll]);
77
88
 
78
89
  // Optional hint (only shows if user hasn't dismissed before and when collapsed)
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
3
+ import { useEffect, useRef, useState, useLayoutEffect } from 'react';
4
4
  import './chat-overrides.css';
5
5
  import { FiSend } from 'react-icons/fi';
6
6
  import { useChatStreaming } from './useChatStreaming';
@@ -9,6 +9,7 @@ export default function ChatWindowStreaming({ api = '/api/chat', badgeId }: { ap
9
9
  const { messages, input, setInput, sending, sendMessage } = useChatStreaming({ api, badgeId });
10
10
  const listRef = useRef<HTMLDivElement>(null);
11
11
  const autoScrollRef = useRef<boolean>(true);
12
+ const prevScrollHeightRef = useRef<number>(0);
12
13
 
13
14
  const handleScroll = () => {
14
15
  const el = listRef.current;
@@ -17,10 +18,18 @@ export default function ChatWindowStreaming({ api = '/api/chat', badgeId }: { ap
17
18
  autoScrollRef.current = nearBottom;
18
19
  };
19
20
 
20
- useEffect(() => {
21
- if (listRef.current && autoScrollRef.current) {
22
- listRef.current.scrollTop = listRef.current.scrollHeight;
21
+ useLayoutEffect(() => {
22
+ const el = listRef.current;
23
+ if (!el) return;
24
+ const newHeight = el.scrollHeight;
25
+ const prevHeight = prevScrollHeightRef.current || newHeight;
26
+ if (autoScrollRef.current) {
27
+ el.scrollTop = newHeight;
28
+ } else {
29
+ const delta = newHeight - prevHeight;
30
+ if (delta !== 0) el.scrollTop = el.scrollTop + delta;
23
31
  }
32
+ prevScrollHeightRef.current = newHeight;
24
33
  }, [messages]);
25
34
 
26
35
  return (