endDrag(evt.pointerId)}
- onPointerCancel={(evt) => endDrag(evt.pointerId)}
- onLostPointerCapture={(evt) => endDrag(evt.pointerId)}
- >
+
{children}
{indicatorTop !== null && (
{
- if (!activeGesture || activeGesture.pointerId === pointerId) {
- activeGesture = { kind, pointerId };
- return true;
- }
- return activeGesture.kind === kind && activeGesture.pointerId === pointerId;
-};
-
-export const getActiveMobileGesture = (pointerId: number): MobileGestureKind | null => {
- if (!activeGesture || activeGesture.pointerId !== pointerId) return null;
- return activeGesture.kind;
-};
-
-export const clearMobileGesture = (pointerId: number) => {
- if (activeGesture?.pointerId === pointerId) {
- activeGesture = null;
- }
-};
diff --git a/overlay/src/app/components/mobile/mobileSwipeReplyBridge.ts b/overlay/src/app/components/mobile/mobileSwipeReplyBridge.ts
new file mode 100644
index 0000000..851a776
--- /dev/null
+++ b/overlay/src/app/components/mobile/mobileSwipeReplyBridge.ts
@@ -0,0 +1,15 @@
+import { Room } from 'matrix-js-sdk';
+import { Editor } from 'slate';
+import { IReplyDraft } from '../../state/room/roomInputDrafts';
+
+export type MobileSwipeReplyBridge = {
+ room: Room;
+ editor: Editor;
+ setReplyDraft: (draft: IReplyDraft | undefined) => void;
+ layerEl: HTMLElement | null;
+ setIndicator: (top: number | null, active: boolean) => void;
+};
+
+export const mobileSwipeReplyBridgeRef: { current: MobileSwipeReplyBridge | null } = {
+ current: null,
+};
diff --git a/overlay/src/app/components/mobile/useWindowPointerDrag.ts b/overlay/src/app/components/mobile/useWindowPointerDrag.ts
deleted file mode 100644
index 8130f8b..0000000
--- a/overlay/src/app/components/mobile/useWindowPointerDrag.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useEffect } from 'react';
-
-type PointerLikeEvent = {
- pointerId: number;
- clientX: number;
- clientY: number;
- preventDefault?: () => void;
-};
-
-type UseWindowPointerDragOptions = {
- enabled: boolean;
- isActivePointer: (pointerId: number) => boolean;
- onMove: (evt: PointerLikeEvent) => void;
- onEnd: (pointerId: number) => void;
-};
-
-export const useWindowPointerDrag = ({
- enabled,
- isActivePointer,
- onMove,
- onEnd,
-}: UseWindowPointerDragOptions) => {
- useEffect(() => {
- if (!enabled) return;
-
- const handlePointerMove = (evt: PointerEvent) => {
- if (!isActivePointer(evt.pointerId)) return;
- onMove(evt);
- };
-
- const handlePointerEnd = (evt: PointerEvent) => {
- if (!isActivePointer(evt.pointerId)) return;
- onEnd(evt.pointerId);
- };
-
- window.addEventListener('pointermove', handlePointerMove, true);
- window.addEventListener('pointerup', handlePointerEnd, true);
- window.addEventListener('pointercancel', handlePointerEnd, true);
-
- return () => {
- window.removeEventListener('pointermove', handlePointerMove, true);
- window.removeEventListener('pointerup', handlePointerEnd, true);
- window.removeEventListener('pointercancel', handlePointerEnd, true);
- };
- }, [enabled, isActivePointer, onEnd, onMove]);
-};
diff --git a/overlay/src/index.css b/overlay/src/index.css
index 7aa5f36..8a1b86b 100644
--- a/overlay/src/index.css
+++ b/overlay/src/index.css
@@ -184,6 +184,23 @@ body.mocha-theme {
animation: fadeSlideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
+/* Disable route slide animation on compact/mobile — conflicts with swipe gestures */
+@media (max-width: 750px) {
+ [data-route-transition='true'] {
+ animation: none;
+ }
+}
+
+html.mobile-gesture-lock,
+html.mobile-gesture-lock body {
+ overscroll-behavior: none;
+}
+
+html.mobile-gesture-lock [data-timeline-scroll] {
+ overflow: hidden !important;
+ touch-action: none;
+}
+
/* Twilight theme enhanced transitions */
.twilight-theme [data-route-transition="true"] {
animation: fadeSlideIn 0.35s cubic-bezier(0.4, 0, 0.2, 1);