@tp3/chat-widget 0.1.10 → 0.1.11

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,18 @@ 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);
70
69
  (0, import_react.useEffect)(() => {
71
70
  if (open && !closing) {
72
- const scrollY = window.scrollY;
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));
78
- document.body.style.position = "";
79
- document.body.style.top = "";
80
- document.body.style.width = "";
81
- window.scrollTo(0, top);
71
+ document.documentElement.style.overflow = "hidden";
72
+ } else {
73
+ document.documentElement.style.overflow = "";
82
74
  }
83
75
  return () => {
84
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
85
- document.body.style.position = "";
86
- document.body.style.top = "";
87
- document.body.style.width = "";
88
- if (open) window.scrollTo(0, top);
76
+ document.documentElement.style.overflow = "";
89
77
  };
90
78
  }, [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
79
  (0, import_react.useEffect)(() => {
107
80
  if (messages.length > 1) {
108
81
  messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
@@ -298,7 +271,6 @@ function ChatWidget({
298
271
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
299
272
  "div",
300
273
  {
301
- ref: chatWindowRef,
302
274
  className: closing ? "chat-window-out" : "chat-window",
303
275
  style: {
304
276
  position: "fixed",
@@ -309,7 +281,6 @@ function ChatWidget({
309
281
  maxWidth: "calc(100vw - 48px)",
310
282
  height: chatHeight,
311
283
  maxHeight: "calc(100dvh - 48px)",
312
- overscrollBehavior: "contain",
313
284
  background: "var(--chat-primary-fg, #fff)",
314
285
  display: "flex",
315
286
  flexDirection: "column",
@@ -374,7 +345,6 @@ function ChatWidget({
374
345
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
375
346
  "div",
376
347
  {
377
- "data-chat-messages": true,
378
348
  style: {
379
349
  flex: 1,
380
350
  overflowY: "auto",
@@ -40,45 +40,18 @@ 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);
46
45
  useEffect(() => {
47
46
  if (open && !closing) {
48
- const scrollY = window.scrollY;
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));
54
- document.body.style.position = "";
55
- document.body.style.top = "";
56
- document.body.style.width = "";
57
- window.scrollTo(0, top);
47
+ document.documentElement.style.overflow = "hidden";
48
+ } else {
49
+ document.documentElement.style.overflow = "";
58
50
  }
59
51
  return () => {
60
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
61
- document.body.style.position = "";
62
- document.body.style.top = "";
63
- document.body.style.width = "";
64
- if (open) window.scrollTo(0, top);
52
+ document.documentElement.style.overflow = "";
65
53
  };
66
54
  }, [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
55
  useEffect(() => {
83
56
  if (messages.length > 1) {
84
57
  messagesEnd.current?.scrollIntoView({ behavior: "smooth" });
@@ -274,7 +247,6 @@ function ChatWidget({
274
247
  open && /* @__PURE__ */ jsxs(
275
248
  "div",
276
249
  {
277
- ref: chatWindowRef,
278
250
  className: closing ? "chat-window-out" : "chat-window",
279
251
  style: {
280
252
  position: "fixed",
@@ -285,7 +257,6 @@ function ChatWidget({
285
257
  maxWidth: "calc(100vw - 48px)",
286
258
  height: chatHeight,
287
259
  maxHeight: "calc(100dvh - 48px)",
288
- overscrollBehavior: "contain",
289
260
  background: "var(--chat-primary-fg, #fff)",
290
261
  display: "flex",
291
262
  flexDirection: "column",
@@ -350,7 +321,6 @@ function ChatWidget({
350
321
  /* @__PURE__ */ jsxs(
351
322
  "div",
352
323
  {
353
- "data-chat-messages": true,
354
324
  style: {
355
325
  flex: 1,
356
326
  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.11",
4
4
  "main": "dist/ChatWidget.js",
5
5
  "module": "dist/ChatWidget.mjs",
6
6
  "types": "dist/ChatWidget.d.ts",
@@ -64,57 +64,24 @@ 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.
71
+ // Using overflow:hidden on <html> is the standard approach used by
72
+ // modal/chat libraries it blocks wheel and touch scroll on desktop
73
+ // and mobile without interfering with the keyboard or viewport.
74
74
  useEffect(() => {
75
75
  if (open && !closing) {
76
- const scrollY = window.scrollY;
77
- 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));
82
- document.body.style.position = "";
83
- document.body.style.top = "";
84
- document.body.style.width = "";
85
- window.scrollTo(0, top);
76
+ document.documentElement.style.overflow = "hidden";
77
+ } else {
78
+ document.documentElement.style.overflow = "";
86
79
  }
87
80
  return () => {
88
- const top = Math.abs(parseInt(document.body.style.top || "0", 10));
89
- document.body.style.position = "";
90
- document.body.style.top = "";
91
- document.body.style.width = "";
92
- if (open) window.scrollTo(0, top);
81
+ document.documentElement.style.overflow = "";
93
82
  };
94
83
  }, [open, closing]);
95
84
 
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
85
  useEffect(() => {
119
86
  // Don't scroll on first render — wait for the open animation
120
87
  if (messages.length > 1) {
@@ -330,7 +297,6 @@ export default function ChatWidget({
330
297
 
331
298
  {open && (
332
299
  <div
333
- ref={chatWindowRef}
334
300
  className={closing ? "chat-window-out" : "chat-window"}
335
301
  style={{
336
302
  position: "fixed",
@@ -341,7 +307,6 @@ export default function ChatWidget({
341
307
  maxWidth: "calc(100vw - 48px)",
342
308
  height: chatHeight,
343
309
  maxHeight: "calc(100dvh - 48px)",
344
- overscrollBehavior: "contain",
345
310
  background: "var(--chat-primary-fg, #fff)",
346
311
  display: "flex",
347
312
  flexDirection: "column",
@@ -401,7 +366,6 @@ export default function ChatWidget({
401
366
 
402
367
  {/* Messages */}
403
368
  <div
404
- data-chat-messages
405
369
  style={{
406
370
  flex: 1,
407
371
  overflowY: "auto",