diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c1a0e..1d44046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes follow Keep a Changelog. published-package integration proofs to the documentation site. - Preserved the upward touch-pan direction at the top boundary so scrollable content can begin scrolling normally from its first row. +- Documented explicit pointer ownership for Pull to Refresh inside a draggable + bottom sheet. ## [0.1.0-alpha.0] - 2026-09-05 diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md index 176421b..14702ee 100644 --- a/docs/INTEGRATIONS.md +++ b/docs/INTEGRATIONS.md @@ -43,6 +43,7 @@ element explicitly: ref={scrollRef} className="sheet-scroll" onRefresh={refresh} + onPointerDown={(event) => event.stopPropagation()} scrollContainer={scrollRef} > {/* feed */} @@ -54,6 +55,11 @@ Make the root itself scrollable. Do not place a default PTR root—with its mechanical overflow containment—inside a second element that is expected to receive chained scrolling. +Stop pointer-down propagation from the feed when the sheet handle is the only +surface that should drag the sheet. This leaves the browser default untouched, +so ordinary list scrolling still works, while preventing the sheet and PTR +from racing to capture the same downward gesture at the top boundary. + The documentation proof uses the published v5 package. Desktop automation verifies that the sheet opens, scrolls the dedicated root, and closes. Combined drag feel on iOS and Android remains manual pending; the alpha does not diff --git a/e2e/gesture.spec.ts b/e2e/gesture.spec.ts index 1c60411..3a68f3c 100644 --- a/e2e/gesture.spec.ts +++ b/e2e/gesture.spec.ts @@ -123,6 +123,37 @@ test('sheet content keeps the native scroll direction available at its top bound .toBeGreaterThan(0) }) +test('sheet feed owns a gradual downward pull before the sheet can claim it', async ({ + page, +}) => { + await page.goto('/#integrations') + await page.getByRole('button', { name: 'Open bottom sheet proof' }).click() + + const sheet = page.getByRole('dialog') + await expect(sheet).toHaveAttribute('data-rsbs-state', 'open') + const initialPosition = await sheet.evaluate((node) => + node.style.getPropertyValue('--rsbs-position'), + ) + const root = page.getByTestId('sheet-ptr') + const box = await root.boundingBox() + if (!box) throw new Error('Sheet PTR root has no box') + const x = box.x + box.width / 2 + const y = box.y + 80 + + await page.mouse.move(x, y) + await page.mouse.down() + await page.mouse.move(x, y + 2) + await page.mouse.move(x, y + 30, { steps: 6 }) + + await expect(root).toHaveAttribute('data-state', 'pulling') + await expect + .poll(() => + sheet.evaluate((node) => node.style.getPropertyValue('--rsbs-position')), + ) + .toBe(initialPosition) + await page.mouse.up() +}) + test('Swipe Actions owns a horizontal row trace', async ({ page }) => { await page.goto('/#integrations') const row = page.locator('.swipe-actions-root').first() diff --git a/website/src/demos.tsx b/website/src/demos.tsx index cd1850f..1094db2 100644 --- a/website/src/demos.tsx +++ b/website/src/demos.tsx @@ -374,6 +374,7 @@ export function BottomSheetProof() { className="sheet-scroll" data-testid="sheet-ptr" scrollContainer={scrollRef} + onPointerDown={(event) => event.stopPropagation()} onRefresh={() => new Promise((resolve) => window.setTimeout(resolve, 400)) }