mirror of
https://github.com/getpaseo/paseo.git
synced 2026-09-14 20:36:44 +08:00
fix(app): restore coordinated bottom sheet gestures (#4431)
Supplying a list controls scroll-container ownership, not whether content can move the sheet. Preserve the shared expand-scroll-dismiss interaction for model selectors and plugin lists, with native gesture regressions and updated plugin guidance.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# Native sheet gestures
|
||||
|
||||
Use an Android emulator at 1080×1920, density 420, with the checkout's native app bundle loaded.
|
||||
Connect the app to an isolated daemon, enable plugins, and install `plugin-examples/modal-ui` there.
|
||||
Set `SHEET_QA_SERVER_ID` to that host's server ID, then run from the repository root:
|
||||
|
||||
```sh
|
||||
agent-device test packages/app/e2e/mobile/modal-sheet/gestures.android.ad \
|
||||
--env EXAMPLES_URL="paseo://h/${SHEET_QA_SERVER_ID}/plugin/modal-ui-example/surface/main" \
|
||||
--record-video --artifacts-dir .dev/sheet-qa/plugin
|
||||
|
||||
agent-device test packages/app/e2e/mobile/modal-sheet/model.android.ad \
|
||||
--env FORM_URL="paseo://new?serverId=${SHEET_QA_SERVER_ID}" \
|
||||
--record-video --artifacts-dir .dev/sheet-qa/model
|
||||
```
|
||||
|
||||
The plugin journey checks body dismissal, reopening, expansion and last-row reachability with SDK
|
||||
ScrollView and FlatList, programmatic scrolling after expansion, downward list scrolling, and
|
||||
horizontal tab selection. The model journey checks body dismissal and the nested sheet's return to
|
||||
its parent without selecting a model or submitting a prompt. It does not assert catalog contents.
|
||||
|
||||
These scripts live outside the default mobile suite because they require the installed example and
|
||||
an explicit connected host. See [mobile testing](../../../../../docs/mobile-testing.md) for device setup.
|
||||
|
||||
## Recorded verification
|
||||
|
||||
Android API 35, 1080×1920 at density 420, development binary 0.7.2 with this checkout's JavaScript.
|
||||
Plugin tests used an isolated daemon. Recordings show the same body drag
|
||||
[before](evidence/android-before.mp4) and [after](evidence/android-after.mp4) the fix.
|
||||
The [browser recording](evidence/browser.webm) covers the existing plugin modal journey at wide and
|
||||
compact sizes.
|
||||
|
||||
```text
|
||||
Baseline dismissal regression:
|
||||
failed at step 6: wait timed out for selector: label="Open ScrollView"
|
||||
Current surface: Bottom sheet handle, Bottom Sheet, Close, Row 1.
|
||||
|
||||
Fixed dismissal regression: 1 passed (6.47s)
|
||||
Full plugin gesture journey: 1 passed (44.3s)
|
||||
Model sheet dismissal/reopen journey: 1 passed (13.7s)
|
||||
Browser plugin-modal-body.spec.ts: 1 passed (33.4s)
|
||||
Root typecheck, lint and format: passed
|
||||
```
|
||||
|
||||
The model form's catalog stayed on “Loading…” in the debug app even though the isolated daemon's
|
||||
provider API returned models. Its sheet dismissal/reopen was exercised; populated model selection
|
||||
was not. iOS and Electron were not exercised.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
# Install plugin-examples/modal-ui on the connected host and set EXAMPLES_URL to
|
||||
# paseo://h/<serverId>/plugin/modal-ui-example/surface/main.
|
||||
# Gesture coordinates target a 1080x1920 Android emulator at density 420.
|
||||
context platform=android timeout=120000 retries=0
|
||||
env APP_ID=sh.paseo.debug
|
||||
|
||||
open "${APP_ID}" "${EXAMPLES_URL}"
|
||||
wait "label=\"Open ScrollView\"" 20000
|
||||
press "label=\"Open ScrollView\""
|
||||
wait "label=\"Row 1\"" 10000
|
||||
gesture pan 540 980 0 820 500
|
||||
wait "label=\"Open ScrollView\"" 3000
|
||||
|
||||
# The caller-owned ScrollView expands with a body drag and reaches its final row.
|
||||
press "label=\"Open ScrollView\""
|
||||
wait "label=\"Row 1\"" 10000
|
||||
gesture pan 540 980 0 -600 500
|
||||
wait "label=\"Row 10\"" 3000
|
||||
scroll bottom
|
||||
wait "label=\"Row 100\"" 3000
|
||||
press "role=\"button\" label=\"Row 100\""
|
||||
press "label=\"Close\""
|
||||
wait "text=\"Selected: Row 100\"" 3000
|
||||
|
||||
# FlatList has the same body dismissal, then supports refs after expansion.
|
||||
press "label=\"Open FlatList\""
|
||||
wait "label=\"Row 1\"" 10000
|
||||
gesture pan 540 1100 0 700 500
|
||||
wait "label=\"Open FlatList\"" 3000
|
||||
press "label=\"Open FlatList\""
|
||||
wait "label=\"Row 1\"" 10000
|
||||
gesture pan 540 1140 0 -750 500
|
||||
wait "label=\"Row 9\"" 3000
|
||||
press "label=\"Jump to last row\""
|
||||
wait "label=\"Row 100\"" 3000
|
||||
# Dragging down while scrolled moves the list, leaving the sheet open.
|
||||
gesture pan 540 650 0 900 500
|
||||
wait "label=\"Row 88\"" 3000
|
||||
is visible "label=\"Close\""
|
||||
press "label=\"Close\""
|
||||
wait "label=\"Open Horizontal\"" 3000
|
||||
|
||||
# Horizontal tabs still receive gestures inside the host-owned vertical body.
|
||||
press "label=\"Open Horizontal\""
|
||||
wait "label=\"Overview\"" 10000
|
||||
gesture pan 960 980 -880 0 500
|
||||
wait stable 300 5000
|
||||
press "role=\"button\" label=\"System logs\""
|
||||
wait stable 300 5000
|
||||
press "label=\"Close\""
|
||||
wait "text=\"Selected: System logs\"" 3000
|
||||
close
|
||||
@@ -0,0 +1,22 @@
|
||||
# FORM_URL is paseo://new?serverId=<connected-serverId>.
|
||||
# Gesture coordinates target a 1080x1920 Android emulator at density 420.
|
||||
context platform=android timeout=60000 retries=0
|
||||
env APP_ID=sh.paseo.debug
|
||||
|
||||
open "${APP_ID}" "${FORM_URL}"
|
||||
wait "id=\"combined-model-selector\"" 20000
|
||||
press "id=\"combined-model-selector\""
|
||||
wait "id=\"agent-controls-model\"" 10000
|
||||
gesture pan 540 790 0 960 500
|
||||
wait "id=\"combined-model-selector\"" 3000
|
||||
|
||||
# Reopen, dismiss the nested model browser, then dismiss its restored parent.
|
||||
press "id=\"combined-model-selector\""
|
||||
wait "id=\"agent-controls-model\"" 10000
|
||||
press "id=\"agent-controls-model\""
|
||||
wait stable 300 5000
|
||||
gesture pan 540 790 0 960 500
|
||||
wait "id=\"agent-controls-model\"" 3000
|
||||
gesture pan 540 790 0 960 500
|
||||
wait "id=\"combined-model-selector\"" 3000
|
||||
close
|
||||
@@ -449,6 +449,7 @@ export interface AdaptiveModalSheetProps {
|
||||
desktopMaxWidth?: number;
|
||||
/** Bound an author-owned list without changing content-sized first-party dialogs. */
|
||||
desktopHeight?: DimensionValue;
|
||||
/** Whether the host supplies the scroll container. Caller-owned lists still share sheet gestures. */
|
||||
scrollable?: boolean;
|
||||
presentation?: "push" | "replace";
|
||||
/** Full body viewport below the header, including space beyond the content. */
|
||||
@@ -646,9 +647,6 @@ export function AdaptiveModalSheet({
|
||||
onDismiss={handleDismiss}
|
||||
backdropComponent={renderBackdrop}
|
||||
enablePanDownToClose
|
||||
// A custom scroll owner must also own body gestures. Gorhom otherwise
|
||||
// locks even imperative list offsets until the sheet reaches its top snap.
|
||||
enableContentPanningGesture={scrollable}
|
||||
backgroundComponent={SheetBackground}
|
||||
handleIndicatorStyle={handleIndicatorStyle}
|
||||
keyboardBehavior="extend"
|
||||
|
||||
@@ -13,11 +13,15 @@ cd packages/app
|
||||
npx playwright test e2e/browser/plugin-modal-body.spec.ts --project=browser --workers=1
|
||||
```
|
||||
|
||||
On Android, open each example and swipe through its content. In FlatList, use **Jump to last row**
|
||||
before expanding the sheet; row 100 should be visible. Drag the handle to expand the sheet. In Form,
|
||||
On Android, open each example and drag up on its content to expand the sheet, then scroll the list.
|
||||
In FlatList, expand before using **Jump to last row**; row 100 should be visible. At the top of either
|
||||
list, drag down on a row to dismiss the sheet. In Form,
|
||||
press **Copy text**, long-press the input, and choose **Paste**. The input should contain
|
||||
“Copied from Paseo”. With the system keyboard enabled, focusing the input should keep it visible.
|
||||
|
||||
Run the [native sheet regression](../../packages/app/e2e/mobile/modal-sheet/README.md) to check body
|
||||
dismissal, list scrolling and horizontal tabs together.
|
||||
|
||||
These captures show Android copy/paste and custom padding on browser and wide native layouts.
|
||||
Android API 35 and Chromium were exercised; iOS and Electron were not tested.
|
||||
|
||||
|
||||
@@ -366,8 +366,7 @@ intact. The host reserves bottom safe-area space on compact native layouts; sett
|
||||
to zero removes the decorative inset, not that space. Keyboard clearance is handled separately.
|
||||
|
||||
With `scrollable={false}`, the body fills the available sheet height, and the centered dialog uses
|
||||
85% of the available height. Use `flex: 1, minHeight: 0` on your list. In this mode the body scrolls
|
||||
at every sheet height; drag the handle to resize or dismiss the sheet. Default scrolling dialogs stay
|
||||
85% of the available height. Use `flex: 1, minHeight: 0` on your list. Default scrolling dialogs stay
|
||||
content-sized on wide layouts. Presentation follows window size, including narrow desktop windows
|
||||
and wide tablets.
|
||||
|
||||
@@ -387,6 +386,11 @@ Use one vertical scroll owner: either the default modal body, or your own list w
|
||||
`scrollable={false}`. A fixed-height vertical list nested inside the default scrolling body can compete
|
||||
with the sheet for gestures on Android. Horizontal scrolling can coexist with the host's vertical body.
|
||||
|
||||
Both the default body and SDK lists share native sheet gestures: drag up to expand before scrolling;
|
||||
drag down at the top of the list to collapse or dismiss. `scrollable={false}` removes the host's scroll
|
||||
container without changing these gestures. Expand the sheet before using list methods such as
|
||||
`scrollToEnd`; the sheet locks list offsets below its largest height.
|
||||
|
||||
```tsx
|
||||
import { FlatList, Modal } from "@getpaseo/plugin/react-native";
|
||||
import { Text } from "react-native";
|
||||
|
||||
Reference in New Issue
Block a user