authorgravatar for 136640686+MatthewFeroz@users.noreply.github.comMatthew Feroz <136640686+MatthewFeroz@users.noreply.github.com> 2026-10-10 23:36:13-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 01:36:13-05:00
log0cb38403feb8f2bbc29dc78dea1649af696b6f08
tree8d17600c3a5e6a8a0c325d3822b14e25d02c273b
parent5047ee78858bfdeb254c7386e2d427d3bccb8430
signature Signed by PGP key B5690EEEBB952194

fix(web): unpin preview dragging on narrow chat canvases (#15556)


2 files changed, 23 insertions(+), 5 deletions(-)

apps/web/src/components/chat/chatCanvasLayout.test.ts+17-2
...@@ -76,6 +76,21 @@ describe("chat canvas layout", () => {...@@ -76,6 +76,21 @@ describe("chat canvas layout", () => {
76 expect(result.chat.left).toBe(48);76 expect(result.chat.left).toBe(48);
77 expectClear(result);77 expectClear(result);
78 });78 });
79 it.each([900, 1000, 1004])("allows dragging across a %spx chat canvas", (width) => {
80 const start = resolve(width, { ...preview, position: { x: width - 332, y: 200 } }).frame!;
81 const moved = resolve(width, {
82 ...preview,
83 position: { x: start.x - 300, y: start.y },
84 lastInteraction: "drag",
85 });
86 expect(moved.frame).toEqual({ ...start, x: start.x - 300 });
87 const returned = resolve(width, {
88 ...preview,
89 position: { x: start.x, y: start.y },
90 lastInteraction: "drag",
91 });
92 expect(returned.frame).toEqual(start);
93 });
79 it("stops leftward dragging before the player can push chat to the right", () => {94 it("stops leftward dragging before the player can push chat to the right", () => {
80 const centered = resolve(1344, null).chat;95 const centered = resolve(1344, null).chat;
81 for (const x of [1100, 800, 700, 600, 400, 12, -100]) {96 for (const x of [1100, 800, 700, 600, 400, 12, -100]) {
...@@ -91,7 +106,7 @@ describe("chat canvas layout", () => {...@@ -91,7 +106,7 @@ describe("chat canvas layout", () => {
91 it("keeps a dragged player above the composer when no readable lane fits beside it", () => {106 it("keeps a dragged player above the composer when no readable lane fits beside it", () => {
92 const result = resolve(1000, { ...preview, position: { x: 12, y: 700 } });107 const result = resolve(1000, { ...preview, position: { x: 12, y: 700 } });
93 expect(result.chat).toEqual(resolve(1000, null).chat);108 expect(result.chat).toEqual(resolve(1000, null).chat);
94 expect(result.frame!.x).toBe(668);109 expect(result.frame!.x).toBe(12);
95 expect(result.frame!.y + result.frame!.height).toBeLessThanOrEqual(708);110 expect(result.frame!.y + result.frame!.height).toBeLessThanOrEqual(708);
96 });111 });
97 it("allows message overlap on narrow screens while excluding the composer", () => {112 it("allows message overlap on narrow screens while excluding the composer", () => {
...@@ -299,7 +314,7 @@ describe("chat canvas layout", () => {...@@ -299,7 +314,7 @@ describe("chat canvas layout", () => {
299 });314 });
300 expect(result.overlapsDetailsCard).toBe(true);315 expect(result.overlapsDetailsCard).toBe(true);
301 expect(result.overlapsChat).toBe(true);316 expect(result.overlapsChat).toBe(true);
302 expect(result.frame).toEqual({ x: 272, y: 396, width: 1300, height: 400 });317 expect(result.frame).toEqual({ x: 12, y: 396, width: 1300, height: 400 });
303 });318 });
304});319});
305320
apps/web/src/components/chat/chatCanvasLayout.ts+6-3
...@@ -66,10 +66,13 @@ export function resolveChatCanvasLayout({...@@ -66,10 +66,13 @@ export function resolveChatCanvasLayout({
66 let overlapsChat = false;66 let overlapsChat = false;
67 if (preview && container.width > 0 && container.height > 0) {67 if (preview && container.width > 0 && container.height > 0) {
68 frame = resolvePreviewMiniPlayerFrame({ ...preview, container });68 frame = resolvePreviewMiniPlayerFrame({ ...preview, container });
69 // Dragging stops at a readable chat lane on the left. Resizing can still69 // Reserve a readable chat lane only while it leaves room to drag. Otherwise
70 // consume that space when the container requires message overlap.70 // the container's right edge would pin the preview at every pointer position.
71 const minimumPreviewX =71 const minimumPreviewX =
72 preview.lastInteraction === "resize" ? GAP : padding + minChatWidth + GAP;72 preview.lastInteraction === "resize" ||
73 container.width <= padding + minChatWidth + frame.width + GAP * 2
74 ? GAP
75 : padding + minChatWidth + GAP;
73 // New players start beside the composer, with the workspace card above them.76 // New players start beside the composer, with the workspace card above them.
74 if (preview.position === null) frame = { ...frame, y: container.height - frame.height - GAP };77 if (preview.position === null) frame = { ...frame, y: container.height - frame.height - GAP };
75 if (preview.lastInteraction === "resize" && composerHeight > 0) {78 if (preview.lastInteraction === "resize" && composerHeight > 0) {