stream-chat-react 13.9.0 → 13.10.1

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.
Files changed (75) hide show
  1. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +7 -3
  2. package/dist/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +8 -3
  3. package/dist/components/Chat/hooks/useChat.js +1 -1
  4. package/dist/components/Dialog/ButtonWithSubmenu.d.ts +2 -2
  5. package/dist/components/Dialog/ButtonWithSubmenu.js +6 -6
  6. package/dist/components/Dialog/DialogAnchor.d.ts +5 -11
  7. package/dist/components/Dialog/DialogAnchor.js +22 -26
  8. package/dist/components/Dialog/DialogPortal.d.ts +2 -1
  9. package/dist/components/Dialog/DialogPortal.js +23 -7
  10. package/dist/components/Dialog/hooks/index.d.ts +1 -0
  11. package/dist/components/Dialog/hooks/useDialog.d.ts +4 -0
  12. package/dist/components/Dialog/hooks/useDialog.js +9 -1
  13. package/dist/components/Dialog/hooks/usePopoverPosition.d.ts +68 -0
  14. package/dist/components/Dialog/hooks/usePopoverPosition.js +54 -0
  15. package/dist/components/Form/Dropdown.d.ts +2 -2
  16. package/dist/components/Message/Message.js +1 -1
  17. package/dist/components/Message/MessageRepliesCountButton.js +3 -1
  18. package/dist/components/Message/MessageStatus.js +6 -2
  19. package/dist/components/Message/renderText/remarkPlugins/index.d.ts +1 -0
  20. package/dist/components/Message/renderText/remarkPlugins/index.js +1 -0
  21. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.d.ts +10 -2
  22. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.js +46 -26
  23. package/dist/components/Message/renderText/remarkPlugins/remarkIgnoreMarkdown.d.ts +8 -0
  24. package/dist/components/Message/renderText/remarkPlugins/remarkIgnoreMarkdown.js +11 -0
  25. package/dist/components/Message/types.d.ts +4 -0
  26. package/dist/components/MessageActions/MessageActions.js +4 -4
  27. package/dist/components/MessageInput/AttachmentSelector.d.ts +1 -1
  28. package/dist/components/MessageInput/AttachmentSelector.js +9 -4
  29. package/dist/components/MessageList/MessageList.js +6 -0
  30. package/dist/components/MessageList/VirtualizedMessageList.d.ts +3 -1
  31. package/dist/components/MessageList/VirtualizedMessageList.js +6 -0
  32. package/dist/components/MessageList/VirtualizedMessageListComponents.js +2 -2
  33. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.d.ts +1 -0
  34. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.js +7 -2
  35. package/dist/components/MessageList/hooks/useLastDeliveredData.d.ts +1 -0
  36. package/dist/components/MessageList/hooks/useLastDeliveredData.js +24 -11
  37. package/dist/components/MessageList/hooks/useLastOwnMessage.d.ts +5 -0
  38. package/dist/components/MessageList/hooks/useLastOwnMessage.js +4 -0
  39. package/dist/components/MessageList/hooks/useLastReadData.d.ts +1 -0
  40. package/dist/components/MessageList/hooks/useLastReadData.js +20 -10
  41. package/dist/components/MessageList/renderMessages.d.ts +4 -2
  42. package/dist/components/MessageList/renderMessages.js +2 -2
  43. package/dist/components/Modal/GlobalModal.js +2 -2
  44. package/dist/components/Reactions/ReactionSelectorWithButton.js +4 -4
  45. package/dist/components/Tooltip/Tooltip.d.ts +2 -2
  46. package/dist/components/Tooltip/Tooltip.js +11 -12
  47. package/dist/context/DialogManagerContext.d.ts +1 -0
  48. package/dist/context/DialogManagerContext.js +1 -0
  49. package/dist/context/MessageContext.d.ts +4 -0
  50. package/dist/css/v2/index.css +1 -1
  51. package/dist/css/v2/index.layout.css +1 -1
  52. package/dist/experimental/MessageActions/MessageActions.js +5 -5
  53. package/dist/experimental/index.browser.cjs +307 -238
  54. package/dist/experimental/index.browser.cjs.map +4 -4
  55. package/dist/experimental/index.node.cjs +307 -238
  56. package/dist/experimental/index.node.cjs.map +4 -4
  57. package/dist/index.browser.cjs +1521 -1338
  58. package/dist/index.browser.cjs.map +4 -4
  59. package/dist/index.node.cjs +1524 -1338
  60. package/dist/index.node.cjs.map +4 -4
  61. package/dist/plugins/Emojis/EmojiPicker.d.ts +9 -4
  62. package/dist/plugins/Emojis/EmojiPicker.js +10 -5
  63. package/dist/plugins/Emojis/index.browser.cjs +89 -29
  64. package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
  65. package/dist/plugins/Emojis/index.node.cjs +89 -29
  66. package/dist/plugins/Emojis/index.node.cjs.map +4 -4
  67. package/dist/scss/v2/Message/Message-layout.scss +4 -0
  68. package/dist/scss/v2/MessageActionsBox/MessageActionsBox-layout.scss +1 -0
  69. package/dist/utils/findReverse.d.ts +1 -0
  70. package/dist/utils/findReverse.js +9 -0
  71. package/package.json +7 -8
  72. package/dist/components/MessageActions/hooks/index.d.ts +0 -1
  73. package/dist/components/MessageActions/hooks/index.js +0 -1
  74. package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.d.ts +0 -18
  75. package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.js +0 -31
