mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-15 12:54:58 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a45dfaf0e8 | ||
|
|
f69dc31086 | ||
|
|
6adf92f089 |
@@ -0,0 +1,27 @@
|
||||
# Android Usage drop regressions
|
||||
|
||||
Use a device you own and fixed reports from an isolated daemon. Keep recordings in `.artifacts/`. Do not upload recordings containing account details.
|
||||
|
||||
## Same-slot drop
|
||||
|
||||
Open Usage with two cards that fit without scrolling. Run:
|
||||
|
||||
```sh
|
||||
ANDROID_SERIAL=emulator-5580 node packages/app/e2e/mobile/usage-drag/android.mjs
|
||||
```
|
||||
|
||||
Set `PASEO_DEVICE_SESSION` to reuse your Agent Device session. The test holds the first handle, moves within its slot, drops it, then swipes the sheet body. It asserts sheet movement while the finger is still down: after release, the sheet can snap back to its starting position.
|
||||
|
||||
## Old-order frame
|
||||
|
||||
Record a drop with Android `screenrecord`. Include the original order, the held arrangement, the release, and at least five seconds after release. Take a reference screenshot before recording. Use fixed labels that stay visible throughout the drop.
|
||||
|
||||
Create a JSON map of label names to screenshot rectangles `[x, y, width, height]`, in physical pixels. Crop each label tightly, without changing text or the pointer. Install `opencv-python-headless` in a local virtual environment under `.artifacts/`, then run:
|
||||
|
||||
```sh
|
||||
.artifacts/frames-venv/bin/python packages/app/e2e/mobile/usage-drag/frames.py \
|
||||
--reference .artifacts/before.png --labels .artifacts/labels.json \
|
||||
.artifacts/drop-up.mp4 .artifacts/drop-down.mp4
|
||||
```
|
||||
|
||||
The check requires recognized labels in the first and last frames and a changed final order. After every label first reaches its final slot, it fails on any frame showing all original slots again. It prints the offending frame numbers for each recording. This exercises the Android compositor; a JavaScript render test cannot establish which intermediate commit reached the screen.
|
||||
@@ -0,0 +1,71 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
|
||||
// Open Usage with a fixture whose cards fit without scrolling before running.
|
||||
// A same-slot drop must release the list's recognizer so a body swipe moves the sheet.
|
||||
const serial = process.env.ANDROID_SERIAL;
|
||||
assert.ok(serial, "Set ANDROID_SERIAL to the device owned by this test");
|
||||
const session = process.env.PASEO_DEVICE_SESSION ?? "usage-drag-regression";
|
||||
const artifacts = resolve(".artifacts/usage-drag-regression");
|
||||
mkdirSync(artifacts, { recursive: true });
|
||||
function ad(...args) {
|
||||
return execFileSync(
|
||||
"agent-device",
|
||||
[...args, "--platform", "android", "--serial", serial, "--session", session],
|
||||
{ encoding: "utf8", timeout: 30_000 },
|
||||
);
|
||||
}
|
||||
function adb(...args) {
|
||||
return execFileSync("adb", ["-s", serial, ...args], { timeout: 30_000 });
|
||||
}
|
||||
function snapshot() {
|
||||
return JSON.parse(ad("snapshot", "--json")).data.nodes;
|
||||
}
|
||||
function touch(kind, x, y) {
|
||||
adb("shell", "input", "motionevent", kind, String(Math.round(x)), String(Math.round(y)));
|
||||
}
|
||||
try {
|
||||
ad("open", process.env.PASEO_MOBILE_APP_ID ?? "sh.paseo.debug");
|
||||
const nodes = snapshot();
|
||||
writeFileSync(resolve(artifacts, "before.json"), JSON.stringify(nodes));
|
||||
const handle = nodes.find((node) => node.identifier === "usage-card-drag-handle");
|
||||
const list = nodes.find((node) => node.identifier === "usage-list");
|
||||
assert.ok(handle?.rect && list?.rect, "Open Usage with a visible card handle");
|
||||
const x = handle.rect.x + handle.rect.width / 2;
|
||||
const y = handle.rect.y + handle.rect.height / 2;
|
||||
touch("DOWN", x, y);
|
||||
await setTimeout(600);
|
||||
touch("MOVE", x, y + 60);
|
||||
await setTimeout(200);
|
||||
touch("MOVE", x, y + 90);
|
||||
touch("MOVE", x, y + 60);
|
||||
ad("screenshot", resolve(artifacts, "dragging.png"));
|
||||
touch("UP", x, y + 60);
|
||||
await setTimeout(2000);
|
||||
ad("screenshot", resolve(artifacts, "dropped.png"));
|
||||
const swipeX = list.rect.x + list.rect.width / 2;
|
||||
touch("DOWN", swipeX, y + 80);
|
||||
for (let offset = 90; offset <= 500; offset += 10) {
|
||||
touch("MOVE", swipeX, y + offset);
|
||||
}
|
||||
const during = snapshot();
|
||||
writeFileSync(resolve(artifacts, "during.json"), JSON.stringify(during));
|
||||
ad("screenshot", resolve(artifacts, "during.png"));
|
||||
touch("UP", swipeX, y + 500);
|
||||
await setTimeout(1500);
|
||||
const after = snapshot();
|
||||
writeFileSync(resolve(artifacts, "after.json"), JSON.stringify(after));
|
||||
ad("screenshot", resolve(artifacts, "after.png"));
|
||||
const beforeSheet = nodes.find((node) => node.label === "Bottom sheet handle");
|
||||
const afterSheet = during.find((node) => node.label === "Bottom sheet handle");
|
||||
assert.ok(beforeSheet?.rect, "Usage must start as a sheet");
|
||||
assert.ok(
|
||||
!afterSheet || afterSheet.rect.y > beforeSheet.rect.y + 50,
|
||||
"A body swipe after a same-slot drop must move the sheet",
|
||||
);
|
||||
} finally {
|
||||
ad("close");
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Check native drop recordings against visible row labels (requires opencv-python-headless).
|
||||
|
||||
Supply a before screenshot and a JSON map of label names to [x, y, width, height].
|
||||
A recording must start in the original order and end in a different order. Once
|
||||
all labels reach their final slots, any frame returning to the original slots fails.
|
||||
Keep the pointer clear of the labels and use fixed fixture data.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--reference", required=True)
|
||||
parser.add_argument("--labels", required=True)
|
||||
parser.add_argument("--top", type=int, default=0)
|
||||
parser.add_argument("--bottom", type=int)
|
||||
parser.add_argument("videos", nargs="+")
|
||||
args = parser.parse_args()
|
||||
reference = cv2.imread(args.reference, cv2.IMREAD_GRAYSCALE)
|
||||
assert reference is not None, "Cannot read reference image"
|
||||
labels = json.loads(Path(args.labels).read_text())
|
||||
results = []
|
||||
for video in args.videos:
|
||||
capture = cv2.VideoCapture(video)
|
||||
frames = []
|
||||
while True:
|
||||
ok, image = capture.read()
|
||||
if not ok:
|
||||
break
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
positions = {}
|
||||
for label, (x, y, width, height) in labels.items():
|
||||
template = reference[y:y + height, x:x + width]
|
||||
matches = cv2.matchTemplate(gray[args.top:args.bottom, x:x + width], template, cv2.TM_CCOEFF_NORMED)
|
||||
_, confidence, _, point = cv2.minMaxLoc(matches)
|
||||
positions[label] = (point[1] + args.top, confidence)
|
||||
frames.append(positions)
|
||||
capture.release()
|
||||
assert frames, f"No frames in {video}"
|
||||
initial, final = frames[0], frames[-1]
|
||||
assert all(confidence >= 0.95 for _, confidence in initial.values()), f"Unrecognized initial labels: {video}"
|
||||
assert all(confidence >= 0.95 for _, confidence in final.values()), f"Unrecognized final labels: {video}"
|
||||
assert sorted(initial, key=lambda key: initial[key][0]) != sorted(final, key=lambda key: final[key][0]), f"Drop did not reorder: {video}"
|
||||
|
||||
def matches_slots(frame, target):
|
||||
return all(confidence >= 0.95 and abs(y - target[label][0]) <= 2
|
||||
for label, (y, confidence) in frame.items())
|
||||
|
||||
reached_final = False
|
||||
wrong = []
|
||||
for number, frame in enumerate(frames, start=1):
|
||||
reached_final = reached_final or matches_slots(frame, final)
|
||||
if reached_final and matches_slots(frame, initial):
|
||||
wrong.append(number)
|
||||
results.append({"recording": video, "oldOrderFrames": wrong, "count": len(wrong)})
|
||||
print(json.dumps(results, indent=2))
|
||||
raise SystemExit(1 if any(result["count"] for result in results) else 0)
|
||||
@@ -43,6 +43,7 @@ vi.mock("@gorhom/portal", () => ({
|
||||
|
||||
/** Every `waitFor` Gorhom received, in render order. Gorhom rebuilds its content pan per value. */
|
||||
const sheetWaitFor = vi.hoisted(() => [] as unknown[]);
|
||||
const sheetSimultaneousHandlers = vi.hoisted(() => [] as unknown[]);
|
||||
|
||||
vi.mock("@gorhom/bottom-sheet", async () => {
|
||||
const { Portal } = await import("@gorhom/portal");
|
||||
@@ -55,10 +56,17 @@ vi.mock("@gorhom/bottom-sheet", async () => {
|
||||
children,
|
||||
stackBehavior,
|
||||
waitFor,
|
||||
}: { children?: React.ReactNode; stackBehavior?: string; waitFor?: unknown },
|
||||
simultaneousHandlers,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
stackBehavior?: string;
|
||||
waitFor?: unknown;
|
||||
simultaneousHandlers?: unknown;
|
||||
},
|
||||
_ref,
|
||||
) => {
|
||||
sheetWaitFor.push(waitFor);
|
||||
sheetSimultaneousHandlers.push(simultaneousHandlers);
|
||||
return React.createElement(
|
||||
Portal,
|
||||
null,
|
||||
@@ -259,6 +267,7 @@ describe("IsolatedBottomSheetModal content drag", () => {
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
sheetWaitFor.length = 0;
|
||||
sheetSimultaneousHandlers.length = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -275,11 +284,19 @@ describe("IsolatedBottomSheetModal content drag", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderSheet(content: React.ReactNode) {
|
||||
function renderSheet(
|
||||
content: React.ReactNode,
|
||||
handlers: Pick<
|
||||
React.ComponentProps<typeof IsolatedBottomSheetModal>,
|
||||
"waitFor" | "simultaneousHandlers"
|
||||
> = {},
|
||||
) {
|
||||
act(() => {
|
||||
root.render(
|
||||
<>
|
||||
<IsolatedBottomSheetModal contextBridge={null}>{content}</IsolatedBottomSheetModal>
|
||||
<IsolatedBottomSheetModal contextBridge={null} {...handlers}>
|
||||
{content}
|
||||
</IsolatedBottomSheetModal>
|
||||
<PortalHostProbe />
|
||||
</>,
|
||||
);
|
||||
@@ -291,9 +308,106 @@ describe("IsolatedBottomSheetModal content drag", () => {
|
||||
expect(sheetWaitFor.at(-1)).toBeUndefined();
|
||||
|
||||
renderSheet(<ContentDrag />);
|
||||
expect(sheetWaitFor.at(-1)).toBe(dragGesture);
|
||||
expect(sheetSimultaneousHandlers.at(-1)).toEqual([dragGesture]);
|
||||
expect(sheetWaitFor.at(-1)).toEqual([dragGesture]);
|
||||
|
||||
renderSheet(null);
|
||||
expect(sheetWaitFor.at(-1)).toBeUndefined();
|
||||
expect(sheetSimultaneousHandlers.at(-1)).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"preserves supplied relations when a content drag mounts and unmounts (array=%s)",
|
||||
(array) => {
|
||||
const waiting = React.createRef<React.ComponentType>();
|
||||
const simultaneous = React.createRef<React.ComponentType>();
|
||||
const handlers = {
|
||||
waitFor: array ? [waiting] : waiting,
|
||||
simultaneousHandlers: array ? [simultaneous] : simultaneous,
|
||||
};
|
||||
renderSheet(<ContentDrag />, handlers);
|
||||
expect(sheetWaitFor.at(-1)).toEqual([waiting, dragGesture]);
|
||||
expect(sheetSimultaneousHandlers.at(-1)).toEqual([simultaneous, dragGesture]);
|
||||
renderSheet(null, handlers);
|
||||
expect(sheetWaitFor.at(-1)).toBe(handlers.waitFor);
|
||||
expect(sheetSimultaneousHandlers.at(-1)).toBe(handlers.simultaneousHandlers);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Load the real vendored implementations at runtime without typechecking Gorhom's entire source
|
||||
// tree (its hooks barrel reaches upstream files that don't compile under the app's TS settings).
|
||||
const gorhomSource = "@gorhom/bottom-sheet/src";
|
||||
const { default: BottomSheetDraggableView } = await import(
|
||||
`${gorhomSource}/components/bottomSheetDraggableView/BottomSheetDraggableView`
|
||||
);
|
||||
const { BottomSheetInternalContext } = await import(`${gorhomSource}/contexts/internal`);
|
||||
const { BottomSheetDraggableContext, BottomSheetGestureHandlersContext } = await import(
|
||||
`${gorhomSource}/contexts/gesture`
|
||||
);
|
||||
const rnghSource = "react-native-gesture-handler/src";
|
||||
const { extractGestureRelations } = await import(
|
||||
`${rnghSource}/handlers/gestures/GestureDetector/utils`
|
||||
);
|
||||
import { Gesture, type GestureType } from "react-native-gesture-handler";
|
||||
|
||||
const noGestureWork = () => {};
|
||||
const gestureHandlerContext = {
|
||||
contentPanGestureHandler: {
|
||||
handleOnStart: noGestureWork,
|
||||
handleOnChange: noGestureWork,
|
||||
handleOnEnd: noGestureWork,
|
||||
handleOnFinalize: noGestureWork,
|
||||
},
|
||||
handlePanGestureHandler: {
|
||||
handleOnStart: noGestureWork,
|
||||
handleOnChange: noGestureWork,
|
||||
handleOnEnd: noGestureWork,
|
||||
handleOnFinalize: noGestureWork,
|
||||
},
|
||||
};
|
||||
function dragInternalContext(relations: SheetContentDragGesture | SheetContentDragGesture[]) {
|
||||
return {
|
||||
enableContentPanningGesture: true,
|
||||
waitFor: relations,
|
||||
simultaneousHandlers: relations,
|
||||
};
|
||||
}
|
||||
|
||||
describe("bottom sheet native drag relations", () => {
|
||||
it.each([false, true])(
|
||||
"resolves both relations through the real content pan (array=%s)",
|
||||
(array) => {
|
||||
const container = document.createElement("div");
|
||||
const root = createRoot(container);
|
||||
const child = Gesture.Pan();
|
||||
child.handlerTag = 42;
|
||||
const ref = { current: child };
|
||||
const relations = array ? [ref] : ref;
|
||||
let resolved: ReturnType<typeof extractGestureRelations> | undefined;
|
||||
const internal = dragInternalContext(relations);
|
||||
function Probe() {
|
||||
const pan = useContext(BottomSheetDraggableContext) as GestureType | null;
|
||||
if (pan) resolved = extractGestureRelations(pan);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
act(() =>
|
||||
root.render(
|
||||
<BottomSheetInternalContext.Provider value={internal}>
|
||||
<BottomSheetGestureHandlersContext.Provider value={gestureHandlerContext}>
|
||||
<BottomSheetDraggableView>
|
||||
<Probe />
|
||||
</BottomSheetDraggableView>
|
||||
</BottomSheetGestureHandlersContext.Provider>
|
||||
</BottomSheetInternalContext.Provider>,
|
||||
),
|
||||
);
|
||||
expect(resolved?.waitFor).toEqual([42]);
|
||||
expect(resolved?.simultaneousHandlers).toEqual([42]);
|
||||
} finally {
|
||||
act(() => root.unmount());
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -105,6 +105,8 @@ export const IsolatedBottomSheetModal = forwardRef<
|
||||
contextBridge,
|
||||
backdropOpacity,
|
||||
enableDismissOnClose = true,
|
||||
waitFor,
|
||||
simultaneousHandlers,
|
||||
...bottomSheetProps
|
||||
} = props;
|
||||
// State, not a ref the child fills in: Gorhom builds its pan once per `waitFor`, and RNGH reads
|
||||
@@ -115,6 +117,19 @@ export const IsolatedBottomSheetModal = forwardRef<
|
||||
setContentDragGesture(gesture);
|
||||
return () => setContentDragGesture((current) => (current === gesture ? undefined : current));
|
||||
}, []);
|
||||
// RNGH Android's shouldBeCancelledByActiveHandler treats a waiting ancestor's raw ACTIVE
|
||||
// state as ownership and cancels its child at activation. Simultaneity lets the child activate;
|
||||
// waitFor still keeps the sheet stationary until the child fails.
|
||||
const contentDragHandlers = useMemo(() => {
|
||||
if (!contentDragGesture) return { waitFor, simultaneousHandlers };
|
||||
return {
|
||||
waitFor: [...(waitFor ? [waitFor].flat() : []), contentDragGesture],
|
||||
simultaneousHandlers: [
|
||||
...(simultaneousHandlers ? [simultaneousHandlers].flat() : []),
|
||||
contentDragGesture,
|
||||
],
|
||||
};
|
||||
}, [contentDragGesture, waitFor, simultaneousHandlers]);
|
||||
const renderBackdrop = useCallback(
|
||||
(backdropProps: BottomSheetBackdropProps) => (
|
||||
<SheetBackdrop
|
||||
@@ -132,7 +147,8 @@ export const IsolatedBottomSheetModal = forwardRef<
|
||||
enableDismissOnClose={enableDismissOnClose}
|
||||
stackBehavior={presentation}
|
||||
backdropComponent={backdropOpacity === undefined ? undefined : renderBackdrop}
|
||||
waitFor={contentDragGesture}
|
||||
waitFor={contentDragHandlers.waitFor}
|
||||
simultaneousHandlers={contentDragHandlers.simultaneousHandlers}
|
||||
>
|
||||
<BottomSheetScope attachContentDrag={attachContentDrag}>
|
||||
{contextBridge ? contextBridge(children) : children}
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetDraggableView/BottomSheetDraggableView.js b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
index f3d8f55..72d4322 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
@@ -55,10 +55,10 @@ const BottomSheetDraggableViewComponent = ({
|
||||
const draggableGesture = (0, _react.useMemo)(() => {
|
||||
let gesture = _reactNativeGestureHandler.Gesture.Pan().enabled(enableContentPanningGesture).shouldCancelWhenOutside(false).runOnJS(false).onStart(contentPanGestureHandler.handleOnStart).onChange(contentPanGestureHandler.handleOnChange).onEnd(contentPanGestureHandler.handleOnEnd).onFinalize(contentPanGestureHandler.handleOnFinalize);
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
if (simultaneousHandlers) {
|
||||
- gesture = gesture.simultaneousWithExternalGesture(simultaneousHandlers);
|
||||
+ gesture = gesture.simultaneousWithExternalGesture(...simultaneousHandlers);
|
||||
}
|
||||
if (activeOffsetX) {
|
||||
gesture = gesture.activeOffsetX(activeOffsetX);
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetHandle/BottomSheetHandleContainer.js b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
index ee9e898..1500886 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
@@ -59,10 +59,10 @@ function BottomSheetHandleContainerComponent({
|
||||
const panGesture = (0, _react.useMemo)(() => {
|
||||
let gesture = _reactNativeGestureHandler.Gesture.Pan().enabled(enableHandlePanningGesture).shouldCancelWhenOutside(false).runOnJS(false).onStart(handlePanGestureHandler.handleOnStart).onChange(handlePanGestureHandler.handleOnChange).onEnd(handlePanGestureHandler.handleOnEnd).onFinalize(handlePanGestureHandler.handleOnFinalize);
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
if (simultaneousHandlers) {
|
||||
- gesture = gesture.simultaneousWithExternalGesture(simultaneousHandlers);
|
||||
+ gesture = gesture.simultaneousWithExternalGesture(...simultaneousHandlers);
|
||||
}
|
||||
if (activeOffsetX) {
|
||||
gesture = gesture.activeOffsetX(activeOffsetX);
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetModal/BottomSheetModal.js b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetModal/BottomSheetModal.js
|
||||
index ee51e76..5fe8f75 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/bottomSheetModal/BottomSheetModal.js
|
||||
@@ -70,6 +104,40 @@ index e44ad86..96555ef 100644
|
||||
var _useScrollEventsHandlersDefault = require("./hooks/useScrollEventsHandlersDefault");
|
||||
var _useGestureEventsHandlersDefault = require("./hooks/useGestureEventsHandlersDefault");
|
||||
var _useBottomSheetGestureHandlers = require("./hooks/useBottomSheetGestureHandlers");
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetDraggableView/BottomSheetDraggableView.js b/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
index 9efadd2..942b78a 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetDraggableView/BottomSheetDraggableView.js
|
||||
@@ -49,10 +49,10 @@ const BottomSheetDraggableViewComponent = ({
|
||||
const draggableGesture = useMemo(() => {
|
||||
let gesture = Gesture.Pan().enabled(enableContentPanningGesture).shouldCancelWhenOutside(false).runOnJS(false).onStart(contentPanGestureHandler.handleOnStart).onChange(contentPanGestureHandler.handleOnChange).onEnd(contentPanGestureHandler.handleOnEnd).onFinalize(contentPanGestureHandler.handleOnFinalize);
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
if (simultaneousHandlers) {
|
||||
- gesture = gesture.simultaneousWithExternalGesture(simultaneousHandlers);
|
||||
+ gesture = gesture.simultaneousWithExternalGesture(...simultaneousHandlers);
|
||||
}
|
||||
if (activeOffsetX) {
|
||||
gesture = gesture.activeOffsetX(activeOffsetX);
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetHandle/BottomSheetHandleContainer.js b/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
index 3d1eafe..bf8e366 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetHandle/BottomSheetHandleContainer.js
|
||||
@@ -53,10 +53,10 @@ function BottomSheetHandleContainerComponent({
|
||||
const panGesture = useMemo(() => {
|
||||
let gesture = Gesture.Pan().enabled(enableHandlePanningGesture).shouldCancelWhenOutside(false).runOnJS(false).onStart(handlePanGestureHandler.handleOnStart).onChange(handlePanGestureHandler.handleOnChange).onEnd(handlePanGestureHandler.handleOnEnd).onFinalize(handlePanGestureHandler.handleOnFinalize);
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
if (simultaneousHandlers) {
|
||||
- gesture = gesture.simultaneousWithExternalGesture(simultaneousHandlers);
|
||||
+ gesture = gesture.simultaneousWithExternalGesture(...simultaneousHandlers);
|
||||
}
|
||||
if (activeOffsetX) {
|
||||
gesture = gesture.activeOffsetX(activeOffsetX);
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetModal/BottomSheetModal.js b/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetModal/BottomSheetModal.js
|
||||
index 94fabdb..d870aa7 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/module/components/bottomSheetModal/BottomSheetModal.js
|
||||
@@ -128,6 +196,19 @@ index 8259637..eb057fe 100644
|
||||
export { useScrollEventsHandlersDefault } from './hooks/useScrollEventsHandlersDefault';
|
||||
export { useGestureEventsHandlersDefault } from './hooks/useGestureEventsHandlersDefault';
|
||||
export { useBottomSheetGestureHandlers } from './hooks/useBottomSheetGestureHandlers';
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/typescript/components/bottomSheet/types.d.ts b/node_modules/@gorhom/bottom-sheet/lib/typescript/components/bottomSheet/types.d.ts
|
||||
index 6b23056..8d92aae 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/typescript/components/bottomSheet/types.d.ts
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/lib/typescript/components/bottomSheet/types.d.ts
|
||||
@@ -353,6 +353,6 @@ export type BottomSheetGestureProps = {
|
||||
|
||||
simultaneousHandlers: Parameters<
|
||||
PanGesture['simultaneousWithExternalGesture']
|
||||
- >[0];
|
||||
- waitFor: Parameters<PanGesture['requireExternalGestureToFail']>[0];
|
||||
+ >[0] | Parameters<PanGesture['simultaneousWithExternalGesture']>;
|
||||
+ waitFor: Parameters<PanGesture['requireExternalGestureToFail']>[0] | Parameters<PanGesture['requireExternalGestureToFail']>;
|
||||
};
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/lib/typescript/index.d.ts b/node_modules/@gorhom/bottom-sheet/lib/typescript/index.d.ts
|
||||
index ffc6caa..550f1b3 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/lib/typescript/index.d.ts
|
||||
@@ -141,6 +222,57 @@ index ffc6caa..550f1b3 100644
|
||||
export { useScrollEventsHandlersDefault } from './hooks/useScrollEventsHandlersDefault';
|
||||
export { useGestureEventsHandlersDefault } from './hooks/useGestureEventsHandlersDefault';
|
||||
export { useBottomSheetGestureHandlers } from './hooks/useBottomSheetGestureHandlers';
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/types.d.ts b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/types.d.ts
|
||||
index 6b23056..8d92aae 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/types.d.ts
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/types.d.ts
|
||||
@@ -353,6 +353,6 @@ export type BottomSheetGestureProps = {
|
||||
|
||||
simultaneousHandlers: Parameters<
|
||||
PanGesture['simultaneousWithExternalGesture']
|
||||
- >[0];
|
||||
- waitFor: Parameters<PanGesture['requireExternalGestureToFail']>[0];
|
||||
+ >[0] | Parameters<PanGesture['simultaneousWithExternalGesture']>;
|
||||
+ waitFor: Parameters<PanGesture['requireExternalGestureToFail']>[0] | Parameters<PanGesture['requireExternalGestureToFail']>;
|
||||
};
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
|
||||
index 923e430..eeef8cf 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx
|
||||
@@ -65,12 +65,12 @@ const BottomSheetDraggableViewComponent = ({
|
||||
.onFinalize(contentPanGestureHandler.handleOnFinalize);
|
||||
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
|
||||
if (simultaneousHandlers) {
|
||||
gesture = gesture.simultaneousWithExternalGesture(
|
||||
- simultaneousHandlers as never
|
||||
+ ...(simultaneousHandlers as Parameters<typeof gesture.simultaneousWithExternalGesture>)
|
||||
);
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx
|
||||
index 0223843..4ca56e5 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx
|
||||
+++ b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx
|
||||
@@ -67,12 +67,12 @@ function BottomSheetHandleContainerComponent({
|
||||
.onFinalize(handlePanGestureHandler.handleOnFinalize);
|
||||
|
||||
if (waitFor) {
|
||||
- gesture = gesture.requireExternalGestureToFail(waitFor);
|
||||
+ gesture = gesture.requireExternalGestureToFail(...(Array.isArray(waitFor) ? waitFor : [waitFor]));
|
||||
}
|
||||
|
||||
if (simultaneousHandlers) {
|
||||
gesture = gesture.simultaneousWithExternalGesture(
|
||||
- simultaneousHandlers as never
|
||||
+ ...(simultaneousHandlers as Parameters<typeof gesture.simultaneousWithExternalGesture>)
|
||||
);
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetModal/BottomSheetModal.tsx b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetModal/BottomSheetModal.tsx
|
||||
index 2954b3f..936300b 100644
|
||||
--- a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheetModal/BottomSheetModal.tsx
|
||||
|
||||
@@ -181,7 +181,7 @@ index e4e861a..f485139 100644
|
||||
|
||||
declare global {
|
||||
diff --git a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
index 7c88afc..a97cb80 100644
|
||||
index 7c88afc..05618ec 100644
|
||||
--- a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
+++ b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
@@ -16,9 +16,11 @@ import {
|
||||
@@ -227,7 +227,37 @@ index 7c88afc..a97cb80 100644
|
||||
const [layoutAnimationDisabled, setLayoutAnimationDisabled] = useState(
|
||||
!propsRef.current.enableLayoutAnimationExperimental
|
||||
);
|
||||
@@ -144,19 +163,88 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -113,10 +132,26 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
|
||||
const dataRef = useRef(props.data);
|
||||
const dataHasChanged =
|
||||
- dataRef.current.map(keyExtractor).join("") !==
|
||||
- props.data.map(keyExtractor).join("");
|
||||
- dataRef.current = props.data;
|
||||
+ dataRef.current.map(keyExtractor).join("") !== props.data.map(keyExtractor).join("");
|
||||
+ // Sheet portals can deliver reordered data after the drop callback commits.
|
||||
+ // Keep the held transforms until that data arrives, then renew moved hosts
|
||||
+ // in the same render as their new slots.
|
||||
if (dataHasChanged) {
|
||||
+ const wasDragging = activeKey !== null;
|
||||
+ const previousIndices = new Map(
|
||||
+ dataRef.current.map((item, index) => [keyExtractor(item, index), index]),
|
||||
+ );
|
||||
+ setTransformHostKeys((previous) => {
|
||||
+ const next = new Map<string, number>();
|
||||
+ props.data.forEach((item, index) => {
|
||||
+ const key = keyExtractor(item, index);
|
||||
+ const generation = previous.get(key) ?? 0;
|
||||
+ next.set(key, generation + (wasDragging && previousIndices.get(key) !== index ? 1 : 0));
|
||||
+ });
|
||||
+ return next;
|
||||
+ });
|
||||
+ dataRef.current = props.data;
|
||||
+ setActiveKey(null);
|
||||
// When data changes make sure `activeKey` is nulled out in the same render pass
|
||||
activeKey = null;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
@@ -144,19 +179,88 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
});
|
||||
}, [props.data, keyExtractor, keyToIndexRef]);
|
||||
|
||||
@@ -323,7 +353,7 @@ index 7c88afc..a97cb80 100644
|
||||
setActiveKey(activeKey);
|
||||
onDragBegin?.(index);
|
||||
}
|
||||
@@ -218,14 +306,46 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -218,8 +322,25 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
props.onRelease?.(index);
|
||||
});
|
||||
|
||||
@@ -350,28 +380,18 @@ index 7c88afc..a97cb80 100644
|
||||
const { onDragEnd, data } = props;
|
||||
|
||||
const newData = [...data];
|
||||
if (from !== to) {
|
||||
newData.splice(from, 1);
|
||||
newData.splice(to, 0, data[from]);
|
||||
+ const movedKeys = data
|
||||
+ .slice(Math.min(from, to), Math.max(from, to) + 1)
|
||||
+ .map((item, offset) => keyExtractor(item, Math.min(from, to) + offset));
|
||||
+ setTransformHostKeys((previous) => {
|
||||
+ const next = new Map<string, number>();
|
||||
+ data.forEach((item, index) => {
|
||||
+ const itemKey = keyExtractor(item, index);
|
||||
+ const hostKey = previous.get(itemKey);
|
||||
+ if (hostKey !== undefined) next.set(itemKey, hostKey);
|
||||
+ });
|
||||
+ for (const movedKey of movedKeys) {
|
||||
+ next.set(movedKey, (previous.get(movedKey) ?? 0) + 1);
|
||||
+ }
|
||||
+ return next;
|
||||
+ });
|
||||
}
|
||||
@@ -230,34 +351,15 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
|
||||
onDragEnd?.({ from, to, data: newData });
|
||||
@@ -238,26 +358,6 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
|
||||
- setActiveKey(null);
|
||||
- }
|
||||
+ // A drop in place doesn't change data, so no data-change reset will clear the armed index.
|
||||
+ if (from === to) reset();
|
||||
+ },
|
||||
);
|
||||
|
||||
const onPlaceholderIndexChange = useStableCallback((index: number) => {
|
||||
props.onPlaceholderIndexChange?.(index);
|
||||
});
|
||||
|
||||
@@ -398,7 +418,7 @@ index 7c88afc..a97cb80 100644
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
return spacerIndexAnim.value;
|
||||
@@ -271,11 +371,26 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -271,11 +373,26 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
);
|
||||
|
||||
const gestureDisabled = useSharedValue(false);
|
||||
@@ -426,7 +446,7 @@ index 7c88afc..a97cb80 100644
|
||||
panGestureState.value = evt.state;
|
||||
})
|
||||
.onUpdate((evt) => {
|
||||
@@ -286,8 +401,15 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -286,8 +403,15 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
: evt.translationY;
|
||||
touchTranslate.value = translation;
|
||||
})
|
||||
@@ -443,7 +463,7 @@ index 7c88afc..a97cb80 100644
|
||||
// Set touch val to current translate val
|
||||
isTouchActiveNative.value = false;
|
||||
const translation = horizontalAnim.value
|
||||
@@ -299,6 +421,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -299,6 +423,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
|
||||
// Only call onDragEnd if actually dragging a cell
|
||||
if (activeIndexAnim.value === -1 || disabled.value) return;
|
||||
@@ -451,7 +471,7 @@ index 7c88afc..a97cb80 100644
|
||||
disabled.value = true;
|
||||
runOnJS(onRelease)(activeIndexAnim.value);
|
||||
const springTo = placeholderOffset.value - activeCellOffset.value;
|
||||
@@ -309,22 +432,85 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -309,22 +434,85 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
runOnJS(onDragEnd)({
|
||||
from: activeIndexAnim.value,
|
||||
to: spacerIndexAnim.value,
|
||||
@@ -540,7 +560,7 @@ index 7c88afc..a97cb80 100644
|
||||
const activeOffset = [-activationDistanceProp, activationDistanceProp];
|
||||
if (props.horizontal) {
|
||||
panGesture.activeOffsetX(activeOffset);
|
||||
@@ -332,6 +518,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -332,6 +520,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
panGesture.activeOffsetY(activeOffset);
|
||||
}
|
||||
}
|
||||
@@ -562,7 +582,7 @@ index 7c88afc..a97cb80 100644
|
||||
|
||||
const onScroll = useStableCallback((scrollOffset: number) => {
|
||||
props.onScrollOffsetChange?.(scrollOffset);
|
||||
@@ -372,9 +573,11 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -372,9 +575,11 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
keyExtractor={keyExtractor}
|
||||
horizontal={!!props.horizontal}
|
||||
layoutAnimationDisabled={layoutAnimationDisabled}
|
||||
|
||||
Reference in New Issue
Block a user