mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
* Upgrade to Expo 57 and unify Explorer across layouts * feat(app): retain Explorer across layout and appearance changes Keep one workspace Explorer host and destination-aware navigation so folding, theme updates and compact sheets preserve the current panel. Scope pull request discovery and timeline demand to active surfaces. * fix(app): keep Explorer resize handles stretched through the dock The shared frame moved the divider into an absolute column, leaving its horizontal handle without height. Restore the row so pointer drags reach it, preserving the existing resize and performance contract. * fix(app): keep Explorer drag widths out of the shared stylesheet Carry the existing inline sizing policy into the shared frame so pointer previews do not accumulate CSS rules. * fix(test): align isolated test runtimes with Expo 57 Hoisted React consumers need the matching DOM renderer at the repository root. Automated Metro processes must not install native DevTools into disposable homes, and development toast overlays must not cover compact actions. Keep Explorer journeys on the shared host and synchronize setup fixtures on daemon output rather than a one-second file-marker window. A delayed real child start reproduces the readiness failure on every platform. * fix(test): await retained shell layout after composer resize The gesture host now stays mounted across breakpoints, so its presence no longer identifies compact layout. Report the current layout on the retained host and synchronize composer journeys with that state. * fix(app): preserve browser interactions in retained workspace hosts Initially disabled gesture hosts still registered pointer and wheel listeners, while hover cards used window positions inside a workspace-local sheet portal. Respect initial gesture enablement and position cards relative to their measured floating host. Keep developer-toast dismissal independent of pointer input and replacement toast visibility so modal, hover, and focus checks retain their input state. * fix(test): share developer toast handling with Electron browser runs The standalone Electron browser runner bypassed the browser fixture, allowing developer errors to cover Settings. Use the same non-pointer dismissal in both runners while retaining their console logs.
272 lines
13 KiB
Diff
272 lines
13 KiB
Diff
diff --git a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
|
|
index 8d78aea..02e3ea6 100644
|
|
--- a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
|
|
+++ b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
|
|
@@ -2,6 +2,7 @@ package com.swmansion.gesturehandler.core
|
|
|
|
import android.graphics.Matrix
|
|
import android.graphics.PointF
|
|
+import android.util.SparseArray
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
@@ -28,6 +29,10 @@ class GestureHandlerOrchestrator(
|
|
private val awaitingHandlers = arrayListOf<GestureHandler>()
|
|
private val preparedHandlers = arrayListOf<GestureHandler>()
|
|
|
|
+ // Used by `cancelTouchesInInterceptedViews`.
|
|
+ private val viewsToCancel = arrayListOf<View>()
|
|
+ private val pointerDownPoints = SparseArray<PointF>()
|
|
+
|
|
// In `onHandlerStateChange` method we iterate through `awaitingHandlers`, but calling `tryActivate` may modify this list.
|
|
// To avoid `ConcurrentModificationException` we iterate through copy. There is one more problem though - if handler was
|
|
// removed from `awaitingHandlers`, it was still present in copy of original list. This hashset helps us identify which handlers
|
|
@@ -46,6 +51,7 @@ class GestureHandlerOrchestrator(
|
|
fun onTouchEvent(event: MotionEvent): Boolean {
|
|
isHandlingTouch = true
|
|
val action = event.actionMasked
|
|
+ trackPointerDownPoints(event)
|
|
if (action == MotionEvent.ACTION_DOWN ||
|
|
action == MotionEvent.ACTION_POINTER_DOWN ||
|
|
action == MotionEvent.ACTION_HOVER_MOVE
|
|
@@ -579,6 +585,107 @@ class GestureHandlerOrchestrator(
|
|
private fun shouldIgnoreSubtreeIfGestureHandlerRootView(view: View) =
|
|
view is RNGestureHandlerRootView && view != wrapperView && view.isRootViewEnabled()
|
|
|
|
+ private fun trackPointerDownPoints(event: MotionEvent) {
|
|
+ val index = event.actionIndex
|
|
+ when (event.actionMasked) {
|
|
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN ->
|
|
+ pointerDownPoints.put(event.getPointerId(index), PointF(event.getX(index), event.getY(index)))
|
|
+ MotionEvent.ACTION_POINTER_UP ->
|
|
+ pointerDownPoints.remove(event.getPointerId(index))
|
|
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
|
|
+ pointerDownPoints.clear()
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fun cancelTouchesInInterceptedViews(event: MotionEvent) {
|
|
+ viewsToCancel.clear()
|
|
+ for (i in 0 until pointerDownPoints.size()) {
|
|
+ val point = pointerDownPoints.valueAt(i)
|
|
+ tempCoords[0] = point.x
|
|
+ tempCoords[1] = point.y
|
|
+ collectViewsAtPoint(wrapperView, tempCoords, viewsToCancel)
|
|
+ }
|
|
+
|
|
+ if (viewsToCancel.isEmpty()) {
|
|
+ return
|
|
+ }
|
|
+
|
|
+ val activeHandlers = gestureHandlers.filter { it.isActive }
|
|
+ val cancelEvent = MotionEvent.obtain(event).apply { action = MotionEvent.ACTION_CANCEL }
|
|
+
|
|
+ for (view in viewsToCancel) {
|
|
+ if (view === wrapperView || isViewDrivenByActiveNativeGesture(view, activeHandlers)) {
|
|
+ continue
|
|
+ }
|
|
+ view.onTouchEvent(cancelEvent)
|
|
+ }
|
|
+
|
|
+ cancelEvent.recycle()
|
|
+ viewsToCancel.clear()
|
|
+ }
|
|
+
|
|
+ // Whether the view's touch is still owned by a NativeViewGestureHandler that survived arbitration.
|
|
+ // Only those are fed through `onTouchEvent`, so only those break if cancelled. Other handlers are
|
|
+ // orchestrator-driven and unaffected.
|
|
+ private fun isViewDrivenByActiveNativeGesture(view: View, activeHandlers: List<GestureHandler>) =
|
|
+ handlerRegistry.getHandlersForView(view)?.let { handlers ->
|
|
+ synchronized(handlers) {
|
|
+ handlers.any { nativeGestureSurvivesArbitration(it, activeHandlers) }
|
|
+ }
|
|
+ } ?: false
|
|
+
|
|
+ // A native handler survives arbitration if it is active, or it does not conflict with any active handler.
|
|
+ private fun nativeGestureSurvivesArbitration(handler: GestureHandler, activeHandlers: List<GestureHandler>) =
|
|
+ handler is NativeViewGestureHandler &&
|
|
+ (handler.isActive || activeHandlers.none { shouldHandlerBeCancelledBy(handler, it) })
|
|
+
|
|
+ // Collects the view path under the point (topmost child first, like touch dispatch), leaf to root.
|
|
+ private fun collectViewsAtPoint(view: View, coords: FloatArray, out: MutableList<View>): Boolean {
|
|
+ val pointerEvents = viewConfigHelper.getPointerEventsConfigForView(view)
|
|
+ if (pointerEvents == PointerEventsConfig.NONE) {
|
|
+ return false
|
|
+ }
|
|
+
|
|
+ var found = false
|
|
+ if (view is ViewGroup && pointerEvents != PointerEventsConfig.BOX_ONLY) {
|
|
+ for (i in view.childCount - 1 downTo 0) {
|
|
+ val child = viewConfigHelper.getChildInDrawingOrderAtIndex(view, i)
|
|
+ if (!canReceiveEvents(child)) {
|
|
+ continue
|
|
+ }
|
|
+ val childPoint = tempPoint
|
|
+ transformPointToChildViewCoords(coords[0], coords[1], view, child, childPoint)
|
|
+ if (isClipping(child) && !isTransformedTouchPointInView(childPoint.x, childPoint.y, child)) {
|
|
+ continue
|
|
+ }
|
|
+ val restoreX = coords[0]
|
|
+ val restoreY = coords[1]
|
|
+ coords[0] = childPoint.x
|
|
+ coords[1] = childPoint.y
|
|
+ found = collectViewsAtPoint(child, coords, out)
|
|
+ coords[0] = restoreX
|
|
+ coords[1] = restoreY
|
|
+
|
|
+ if (found) {
|
|
+ break
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // BOX_NONE views can't be the target themselves, only their children can.
|
|
+ val selfIsTarget = pointerEvents != PointerEventsConfig.BOX_NONE &&
|
|
+ isTransformedTouchPointInView(coords[0], coords[1], view)
|
|
+
|
|
+ if (found || selfIsTarget) {
|
|
+ // `out` may already contain this view when several pointers share part of their path.
|
|
+ if (!out.contains(view)) {
|
|
+ out.add(view)
|
|
+ }
|
|
+ return true
|
|
+ }
|
|
+ return false
|
|
+ }
|
|
+
|
|
private fun extractGestureHandlers(
|
|
viewGroup: ViewGroup,
|
|
coords: FloatArray,
|
|
diff --git a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
|
|
index 629c155..e1909ff 100644
|
|
--- a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
|
|
+++ b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
|
|
@@ -19,6 +19,7 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
|
|
private val jsGestureHandler: GestureHandler?
|
|
val rootView: ViewGroup
|
|
private var shouldIntercept = false
|
|
+ private var wasIntercepting = false
|
|
private var passingTouch = false
|
|
|
|
init {
|
|
@@ -118,6 +119,14 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
|
|
passingTouch = true
|
|
orchestrator!!.onTouchEvent(event)
|
|
passingTouch = false
|
|
+
|
|
+ // On the transition into interception, cancel the native views the pointers landed on - the
|
|
+ // framework's ACTION_CANCEL never reaches them since RNGH ignores `onInterceptTouchEvent`.
|
|
+ if (shouldIntercept && !wasIntercepting) {
|
|
+ orchestrator!!.cancelTouchesInInterceptedViews(ev)
|
|
+ }
|
|
+ wasIntercepting = shouldIntercept
|
|
+
|
|
return shouldIntercept
|
|
}
|
|
|
|
diff --git a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/GestureHandler.js b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/GestureHandler.js
|
|
index 96c5227..cbf1a88 100644
|
|
--- a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/GestureHandler.js
|
|
+++ b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/GestureHandler.js
|
|
@@ -58,7 +58,9 @@ class GestureHandler {
|
|
manager.setOnPointerMoveOver(this.onPointerMoveOver.bind(this));
|
|
manager.setOnPointerMoveOut(this.onPointerMoveOut.bind(this));
|
|
manager.setOnWheel(this.onWheel.bind(this));
|
|
- manager.registerListeners();
|
|
+ if (this.enabled) {
|
|
+ manager.registerListeners();
|
|
+ }
|
|
}
|
|
|
|
//
|
|
diff --git a/node_modules/react-native-gesture-handler/lib/commonjs/web/tools/PointerEventManager.js b/node_modules/react-native-gesture-handler/lib/commonjs/web/tools/PointerEventManager.js
|
|
index 76eb808..7c40526 100644
|
|
--- a/node_modules/react-native-gesture-handler/lib/commonjs/web/tools/PointerEventManager.js
|
|
+++ b/node_modules/react-native-gesture-handler/lib/commonjs/web/tools/PointerEventManager.js
|
|
@@ -58,7 +58,14 @@ class PointerEventManager extends _EventManager.default {
|
|
const adaptedEvent = this.mapEvent(event, _interfaces.EventTypes.UP);
|
|
const target = event.target;
|
|
if (!POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
|
|
- target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ try {
|
|
+ if (target.hasPointerCapture(adaptedEvent.pointerId)) {
|
|
+ target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ }
|
|
+ } catch {
|
|
+ // Pointer capture is implicitly released after pointerup/pointercancel.
|
|
+ // Some browsers can report stale capture state during gesture teardown.
|
|
+ }
|
|
}
|
|
this.markAsOutOfBounds(adaptedEvent.pointerId);
|
|
this.trackedPointers.delete(adaptedEvent.pointerId);
|
|
diff --git a/node_modules/react-native-gesture-handler/lib/module/web/handlers/GestureHandler.js b/node_modules/react-native-gesture-handler/lib/module/web/handlers/GestureHandler.js
|
|
index 0791c66..2f41c77 100644
|
|
--- a/node_modules/react-native-gesture-handler/lib/module/web/handlers/GestureHandler.js
|
|
+++ b/node_modules/react-native-gesture-handler/lib/module/web/handlers/GestureHandler.js
|
|
@@ -52,7 +52,9 @@ export default class GestureHandler {
|
|
manager.setOnPointerMoveOver(this.onPointerMoveOver.bind(this));
|
|
manager.setOnPointerMoveOut(this.onPointerMoveOut.bind(this));
|
|
manager.setOnWheel(this.onWheel.bind(this));
|
|
- manager.registerListeners();
|
|
+ if (this.enabled) {
|
|
+ manager.registerListeners();
|
|
+ }
|
|
}
|
|
|
|
//
|
|
diff --git a/node_modules/react-native-gesture-handler/lib/module/web/tools/PointerEventManager.js b/node_modules/react-native-gesture-handler/lib/module/web/tools/PointerEventManager.js
|
|
index 6ce3b8f..8fe28bc 100644
|
|
--- a/node_modules/react-native-gesture-handler/lib/module/web/tools/PointerEventManager.js
|
|
+++ b/node_modules/react-native-gesture-handler/lib/module/web/tools/PointerEventManager.js
|
|
@@ -53,7 +53,14 @@ export default class PointerEventManager extends EventManager {
|
|
const adaptedEvent = this.mapEvent(event, EventTypes.UP);
|
|
const target = event.target;
|
|
if (!POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
|
|
- target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ try {
|
|
+ if (target.hasPointerCapture(adaptedEvent.pointerId)) {
|
|
+ target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ }
|
|
+ } catch {
|
|
+ // Pointer capture is implicitly released after pointerup/pointercancel.
|
|
+ // Some browsers can report stale capture state during gesture teardown.
|
|
+ }
|
|
}
|
|
this.markAsOutOfBounds(adaptedEvent.pointerId);
|
|
this.trackedPointers.delete(adaptedEvent.pointerId);
|
|
diff --git a/node_modules/react-native-gesture-handler/src/web/handlers/GestureHandler.ts b/node_modules/react-native-gesture-handler/src/web/handlers/GestureHandler.ts
|
|
index ddb79a3..4e1274c 100644
|
|
--- a/node_modules/react-native-gesture-handler/src/web/handlers/GestureHandler.ts
|
|
+++ b/node_modules/react-native-gesture-handler/src/web/handlers/GestureHandler.ts
|
|
@@ -79,7 +79,9 @@ export default abstract class GestureHandler implements IGestureHandler {
|
|
manager.setOnPointerMoveOut(this.onPointerMoveOut.bind(this));
|
|
manager.setOnWheel(this.onWheel.bind(this));
|
|
|
|
- manager.registerListeners();
|
|
+ if (this.enabled) {
|
|
+ manager.registerListeners();
|
|
+ }
|
|
}
|
|
|
|
//
|
|
diff --git a/node_modules/react-native-gesture-handler/src/web/tools/PointerEventManager.ts b/node_modules/react-native-gesture-handler/src/web/tools/PointerEventManager.ts
|
|
index 18d3627..769b0fe 100644
|
|
--- a/node_modules/react-native-gesture-handler/src/web/tools/PointerEventManager.ts
|
|
+++ b/node_modules/react-native-gesture-handler/src/web/tools/PointerEventManager.ts
|
|
@@ -67,7 +67,14 @@ export default class PointerEventManager extends EventManager<HTMLElement> {
|
|
const target = event.target as HTMLElement;
|
|
|
|
if (!POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
|
|
- target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ try {
|
|
+ if (target.hasPointerCapture(adaptedEvent.pointerId)) {
|
|
+ target.releasePointerCapture(adaptedEvent.pointerId);
|
|
+ }
|
|
+ } catch {
|
|
+ // Pointer capture is implicitly released after pointerup/pointercancel.
|
|
+ // Some browsers can report stale capture state during gesture teardown.
|
|
+ }
|
|
}
|
|
|
|
this.markAsOutOfBounds(adaptedEvent.pointerId);
|