@droppii-org/chat-mobile 0.2.107 → 0.2.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/components/ThreadCard/ThreadCard.js +2 -0
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/fileMessage/index.js +9 -2
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +6 -1
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/context/TimelinePressGuardContext.js +14 -0
- package/lib/module/context/TimelinePressGuardContext.js.map +1 -0
- package/lib/module/hooks/useConversationList.js +4 -0
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useDismissKeyboardFirst.js +19 -0
- package/lib/module/hooks/useDismissKeyboardFirst.js.map +1 -0
- package/lib/module/hooks/useExpandableText.js +6 -1
- package/lib/module/hooks/useExpandableText.js.map +1 -1
- package/lib/module/hooks/useGuardedPress.js +19 -0
- package/lib/module/hooks/useGuardedPress.js.map +1 -0
- package/lib/module/screens/chat-detail/ChatComposer.js +89 -498
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +38 -20
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +113 -76
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/components/ChatComposerAccessorySlot.js +47 -0
- package/lib/module/screens/chat-detail/components/ChatComposerAccessorySlot.js.map +1 -0
- package/lib/module/screens/chat-detail/components/ChatComposerInputArea.js +149 -0
- package/lib/module/screens/chat-detail/components/ChatComposerInputArea.js.map +1 -0
- package/lib/module/screens/chat-detail/components/ChatComposerStatusBanner.js +104 -0
- package/lib/module/screens/chat-detail/components/ChatComposerStatusBanner.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +0 -17
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +16 -2
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useComposerBotMenu.js +122 -0
- package/lib/module/screens/chat-detail/hooks/useComposerBotMenu.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelActions.js +46 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelActions.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelDismiss.js +75 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelDismiss.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanels.js +136 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanels.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerSend.js +74 -0
- package/lib/module/screens/chat-detail/hooks/useComposerSend.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +2 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/types.js +5 -0
- package/lib/module/screens/chat-detail/types.js.map +1 -1
- package/lib/module/store/message.js +14 -0
- package/lib/module/store/message.js.map +1 -1
- package/lib/module/utils/composerPanelDismissGesture.js +52 -0
- package/lib/module/utils/composerPanelDismissGesture.js.map +1 -0
- package/lib/module/utils/perf-marker.js +44 -0
- package/lib/module/utils/perf-marker.js.map +1 -0
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/context/TimelinePressGuardContext.d.ts +3 -0
- package/lib/typescript/src/context/TimelinePressGuardContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useDismissKeyboardFirst.d.ts +8 -0
- package/lib/typescript/src/hooks/useDismissKeyboardFirst.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useExpandableText.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useGuardedPress.d.ts +9 -0
- package/lib/typescript/src/hooks/useGuardedPress.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +7 -3
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +5 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerAccessorySlot.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerAccessorySlot.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerInputArea.d.ts +30 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerInputArea.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerStatusBanner.d.ts +19 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerStatusBanner.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +0 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +7 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerBotMenu.d.ts +7 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerBotMenu.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelActions.d.ts +17 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelActions.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelDismiss.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelDismiss.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanels.d.ts +25 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanels.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerSend.d.ts +16 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerSend.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +8 -0
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts.map +1 -1
- package/lib/typescript/src/utils/composerPanelDismissGesture.d.ts +33 -0
- package/lib/typescript/src/utils/composerPanelDismissGesture.d.ts.map +1 -0
- package/lib/typescript/src/utils/perf-marker.d.ts +8 -0
- package/lib/typescript/src/utils/perf-marker.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/ThreadCard/ThreadCard.tsx +2 -0
- package/src/components/messages/fileMessage/index.tsx +17 -3
- package/src/components/messages/quotedMessage/index.tsx +5 -1
- package/src/context/TimelinePressGuardContext.tsx +15 -0
- package/src/hooks/useConversationList.ts +2 -0
- package/src/hooks/useDismissKeyboardFirst.ts +19 -0
- package/src/hooks/useExpandableText.ts +5 -1
- package/src/hooks/useGuardedPress.ts +20 -0
- package/src/screens/chat-detail/ChatComposer.tsx +105 -631
- package/src/screens/chat-detail/ChatDetail.tsx +38 -20
- package/src/screens/chat-detail/ChatListLegend.tsx +122 -71
- package/src/screens/chat-detail/components/ChatComposerAccessorySlot.tsx +61 -0
- package/src/screens/chat-detail/components/ChatComposerInputArea.tsx +198 -0
- package/src/screens/chat-detail/components/ChatComposerStatusBanner.tsx +116 -0
- package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +0 -29
- package/src/screens/chat-detail/hooks/useChatComposerState.ts +25 -2
- package/src/screens/chat-detail/hooks/useComposerBotMenu.tsx +140 -0
- package/src/screens/chat-detail/hooks/useComposerPanelActions.ts +57 -0
- package/src/screens/chat-detail/hooks/useComposerPanelDismiss.ts +111 -0
- package/src/screens/chat-detail/hooks/useComposerPanels.ts +197 -0
- package/src/screens/chat-detail/hooks/useComposerSend.ts +98 -0
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +2 -0
- package/src/screens/chat-detail/types.ts +9 -0
- package/src/store/message.ts +14 -0
- package/src/utils/composerPanelDismissGesture.ts +72 -0
- package/src/utils/perf-marker.ts +39 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-touch state for dismissing the composer's attachment/sticker panel by
|
|
3
|
+
* tapping the chat timeline: a touch that began while the panel was open and
|
|
4
|
+
* ended without dragging is a dismiss tap. Drag detection is explicit — a
|
|
5
|
+
* movement threshold or the list's scroll-begin — since the touch is only
|
|
6
|
+
* observed (touch events), never claimed as the JS responder.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DISMISS_DRAG_THRESHOLD_PX = 10;
|
|
9
|
+
export interface DismissTouch {
|
|
10
|
+
panelOpenAtStart: boolean;
|
|
11
|
+
dragged: boolean;
|
|
12
|
+
startX: number;
|
|
13
|
+
startY: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const IDLE_DISMISS_TOUCH: DismissTouch;
|
|
16
|
+
export declare const startDismissTouch: (panelOpen: boolean, x: number, y: number) => DismissTouch;
|
|
17
|
+
export declare const moveDismissTouch: (touch: DismissTouch, x: number, y: number) => DismissTouch;
|
|
18
|
+
export declare const markDismissTouchDragged: (touch: DismissTouch) => DismissTouch;
|
|
19
|
+
/** Whether releasing this touch should close the panel. */
|
|
20
|
+
export declare const shouldDismissOnRelease: (touch: DismissTouch) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* How long after the finger lifts a press still belongs to the dismissing
|
|
23
|
+
* touch. RNGH Tap and JS onPress callbacks land right after touchEnd; a later
|
|
24
|
+
* press (e.g. a screen-reader activation, which sends no touch events) must
|
|
25
|
+
* not be swallowed by a stale flag.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SWALLOW_AFTER_RELEASE_MS = 500;
|
|
28
|
+
/**
|
|
29
|
+
* Whether a timeline press must be swallowed. `releasedAt` is null while the
|
|
30
|
+
* dismissing touch is still down.
|
|
31
|
+
*/
|
|
32
|
+
export declare const isPressSwallowed: (swallow: boolean, releasedAt: number | null, now: number) => boolean;
|
|
33
|
+
//# sourceMappingURL=composerPanelDismissGesture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composerPanelDismissGesture.d.ts","sourceRoot":"","sources":["../../../../src/utils/composerPanelDismissGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,EAAE,YAKhC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,OAAO,EAClB,GAAG,MAAM,EACT,GAAG,MAAM,KACR,YAKD,CAAC;AAEH,eAAO,MAAM,gBAAgB,GAC3B,OAAO,YAAY,EACnB,GAAG,MAAM,EACT,GAAG,MAAM,KACR,YAMF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,OAAO,YAAY,KAAG,YACT,CAAC;AAEtD,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,GAAI,OAAO,YAAY,KAAG,OACnB,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,OAAO,EAChB,YAAY,MAAM,GAAG,IAAI,EACzB,KAAK,MAAM,KACV,OAEmE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const enablePerfMarkers: (on?: boolean) => void;
|
|
2
|
+
export declare const perfMark: (name: string, data?: Record<string, unknown>) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Counts render-function invocations of `component`, including renders React later discards.
|
|
5
|
+
* Counts are batched and emitted as a delta every ~500 ms, so logging never happens per render.
|
|
6
|
+
*/
|
|
7
|
+
export declare const perfCountRender: (component: string) => void;
|
|
8
|
+
//# sourceMappingURL=perf-marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-marker.d.ts","sourceRoot":"","sources":["../../../../src/utils/perf-marker.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,iBAAiB,GAAI,YAAS,SAE1C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAGpE,CAAC;AASF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,SAKhD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@droppii-org/chat-mobile",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.108",
|
|
4
4
|
"description": "Droppii chat mobile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"typecheck": "tsc -p tsconfig.build.json 2>&1 | grep -E 'src/.*error' || echo 'Typecheck passed (external dependency warnings ignored)'",
|
|
64
64
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
65
65
|
"test": "jest",
|
|
66
|
+
"perf": "node performance/scripts/cli.mjs",
|
|
66
67
|
"release": "release-it --no-git --ci --no-increment",
|
|
67
68
|
"web": "vite",
|
|
68
69
|
"build:web": "vite build",
|
|
@@ -25,6 +25,7 @@ import { MuteThread } from './MuteThread';
|
|
|
25
25
|
import { PinThread } from './PinThread';
|
|
26
26
|
import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
|
|
27
27
|
import isEmpty from 'lodash/isEmpty';
|
|
28
|
+
import { perfCountRender } from '../../utils/perf-marker';
|
|
28
29
|
|
|
29
30
|
interface ThreadCardProps {
|
|
30
31
|
item: string;
|
|
@@ -35,6 +36,7 @@ interface ThreadCardProps {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
|
|
39
|
+
perfCountRender('ThreadCard');
|
|
38
40
|
const conversation = useConversation(item);
|
|
39
41
|
|
|
40
42
|
const {
|
|
@@ -7,6 +7,9 @@ import { getTailCornerStyle } from '../bubbleTail.utils';
|
|
|
7
7
|
import { BubbleTailRow } from '../BubbleTailRow';
|
|
8
8
|
import type { BaseLegendMessageProps } from '../types';
|
|
9
9
|
import { useFileDownload } from '../../../hooks/useFileDownload';
|
|
10
|
+
import { useGuardedPress } from '../../../hooks/useGuardedPress';
|
|
11
|
+
import { useDismissKeyboardFirst } from '../../../hooks/useDismissKeyboardFirst';
|
|
12
|
+
import { useTimelinePressGuard } from '../../../context/TimelinePressGuardContext';
|
|
10
13
|
import {
|
|
11
14
|
getFileIcon,
|
|
12
15
|
getMimeType,
|
|
@@ -22,9 +25,20 @@ export const FileMessage = memo(
|
|
|
22
25
|
(message.fileElem as any)?.url ||
|
|
23
26
|
'';
|
|
24
27
|
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const {
|
|
29
|
+
state,
|
|
30
|
+
handleView: handleViewRaw,
|
|
31
|
+
handleDownload: handleDownloadRaw,
|
|
32
|
+
} = useFileDownload(fileName, url);
|
|
33
|
+
// Skipped for the tap that dismisses a composer panel (timeline only).
|
|
34
|
+
const shouldSwallowPress = useTimelinePressGuard();
|
|
35
|
+
const handleView = useGuardedPress(
|
|
36
|
+
useDismissKeyboardFirst(handleViewRaw),
|
|
37
|
+
shouldSwallowPress
|
|
38
|
+
);
|
|
39
|
+
const handleDownload = useGuardedPress(
|
|
40
|
+
handleDownloadRaw,
|
|
41
|
+
shouldSwallowPress
|
|
28
42
|
);
|
|
29
43
|
|
|
30
44
|
const mimeType = useMemo(() => getMimeType(fileName), [fileName]);
|
|
@@ -25,6 +25,7 @@ import { renderLinkifiedText } from '../LinkifiedText';
|
|
|
25
25
|
import { ExpandableMessageText } from '../ExpandableMessageText';
|
|
26
26
|
import { useUserStore } from '../../../store';
|
|
27
27
|
import { useChatDetailContext } from '../../../context';
|
|
28
|
+
import { useTimelinePressGuard } from '../../../context/TimelinePressGuardContext';
|
|
28
29
|
import { ChatEvent } from '../../../types/events';
|
|
29
30
|
|
|
30
31
|
interface QuotedMessageProps extends BaseLegendMessageProps {
|
|
@@ -85,11 +86,14 @@ const QuotedMessage = memo((props: QuotedMessageProps) => {
|
|
|
85
86
|
return getFileIcon(mimeType);
|
|
86
87
|
}, [quotedMessage]);
|
|
87
88
|
|
|
89
|
+
// Skipped for the tap that dismisses a composer panel (timeline only).
|
|
90
|
+
const shouldSwallowPress = useTimelinePressGuard();
|
|
88
91
|
const handlePressQuotedMessage = useCallback(() => {
|
|
92
|
+
if (shouldSwallowPress()) return;
|
|
89
93
|
DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
|
|
90
94
|
clientMessageId: quotedMessage?.clientMsgID || '',
|
|
91
95
|
});
|
|
92
|
-
}, [quotedMessage]);
|
|
96
|
+
}, [quotedMessage, shouldSwallowPress]);
|
|
93
97
|
|
|
94
98
|
return (
|
|
95
99
|
<>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
const neverSwallow = () => false;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Timeline-only press guard. ChatListLegend provides the tap-outside panel
|
|
7
|
+
* dismiss's `shouldSwallowPress` (useComposerPanelDismiss); every other
|
|
8
|
+
* surface (Pin Management, toolbox overlay) gets the no-op default.
|
|
9
|
+
*/
|
|
10
|
+
const TimelinePressGuardContext = createContext<() => boolean>(neverSwallow);
|
|
11
|
+
|
|
12
|
+
export const TimelinePressGuardProvider = TimelinePressGuardContext.Provider;
|
|
13
|
+
|
|
14
|
+
export const useTimelinePressGuard = () =>
|
|
15
|
+
useContext(TimelinePressGuardContext);
|
|
@@ -4,6 +4,7 @@ import OpenIMSDK from '@droppii/openim-rn-client-sdk';
|
|
|
4
4
|
import { conversationQueryKeys } from './query-keys';
|
|
5
5
|
import { useConversationStore } from '../store';
|
|
6
6
|
import type { DChatApplicationType } from '../types/chat';
|
|
7
|
+
import { perfMark } from '../utils/perf-marker';
|
|
7
8
|
|
|
8
9
|
type Params = {
|
|
9
10
|
applicationType: DChatApplicationType;
|
|
@@ -33,6 +34,7 @@ export function useConversationList({
|
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
useConversationStore.getState().updateConversations(conversations);
|
|
37
|
+
perfMark('conversation_list_loaded', { count: conversations.length });
|
|
36
38
|
return conversations;
|
|
37
39
|
},
|
|
38
40
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Keyboard } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a press that leaves the timeline (media viewer, link, file viewer) so
|
|
6
|
+
* the keyboard is dismissed first. The timeline uses
|
|
7
|
+
* `keyboardShouldPersistTaps="handled"`, so the keyboard would otherwise stay
|
|
8
|
+
* up over the screen the press opens. An absent handler stays absent.
|
|
9
|
+
*/
|
|
10
|
+
export const useDismissKeyboardFirst = <A extends unknown[]>(
|
|
11
|
+
handler: ((...args: A) => unknown) | undefined
|
|
12
|
+
): ((...args: A) => void) | undefined =>
|
|
13
|
+
useMemo(() => {
|
|
14
|
+
if (!handler) return handler;
|
|
15
|
+
return (...args: A) => {
|
|
16
|
+
Keyboard.dismiss();
|
|
17
|
+
handler(...args);
|
|
18
|
+
};
|
|
19
|
+
}, [handler]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
import { useWindowDimensions, type TextLayoutEvent } from 'react-native';
|
|
3
3
|
import { useExpandedMessageStore } from '../store/expandedMessage';
|
|
4
|
+
import { useTimelinePressGuard } from '../context/TimelinePressGuardContext';
|
|
4
5
|
import {
|
|
5
6
|
COLLAPSED_LINES,
|
|
6
7
|
expandedKey,
|
|
@@ -125,9 +126,12 @@ export function useExpandableText(
|
|
|
125
126
|
[fit, key, settle]
|
|
126
127
|
);
|
|
127
128
|
|
|
129
|
+
// Skipped for the tap that dismisses a composer panel (timeline only).
|
|
130
|
+
const shouldSwallowPress = useTimelinePressGuard();
|
|
128
131
|
const expand = useCallback(() => {
|
|
132
|
+
if (shouldSwallowPress()) return;
|
|
129
133
|
expandInStore(surface, messageId);
|
|
130
|
-
}, [expandInStore, surface, messageId]);
|
|
134
|
+
}, [expandInStore, surface, messageId, shouldSwallowPress]);
|
|
131
135
|
|
|
132
136
|
return {
|
|
133
137
|
mode,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a timeline press handler so it is skipped while `shouldSwallow()` is
|
|
5
|
+
* true — the tap-outside panel dismiss (useComposerPanelDismiss) swallows the
|
|
6
|
+
* press of a touch that began with a composer panel open. An absent handler
|
|
7
|
+
* stays absent, so "not tappable" rows keep behaving as before. The wrapper's
|
|
8
|
+
* return value is dropped: press handlers are fire-and-forget.
|
|
9
|
+
*/
|
|
10
|
+
export const useGuardedPress = <A extends unknown[]>(
|
|
11
|
+
handler: ((...args: A) => unknown) | undefined,
|
|
12
|
+
shouldSwallow: (() => boolean) | undefined
|
|
13
|
+
): ((...args: A) => void) | undefined =>
|
|
14
|
+
useMemo(() => {
|
|
15
|
+
if (!handler || !shouldSwallow) return handler;
|
|
16
|
+
return (...args: A) => {
|
|
17
|
+
if (shouldSwallow()) return;
|
|
18
|
+
handler(...args);
|
|
19
|
+
};
|
|
20
|
+
}, [handler, shouldSwallow]);
|