@@ -8,9 +8,13 @@ export const useCreateChannelStateContext = (value) => {
8
8
  const notificationsLength = notifications.length;
9
9
  const readUsers = Object.values(read);
10
10
  const readUsersLength = readUsers.length;
11
- const readUsersLastReads = readUsers
12
- .map(({ last_read }) => last_read.toISOString())
13
- .join();
11
+ const readUsersLastReadDateStrings = [];
12
+ for (const { last_read } of readUsers) {
13
+ if (!lastRead)
14
+ continue;
15
+ readUsersLastReadDateStrings.push(last_read?.toISOString());
16
+ }
17
+ const readUsersLastReads = readUsersLastReadDateStrings.join();
14
18
  const threadMessagesLength = threadMessages?.length;
15
19
  const channelCapabilities = {};
16
20
  channelCapabilitiesArray.forEach((capability) => {
@@ -22,12 +22,17 @@ export const useMessageDeliveryStatus = ({ channel, lastMessage, }) => {
22
22
  msgId: lastMessage.id,
23
23
  timestampMs: lastMessage.created_at.getTime(),
24
24
  };
25
- setMessageDeliveryStatus(channel.messageReceiptsTracker.readersForMessage(msgRef).length > 0
25
+ const readersForMessage = channel.messageReceiptsTracker.readersForMessage(msgRef);
26
+ const deliveredForMessage = channel.messageReceiptsTracker.deliveredForMessage(msgRef);
27
+ setMessageDeliveryStatus(readersForMessage.length > 1 ||
28
+ (readersForMessage.length === 1 && readersForMessage[0].id !== client.user?.id)
26
29
  ? MessageDeliveryStatus.READ
27
- : channel.messageReceiptsTracker.deliveredForMessage(msgRef).length > 0
30
+ : deliveredForMessage.length > 1 ||
31
+ (deliveredForMessage.length === 1 &&
32
+ deliveredForMessage[0].id !== client.user?.id)
28
33
  ? MessageDeliveryStatus.DELIVERED
29
34
  : MessageDeliveryStatus.SENT);
30
- }, [channel, isOwnMessage, lastMessage]);
35
+ }, [channel, client, isOwnMessage, lastMessage]);
31
36
  useEffect(() => {
32
37
  const handleMessageNew = (event) => {
33
38
  // the last message is not mine, so do not show the delivery status
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
24
24
  useEffect(() => {
25
25
  if (!client)
26
26
  return;
27
- const version = "13.9.0";
27
+ const version = "13.10.1";
28
28
  const userAgent = client.getUserAgent();
29
29
  if (!userAgent.includes('stream-chat-react')) {
30
30
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { ComponentProps, ComponentType } from 'react';
3
- import type { Placement } from '@popperjs/core';
3
+ import type { PopperLikePlacement } from './hooks';
4
4
  type ButtonWithSubmenu = ComponentProps<'button'> & {
5
5
  children: React.ReactNode;
6
- placement: Placement;
6
+ placement: PopperLikePlacement;
7
7
  Submenu: ComponentType;
8
8
  submenuContainerProps?: ComponentProps<'div'>;
9
9
  };
@@ -1,6 +1,6 @@
1
1
  import clsx from 'clsx';
2
2
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { useDialog, useDialogIsOpen } from './hooks';
3
+ import { useDialogIsOpen, useDialogOnNearestManager } from './hooks';
4
4
  import { useDialogAnchor } from './DialogAnchor';
5
5
  export const ButtonWithSubmenu = ({ children, className, placement, Submenu, submenuContainerProps, ...buttonProps }) => {
6
6
  const buttonRef = useRef(null);
@@ -8,9 +8,9 @@ export const ButtonWithSubmenu = ({ children, className, placement, Submenu, sub
8
8
  const keepSubmenuOpen = useRef(false);
9
9
  const dialogCloseTimeout = useRef(null);
10
10
  const dialogId = useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
11
- const dialog = useDialog({ id: dialogId });
12
- const dialogIsOpen = useDialogIsOpen(dialogId);
13
- const { attributes, setPopperElement, styles } = useDialogAnchor({
11
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId });
12
+ const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id);
13
+ const { setPopperElement, styles } = useDialogAnchor({
14
14
  open: dialogIsOpen,
15
15
  placement,
16
16
  referenceElement: buttonRef.current,
@@ -66,7 +66,7 @@ export const ButtonWithSubmenu = ({ children, className, placement, Submenu, sub
66
66
  keepSubmenuOpen.current = false;
67
67
  closeDialogLazily();
68
68
  }, ref: buttonRef, role: 'option', ...buttonProps }, children),
69
- dialogIsOpen && (React.createElement("div", { ...attributes.popper, onBlur: (event) => {
69
+ dialogIsOpen && (React.createElement("div", { onBlur: (event) => {
70
70
  const isBlurredDescendant = event.relatedTarget instanceof Node &&
71
71
  dialogContainer?.contains(event.relatedTarget);
72
72
  if (isBlurredDescendant)
@@ -83,6 +83,6 @@ export const ButtonWithSubmenu = ({ children, className, placement, Submenu, sub
83
83
  }, ref: (element) => {
84
84
  setPopperElement(element);
85
85
  setDialogContainer(element);
86
- }, style: styles.popper, tabIndex: -1, ...submenuContainerProps },
86
+ }, style: styles, tabIndex: -1, ...submenuContainerProps },
87
87
  React.createElement(Submenu, null)))));
88
88
  };
@@ -1,26 +1,20 @@
1
- import type { Placement } from '@popperjs/core';
2
1
  import type { ComponentProps, PropsWithChildren } from 'react';
3
2
  import React from 'react';
3
+ import type { PopperLikePlacement } from './hooks';
4
4
  export interface DialogAnchorOptions {
5
5
  open: boolean;
6
- placement: Placement;
6
+ placement: PopperLikePlacement;
7
7
  referenceElement: HTMLElement | null;
8
8
  allowFlip?: boolean;
9
9
  }
10
10
  export declare function useDialogAnchor<T extends HTMLElement>({ allowFlip, open, placement, referenceElement, }: DialogAnchorOptions): {
11
- attributes: {
12
- [key: string]: {
13
- [key: string]: string;
14
- } | undefined;
15
- };
16
11
  setPopperElement: React.Dispatch<React.SetStateAction<T | null>>;
17
- styles: {
18
- [key: string]: React.CSSProperties;
19
- };
12
+ styles: React.CSSProperties;
20
13
  };
21
14
  export type DialogAnchorProps = PropsWithChildren<Partial<DialogAnchorOptions>> & {
22
15
  id: string;
16
+ dialogManagerId?: string;
23
17
  focus?: boolean;
24
18
  trapFocus?: boolean;
25
19
  } & ComponentProps<'div'>;
26
- export declare const DialogAnchor: ({ allowFlip, children, className, focus, id, placement, referenceElement, tabIndex, trapFocus, ...restDivProps }: DialogAnchorProps) => React.JSX.Element | null;
20
+ export declare const DialogAnchor: ({ allowFlip, children, className, dialogManagerId, focus, id, placement, referenceElement, tabIndex, trapFocus, ...restDivProps }: DialogAnchorProps) => React.JSX.Element | null;
@@ -1,29 +1,22 @@
1
1
  import clsx from 'clsx';
2
2
  import React, { useEffect, useState } from 'react';
3
3
  import { FocusScope } from '@react-aria/focus';
4
- import { usePopper } from 'react-popper';
5
4
  import { DialogPortalEntry } from './DialogPortal';
6
5
  import { useDialog, useDialogIsOpen } from './hooks';
6
+ import { usePopoverPosition } from './hooks/usePopoverPosition';
7
7
  export function useDialogAnchor({ allowFlip, open, placement, referenceElement, }) {
8
8
  const [popperElement, setPopperElement] = useState(null);
9
- const { attributes, styles, update } = usePopper(referenceElement, popperElement, {
10
- modifiers: [
11
- {
12
- enabled: !!allowFlip, // Prevent flipping
13
- name: 'flip',
14
- },
15
- {
16
- name: 'eventListeners',
17
- options: {
18
- // It's not safe to update popper position on resize and scroll, since popper's
19
- // reference element might not be visible at the time.
20
- resize: false,
21
- scroll: false,
22
- },
23
- },
24
- ],
9
+ const { refs, strategy, update, x, y } = usePopoverPosition({
10
+ allowFlip,
11
+ freeze: true,
25
12
  placement,
26
13
  });
14
+ useEffect(() => {
15
+ refs.setReference(referenceElement);
16
+ }, [referenceElement, refs]);
17
+ useEffect(() => {
18
+ refs.setFloating(popperElement);
19
+ }, [popperElement, refs]);
27
20
  useEffect(() => {
28
21
  if (open && popperElement) {
29
22
  // Since the popper's reference element might not be (and usually is not) visible
@@ -31,20 +24,23 @@ export function useDialogAnchor({ allowFlip, open, placement, referenceElement,
31
24
  // update is non-null only if popperElement is non-null
32
25
  update?.();
33
26
  }
34
- }, [open, popperElement, update]);
27
+ }, [open, placement, popperElement, update]);
35
28
  if (popperElement && !open) {
36
29
  setPopperElement(null);
37
30
  }
38
31
  return {
39
- attributes,
40
32
  setPopperElement,
41
- styles,
33
+ styles: {
34
+ left: x ?? 0,
35
+ position: strategy,
36
+ top: y ?? 0,
37
+ },
42
38
  };
43
39
  }
44
- export const DialogAnchor = ({ allowFlip = true, children, className, focus = true, id, placement = 'auto', referenceElement = null, tabIndex, trapFocus, ...restDivProps }) => {
45
- const dialog = useDialog({ id });
46
- const open = useDialogIsOpen(id);
47
- const { attributes, setPopperElement, styles } = useDialogAnchor({
40
+ export const DialogAnchor = ({ allowFlip = true, children, className, dialogManagerId, focus = true, id, placement = 'auto', referenceElement = null, tabIndex, trapFocus, ...restDivProps }) => {
41
+ const dialog = useDialog({ dialogManagerId, id });
42
+ const open = useDialogIsOpen(id, dialogManagerId);
43
+ const { setPopperElement, styles } = useDialogAnchor({
48
44
  allowFlip,
49
45
  open,
50
46
  placement,
@@ -67,7 +63,7 @@ export const DialogAnchor = ({ allowFlip = true, children, className, focus = tr
67
63
  if (!open) {
68
64
  return null;
69
65
  }
70
- return (React.createElement(DialogPortalEntry, { dialogId: id },
66
+ return (React.createElement(DialogPortalEntry, { dialogId: id, dialogManagerId: dialogManagerId },
71
67
  React.createElement(FocusScope, { autoFocus: focus, contain: trapFocus, restoreFocus: true },
72
- React.createElement("div", { ...restDivProps, ...attributes.popper, className: clsx('str-chat__dialog-contents', className), "data-testid": 'str-chat__dialog-contents', ref: setPopperElement, style: styles.popper, tabIndex: typeof tabIndex !== 'undefined' ? tabIndex : 0 }, children))));
68
+ React.createElement("div", { ...restDivProps, className: clsx('str-chat__dialog-contents', className), "data-testid": 'str-chat__dialog-contents', ref: setPopperElement, style: styles, tabIndex: typeof tabIndex !== 'undefined' ? tabIndex : 0 }, children))));
73
69
  };
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  export declare const DialogPortalDestination: () => React.JSX.Element | null;
4
4
  type DialogPortalEntryProps = {
5
5
  dialogId: string;
6
+ dialogManagerId?: string;
6
7
  };
7
- export declare const DialogPortalEntry: ({ children, dialogId, }: PropsWithChildren<DialogPortalEntryProps>) => React.JSX.Element;
8
+ export declare const DialogPortalEntry: ({ children, dialogId, dialogManagerId, }: PropsWithChildren<DialogPortalEntryProps>) => React.JSX.Element;
8
9
  export {};
@@ -1,19 +1,35 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import { useDialogIsOpen, useOpenedDialogCount } from './hooks';
3
3
  import { Portal } from '../Portal/Portal';
4
- import { useDialogManager } from '../../context';
4
+ import { useDialogManager, useNearestDialogManagerContext } from '../../context';
5
5
  export const DialogPortalDestination = () => {
6
- const { dialogManager } = useDialogManager();
7
- const openedDialogCount = useOpenedDialogCount();
6
+ const { dialogManager } = useNearestDialogManagerContext() ?? {};
7
+ const openedDialogCount = useOpenedDialogCount({ dialogManagerId: dialogManager?.id });
8
+ // const [destinationRoot, setDestinationRoot] = useState<HTMLDivElement | null>(null);
9
+ // todo: allow to configure and then enable
10
+ // useEffect(() => {
11
+ // if (!destinationRoot) return;
12
+ // const handleClickOutside = (event: MouseEvent) => {
13
+ // if (!destinationRoot?.contains(event.target as Node)) {
14
+ // dialogManager?.closeAll();
15
+ // }
16
+ // };
17
+ // document.addEventListener('click', handleClickOutside, { capture: true });
18
+ // return () => {
19
+ // document.removeEventListener('click', handleClickOutside, { capture: true });
20
+ // };
21
+ // }, [destinationRoot, dialogManager]);
8
22
  if (!openedDialogCount)
9
23
  return null;
10
- return (React.createElement("div", { className: 'str-chat__dialog-overlay', "data-str-chat__portal-id": dialogManager.id, "data-testid": 'str-chat__dialog-overlay', onClick: () => dialogManager.closeAll(), style: {
24
+ return (React.createElement("div", { className: 'str-chat__dialog-overlay', "data-str-chat__portal-id": dialogManager?.id, "data-testid": 'str-chat__dialog-overlay', onClick: () => dialogManager?.closeAll(),
25
+ // ref={setDestinationRoot}
26
+ style: {
11
27
  '--str-chat__dialog-overlay-height': openedDialogCount > 0 ? '100%' : '0',
12
28
  } }));
13
29
  };
14
- export const DialogPortalEntry = ({ children, dialogId, }) => {
15
- const { dialogManager } = useDialogManager({ dialogId });
16
- const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager.id);
30
+ export const DialogPortalEntry = ({ children, dialogId, dialogManagerId, }) => {
31
+ const { dialogManager } = useDialogManager({ dialogId, dialogManagerId });
32
+ const dialogIsOpen = useDialogIsOpen(dialogId, dialogManagerId);
17
33
  const getPortalDestination = useCallback(() => document.querySelector(`div[data-str-chat__portal-id="${dialogManager.id}"]`), [dialogManager.id]);
18
34
  return (React.createElement(Portal, { getPortalDestination: getPortalDestination, isOpen: dialogIsOpen }, children));
19
35
  };
@@ -1 +1,2 @@
1
1
  export * from './useDialog';
2
+ export type { PopperLikePlacement } from './usePopoverPosition';
@@ -3,6 +3,10 @@ export type UseDialogParams = GetOrCreateDialogParams & {
3
3
  dialogManagerId?: string;
4
4
  };
5
5
  export declare const useDialog: ({ dialogManagerId, id }: UseDialogParams) => import("../DialogManager").Dialog;
6
+ export declare const useDialogOnNearestManager: ({ id }: Pick<UseDialogParams, 'id'>) => {
7
+ dialog: import("../DialogManager").Dialog;
8
+ dialogManager: import("../DialogManager").DialogManager | undefined;
9
+ };
6
10
  export declare const modalDialogId: "modal-dialog";
7
11
  export declare const useModalDialog: () => import("../DialogManager").Dialog;
8
12
  export declare const useDialogIsOpen: (id: string, dialogManagerId?: string) => boolean;
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect } from 'react';
2
- import { modalDialogManagerId, useDialogManager } from '../../../context';
2
+ import { modalDialogManagerId, useDialogManager, useNearestDialogManagerContext, } from '../../../context';
3
3
  import { useStateStore } from '../../../store';
4
4
  export const useDialog = ({ dialogManagerId, id }) => {
5
5
  const { dialogManager } = useDialogManager({ dialogManagerId });
@@ -12,6 +12,14 @@ export const useDialog = ({ dialogManagerId, id }) => {
12
12
  }, [dialogManager, id]);
13
13
  return dialogManager.getOrCreate({ id });
14
14
  };
15
+ export const useDialogOnNearestManager = ({ id }) => {
16
+ const { dialogManager } = useNearestDialogManagerContext() ?? {};
17
+ const dialog = useDialog({ dialogManagerId: dialogManager?.id, id });
18
+ return {
19
+ dialog,
20
+ dialogManager,
21
+ };
22
+ };
15
23
  export const modalDialogId = 'modal-dialog';
16
24
  export const useModalDialog = () => useDialog({ dialogManagerId: modalDialogManagerId, id: modalDialogId });
17
25
  export const useDialogIsOpen = (id, dialogManagerId) => {
@@ -0,0 +1,68 @@
1
+ /// <reference types="react" />
2
+ import { autoUpdate, type Placement } from '@floating-ui/react';
3
+ export type PopperLikePlacement = Placement | 'auto' | 'auto-start' | 'auto-end';
4
+ type OffsetOpt = number | {
5
+ mainAxis?: number;
6
+ crossAxis?: number;
7
+ alignmentAxis?: number;
8
+ } | [crossAxis: number, mainAxis: number];
9
+ export type UsePopoverParams = {
10
+ placement?: PopperLikePlacement;
11
+ /** Add flip() when placement is not 'auto*' */
12
+ allowFlip?: boolean;
13
+ /** Keep in viewport; default true to match common popper setups */
14
+ allowShift?: boolean;
15
+ /** The floating UI is fitted to the available space (by constraining its max size) instead of letting it overflow; default false */
16
+ fitAvailableSpace?: boolean;
17
+ /** Offset (number, object, or [crossAxis, mainAxis] tuple) */
18
+ offset?: OffsetOpt;
19
+ /**
20
+ * Freeze behavior like Popper's eventListeners: { scroll:false, resize:false }.
21
+ * If true → no autoUpdate (you can call `update()` manually).
22
+ */
23
+ freeze?: boolean;
24
+ /**
25
+ * Fine-grained control of autoUpdate triggers (only if freeze=false).
26
+ * Defaults match Popper's "disabled" example when all set to false.
27
+ */
28
+ autoUpdateOptions?: Partial<Parameters<typeof autoUpdate>[3]>;
29
+ };
30
+ export declare function usePopoverPosition({ allowFlip, allowShift, autoUpdateOptions, fitAvailableSpace, freeze, offset, placement, }: UsePopoverParams): {
31
+ placement: Placement;
32
+ strategy: import("@floating-ui/utils").Strategy;
33
+ middlewareData: import("@floating-ui/core").MiddlewareData;
34
+ y: number;
35
+ x: number;
36
+ isPositioned: boolean;
37
+ update: () => void;
38
+ floatingStyles: import("react").CSSProperties;
39
+ refs: {
40
+ reference: import("react").MutableRefObject<import("@floating-ui/react-dom").ReferenceType | null>;
41
+ floating: import("react").MutableRefObject<HTMLElement | null>;
42
+ setReference: (node: import("@floating-ui/react-dom").ReferenceType | null) => void;
43
+ setFloating: (node: HTMLElement | null) => void;
44
+ } & import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
45
+ elements: {
46
+ reference: import("@floating-ui/react-dom").ReferenceType | null;
47
+ floating: HTMLElement | null;
48
+ } & import("@floating-ui/react").ExtendedElements<import("@floating-ui/react").ReferenceType>;
49
+ context: {
50
+ y: number;
51
+ x: number;
52
+ placement: Placement;
53
+ strategy: import("@floating-ui/utils").Strategy;
54
+ middlewareData: import("@floating-ui/core").MiddlewareData;
55
+ isPositioned: boolean;
56
+ update: () => void;
57
+ floatingStyles: import("react").CSSProperties;
58
+ open: boolean;
59
+ onOpenChange: (open: boolean, event?: Event | undefined, reason?: import("@floating-ui/react").OpenChangeReason | undefined) => void;
60
+ events: import("@floating-ui/react").FloatingEvents;
61
+ dataRef: import("react").MutableRefObject<import("@floating-ui/react").ContextData>;
62
+ nodeId: string | undefined;
63
+ floatingId: string | undefined;
64
+ refs: import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
65
+ elements: import("@floating-ui/react").ExtendedElements<import("@floating-ui/react").ReferenceType>;
66
+ };
67
+ };
68
+ export {};
@@ -0,0 +1,54 @@
1
+ import { autoPlacement, autoUpdate, flip as flipMw, offset as offsetMw, shift as shiftMw, size as sizeMw, useFloating, } from '@floating-ui/react';
2
+ const hasResizeObserver = typeof window !== 'undefined' && 'ResizeObserver' in window;
3
+ function autoMiddlewareFor(p) {
4
+ if (!String(p).startsWith('auto'))
5
+ return null;
6
+ const alignment = p === 'auto-start' ? 'start' : p === 'auto-end' ? 'end' : undefined;
7
+ return autoPlacement({ alignment });
8
+ }
9
+ function toOffsetMw(opt) {
10
+ if (opt == null)
11
+ return null;
12
+ if (Array.isArray(opt)) {
13
+ const [crossAxis, mainAxis] = opt;
14
+ return offsetMw({ crossAxis, mainAxis });
15
+ }
16
+ if (typeof opt === 'number')
17
+ return offsetMw(opt);
18
+ return offsetMw(opt);
19
+ }
20
+ export function usePopoverPosition({ allowFlip = true, allowShift = true, autoUpdateOptions, fitAvailableSpace = false, freeze = false, offset, placement = 'bottom-start', }) {
21
+ const autoMw = autoMiddlewareFor(placement);
22
+ const offsetMiddleware = toOffsetMw(offset);
23
+ const isSidePlacement = placement.startsWith('left') || placement.startsWith('right');
24
+ const middleware = [
25
+ // offset first (mirrors common Popper setups)
26
+ ...(offsetMiddleware ? [offsetMiddleware] : []),
27
+ // choose between autoPlacement (Popper's "auto*") OR flip()
28
+ // only allow flip when not explicitly 'left*' or 'right*'
29
+ ...(autoMw ? [autoMw] : allowFlip && !isSidePlacement ? [flipMw()] : []),
30
+ // viewport collision adjustments
31
+ ...(allowShift ? [shiftMw({ padding: 8 })] : []),
32
+ // optional size constraining
33
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
34
+ ...(fitAvailableSpace ? [sizeMw({ apply: () => { } })] : []),
35
+ ];
36
+ // if placement is 'auto*', seed with any static placement; autoPlacement will pick the final one
37
+ const seedPlacement = String(placement).startsWith('auto')
38
+ ? 'bottom'
39
+ : placement;
40
+ return useFloating({
41
+ middleware,
42
+ placement: seedPlacement,
43
+ strategy: 'fixed',
44
+ whileElementsMounted: freeze
45
+ ? undefined
46
+ : (reference, floating, update) => autoUpdate(reference, floating, update, {
47
+ ancestorResize: true,
48
+ ancestorScroll: true,
49
+ animationFrame: false,
50
+ elementResize: hasResizeObserver,
51
+ ...autoUpdateOptions,
52
+ }),
53
+ });
54
+ }
@@ -1,6 +1,6 @@
1
1
  import type { PropsWithChildren } from 'react';
2
2
  import React from 'react';
3
- import type { Placement } from '@popperjs/core';
3
+ import type { PopperLikePlacement } from '../Dialog';
4
4
  type DropdownContextValue = {
5
5
  close(): void;
6
6
  };
@@ -8,7 +8,7 @@ export declare const useDropdownContext: () => DropdownContextValue;
8
8
  export type DropdownProps = PropsWithChildren<{
9
9
  className?: string;
10
10
  openButtonProps?: React.HTMLAttributes<HTMLButtonElement>;
11
- placement?: Placement;
11
+ placement?: PopperLikePlacement;
12
12
  }>;
13
13
  export declare const Dropdown: (props: DropdownProps) => React.JSX.Element;
14
14
  export {};
@@ -116,5 +116,5 @@ export const Message = (props) => {
116
116
  notify: addNotification,
117
117
  });
118
118
  const highlighted = highlightedMessageId === message.id;
119
- return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: props.customMessageActions, deliveredTo: props.deliveredTo, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage, lastReceivedId: props.lastReceivedId, message: message, Message: props.Message, messageActions: props.messageActions, messageListRect: props.messageListRect, mutes: mutes, onMentionsClickMessage: onMentionsClick, onMentionsHoverMessage: onMentionsHover, onUserClick: props.onUserClick, onUserHover: props.onUserHover, pinPermissions: props.pinPermissions, reactionDetailsSort: reactionDetailsSort, readBy: props.readBy, renderText: props.renderText, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: props.threadList, unsafeHTML: props.unsafeHTML, userRoles: userRoles }));
119
+ return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: props.customMessageActions, deliveredTo: props.deliveredTo, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage, lastOwnMessage: props.lastOwnMessage, lastReceivedId: props.lastReceivedId, message: message, Message: props.Message, messageActions: props.messageActions, messageListRect: props.messageListRect, mutes: mutes, onMentionsClickMessage: onMentionsClick, onMentionsHoverMessage: onMentionsHover, onUserClick: props.onUserClick, onUserHover: props.onUserHover, pinPermissions: props.pinPermissions, reactionDetailsSort: reactionDetailsSort, readBy: props.readBy, renderText: props.renderText, returnAllReadData: props.returnAllReadData, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: props.threadList, unsafeHTML: props.unsafeHTML, userRoles: userRoles }));
120
120
  };
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import { useTranslationContext } from '../../context/TranslationContext';
3
+ import { useChannelStateContext } from '../../context';
3
4
  const UnMemoizedMessageRepliesCountButton = (props) => {
4
5
  const { labelPlural, labelSingle, onClick, reply_count = 0 } = props;
6
+ const { channelCapabilities } = useChannelStateContext();
5
7
  const { t } = useTranslationContext('MessageRepliesCountButton');
6
8
  if (!reply_count)
7
9
  return null;
@@ -13,6 +15,6 @@ const UnMemoizedMessageRepliesCountButton = (props) => {
13
15
  replyCountText = `1 ${labelSingle}`;
14
16
  }
15
17
  return (React.createElement("div", { className: 'str-chat__message-replies-count-button-wrapper' },
16
- React.createElement("button", { className: 'str-chat__message-replies-count-button', "data-testid": 'replies-count-button', onClick: onClick }, replyCountText)));
18
+ React.createElement("button", { className: 'str-chat__message-replies-count-button', "data-testid": 'replies-count-button', disabled: !channelCapabilities['send-reply'], onClick: onClick }, replyCountText)));
17
19
  };
18
20
  export const MessageRepliesCountButton = React.memo(UnMemoizedMessageRepliesCountButton);
@@ -15,7 +15,7 @@ const UnMemoizedMessageStatus = (props) => {
15
15
  const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers();
16
16
  const { client } = useChatContext('MessageStatus');
17
17
  const { Avatar: contextAvatar } = useComponentContext('MessageStatus');
18
- const { deliveredTo, isMyMessage, message, readBy, threadList } = useMessageContext('MessageStatus');
18
+ const { deliveredTo, isMyMessage, lastOwnMessage, message, readBy, returnAllReadData, threadList, } = useMessageContext('MessageStatus');
19
19
  const { t } = useTranslationContext('MessageStatus');
20
20
  const [referenceElement, setReferenceElement] = useState(null);
21
21
  const Avatar = propAvatar || contextAvatar || DefaultAvatar;
@@ -26,7 +26,11 @@ const UnMemoizedMessageStatus = (props) => {
26
26
  const sending = message.status === 'sending';
27
27
  const read = !!(readBy?.length && !justReadByMe && !threadList);
28
28
  const delivered = !!(deliveredTo?.length && !deliveredOnlyToMe && !read && !threadList);
29
- const sent = message.status === 'received' && !delivered && !read && !threadList;
29
+ const sent = (returnAllReadData || lastOwnMessage?.id === message.id) &&
30
+ message.status === 'received' &&
31
+ !delivered &&
32
+ !read &&
33
+ !threadList;
30
34
  const readersWithoutOwnUser = read
31
35
  ? readBy.filter((item) => item.id !== client.user?.id)
32
36
  : [];
@@ -2,3 +2,4 @@ export * from './htmlToTextPlugin';
2
2
  export * from './imageToLink';
3
3
  export * from './keepLineBreaksPlugin';
4
4
  export * from './plusPlusToEmphasis';
5
+ export * from './remarkIgnoreMarkdown';
@@ -2,3 +2,4 @@ export * from './htmlToTextPlugin';
2
2
  export * from './imageToLink';
3
3
  export * from './keepLineBreaksPlugin';
4
4
  export * from './plusPlusToEmphasis';
5
+ export * from './remarkIgnoreMarkdown';
@@ -1,2 +1,10 @@
1
- import type { Nodes } from 'hast-util-find-and-replace/lib';
2
- export declare const keepLineBreaksPlugin: () => (tree: Nodes) => void;
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'mdast';
3
+ /**
4
+ * Inserts runs of <br> between sibling block nodes to preserve the exact
5
+ * number of *blank source lines* between them. No paragraph wrappers are added.
6
+ *
7
+ * Works because `mdast-util-to-hast` respects `data.hName`, turning our
8
+ * `thematicBreak` into `<br>`. Multiple blank lines -> multiple `<br>` siblings.
9
+ */
10
+ export declare const keepLineBreaksPlugin: Plugin<[], Root>;
@@ -1,28 +1,48 @@
1
1
  import { visit } from 'unist-util-visit';
2
- import { u } from 'unist-builder';
3
- const visitor = (node, index, parent) => {
4
- if (!(index && parent && node.position))
5
- return;
6
- const prevSibling = parent.children.at(index - 1);
7
- if (!prevSibling?.position)
8
- return;
9
- if (node.position.start.line === prevSibling.position.start.line)
10
- return;
11
- const ownStartLine = node.position.start.line;
12
- const prevEndLine = prevSibling.position.end.line;
13
- // the -1 is adjustment for the single line break into which multiple line breaks are converted
14
- const countTruncatedLineBreaks = ownStartLine - prevEndLine - 1;
15
- if (countTruncatedLineBreaks < 1)
16
- return;
17
- const lineBreaks = Array.from({ length: countTruncatedLineBreaks }, () => u('break', { tagName: 'br' }));
18
- parent.children = [
19
- ...parent.children.slice(0, index),
20
- ...lineBreaks,
21
- ...parent.children.slice(index),
22
- ];
23
- return;
2
+ /** Type guard: does the node have mdast children? */
3
+ function isParentWithChildren(node) {
4
+ const maybe = node;
5
+ return Array.isArray(maybe.children);
6
+ }
7
+ /** Build a single <br> by mapping a standard mdast node via data.hName */
8
+ function brNode() {
9
+ return { data: { hName: 'br' }, type: 'thematicBreak' };
10
+ }
11
+ /**
12
+ * Inserts runs of <br> between sibling block nodes to preserve the exact
13
+ * number of *blank source lines* between them. No paragraph wrappers are added.
14
+ *
15
+ * Works because `mdast-util-to-hast` respects `data.hName`, turning our
16
+ * `thematicBreak` into `<br>`. Multiple blank lines -> multiple `<br>` siblings.
17
+ */
18
+ export const keepLineBreaksPlugin = () => (tree) => {
19
+ visit(tree, // visit needs a Unist parent-like root
20
+ isParentWithChildren, // limit to parents with children
21
+ (parent) => {
22
+ const children = parent.children;
23
+ if (children.length < 2)
24
+ return;
25
+ const out = [];
26
+ for (let i = 0; i < children.length; i++) {
27
+ const curr = children[i];
28
+ out.push(curr);
29
+ if (i === children.length - 1)
30
+ break;
31
+ const next = children[i + 1];
32
+ const currEndLine = curr.position && curr.position.end ? curr.position.end.line : undefined;
33
+ const nextStartLine = next.position && next.position.start ? next.position.start.line : undefined;
34
+ if (typeof currEndLine !== 'number' || typeof nextStartLine !== 'number') {
35
+ continue;
36
+ }
37
+ // Markdown already separates blocks by at least one visual gap.
38
+ // We add back only the *extra* blank lines from the source.
39
+ const extraBlankLines = Math.max(0, nextStartLine - currEndLine - 1);
40
+ if (extraBlankLines > 0) {
41
+ for (let k = 0; k < extraBlankLines; k++) {
42
+ out.push(brNode());
43
+ }
44
+ }
45
+ }
46
+ parent.children = out;
47
+ });
24
48
  };
25
- const transform = (tree) => {
26
- visit(tree, visitor);
27
- };
28
- export const keepLineBreaksPlugin = () => transform;
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'mdast';
3
+ /**
4
+ * Replace the parsed Markdown tree with a single paragraph containing the
5
+ * original source as a plain text node. No Markdown formatting is interpreted.
6
+ * React will escape it.
7
+ */
8
+ export declare const remarkIgnoreMarkdown: Plugin<[], Root>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Replace the parsed Markdown tree with a single paragraph containing the
3
+ * original source as a plain text node. No Markdown formatting is interpreted.
4
+ * React will escape it.
5
+ */
6
+ export const remarkIgnoreMarkdown = () => (tree, file) => {
7
+ const source = String(file.value ?? '');
8
+ const text = { type: 'text', value: source };
9
+ const paragraph = { children: [text], type: 'paragraph' };
10
+ tree.children = [paragraph];
11
+ };