Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ All notable changes follow Keep a Changelog.
confirmed.
- Added live pull metrics, lifecycle stress controls, browser guidance, and
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.

## [0.1.0-alpha.0] - 2026-09-05

Expand Down
19 changes: 13 additions & 6 deletions docs/INTEGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,22 @@ element explicitly:
```tsx
<Sheet.Content>
<Sheet.Handle />
<div ref={scrollRef} className="sheet-scroll">
<PullToRefresh.Root onRefresh={refresh} scrollContainer={scrollRef}>
<PullToRefresh.Content>{/* feed */}</PullToRefresh.Content>
</PullToRefresh.Root>
</div>
<PullToRefresh.Root
ref={scrollRef}
className="sheet-scroll"
onRefresh={refresh}
scrollContainer={scrollRef}
>
<PullToRefresh.Content>{/* feed */}</PullToRefresh.Content>
</PullToRefresh.Root>
</Sheet.Content>
```

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.

The documentation proof uses the published v5 package. Desktop automation
verifies that the sheet opens, exposes a dedicated scroll root, and closes.
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
claim physical-device verification.
36 changes: 36 additions & 0 deletions e2e/gesture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ test('documents ownership, native refresh, and truthful device status', async ({
test('renders real sibling primitive integration proofs', async ({ page }) => {
await page.goto('/#integrations')

await expect(
page.getByRole('link', { name: 'React Swipe Actions' }),
).toHaveAttribute('href', 'https://react-swipe-actions.nipesolutions.com/')
await expect(
page.getByRole('link', { name: 'React Spring Bottom Sheet' }),
).toHaveAttribute(
'href',
'https://react-spring-bottom-sheet.nipesolutions.com/',
)

const swipe = page.getByTestId('swipe-integration')
await expect(swipe).toHaveAttribute('data-integration', 'swipe-actions')
await expect(swipe.locator('.swipe-actions-root').first()).toBeVisible()
Expand All @@ -87,6 +97,32 @@ test('renders real sibling primitive integration proofs', async ({ page }) => {
await page.getByRole('button', { name: 'Close sheet proof' }).click()
})

test('sheet content keeps the native scroll direction available at its top boundary', async ({
page,
}) => {
await page.goto('/#integrations')
await page.getByRole('button', { name: 'Open bottom sheet proof' }).click()

const root = page.getByTestId('sheet-ptr')
await expect(root).toHaveAttribute('data-at-top', 'true')
const touchAction = await root.evaluate(
(node) => window.getComputedStyle(node).touchAction,
)
expect(['pan-x pan-up', 'pan-x pan-y']).toContain(touchAction)

const scroll = page.locator('.sheet-scroll')
const dimensions = await scroll.evaluate((node) => ({
clientHeight: node.clientHeight,
scrollHeight: node.scrollHeight,
}))
expect(dimensions.scrollHeight).toBeGreaterThan(dimensions.clientHeight)
await scroll.hover()
await page.mouse.wheel(0, 180)
await expect
.poll(() => scroll.evaluate((node) => node.scrollTop))
.toBeGreaterThan(0)
})

test('Swipe Actions owns a horizontal row trace', async ({ page }) => {
await page.goto('/#integrations')
const row = page.locator('.swipe-actions-root').first()
Expand Down
2 changes: 1 addition & 1 deletion src/core.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}

.ptr-root[data-at-top='true'] {
touch-action: pan-x pan-down;
touch-action: pan-x pan-up;
}

.ptr-indicator {
Expand Down
40 changes: 20 additions & 20 deletions website/src/demos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,26 +369,26 @@ export function BottomSheetProof() {
</div>
<Sheet.Close>Close sheet proof</Sheet.Close>
</div>
<div className="sheet-scroll" ref={scrollRef}>
<PullToRefresh.Root
data-testid="sheet-ptr"
scrollContainer={scrollRef}
onRefresh={() =>
new Promise((resolve) => window.setTimeout(resolve, 400))
}
>
<PullToRefresh.Indicator>
<TensionIndicator />
</PullToRefresh.Indicator>
<PullToRefresh.Content>
{Array.from({ length: 8 }, (_, index) => (
<div className="sheet-row" key={index}>
Sheet content {index + 1}
</div>
))}
</PullToRefresh.Content>
</PullToRefresh.Root>
</div>
<PullToRefresh.Root
ref={scrollRef}
className="sheet-scroll"
data-testid="sheet-ptr"
scrollContainer={scrollRef}
onRefresh={() =>
new Promise((resolve) => window.setTimeout(resolve, 400))
}
>
<PullToRefresh.Indicator>
<TensionIndicator />
</PullToRefresh.Indicator>
<PullToRefresh.Content>
{Array.from({ length: 8 }, (_, index) => (
<div className="sheet-row" key={index}>
Sheet content {index + 1}
</div>
))}
</PullToRefresh.Content>
</PullToRefresh.Root>
</Sheet.Content>
</Sheet.Viewport>
</Sheet.Portal>
Expand Down
14 changes: 12 additions & 2 deletions website/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,25 @@ function IntegrationProofs() {
</div>
<div className="integration-grid">
<div>
<h3>Swipe Actions inside a feed</h3>
<h3>
<a href="https://react-swipe-actions.nipesolutions.com/">
React Swipe Actions
</a>{' '}
inside a feed
</h3>
<p>
Horizontal motion reveals a row. Vertical motion stays with
scrolling. A downward pull at the top belongs to refresh.
</p>
<SwipeIntegration />
</div>
<div>
<h3>Pull to Refresh inside a sheet</h3>
<h3>
Pull to Refresh inside{' '}
<a href="https://react-spring-bottom-sheet.nipesolutions.com/">
React Spring Bottom Sheet
</a>
</h3>
<p>
The sheet handle remains outside the scrolling feed. The feed
element is passed explicitly as the pull boundary.
Expand Down
9 changes: 9 additions & 0 deletions website/src/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,15 @@ pre {
.integrations .section-heading {
margin-bottom: 56px;
}
.integrations h3 a {
color: inherit;
text-decoration-color: var(--tension-deep);
text-decoration-thickness: 2px;
text-underline-offset: 5px;
}
.integrations h3 a:hover {
color: var(--tension-deep);
}
.integration-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
Expand Down