@tp3/chat-widget 0.1.9 → 0.1.10
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/ChatWidget.js +19 -0
- package/dist/ChatWidget.mjs +19 -0
- package/package.json +1 -1
- package/src/ChatWidget.tsx +29 -1
package/dist/ChatWidget.js
CHANGED
|
@@ -64,6 +64,7 @@ function ChatWidget({
|
|
|
64
64
|
const wsRef = (0, import_react.useRef)(null);
|
|
65
65
|
const messagesEnd = (0, import_react.useRef)(null);
|
|
66
66
|
const inputRef = (0, import_react.useRef)(null);
|
|
67
|
+
const chatWindowRef = (0, import_react.useRef)(null);
|
|
67
68
|
const typewriterQueue = (0, import_react.useRef)([]);
|
|
68
69
|
const typewriterTimer = (0, import_react.useRef)(null);
|
|
69
70
|
(0, import_react.useEffect)(() => {
|
|
@@ -87,6 +88,21 @@ function ChatWidget({
|
|
|
87
88
|
if (open) window.scrollTo(0, top);
|
|
88
89
|
};
|
|
89
90
|
}, [open, closing]);
|
|
91
|
+
(0, import_react.useEffect)(() => {
|
|
92
|
+
const el = chatWindowRef.current;
|
|
93
|
+
if (!el || !open) return;
|
|
94
|
+
function blockIfOutsideMessages(e) {
|
|
95
|
+
const target = e.target;
|
|
96
|
+
if (target.closest("[data-chat-messages]")) return;
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
}
|
|
99
|
+
el.addEventListener("wheel", blockIfOutsideMessages, { passive: false });
|
|
100
|
+
el.addEventListener("touchmove", blockIfOutsideMessages, { passive: false });
|
|
101
|
+
return () => {
|
|
102
|
+
el.removeEventListener("wheel", blockIfOutsideMessages);
|
|
103
|
+
el.removeEventListener("touchmove", blockIfOutsideMessages);
|
|
104
|
+
};
|
|
105
|
+
}, [open]);
|
|
90
106
|
(0, import_react.useEffect)(() => {
|
|
91
107
|
if (messages.length > 1) {
|
|
92
108
|
messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
|
|
@@ -282,6 +298,7 @@ function ChatWidget({
|
|
|
282
298
|
open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
283
299
|
"div",
|
|
284
300
|
{
|
|
301
|
+
ref: chatWindowRef,
|
|
285
302
|
className: closing ? "chat-window-out" : "chat-window",
|
|
286
303
|
style: {
|
|
287
304
|
position: "fixed",
|
|
@@ -292,6 +309,7 @@ function ChatWidget({
|
|
|
292
309
|
maxWidth: "calc(100vw - 48px)",
|
|
293
310
|
height: chatHeight,
|
|
294
311
|
maxHeight: "calc(100dvh - 48px)",
|
|
312
|
+
overscrollBehavior: "contain",
|
|
295
313
|
background: "var(--chat-primary-fg, #fff)",
|
|
296
314
|
display: "flex",
|
|
297
315
|
flexDirection: "column",
|
|
@@ -356,6 +374,7 @@ function ChatWidget({
|
|
|
356
374
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
357
375
|
"div",
|
|
358
376
|
{
|
|
377
|
+
"data-chat-messages": true,
|
|
359
378
|
style: {
|
|
360
379
|
flex: 1,
|
|
361
380
|
overflowY: "auto",
|
package/dist/ChatWidget.mjs
CHANGED
|
@@ -40,6 +40,7 @@ function ChatWidget({
|
|
|
40
40
|
const wsRef = useRef(null);
|
|
41
41
|
const messagesEnd = useRef(null);
|
|
42
42
|
const inputRef = useRef(null);
|
|
43
|
+
const chatWindowRef = useRef(null);
|
|
43
44
|
const typewriterQueue = useRef([]);
|
|
44
45
|
const typewriterTimer = useRef(null);
|
|
45
46
|
useEffect(() => {
|
|
@@ -63,6 +64,21 @@ function ChatWidget({
|
|
|
63
64
|
if (open) window.scrollTo(0, top);
|
|
64
65
|
};
|
|
65
66
|
}, [open, closing]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const el = chatWindowRef.current;
|
|
69
|
+
if (!el || !open) return;
|
|
70
|
+
function blockIfOutsideMessages(e) {
|
|
71
|
+
const target = e.target;
|
|
72
|
+
if (target.closest("[data-chat-messages]")) return;
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
}
|
|
75
|
+
el.addEventListener("wheel", blockIfOutsideMessages, { passive: false });
|
|
76
|
+
el.addEventListener("touchmove", blockIfOutsideMessages, { passive: false });
|
|
77
|
+
return () => {
|
|
78
|
+
el.removeEventListener("wheel", blockIfOutsideMessages);
|
|
79
|
+
el.removeEventListener("touchmove", blockIfOutsideMessages);
|
|
80
|
+
};
|
|
81
|
+
}, [open]);
|
|
66
82
|
useEffect(() => {
|
|
67
83
|
if (messages.length > 1) {
|
|
68
84
|
messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
|
|
@@ -258,6 +274,7 @@ function ChatWidget({
|
|
|
258
274
|
open && /* @__PURE__ */ jsxs(
|
|
259
275
|
"div",
|
|
260
276
|
{
|
|
277
|
+
ref: chatWindowRef,
|
|
261
278
|
className: closing ? "chat-window-out" : "chat-window",
|
|
262
279
|
style: {
|
|
263
280
|
position: "fixed",
|
|
@@ -268,6 +285,7 @@ function ChatWidget({
|
|
|
268
285
|
maxWidth: "calc(100vw - 48px)",
|
|
269
286
|
height: chatHeight,
|
|
270
287
|
maxHeight: "calc(100dvh - 48px)",
|
|
288
|
+
overscrollBehavior: "contain",
|
|
271
289
|
background: "var(--chat-primary-fg, #fff)",
|
|
272
290
|
display: "flex",
|
|
273
291
|
flexDirection: "column",
|
|
@@ -332,6 +350,7 @@ function ChatWidget({
|
|
|
332
350
|
/* @__PURE__ */ jsxs(
|
|
333
351
|
"div",
|
|
334
352
|
{
|
|
353
|
+
"data-chat-messages": true,
|
|
335
354
|
style: {
|
|
336
355
|
flex: 1,
|
|
337
356
|
overflowY: "auto",
|
package/package.json
CHANGED
package/src/ChatWidget.tsx
CHANGED
|
@@ -64,10 +64,13 @@ export default function ChatWidget({
|
|
|
64
64
|
const wsRef = useRef<WebSocket | null>(null);
|
|
65
65
|
const messagesEnd = useRef<HTMLDivElement>(null);
|
|
66
66
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
67
|
+
const chatWindowRef = useRef<HTMLDivElement>(null);
|
|
67
68
|
const typewriterQueue = useRef<string[]>([]);
|
|
68
69
|
const typewriterTimer = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
69
70
|
|
|
70
|
-
// Lock body scroll when chat is open
|
|
71
|
+
// Lock body scroll when chat is open. Uses two mechanisms:
|
|
72
|
+
// 1. position:fixed on body (mobile keyboard safe)
|
|
73
|
+
// 2. wheel/touch listeners on the chat window to prevent event leakage
|
|
71
74
|
useEffect(() => {
|
|
72
75
|
if (open && !closing) {
|
|
73
76
|
const scrollY = window.scrollY;
|
|
@@ -90,6 +93,28 @@ export default function ChatWidget({
|
|
|
90
93
|
};
|
|
91
94
|
}, [open, closing]);
|
|
92
95
|
|
|
96
|
+
// Prevent events on the chat window from scrolling the page behind.
|
|
97
|
+
// Only blocks events targeting the window itself, not the scrollable messages area.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const el = chatWindowRef.current;
|
|
100
|
+
if (!el || !open) return;
|
|
101
|
+
|
|
102
|
+
function blockIfOutsideMessages(e: WheelEvent | TouchEvent) {
|
|
103
|
+
const target = e.target as HTMLElement;
|
|
104
|
+
// Allow scroll inside the messages container
|
|
105
|
+
if (target.closest('[data-chat-messages]')) return;
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
el.addEventListener("wheel", blockIfOutsideMessages, { passive: false });
|
|
110
|
+
el.addEventListener("touchmove", blockIfOutsideMessages, { passive: false });
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
el.removeEventListener("wheel", blockIfOutsideMessages);
|
|
114
|
+
el.removeEventListener("touchmove", blockIfOutsideMessages);
|
|
115
|
+
};
|
|
116
|
+
}, [open]);
|
|
117
|
+
|
|
93
118
|
useEffect(() => {
|
|
94
119
|
// Don't scroll on first render — wait for the open animation
|
|
95
120
|
if (messages.length > 1) {
|
|
@@ -305,6 +330,7 @@ export default function ChatWidget({
|
|
|
305
330
|
|
|
306
331
|
{open && (
|
|
307
332
|
<div
|
|
333
|
+
ref={chatWindowRef}
|
|
308
334
|
className={closing ? "chat-window-out" : "chat-window"}
|
|
309
335
|
style={{
|
|
310
336
|
position: "fixed",
|
|
@@ -315,6 +341,7 @@ export default function ChatWidget({
|
|
|
315
341
|
maxWidth: "calc(100vw - 48px)",
|
|
316
342
|
height: chatHeight,
|
|
317
343
|
maxHeight: "calc(100dvh - 48px)",
|
|
344
|
+
overscrollBehavior: "contain",
|
|
318
345
|
background: "var(--chat-primary-fg, #fff)",
|
|
319
346
|
display: "flex",
|
|
320
347
|
flexDirection: "column",
|
|
@@ -374,6 +401,7 @@ export default function ChatWidget({
|
|
|
374
401
|
|
|
375
402
|
{/* Messages */}
|
|
376
403
|
<div
|
|
404
|
+
data-chat-messages
|
|
377
405
|
style={{
|
|
378
406
|
flex: 1,
|
|
379
407
|
overflowY: "auto",
|