stream-chat-react-native-core 9.0.0-beta.21 → 9.0.0-beta.23
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/commonjs/components/Message/hooks/useMessageActionHandlers.js +5 -1
- package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
- package/lib/commonjs/components/MessageInput/MessageComposer.js +4 -9
- package/lib/commonjs/components/MessageInput/MessageComposer.js.map +1 -1
- package/lib/commonjs/components/MessageInput/MessageComposerLeadingView.js +3 -4
- package/lib/commonjs/components/MessageInput/MessageComposerLeadingView.js.map +1 -1
- package/lib/commonjs/components/MessageInput/MessageInputHeaderView.js +8 -6
- package/lib/commonjs/components/MessageInput/MessageInputHeaderView.js.map +1 -1
- package/lib/commonjs/components/MessageInput/MessageInputTrailingView.js +3 -4
- package/lib/commonjs/components/MessageInput/MessageInputTrailingView.js.map +1 -1
- package/lib/commonjs/components/MessageInput/components/OutputButtons/index.js +11 -9
- package/lib/commonjs/components/MessageInput/components/OutputButtons/index.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageFlashList.js +3 -2
- package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +4 -4
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/hooks/index.js +22 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useAfterKeyboardOpenCallback.js +48 -0
- package/lib/commonjs/hooks/useAfterKeyboardOpenCallback.js.map +1 -0
- package/lib/commonjs/hooks/usePortalSettledCallback.js +41 -0
- package/lib/commonjs/hooks/usePortalSettledCallback.js.map +1 -0
- package/lib/commonjs/state-store/message-overlay-store.js +2 -7
- package/lib/commonjs/state-store/message-overlay-store.js.map +1 -1
- package/lib/commonjs/utils/transitions.js +13 -0
- package/lib/commonjs/utils/transitions.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Message/hooks/useMessageActionHandlers.js +5 -1
- package/lib/module/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
- package/lib/module/components/MessageInput/MessageComposer.js +4 -9
- package/lib/module/components/MessageInput/MessageComposer.js.map +1 -1
- package/lib/module/components/MessageInput/MessageComposerLeadingView.js +3 -4
- package/lib/module/components/MessageInput/MessageComposerLeadingView.js.map +1 -1
- package/lib/module/components/MessageInput/MessageInputHeaderView.js +8 -6
- package/lib/module/components/MessageInput/MessageInputHeaderView.js.map +1 -1
- package/lib/module/components/MessageInput/MessageInputTrailingView.js +3 -4
- package/lib/module/components/MessageInput/MessageInputTrailingView.js.map +1 -1
- package/lib/module/components/MessageInput/components/OutputButtons/index.js +11 -9
- package/lib/module/components/MessageInput/components/OutputButtons/index.js.map +1 -1
- package/lib/module/components/MessageList/MessageFlashList.js +3 -2
- package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +4 -4
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/hooks/index.js +22 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useAfterKeyboardOpenCallback.js +48 -0
- package/lib/module/hooks/useAfterKeyboardOpenCallback.js.map +1 -0
- package/lib/module/hooks/usePortalSettledCallback.js +41 -0
- package/lib/module/hooks/usePortalSettledCallback.js.map +1 -0
- package/lib/module/state-store/message-overlay-store.js +2 -7
- package/lib/module/state-store/message-overlay-store.js.map +1 -1
- package/lib/module/utils/transitions.js +13 -0
- package/lib/module/utils/transitions.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Message/hooks/useMessageActionHandlers.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/MessageComposer.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/MessageComposerLeadingView.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/MessageInputHeaderView.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/MessageInputTrailingView.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/components/OutputButtons/index.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +2 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useAfterKeyboardOpenCallback.d.ts +9 -0
- package/lib/typescript/hooks/useAfterKeyboardOpenCallback.d.ts.map +1 -0
- package/lib/typescript/hooks/usePortalSettledCallback.d.ts +32 -0
- package/lib/typescript/hooks/usePortalSettledCallback.d.ts.map +1 -0
- package/lib/typescript/state-store/message-overlay-store.d.ts.map +1 -1
- package/lib/typescript/utils/transitions.d.ts +9 -0
- package/lib/typescript/utils/transitions.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/offline-support/offline-feature.js +7 -4
- package/src/components/Message/hooks/useMessageActionHandlers.ts +12 -2
- package/src/components/MessageInput/MessageComposer.tsx +5 -11
- package/src/components/MessageInput/MessageComposerLeadingView.tsx +3 -2
- package/src/components/MessageInput/MessageInputHeaderView.tsx +5 -4
- package/src/components/MessageInput/MessageInputTrailingView.tsx +3 -2
- package/src/components/MessageInput/components/OutputButtons/index.tsx +10 -9
- package/src/components/MessageList/MessageFlashList.tsx +3 -2
- package/src/components/MessageList/MessageList.tsx +4 -5
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAfterKeyboardOpenCallback.ts +62 -0
- package/src/hooks/usePortalSettledCallback.ts +78 -0
- package/src/state-store/message-overlay-store.ts +1 -7
- package/src/utils/transitions.ts +9 -0
- package/src/version.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A utility hook that returns a stable callback which focuses the message input
|
|
3
|
+
* and invokes the callback once the keyboard is open.
|
|
4
|
+
*
|
|
5
|
+
* @param callback - callback we want to run once the keyboard is ready
|
|
6
|
+
* @returns A stable callback that will wait for the keyboard to be open before executing.
|
|
7
|
+
*/
|
|
8
|
+
export declare const useAfterKeyboardOpenCallback: <T extends unknown[]>(callback: (...args: T) => void) => import("./useStableCallback").StableCallback<T, void>;
|
|
9
|
+
//# sourceMappingURL=useAfterKeyboardOpenCallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAfterKeyboardOpenCallback.d.ts","sourceRoot":"","sources":["../../../src/hooks/useAfterKeyboardOpenCallback.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,CAAC,SAAS,OAAO,EAAE,EAC9D,UAAU,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,0DA2C/B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a stable callback that is safe to run after a `PortalWhileClosingView`
|
|
3
|
+
* has settled back into its original tree.
|
|
4
|
+
*
|
|
5
|
+
* Some followup actions are sensitive to that handoff window. If they run
|
|
6
|
+
* while a view is still being returned from a portal host to its in place host,
|
|
7
|
+
* they can target a node that is about to be reattached. On Android, that is
|
|
8
|
+
* especially noticeable with focus sensitive work, where the target can lose
|
|
9
|
+
* focus again mid keyboard animation.
|
|
10
|
+
*
|
|
11
|
+
* Two frames are intentional here:
|
|
12
|
+
* - frame 1 lets the portal retarget and React commit the component tree
|
|
13
|
+
* - frame 2 lets the native view hierarchy settle in its final host
|
|
14
|
+
*
|
|
15
|
+
* iOS does not currently need this settle window for this flow.
|
|
16
|
+
*
|
|
17
|
+
* A good example is the message composer edit action: after closing the message
|
|
18
|
+
* overlay, we wait for the portal handoff to settle before focusing the input
|
|
19
|
+
* and opening the keyboard. Doing this prematurely will result in the keyboard
|
|
20
|
+
* being immediately closed.
|
|
21
|
+
*
|
|
22
|
+
* Another good example would be having a button wrapped in a `PortalWhileClosingView`,
|
|
23
|
+
* that possibly renders (or morphs into) something when pressed. Handling `onPress`
|
|
24
|
+
* prematurely here may lead to the morphed button rendering into a completely different
|
|
25
|
+
* part of the UI hierarchy, causing unknown behaviour. This hook prevents that from
|
|
26
|
+
* happening.
|
|
27
|
+
*
|
|
28
|
+
* @param callback - callback we want to invoke once the portal handoff has settled
|
|
29
|
+
* @returns A stable callback gated behind the portal settle window.
|
|
30
|
+
*/
|
|
31
|
+
export declare const usePortalSettledCallback: <T extends unknown[]>(callback: (...args: T) => void) => import("./useStableCallback").StableCallback<T, void>;
|
|
32
|
+
//# sourceMappingURL=usePortalSettledCallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePortalSettledCallback.d.ts","sourceRoot":"","sources":["../../../src/hooks/usePortalSettledCallback.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,SAAS,OAAO,EAAE,EAAE,UAAU,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,0DAgB3F,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-overlay-store.d.ts","sourceRoot":"","sources":["../../../src/state-store/message-overlay-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,KAAK,YAAY,GAAG;IAClB,0BAA0B,EAAE,MAAM,EAAE,CAAC;IACrC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAmBF,KAAK,4BAA4B,GAAG;IAClC,yBAAyB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC/B,CAAC;AAIF,eAAO,MAAM,oCAAoC,GAAI,YAAY,4BAA4B,eAO5F,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,IAAI,SAEhD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,IAAI,SAExC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,SAAS,IAAI,SAE9C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,8BAAyB,SAElE,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,iBAAiB,GAAG,MAAM,SAS7D,CAAC;AAEF,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"message-overlay-store.d.ts","sourceRoot":"","sources":["../../../src/state-store/message-overlay-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,KAAK,YAAY,GAAG;IAClB,0BAA0B,EAAE,MAAM,EAAE,CAAC;IACrC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAmBF,KAAK,4BAA4B,GAAG;IAClC,yBAAyB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC/B,CAAC;AAIF,eAAO,MAAM,oCAAoC,GAAI,YAAY,4BAA4B,eAO5F,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,IAAI,SAEhD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,IAAI,SAExC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,SAAS,IAAI,SAE9C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,8BAAyB,SAElE,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,iBAAiB,GAAG,MAAM,SAS7D,CAAC;AAEF,eAAO,MAAM,YAAY,YAMxB,CAAC;AAIF,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,SAOvE,CAAC;AAEF,eAAO,MAAM,oBAAoB,YAMhC,CAAC;AAEF,eAAO,MAAM,YAAY,0BAA6C,CAAC;AAKvE,eAAO,MAAM,uCAAuC,cACiB,CAAC;AAuCtE,eAAO,MAAM,sBAAsB,GAAI,UAAU,MAAM,EAAE,IAAI,MAAM,EAAE,QAAQ,IAAI,SAkBhF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,UAAU,MAAM,EAAE,IAAI,MAAM,SAkBpE,CAAC;AAuBF,eAAO,MAAM,oBAAoB;;;;CAEhC,CAAC;AAUF,eAAO,MAAM,mBAAmB,eAE/B,CAAC;AAEF,eAAO,MAAM,kCAAkC,gBAE9C,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAAI,UAAU,MAAM,EAAE,IAAI,MAAM,YAY5E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,WAAW,MAAM,EAAE,EAAE,iBAAc,SAyBhF,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,kDAWnC,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAAI,IAAI,MAAM;;;CAQ5C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,WAAW,YAAY;;CAA2B,CAAC;AAEpF,eAAO,MAAM,cAAc,eAI1B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FadeIn, FadeOut, LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanimated';
|
|
2
|
+
export declare const transitions: {
|
|
3
|
+
readonly fadeIn200: FadeIn;
|
|
4
|
+
readonly fadeOut200: FadeOut;
|
|
5
|
+
readonly layout200: LinearTransition;
|
|
6
|
+
readonly zoomIn200: ZoomIn;
|
|
7
|
+
readonly zoomOut200: ZoomOut;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=transitions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.d.ts","sourceRoot":"","sources":["../../../src/utils/transitions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAE7F,eAAO,MAAM,WAAW;;;;;;CAMd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat-react-native-core",
|
|
3
3
|
"description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
|
|
4
|
-
"version": "9.0.0-beta.
|
|
4
|
+
"version": "9.0.0-beta.23",
|
|
5
5
|
"author": {
|
|
6
6
|
"company": "Stream.io Inc",
|
|
7
7
|
"name": "Stream.io Inc"
|
|
@@ -338,10 +338,13 @@ export const Generic = () => {
|
|
|
338
338
|
act(() => dispatchConnectionChangedEvent(chatClient));
|
|
339
339
|
await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true));
|
|
340
340
|
|
|
341
|
-
await waitFor(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
await waitFor(
|
|
342
|
+
async () => {
|
|
343
|
+
expect(screen.getByTestId('channel-list')).toBeTruthy();
|
|
344
|
+
await expectAllChannelsWithStateToBeInDB(screen.queryAllByLabelText);
|
|
345
|
+
},
|
|
346
|
+
{ timeout: 5000 },
|
|
347
|
+
);
|
|
345
348
|
});
|
|
346
349
|
|
|
347
350
|
it('should fetch channels from the db correctly even if they are empty', async () => {
|
|
@@ -12,10 +12,20 @@ import type { MessageContextValue } from '../../../contexts/messageContext/Messa
|
|
|
12
12
|
import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';
|
|
13
13
|
|
|
14
14
|
import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useAfterKeyboardOpenCallback,
|
|
17
|
+
usePortalSettledCallback,
|
|
18
|
+
useStableCallback,
|
|
19
|
+
} from '../../../hooks';
|
|
16
20
|
import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage';
|
|
17
21
|
import { NativeHandlers } from '../../../native';
|
|
18
22
|
|
|
23
|
+
const useWithPortalKeyboardSafety = <T extends unknown[]>(callback: (...args: T) => void) => {
|
|
24
|
+
const callbackAfterKeyboardOpen = useAfterKeyboardOpenCallback(callback);
|
|
25
|
+
|
|
26
|
+
return usePortalSettledCallback(callbackAfterKeyboardOpen);
|
|
27
|
+
};
|
|
28
|
+
|
|
19
29
|
export const useMessageActionHandlers = ({
|
|
20
30
|
channel,
|
|
21
31
|
client,
|
|
@@ -114,7 +124,7 @@ export const useMessageActionHandlers = ({
|
|
|
114
124
|
}
|
|
115
125
|
});
|
|
116
126
|
|
|
117
|
-
const handleEditMessage =
|
|
127
|
+
const handleEditMessage = useWithPortalKeyboardSafety(() => {
|
|
118
128
|
setEditingState(message);
|
|
119
129
|
});
|
|
120
130
|
|
|
@@ -5,7 +5,6 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
|
5
5
|
import Animated, {
|
|
6
6
|
Extrapolation,
|
|
7
7
|
interpolate,
|
|
8
|
-
LinearTransition,
|
|
9
8
|
useAnimatedStyle,
|
|
10
9
|
useSharedValue,
|
|
11
10
|
} from 'react-native-reanimated';
|
|
@@ -54,6 +53,7 @@ import { useStateStore } from '../../hooks/useStateStore';
|
|
|
54
53
|
import { AudioRecorderManagerState } from '../../state-store/audio-recorder-manager';
|
|
55
54
|
import { MessageInputHeightState } from '../../state-store/message-input-height-store';
|
|
56
55
|
import { primitives } from '../../theme';
|
|
56
|
+
import { transitions } from '../../utils/transitions';
|
|
57
57
|
import { type TextInputOverrideComponent } from '../AutoCompleteInput/AutoCompleteInput';
|
|
58
58
|
import { CreatePoll } from '../Poll/CreatePollContent';
|
|
59
59
|
import { PortalWhileClosingView } from '../UIComponents/PortalWhileClosingView';
|
|
@@ -219,7 +219,6 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => {
|
|
|
219
219
|
closePollCreationDialog,
|
|
220
220
|
CreatePollContent,
|
|
221
221
|
createPollOptionGap,
|
|
222
|
-
editing,
|
|
223
222
|
InputView,
|
|
224
223
|
MessageComposerLeadingView,
|
|
225
224
|
MessageComposerTrailingView,
|
|
@@ -272,12 +271,6 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => {
|
|
|
272
271
|
[closeAttachmentPicker],
|
|
273
272
|
);
|
|
274
273
|
|
|
275
|
-
useEffect(() => {
|
|
276
|
-
if (editing && inputBoxRef.current) {
|
|
277
|
-
inputBoxRef.current.focus();
|
|
278
|
-
}
|
|
279
|
-
}, [editing, inputBoxRef]);
|
|
280
|
-
|
|
281
274
|
/**
|
|
282
275
|
* Effect to get the draft data for legacy thread composer and set it to message composer.
|
|
283
276
|
* TODO: This can be removed once we remove legacy thread composer.
|
|
@@ -383,7 +376,7 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => {
|
|
|
383
376
|
]
|
|
384
377
|
: null
|
|
385
378
|
}
|
|
386
|
-
layout={
|
|
379
|
+
layout={transitions.layout200}
|
|
387
380
|
>
|
|
388
381
|
<PortalWhileClosingView portalHostName='overlay-composer' portalName='message-composer'>
|
|
389
382
|
<View
|
|
@@ -419,7 +412,7 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => {
|
|
|
419
412
|
<View style={[styles.container, container]}>
|
|
420
413
|
<MessageComposerLeadingView />
|
|
421
414
|
<Animated.View
|
|
422
|
-
layout={
|
|
415
|
+
layout={transitions.layout200}
|
|
423
416
|
style={[
|
|
424
417
|
styles.inputBoxWrapper,
|
|
425
418
|
messageInputFloating ? [styles.shadow, inputFloatingContainer] : null,
|
|
@@ -438,7 +431,7 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => {
|
|
|
438
431
|
|
|
439
432
|
<Animated.View
|
|
440
433
|
style={[styles.inputContainer, inputContainer]}
|
|
441
|
-
layout={
|
|
434
|
+
layout={transitions.layout200}
|
|
442
435
|
>
|
|
443
436
|
{!isRecordingStateIdle ? (
|
|
444
437
|
<AudioRecorder slideToCancelStyle={slideToCancelAnimatedStyle} />
|
|
@@ -746,6 +739,7 @@ export const MessageComposer = (props: MessageComposerProps) => {
|
|
|
746
739
|
closePollCreationDialog,
|
|
747
740
|
compressImageQuality,
|
|
748
741
|
CreatePollContent,
|
|
742
|
+
// TODO: probably not needed anymore, please check
|
|
749
743
|
editing,
|
|
750
744
|
Input,
|
|
751
745
|
InputView,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import Animated
|
|
4
|
+
import Animated from 'react-native-reanimated';
|
|
5
5
|
|
|
6
6
|
import { InputButtons } from './components/InputButtons';
|
|
7
7
|
import { idleRecordingStateSelector } from './utils/audioRecorderSelectors';
|
|
@@ -9,6 +9,7 @@ import { idleRecordingStateSelector } from './utils/audioRecorderSelectors';
|
|
|
9
9
|
import { useMessageInputContext } from '../../contexts/messageInputContext/MessageInputContext';
|
|
10
10
|
import { useTheme } from '../../contexts/themeContext/ThemeContext';
|
|
11
11
|
import { useStateStore } from '../../hooks/useStateStore';
|
|
12
|
+
import { transitions } from '../../utils/transitions';
|
|
12
13
|
|
|
13
14
|
export const MessageComposerLeadingView = () => {
|
|
14
15
|
const {
|
|
@@ -24,7 +25,7 @@ export const MessageComposerLeadingView = () => {
|
|
|
24
25
|
|
|
25
26
|
return isRecordingStateIdle ? (
|
|
26
27
|
<Animated.View
|
|
27
|
-
layout={
|
|
28
|
+
layout={transitions.layout200}
|
|
28
29
|
style={[
|
|
29
30
|
styles.inputButtonsContainer,
|
|
30
31
|
messageInputFloating ? styles.shadow : null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import Animated
|
|
4
|
+
import Animated from 'react-native-reanimated';
|
|
5
5
|
|
|
6
6
|
import { LinkPreviewList } from './components/LinkPreviewList';
|
|
7
7
|
import { useHasLinkPreviews } from './hooks/useLinkPreviews';
|
|
@@ -17,6 +17,7 @@ import { useMessagesContext } from '../../contexts/messagesContext/MessagesConte
|
|
|
17
17
|
import { useTheme } from '../../contexts/themeContext/ThemeContext';
|
|
18
18
|
import { useStateStore } from '../../hooks/useStateStore';
|
|
19
19
|
import { primitives } from '../../theme';
|
|
20
|
+
import { transitions } from '../../utils/transitions';
|
|
20
21
|
|
|
21
22
|
export const MessageInputHeaderView = () => {
|
|
22
23
|
const {
|
|
@@ -42,7 +43,7 @@ export const MessageInputHeaderView = () => {
|
|
|
42
43
|
|
|
43
44
|
return isRecordingStateIdle ? (
|
|
44
45
|
<Animated.View
|
|
45
|
-
layout={
|
|
46
|
+
layout={transitions.layout200}
|
|
46
47
|
style={[
|
|
47
48
|
styles.contentContainer,
|
|
48
49
|
{
|
|
@@ -55,7 +56,7 @@ export const MessageInputHeaderView = () => {
|
|
|
55
56
|
]}
|
|
56
57
|
>
|
|
57
58
|
{editing ? (
|
|
58
|
-
<Animated.View entering={
|
|
59
|
+
<Animated.View entering={transitions.fadeIn200} exiting={transitions.fadeOut200}>
|
|
59
60
|
<Reply
|
|
60
61
|
mode='edit'
|
|
61
62
|
onDismiss={clearEditingState}
|
|
@@ -64,7 +65,7 @@ export const MessageInputHeaderView = () => {
|
|
|
64
65
|
</Animated.View>
|
|
65
66
|
) : null}
|
|
66
67
|
{quotedMessage && !editing ? (
|
|
67
|
-
<Animated.View entering={
|
|
68
|
+
<Animated.View entering={transitions.fadeIn200} exiting={transitions.fadeOut200}>
|
|
68
69
|
<Reply onDismiss={editing ? undefined : onDismissReply} mode='reply' />
|
|
69
70
|
</Animated.View>
|
|
70
71
|
) : null}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import Animated
|
|
4
|
+
import Animated from 'react-native-reanimated';
|
|
5
5
|
|
|
6
6
|
import { OutputButtons } from './components/OutputButtons';
|
|
7
7
|
|
|
@@ -11,6 +11,7 @@ import { useMessageInputContext } from '../../contexts/messageInputContext/Messa
|
|
|
11
11
|
import { useTheme } from '../../contexts/themeContext/ThemeContext';
|
|
12
12
|
import { useStateStore } from '../../hooks/useStateStore';
|
|
13
13
|
import { primitives } from '../../theme';
|
|
14
|
+
import { transitions } from '../../utils/transitions';
|
|
14
15
|
|
|
15
16
|
export const MessageInputTrailingView = () => {
|
|
16
17
|
const {
|
|
@@ -25,7 +26,7 @@ export const MessageInputTrailingView = () => {
|
|
|
25
26
|
);
|
|
26
27
|
return (recordingStatus === 'idle' || recordingStatus === 'recording') && !micLocked ? (
|
|
27
28
|
<Animated.View
|
|
28
|
-
layout={
|
|
29
|
+
layout={transitions.layout200}
|
|
29
30
|
style={[styles.outputButtonsContainer, outputButtonsContainer]}
|
|
30
31
|
>
|
|
31
32
|
<OutputButtons />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
|
|
3
|
-
import Animated
|
|
3
|
+
import Animated from 'react-native-reanimated';
|
|
4
4
|
|
|
5
5
|
import { TextComposerState } from 'stream-chat';
|
|
6
6
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
useMessageInputContext,
|
|
22
22
|
} from '../../../../contexts/messageInputContext/MessageInputContext';
|
|
23
23
|
import { useStateStore } from '../../../../hooks/useStateStore';
|
|
24
|
+
import { transitions } from '../../../../utils/transitions';
|
|
24
25
|
import { AIStates, useAIState } from '../../../AITypingIndicatorView';
|
|
25
26
|
import { useIsCooldownActive } from '../../hooks/useIsCooldownActive';
|
|
26
27
|
|
|
@@ -88,8 +89,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) =
|
|
|
88
89
|
} else if (editing || command) {
|
|
89
90
|
return (
|
|
90
91
|
<Animated.View
|
|
91
|
-
entering={
|
|
92
|
-
exiting={
|
|
92
|
+
entering={transitions.zoomIn200}
|
|
93
|
+
exiting={transitions.zoomOut200}
|
|
93
94
|
key='edit-button'
|
|
94
95
|
style={editButtonContainer}
|
|
95
96
|
>
|
|
@@ -99,8 +100,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) =
|
|
|
99
100
|
} else if (cooldownIsActive) {
|
|
100
101
|
return (
|
|
101
102
|
<Animated.View
|
|
102
|
-
entering={
|
|
103
|
-
exiting={
|
|
103
|
+
entering={transitions.zoomIn200}
|
|
104
|
+
exiting={transitions.zoomOut200}
|
|
104
105
|
key='cooldown-timer'
|
|
105
106
|
style={cooldownButtonContainer}
|
|
106
107
|
>
|
|
@@ -110,8 +111,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) =
|
|
|
110
111
|
} else if (audioRecordingEnabled && textIsEmpty && !hasAttachments) {
|
|
111
112
|
return (
|
|
112
113
|
<Animated.View
|
|
113
|
-
entering={
|
|
114
|
-
exiting={
|
|
114
|
+
entering={transitions.zoomIn200}
|
|
115
|
+
exiting={transitions.zoomOut200}
|
|
115
116
|
key='audio-recording-button'
|
|
116
117
|
style={audioRecordingButtonContainer}
|
|
117
118
|
>
|
|
@@ -121,8 +122,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) =
|
|
|
121
122
|
} else {
|
|
122
123
|
return (
|
|
123
124
|
<Animated.View
|
|
124
|
-
entering={
|
|
125
|
-
exiting={
|
|
125
|
+
entering={transitions.zoomIn200}
|
|
126
|
+
exiting={transitions.zoomOut200}
|
|
126
127
|
key='send-button'
|
|
127
128
|
style={sendButtonContainer}
|
|
128
129
|
>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ViewToken,
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
|
|
11
|
-
import Animated
|
|
11
|
+
import Animated from 'react-native-reanimated';
|
|
12
12
|
|
|
13
13
|
import type { FlashListProps, FlashListRef } from '@shopify/flash-list';
|
|
14
14
|
import type { Channel, Event, LocalMessage, MessageResponse } from 'stream-chat';
|
|
@@ -56,6 +56,7 @@ import { useStableCallback, useStateStore } from '../../hooks';
|
|
|
56
56
|
import { bumpOverlayLayoutRevision } from '../../state-store';
|
|
57
57
|
import { MessageInputHeightState } from '../../state-store/message-input-height-store';
|
|
58
58
|
import { primitives } from '../../theme';
|
|
59
|
+
import { transitions } from '../../utils/transitions';
|
|
59
60
|
import { MessageWrapper } from '../Message/MessageItemView/MessageWrapper';
|
|
60
61
|
|
|
61
62
|
type FlashListContextApi = { getRef?: () => FlashListRef<LocalMessage> | null } | undefined;
|
|
@@ -1094,7 +1095,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1094
1095
|
) : null}
|
|
1095
1096
|
</View>
|
|
1096
1097
|
<Animated.View
|
|
1097
|
-
layout={
|
|
1098
|
+
layout={transitions.layout200}
|
|
1098
1099
|
style={[
|
|
1099
1100
|
styles.scrollToBottomButtonContainer,
|
|
1100
1101
|
{
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
ViewToken,
|
|
12
12
|
} from 'react-native';
|
|
13
13
|
|
|
14
|
-
import Animated
|
|
14
|
+
import Animated from 'react-native-reanimated';
|
|
15
15
|
|
|
16
16
|
import debounce from 'lodash/debounce';
|
|
17
17
|
|
|
@@ -68,6 +68,7 @@ import { useStateStore } from '../../hooks/useStateStore';
|
|
|
68
68
|
import { bumpOverlayLayoutRevision } from '../../state-store';
|
|
69
69
|
import { MessageInputHeightState } from '../../state-store/message-input-height-store';
|
|
70
70
|
import { primitives } from '../../theme';
|
|
71
|
+
import { transitions } from '../../utils/transitions';
|
|
71
72
|
import { MessageWrapper } from '../Message/MessageItemView/MessageWrapper';
|
|
72
73
|
|
|
73
74
|
// This is just to make sure that the scrolling happens in a different task queue.
|
|
@@ -1263,7 +1264,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
1263
1264
|
<MessageListItemProvider value={messageListItemContextValue}>
|
|
1264
1265
|
<ListComponent
|
|
1265
1266
|
// TODO: Consider hiding this behind a feature flag.
|
|
1266
|
-
layout={
|
|
1267
|
+
layout={transitions.layout200}
|
|
1267
1268
|
contentContainerStyle={flatListContentContainerStyle}
|
|
1268
1269
|
/** Disables the MessageList UI. Which means, message actions, reactions won't work. */
|
|
1269
1270
|
data={processedMessageListWithNeighbors}
|
|
@@ -1310,7 +1311,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
1310
1311
|
</View>
|
|
1311
1312
|
{scrollToBottomButtonVisible ? (
|
|
1312
1313
|
<Animated.View
|
|
1313
|
-
layout={
|
|
1314
|
+
layout={transitions.layout200}
|
|
1314
1315
|
style={[
|
|
1315
1316
|
{
|
|
1316
1317
|
bottom: messageInputFloating
|
|
@@ -1462,5 +1463,3 @@ export const MessageList = (props: MessageListProps) => {
|
|
|
1462
1463
|
const AnimatedList = React.memo(
|
|
1463
1464
|
Animated.createAnimatedComponent(FlatList<MessageListItemWithNeighbours>),
|
|
1464
1465
|
);
|
|
1465
|
-
|
|
1466
|
-
const LayoutTransition = LinearTransition.duration(200);
|
package/src/hooks/index.ts
CHANGED
|
@@ -7,8 +7,10 @@ export * from './useStableCallback';
|
|
|
7
7
|
export * from './useLoadingImage';
|
|
8
8
|
export * from './useMessageReminder';
|
|
9
9
|
export * from './useQueryReminders';
|
|
10
|
+
export * from './useAfterKeyboardOpenCallback';
|
|
10
11
|
export * from './useClientNotifications';
|
|
11
12
|
export * from './useInAppNotificationsState';
|
|
13
|
+
export * from './usePortalSettledCallback';
|
|
12
14
|
export * from './useRAFCoalescedValue';
|
|
13
15
|
export * from './useAudioPlayerControl';
|
|
14
16
|
export * from './useAttachmentPickerState';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { EventSubscription, Keyboard, Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useKeyboardVisibility } from './useKeyboardVisibility';
|
|
5
|
+
|
|
6
|
+
import { useStableCallback } from './useStableCallback';
|
|
7
|
+
|
|
8
|
+
import { KeyboardControllerPackage } from '../components/KeyboardCompatibleView/KeyboardControllerAvoidingView';
|
|
9
|
+
import { useMessageInputContext } from '../contexts/messageInputContext/MessageInputContext';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A utility hook that returns a stable callback which focuses the message input
|
|
13
|
+
* and invokes the callback once the keyboard is open.
|
|
14
|
+
*
|
|
15
|
+
* @param callback - callback we want to run once the keyboard is ready
|
|
16
|
+
* @returns A stable callback that will wait for the keyboard to be open before executing.
|
|
17
|
+
*/
|
|
18
|
+
export const useAfterKeyboardOpenCallback = <T extends unknown[]>(
|
|
19
|
+
callback: (...args: T) => void,
|
|
20
|
+
) => {
|
|
21
|
+
const isKeyboardVisible = useKeyboardVisibility();
|
|
22
|
+
const { inputBoxRef } = useMessageInputContext();
|
|
23
|
+
const keyboardSubscriptionRef = useRef<EventSubscription | undefined>(undefined);
|
|
24
|
+
// This callback runs from a keyboard event listener, so it must stay fresh across rerenders.
|
|
25
|
+
const stableCallback = useStableCallback(callback);
|
|
26
|
+
|
|
27
|
+
/** Clears the pending keyboard listener, if any. */
|
|
28
|
+
const clearKeyboardSubscription = useStableCallback(() => {
|
|
29
|
+
keyboardSubscriptionRef.current?.remove();
|
|
30
|
+
keyboardSubscriptionRef.current = undefined;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
useEffect(() => clearKeyboardSubscription, [clearKeyboardSubscription]);
|
|
34
|
+
|
|
35
|
+
return useStableCallback((...args: T) => {
|
|
36
|
+
clearKeyboardSubscription();
|
|
37
|
+
|
|
38
|
+
const runCallback = () => {
|
|
39
|
+
clearKeyboardSubscription();
|
|
40
|
+
stableCallback(...args);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (!inputBoxRef.current) {
|
|
44
|
+
runCallback();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isKeyboardVisible) {
|
|
49
|
+
inputBoxRef.current.focus();
|
|
50
|
+
runCallback();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const keyboardEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
55
|
+
|
|
56
|
+
keyboardSubscriptionRef.current = KeyboardControllerPackage?.KeyboardEvents
|
|
57
|
+
? KeyboardControllerPackage.KeyboardEvents.addListener(keyboardEvent, runCallback)
|
|
58
|
+
: Keyboard.addListener(keyboardEvent, runCallback);
|
|
59
|
+
|
|
60
|
+
inputBoxRef.current.focus();
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useStableCallback } from './useStableCallback';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Number of frames we wait before invoking input focus sensitive work after the
|
|
8
|
+
* overlay closes.
|
|
9
|
+
*/
|
|
10
|
+
const SETTLE_FRAMES = Platform.OS === 'android' ? 2 : 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs a callback after a fixed number of animation frames.
|
|
14
|
+
*
|
|
15
|
+
* We use RAFs here because the settling work we care about is tied to the next
|
|
16
|
+
* rendered frames after the overlay close transition.
|
|
17
|
+
*
|
|
18
|
+
* @param callback - callback to run once the frame budget has elapsed
|
|
19
|
+
* @param frames - number of frames to wait
|
|
20
|
+
* @param rafIds - accumulator used for later cancellation/cleanup
|
|
21
|
+
*/
|
|
22
|
+
const scheduleAfterFrames = (callback: () => void, frames: number, rafIds: number[]) => {
|
|
23
|
+
if (frames <= 0) {
|
|
24
|
+
callback();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rafId = requestAnimationFrame(() => scheduleAfterFrames(callback, frames - 1, rafIds));
|
|
29
|
+
rafIds.push(rafId);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns a stable callback that is safe to run after a `PortalWhileClosingView`
|
|
34
|
+
* has settled back into its original tree.
|
|
35
|
+
*
|
|
36
|
+
* Some followup actions are sensitive to that handoff window. If they run
|
|
37
|
+
* while a view is still being returned from a portal host to its in place host,
|
|
38
|
+
* they can target a node that is about to be reattached. On Android, that is
|
|
39
|
+
* especially noticeable with focus sensitive work, where the target can lose
|
|
40
|
+
* focus again mid keyboard animation.
|
|
41
|
+
*
|
|
42
|
+
* Two frames are intentional here:
|
|
43
|
+
* - frame 1 lets the portal retarget and React commit the component tree
|
|
44
|
+
* - frame 2 lets the native view hierarchy settle in its final host
|
|
45
|
+
*
|
|
46
|
+
* iOS does not currently need this settle window for this flow.
|
|
47
|
+
*
|
|
48
|
+
* A good example is the message composer edit action: after closing the message
|
|
49
|
+
* overlay, we wait for the portal handoff to settle before focusing the input
|
|
50
|
+
* and opening the keyboard. Doing this prematurely will result in the keyboard
|
|
51
|
+
* being immediately closed.
|
|
52
|
+
*
|
|
53
|
+
* Another good example would be having a button wrapped in a `PortalWhileClosingView`,
|
|
54
|
+
* that possibly renders (or morphs into) something when pressed. Handling `onPress`
|
|
55
|
+
* prematurely here may lead to the morphed button rendering into a completely different
|
|
56
|
+
* part of the UI hierarchy, causing unknown behaviour. This hook prevents that from
|
|
57
|
+
* happening.
|
|
58
|
+
*
|
|
59
|
+
* @param callback - callback we want to invoke once the portal handoff has settled
|
|
60
|
+
* @returns A stable callback gated behind the portal settle window.
|
|
61
|
+
*/
|
|
62
|
+
export const usePortalSettledCallback = <T extends unknown[]>(callback: (...args: T) => void) => {
|
|
63
|
+
const rafIdsRef = useRef<number[]>([]);
|
|
64
|
+
// This callback runs from deferred RAF work, so it must stay fresh across rerenders.
|
|
65
|
+
const stableCallback = useStableCallback(callback);
|
|
66
|
+
|
|
67
|
+
const clearScheduledFrames = useStableCallback(() => {
|
|
68
|
+
rafIdsRef.current.forEach((rafId) => cancelAnimationFrame(rafId));
|
|
69
|
+
rafIdsRef.current = [];
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
useEffect(() => clearScheduledFrames, [clearScheduledFrames]);
|
|
73
|
+
|
|
74
|
+
return useStableCallback((...args: T) => {
|
|
75
|
+
clearScheduledFrames();
|
|
76
|
+
scheduleAfterFrames(() => stableCallback(...args), SETTLE_FRAMES, rafIdsRef.current);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
@@ -94,13 +94,7 @@ export const closeOverlay = () => {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
if (!overlayStore.getLatestValue().id) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
overlayStore.partialNext({ closing: true });
|
|
103
|
-
});
|
|
97
|
+
overlayStore.partialNext({ closing: true });
|
|
104
98
|
};
|
|
105
99
|
|
|
106
100
|
let actionQueue: Array<() => void | Promise<void>> = [];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FadeIn, FadeOut, LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanimated';
|
|
2
|
+
|
|
3
|
+
export const transitions = {
|
|
4
|
+
fadeIn200: FadeIn.duration(200),
|
|
5
|
+
fadeOut200: FadeOut.duration(200),
|
|
6
|
+
layout200: LinearTransition.duration(200),
|
|
7
|
+
zoomIn200: ZoomIn.duration(200),
|
|
8
|
+
zoomOut200: ZoomOut.duration(200),
|
|
9
|
+
} as const;
|
package/src/version.json
CHANGED