@tp3/chat-widget 0.1.10 → 0.1.12

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.
@@ -64,45 +64,30 @@ 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);
68
67
  const typewriterQueue = (0, import_react.useRef)([]);
69
68
  const typewriterTimer = (0, import_react.useRef)(null);
69
+ const savedScrollY = (0, import_react.useRef)(0);
70
70
  (0, import_react.useEffect)(() => {
71
71
  if (open && !closing) {
72
- const scrollY = window.scrollY;
72
+ savedScrollY.current = window.scrollY;
73
73
  document.body.style.position = "fixed";
74
- document.body.style.top = `-${scrollY}px`;
75
- document.body.style.width = "100%";
76
- } else if (!open) {
77
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
74
+ document.body.style.top = `-${savedScrollY.current}px`;
75
+ document.body.style.left = "0";
76
+ document.body.style.right = "0";
77
+ } else {
78
78
  document.body.style.position = "";
79
79
  document.body.style.top = "";
80
- document.body.style.width = "";
81
- window.scrollTo(0, top);
80
+ document.body.style.left = "";
81
+ document.body.style.right = "";
82
+ window.scrollTo(0, savedScrollY.current);
82
83
  }
83
84
  return () => {
84
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
85
85
  document.body.style.position = "";
86
86
  document.body.style.top = "";
87
- document.body.style.width = "";
88
- if (open) window.scrollTo(0, top);
87
+ document.body.style.left = "";
88
+ document.body.style.right = "";
89
89
  };
90
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]);
106
91
  (0, import_react.useEffect)(() => {
107
92
  if (messages.length > 1) {
108
93
  messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
@@ -298,7 +283,6 @@ function ChatWidget({
298
283
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
299
284
  "div",
300
285
  {
301
- ref: chatWindowRef,
302
286
  className: closing ? "chat-window-out" : "chat-window",
303
287
  style: {
304
288
  position: "fixed",
@@ -309,7 +293,6 @@ function ChatWidget({
309
293
  maxWidth: "calc(100vw - 48px)",
310
294
  height: chatHeight,
311
295
  maxHeight: "calc(100dvh - 48px)",
312
- overscrollBehavior: "contain",
313
296
  background: "var(--chat-primary-fg, #fff)",
314
297
  display: "flex",
315
298
  flexDirection: "column",
@@ -374,7 +357,6 @@ function ChatWidget({
374
357
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
375
358
  "div",
376
359
  {
377
- "data-chat-messages": true,
378
360
  style: {
379
361
  flex: 1,
380
362
  overflowY: "auto",
@@ -40,45 +40,30 @@ 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);
44
43
  const typewriterQueue = useRef([]);
45
44
  const typewriterTimer = useRef(null);
45
+ const savedScrollY = useRef(0);
46
46
  useEffect(() => {
47
47
  if (open && !closing) {
48
- const scrollY = window.scrollY;
48
+ savedScrollY.current = window.scrollY;
49
49
  document.body.style.position = "fixed";
50
- document.body.style.top = `-${scrollY}px`;
51
- document.body.style.width = "100%";
52
- } else if (!open) {
53
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
50
+ document.body.style.top = `-${savedScrollY.current}px`;
51
+ document.body.style.left = "0";
52
+ document.body.style.right = "0";
53
+ } else {
54
54
  document.body.style.position = "";
55
55
  document.body.style.top = "";
56
- document.body.style.width = "";
57
- window.scrollTo(0, top);
56
+ document.body.style.left = "";
57
+ document.body.style.right = "";
58
+ window.scrollTo(0, savedScrollY.current);
58
59
  }
59
60
  return () => {
60
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
61
61
  document.body.style.position = "";
62
62
  document.body.style.top = "";
63
- document.body.style.width = "";
64
- if (open) window.scrollTo(0, top);
63
+ document.body.style.left = "";
64
+ document.body.style.right = "";
65
65
  };
66
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]);
82
67
  useEffect(() => {
83
68
  if (messages.length > 1) {
84
69
  messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
@@ -274,7 +259,6 @@ function ChatWidget({
274
259
  open && /* @__PURE__ */ jsxs(
275
260
  "div",
276
261
  {
277
- ref: chatWindowRef,
278
262
  className: closing ? "chat-window-out" : "chat-window",
279
263
  style: {
280
264
  position: "fixed",
@@ -285,7 +269,6 @@ function ChatWidget({
285
269
  maxWidth: "calc(100vw - 48px)",
286
270
  height: chatHeight,
287
271
  maxHeight: "calc(100dvh - 48px)",
288
- overscrollBehavior: "contain",
289
272
  background: "var(--chat-primary-fg, #fff)",
290
273
  display: "flex",
291
274
  flexDirection: "column",
@@ -350,7 +333,6 @@ function ChatWidget({
350
333
  /* @__PURE__ */ jsxs(
351
334
  "div",
352
335
  {
353
- "data-chat-messages": true,
354
336
  style: {
355
337
  flex: 1,
356
338
  overflowY: "auto",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tp3/chat-widget",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "main": "dist/ChatWidget.js",
5
5
  "module": "dist/ChatWidget.mjs",
6
6
  "types": "dist/ChatWidget.d.ts",
@@ -64,57 +64,37 @@ 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);
68
67
  const typewriterQueue = useRef<string[]>([]);
69
68
  const typewriterTimer = useRef<ReturnType<typeof setInterval> | null>(null);
70
69
 
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
70
+ // Lock scroll on the page behind the chat without blocking scroll
71
+ // inside the chat messages area. Uses position:fixed on body the
72
+ // chat window is also fixed, so it stays visible and its internal
73
+ // overflow-y:auto container scrolls independently.
74
+ const savedScrollY = useRef(0);
75
+
74
76
  useEffect(() => {
75
77
  if (open && !closing) {
76
- const scrollY = window.scrollY;
78
+ savedScrollY.current = window.scrollY;
77
79
  document.body.style.position = "fixed";
78
- document.body.style.top = `-${scrollY}px`;
79
- document.body.style.width = "100%";
80
- } else if (!open) {
81
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
80
+ document.body.style.top = `-${savedScrollY.current}px`;
81
+ document.body.style.left = "0";
82
+ document.body.style.right = "0";
83
+ } else {
82
84
  document.body.style.position = "";
83
85
  document.body.style.top = "";
84
- document.body.style.width = "";
85
- window.scrollTo(0, top);
86
+ document.body.style.left = "";
87
+ document.body.style.right = "";
88
+ window.scrollTo(0, savedScrollY.current);
86
89
  }
87
90
  return () => {
88
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
89
91
  document.body.style.position = "";
90
92
  document.body.style.top = "";
91
- document.body.style.width = "";
92
- if (open) window.scrollTo(0, top);
93
+ document.body.style.left = "";
94
+ document.body.style.right = "";
93
95
  };
94
96
  }, [open, closing]);
95
97
 
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
-
118
98
  useEffect(() => {
119
99
  // Don't scroll on first render — wait for the open animation
120
100
  if (messages.length > 1) {
@@ -330,7 +310,6 @@ export default function ChatWidget({
330
310
 
331
311
  {open && (
332
312
  <div
333
- ref={chatWindowRef}
334
313
  className={closing ? "chat-window-out" : "chat-window"}
335
314
  style={{
336
315
  position: "fixed",
@@ -341,7 +320,6 @@ export default function ChatWidget({
341
320
  maxWidth: "calc(100vw - 48px)",
342
321
  height: chatHeight,
343
322
  maxHeight: "calc(100dvh - 48px)",
344
- overscrollBehavior: "contain",
345
323
  background: "var(--chat-primary-fg, #fff)",
346
324
  display: "flex",
347
325
  flexDirection: "column",
@@ -401,7 +379,6 @@ export default function ChatWidget({
401
379
 
402
380
  {/* Messages */}
403
381
  <div
404
- data-chat-messages
405
382
  style={{
406
383
  flex: 1,
407
384
  overflowY: "auto",