From 2e375f771ce326f151554710c720e19a5dfbc3b7 Mon Sep 17 00:00:00 2001 From: Stream SDK Bot <60655709+Stream-SDK-Bot@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:56:41 +0100 Subject: [PATCH 1/3] chore: update sdk size (#3702) This PR was created automatically by CI. Co-authored-by: Stream Bot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d466b59f5b..64abe39f7e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![NPM](https://img.shields.io/npm/v/stream-chat-react-native.svg)](https://www.npmjs.com/package/stream-chat-react-native) [![Build Status](https://github.com/GetStream/stream-chat-react-native/actions/workflows/release.yml/badge.svg)](https://github.com/GetStream/stream-chat-react-native/actions) [![Component Reference](https://img.shields.io/badge/docs-component%20reference-blue.svg)](https://getstream.io/chat/docs/sdk/reactnative) -![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-1974%20KB-blue) +![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-1975%20KB-blue) From 448ebd87f609083788e9262da3bb739d5db728d9 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:43:24 +0200 Subject: [PATCH 2/3] feat: localized unread count (#3679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal SDK followup to this PR: https://github.com/GetStream/stream-chat-js/pull/1787. All details can be read there. ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- examples/ExpoMessaging/package.json | 2 +- examples/SampleApp/package.json | 2 +- examples/TypeScriptMessaging/package.json | 2 +- package/package.json | 2 +- package/src/components/Channel/Channel.tsx | 43 ++++++++++++++----- .../Channel/hooks/useCreateChannelContext.ts | 6 --- .../hooks/useChannelPreviewData.ts | 10 ++++- .../MessageList/MessageFlashList.tsx | 7 ++- .../components/MessageList/MessageList.tsx | 7 ++- .../Thread/__tests__/Thread.test.tsx | 8 ++-- .../channelContext/ChannelContext.tsx | 3 -- yarn.lock | 16 +++---- 12 files changed, 70 insertions(+), 38 deletions(-) diff --git a/examples/ExpoMessaging/package.json b/examples/ExpoMessaging/package.json index 6aa3e0a6e4..dcdfd2e3b8 100644 --- a/examples/ExpoMessaging/package.json +++ b/examples/ExpoMessaging/package.json @@ -51,7 +51,7 @@ "react-native-teleport": "^1.0.2", "react-native-web": "^0.21.0", "react-native-worklets": "0.8.3", - "stream-chat": "^9.48.0", + "stream-chat": "^9.50.0", "stream-chat-expo": "workspace:^", "stream-chat-react-native-core": "workspace:^" }, diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index 504a724331..0110437fa8 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -64,7 +64,7 @@ "react-native-teleport": "^1.1.7", "react-native-video": "^6.19.2", "react-native-worklets": "^0.8.3", - "stream-chat": "^9.48.0", + "stream-chat": "^9.50.0", "stream-chat-react-native": "workspace:^", "stream-chat-react-native-core": "workspace:^" }, diff --git a/examples/TypeScriptMessaging/package.json b/examples/TypeScriptMessaging/package.json index 57c2924d91..abdf714598 100644 --- a/examples/TypeScriptMessaging/package.json +++ b/examples/TypeScriptMessaging/package.json @@ -33,7 +33,7 @@ "react-native-svg": "^15.12.0", "react-native-video": "^6.16.1", "react-native-worklets": "^0.4.1", - "stream-chat": "^9.48.0", + "stream-chat": "^9.50.0", "stream-chat-react-native": "workspace:^", "stream-chat-react-native-core": "workspace:^" }, diff --git a/package/package.json b/package/package.json index ef657d8524..b0111dd906 100644 --- a/package/package.json +++ b/package/package.json @@ -78,7 +78,7 @@ "path": "0.12.7", "react-native-markdown-package": "1.8.2", "react-native-url-polyfill": "^2.0.0", - "stream-chat": "^9.48.0", + "stream-chat": "^9.50.0", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 52070c39b8..f972ba4fb1 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -511,12 +511,12 @@ const ChannelWithContext = (props: PropsWithChildren) = const styles = useStyles(); const [deleted, setDeleted] = useState(false); const [error, setError] = useState(false); - const [lastRead, setLastRead] = useState(); + const lastReadRef = useRef(undefined); const [thread, setThread] = useState(threadProps || null); const [threadHasMore, setThreadHasMore] = useState(true); const [threadLoadingMore, setThreadLoadingMore] = useState(false); - const [channelUnreadStateStore] = useState(new ChannelUnreadStateStore()); - const [messageInputHeightStore] = useState(new MessageInputHeightStore()); + const [channelUnreadStateStore] = useState(() => new ChannelUnreadStateStore()); + const [messageInputHeightStore] = useState(() => new MessageInputHeightStore()); // TODO: Think if we can remove this and just rely on the channelUnreadStateStore everywhere. const setChannelUnreadState = useCallback( (data: ChannelUnreadStateStoreType['channelUnreadState']) => { @@ -690,6 +690,13 @@ const ChannelWithContext = (props: PropsWithChildren) = return; } + if (event.type === 'message.read_locally') { + // When local unread reset happens, the count is already updated in the client state, + // and the preview badge / unread divider are handled elsewhere, so there is nothing + // to copy into channel state here. Thus, we skip it. + return; + } + if (event.type === 'message.read' || event.type === 'notification.mark_read') { setReadThrottled(); return; @@ -703,7 +710,7 @@ const ChannelWithContext = (props: PropsWithChildren) = useEffect(() => { let listener: ReturnType; const initChannel = async () => { - setLastRead(new Date()); + lastReadRef.current = new Date(); const unreadCount = channel.countUnread(); const shouldLoadAtFirstUnread = shouldLoadInitialChannelAtFirstUnreadMessage(unreadCount); if (!channel || !shouldSyncChannel) { @@ -812,7 +819,25 @@ const ChannelWithContext = (props: PropsWithChildren) = const markReadInternal: ChannelContextValue['markRead'] = throttle( async (options?: MarkReadFunctionOptions) => { const { updateChannelUnreadState = true } = options ?? {}; - if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) { + if (!channel || channel?.disconnected) { + return; + } + + // When read events are disabled (e.g. livestreams) we cannot mark read on the backend. If the + // client opted into a local unread count, reset it locally instead so the user's "caught up" + // state is reflected without a server round trip. + if (!clientChannelConfig?.read_events) { + if (client.options.isLocalUnreadCountEnabled) { + const event = channel.markReadLocally(); + if (updateChannelUnreadState && event && lastReadRef.current) { + setChannelUnreadState({ + last_read: lastReadRef.current, + last_read_message_id: event.last_read_message_id, + unread_messages: 0, + }); + lastReadRef.current = new Date(); + } + } return; } @@ -821,13 +846,13 @@ const ChannelWithContext = (props: PropsWithChildren) = } else { try { const response = await channel.markRead(); - if (updateChannelUnreadState && response && lastRead) { + if (updateChannelUnreadState && response && lastReadRef.current) { setChannelUnreadState({ - last_read: lastRead, + last_read: lastReadRef.current, last_read_message_id: response?.event.last_read_message_id, unread_messages: 0, }); - setLastRead(new Date()); + lastReadRef.current = new Date(); } } catch (err) { console.log('Error marking channel as read:', err); @@ -1578,7 +1603,6 @@ const ChannelWithContext = (props: PropsWithChildren) = hideStickyDateHeader, highlightedMessageId, isChannelActive: shouldSyncChannel, - lastRead, loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading: channelMessagesState.loading, @@ -1590,7 +1614,6 @@ const ChannelWithContext = (props: PropsWithChildren) = reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, - setLastRead, setTargetedMessage, hasPendingInitialTargetLoad, targetedMessage, diff --git a/package/src/components/Channel/hooks/useCreateChannelContext.ts b/package/src/components/Channel/hooks/useCreateChannelContext.ts index 8e8870707c..67a495449b 100644 --- a/package/src/components/Channel/hooks/useCreateChannelContext.ts +++ b/package/src/components/Channel/hooks/useCreateChannelContext.ts @@ -13,7 +13,6 @@ export const useCreateChannelContext = ({ hideStickyDateHeader, highlightedMessageId, isChannelActive, - lastRead, loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading, @@ -25,7 +24,6 @@ export const useCreateChannelContext = ({ reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, - setLastRead, setTargetedMessage, hasPendingInitialTargetLoad, targetedMessage, @@ -35,7 +33,6 @@ export const useCreateChannelContext = ({ watchers, }: ChannelContextValue) => { const channelId = channel?.id; - const lastReadTime = lastRead?.getTime(); const membersLength = Object.keys(members).length; const readUsers = Object.values(read); @@ -56,7 +53,6 @@ export const useCreateChannelContext = ({ hideStickyDateHeader, highlightedMessageId, isChannelActive, - lastRead, loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading, @@ -68,7 +64,6 @@ export const useCreateChannelContext = ({ reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, - setLastRead, setTargetedMessage, hasPendingInitialTargetLoad, targetedMessage, @@ -84,7 +79,6 @@ export const useCreateChannelContext = ({ error, isChannelActive, highlightedMessageId, - lastReadTime, loading, membersLength, readUsersLength, diff --git a/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts b/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts index bd81574bbb..da37a0b050 100644 --- a/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +++ b/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts @@ -109,8 +109,14 @@ export const useChannelPreviewData = ( setForceUpdate((prev) => prev + 1); } }; - const { unsubscribe } = client.on('message.read', handleReadEvent); - return unsubscribe; + const readSubscription = client.on('message.read', handleReadEvent); + // `message.read_locally` is the client-only equivalent emitted by `channel.markReadLocally()` when + // read events are disabled (e.g. livestreams with `isLocalUnreadCountEnabled`). + const localReadSubscription = client.on('message.read_locally', handleReadEvent); + return () => { + readSubscription.unsubscribe(); + localReadSubscription.unsubscribe(); + }; }, [client, channel]); /** diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 8eebd18cb1..2fd8404012 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -718,9 +718,14 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => const lastReadMessageId = channelUnreadState?.last_read_message_id; const lastReadMessageVisible = viewableItems.some((item) => item.item.id === lastReadMessageId); + // Channels with disabled `read-events` (i.e livestreams) still surface the unread + // notification when the client opted into a local unread count, so the gate accepts + // either source. + const unreadNotificationSupported = readEvents || client.options.isLocalUnreadCountEnabled; + if ( !viewableItems.length || - !readEvents || + !unreadNotificationSupported || lastReadMessageVisible || attachmentPickerStore.state.getLatestValue().selectedPicker === 'images' ) { diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index ede0407d0c..2ef08ac23b 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -555,9 +555,14 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { (item) => item.item.message.id === lastReadMessageId, ); + // Channels with disabled `read-events` (i.e livestreams) still surface the unread + // notification when the client opted into a local unread count, so the gate accepts + // either source. + const unreadNotificationSupported = readEvents || client.options.isLocalUnreadCountEnabled; + if ( !viewableItems.length || - !readEvents || + !unreadNotificationSupported || lastReadMessageVisible || attachmentPickerStore.state.getLatestValue().selectedPicker === 'images' ) { diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index 72577b01d1..56aad0705c 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -142,7 +142,9 @@ describe('Thread', () => { threadResponses as unknown as Parameters[0], ); - let setLastRead: ((date?: Date) => void) | undefined; + let setChannelUnreadState: + | React.ContextType['setChannelUnreadState'] + | undefined; const { getByText, toJSON } = render( @@ -161,7 +163,7 @@ describe('Thread', () => { {(c) => { - setLastRead = c.setLastRead; + setChannelUnreadState = c.setChannelUnreadState; return ; }} @@ -178,7 +180,7 @@ describe('Thread', () => { expect(getByText('Message6')).toBeTruthy(); }); - act(() => setLastRead!(new Date('2020-08-17T18:08:03.196Z'))); + act(() => setChannelUnreadState!(undefined)); const snapshot = toJSON() as unknown as { children: Array<{ diff --git a/package/src/contexts/channelContext/ChannelContext.tsx b/package/src/contexts/channelContext/ChannelContext.tsx index a14119ac0e..e9de901882 100644 --- a/package/src/contexts/channelContext/ChannelContext.tsx +++ b/package/src/contexts/channelContext/ChannelContext.tsx @@ -110,7 +110,6 @@ export type ChannelContextValue = { reloadChannel: () => Promise; scrollToFirstUnreadThreshold: number; setChannelUnreadState: (data: ChannelUnreadStateStoreType['channelUnreadState']) => void; - setLastRead: React.Dispatch>; setTargetedMessage: (messageId?: string) => void; /** * Returns true when Channel is about to load an initial targeted message. @@ -131,8 +130,6 @@ export type ChannelContextValue = { */ highlightedMessageId?: string; isChannelActive?: boolean; - - lastRead?: Date; loading?: boolean; /** * Maximum time in milliseconds that should occur between messages diff --git a/yarn.lock b/yarn.lock index 968b62bc64..14410c95fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7442,7 +7442,7 @@ __metadata: react-native-teleport: "npm:^1.0.2" react-native-web: "npm:^0.21.0" react-native-worklets: "npm:0.8.3" - stream-chat: "npm:^9.48.0" + stream-chat: "npm:^9.50.0" stream-chat-expo: "workspace:^" stream-chat-react-native-core: "workspace:^" typescript: "npm:~5.9.2" @@ -18884,7 +18884,7 @@ __metadata: react-native-teleport: "npm:^1.1.7" react-native-video: "npm:^6.19.2" react-native-worklets: "npm:^0.8.3" - stream-chat: "npm:^9.48.0" + stream-chat: "npm:^9.50.0" stream-chat-react-native: "workspace:^" stream-chat-react-native-core: "workspace:^" typescript: "npm:5.9.3" @@ -19611,7 +19611,7 @@ __metadata: react-native-worklets: "npm:^0.9.1" react-test-renderer: "npm:19.2.3" rimraf: "npm:^6.0.1" - stream-chat: "npm:^9.48.0" + stream-chat: "npm:^9.50.0" typescript: "npm:5.9.3" use-sync-external-store: "npm:^1.5.0" uuid: "npm:^11.1.0" @@ -19683,9 +19683,9 @@ __metadata: languageName: unknown linkType: soft -"stream-chat@npm:^9.48.0": - version: 9.48.0 - resolution: "stream-chat@npm:9.48.0" +"stream-chat@npm:^9.50.0": + version: 9.50.0 + resolution: "stream-chat@npm:9.50.0" dependencies: "@types/jsonwebtoken": "npm:^9.0.8" "@types/ws": "npm:^8.18.1" @@ -19701,7 +19701,7 @@ __metadata: built: true husky: built: true - checksum: 10c0/45ecea98e5c566098ac73580ca91e6bcfd980015e0e33a6623e53fcab9a57a700369c9a09908d49dae1fba66cd01460a6b47a4c99b8c56198e8a3c7b9b3ba962 + checksum: 10c0/be5941d74946bd1a1371192dd71933a991fd4d8dd070975ee47dca31f938a9c604b6590af5639932f433735907e76a03a7d3519df23c0807f55ef189b21e0c1d languageName: node linkType: hard @@ -20536,7 +20536,7 @@ __metadata: react-native-svg: "npm:^15.12.0" react-native-video: "npm:^6.16.1" react-native-worklets: "npm:^0.4.1" - stream-chat: "npm:^9.48.0" + stream-chat: "npm:^9.50.0" stream-chat-react-native: "workspace:^" stream-chat-react-native-core: "workspace:^" typescript: "npm:5.9.3" From 9e89a66e8c42645a18632c145296841e0c047669 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> Date: Fri, 3 Jul 2026 18:03:14 +0200 Subject: [PATCH 3/3] fix: expose default native handlers (#3704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal This PR addresses [this GH issue](https://github.com/GetStream/stream-chat-react-native/issues/3379). While exposing a prop might be slightly more convenient, our native handlers have a ton of arguments we don't want to expose every single one of them separately as it will cause an argument storm. Instead, we expose the default native handlers and allow integrators to use them as they see fit. For example, they could invoke them with different properties, make them conditionally depend on configuration and similar. It would also make it easier to conditionally override the handlers in certain scenarios. Runtime passing of different arguments should still be done on the callsites themselves, this approach is more of a global switch to something else if necessary. ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/expo-package/src/index.js | 11 ++++++++-- package/expo-package/types/index.d.ts | 29 +++++++++++++++++++++++++ package/native-package/src/index.js | 11 ++++++++-- package/native-package/types/index.d.ts | 29 +++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/package/expo-package/src/index.js b/package/expo-package/src/index.js index 4bb4a5b005..3a624dd845 100644 --- a/package/expo-package/src/index.js +++ b/package/expo-package/src/index.js @@ -24,7 +24,10 @@ import { Video, } from './optionalDependencies'; -registerNativeHandlers({ +/** + * The default native handlers this package registers with the core SDK. + */ +export const defaultNativeHandlers = { Audio, compressImage, deleteFile, @@ -39,13 +42,17 @@ registerNativeHandlers({ pickDocument, pickImage, saveFile, - SDK: 'stream-chat-expo', setClipboardString, shareImage, Sound, takePhoto, triggerHaptic, Video, +}; + +registerNativeHandlers({ + ...defaultNativeHandlers, + SDK: 'stream-chat-expo', }); export * from 'stream-chat-react-native-core'; diff --git a/package/expo-package/types/index.d.ts b/package/expo-package/types/index.d.ts index c6ea2df166..8d03079494 100644 --- a/package/expo-package/types/index.d.ts +++ b/package/expo-package/types/index.d.ts @@ -1 +1,30 @@ +import { registerNativeHandlers } from 'stream-chat-react-native-core'; + export * from 'stream-chat-react-native-core'; + +/** + * The default native handlers this package registers with the core SDK. + * + * Exposed so integrators can compose or wrap a single handler (for example to + * force `takePhoto` to capture images only) without reimplementing it or + * reaching into internal module paths. Register your override *after* importing + * this package so it takes precedence. + * + * Example: + * + * ```ts + * import { registerNativeHandlers, defaultNativeHandlers } from 'stream-chat-expo'; + * + * const localTakePhoto = defaultNativeHandlers.takePhoto; + * + * registerNativeHandlers({ + * takePhoto: localTakePhoto + * ? (options) => { + * console.log('[#3379 demo] wrapped takePhoto โ€” forcing mediaType "image"', options); + * return localTakePhoto({ ...options, mediaType: 'image' }); + * } + * : undefined, + * }); + * ``` + */ +export declare const defaultNativeHandlers: Parameters[0]; diff --git a/package/native-package/src/index.js b/package/native-package/src/index.js index 0c5e1d2b7e..5f99773b12 100644 --- a/package/native-package/src/index.js +++ b/package/native-package/src/index.js @@ -25,7 +25,10 @@ import { Video, } from './optionalDependencies'; -registerNativeHandlers({ +/** + * The default native handlers this package registers with the core SDK. + */ +export const defaultNativeHandlers = { Audio, compressImage, deleteFile, @@ -40,13 +43,17 @@ registerNativeHandlers({ pickDocument, pickImage, saveFile, - SDK: 'stream-chat-react-native', setClipboardString, shareImage, Sound, takePhoto, triggerHaptic, Video, +}; + +registerNativeHandlers({ + ...defaultNativeHandlers, + SDK: 'stream-chat-react-native', }); if (Platform.OS === 'android') { diff --git a/package/native-package/types/index.d.ts b/package/native-package/types/index.d.ts index c6ea2df166..8d03079494 100644 --- a/package/native-package/types/index.d.ts +++ b/package/native-package/types/index.d.ts @@ -1 +1,30 @@ +import { registerNativeHandlers } from 'stream-chat-react-native-core'; + export * from 'stream-chat-react-native-core'; + +/** + * The default native handlers this package registers with the core SDK. + * + * Exposed so integrators can compose or wrap a single handler (for example to + * force `takePhoto` to capture images only) without reimplementing it or + * reaching into internal module paths. Register your override *after* importing + * this package so it takes precedence. + * + * Example: + * + * ```ts + * import { registerNativeHandlers, defaultNativeHandlers } from 'stream-chat-expo'; + * + * const localTakePhoto = defaultNativeHandlers.takePhoto; + * + * registerNativeHandlers({ + * takePhoto: localTakePhoto + * ? (options) => { + * console.log('[#3379 demo] wrapped takePhoto โ€” forcing mediaType "image"', options); + * return localTakePhoto({ ...options, mediaType: 'image' }); + * } + * : undefined, + * }); + * ``` + */ +export declare const defaultNativeHandlers: Parameters[0];