Skip to content

Commit 9d8e4d8

Browse files
authored
fix(chat): contain mobile composer scrolling (#8889)
1 parent b3f459d commit 9d8e4d8

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const OVERLAY_CLASSES = cn(
9797

9898
/** Single scroll container for the textarea + overlay; caps height and hides its scrollbar. */
9999
export const SCROLLER_CLASSES = cn(
100-
'relative overflow-y-auto overflow-x-hidden',
100+
'relative overflow-y-auto overflow-x-hidden max-md:overscroll-y-contain',
101101
'[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
102102
)
103103

‎apps/sim/scripts/test-mobile-e2e.ts‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,91 @@ async function exerciseViewport(
552552
})
553553
}
554554

555+
if (viewport.name === 'desktop') {
556+
for (const width of [390, viewport.width]) {
557+
await check(
558+
`${prefix}/${width}px draft scroll boundaries preserve the surrounding page`,
559+
page,
560+
async () => {
561+
await visit(page, 'home')
562+
const composer = page.getByRole('textbox', { name: 'Message', exact: true })
563+
const heading = page.getByRole('heading', { level: 1 })
564+
const readScroll = () =>
565+
composer.evaluate((element) => {
566+
let ancestor = element.parentElement
567+
while (ancestor) {
568+
if (
569+
ancestor.scrollHeight > ancestor.clientHeight &&
570+
getComputedStyle(ancestor).overflowY === 'auto'
571+
) {
572+
const bounds = ancestor.getBoundingClientRect()
573+
return {
574+
top: ancestor.scrollTop,
575+
max: ancestor.scrollHeight - ancestor.clientHeight,
576+
x: bounds.x + bounds.width / 2,
577+
y: bounds.y + bounds.height / 2,
578+
}
579+
}
580+
ancestor = ancestor.parentElement
581+
}
582+
throw new Error('The long draft must have a scrollable editor')
583+
})
584+
const settle = () =>
585+
page.evaluate(
586+
() =>
587+
new Promise<void>((resolve) =>
588+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
589+
)
590+
)
591+
try {
592+
await page.setViewportSize({ width, height: 520 })
593+
await composer.fill('A draft line\n'.repeat(30))
594+
const editor = await readScroll()
595+
assert(editor.max > 100, 'The draft must overflow its editor')
596+
await page.mouse.move(editor.x, editor.y)
597+
await page.mouse.wheel(0, 10_000)
598+
await expect
599+
.poll(
600+
async () => {
601+
const scroll = await readScroll()
602+
return Math.abs(scroll.top - scroll.max)
603+
},
604+
{ message: 'The draft must reach its lower scroll boundary' }
605+
)
606+
.toBeLessThanOrEqual(1)
607+
const before = await heading.boundingBox()
608+
assert(before)
609+
await page.mouse.wheel(0, 600)
610+
await settle()
611+
const after = await heading.boundingBox()
612+
assert(after)
613+
if (width < 768) {
614+
assert(
615+
Math.abs(after.y - before.y) <= 1,
616+
'Scrolling past a mobile draft must not move Home'
617+
)
618+
await page.mouse.move(width - 2, 300)
619+
await page.mouse.wheel(0, 600)
620+
await expect
621+
.poll(async () => (await heading.boundingBox())?.y ?? before.y)
622+
.toBeLessThan(before.y - 10)
623+
} else {
624+
await expect
625+
.poll(async () => (await heading.boundingBox())?.y ?? before.y, {
626+
message: 'Desktop scroll handoff must remain unchanged',
627+
})
628+
.toBeLessThan(before.y - 10)
629+
}
630+
await capture(page, `${browserName}-${width}-draft-scroll`)
631+
} finally {
632+
await composer.fill('')
633+
await page.setViewportSize({ width: viewport.width, height: viewport.height })
634+
}
635+
}
636+
)
637+
}
638+
}
639+
555640
if (viewport.width < 768) {
556641
await check(
557642
`${prefix}/navigation opens, dismisses, and preserves desktop preferences`,

0 commit comments

Comments
 (0)