@droppii-org/chat-sdk 0.6.37 → 0.6.39

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 (66) hide show
  1. package/dist/assets/images/stickerInvalid.png +0 -0
  2. package/dist/components/conversation-detail/ConversationDetailInput.d.ts +3 -1
  3. package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
  4. package/dist/components/conversation-detail/ConversationDetailInput.js +32 -5
  5. package/dist/components/conversation-detail/TypingIndicatorBar.d.ts +10 -0
  6. package/dist/components/conversation-detail/TypingIndicatorBar.d.ts.map +1 -0
  7. package/dist/components/conversation-detail/TypingIndicatorBar.js +24 -0
  8. package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.d.ts +9 -0
  9. package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.d.ts.map +1 -0
  10. package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.js +38 -0
  11. package/dist/components/conversation-detail/sticker-picker/StickerPicker.d.ts +7 -0
  12. package/dist/components/conversation-detail/sticker-picker/StickerPicker.d.ts.map +1 -0
  13. package/dist/components/conversation-detail/sticker-picker/StickerPicker.js +113 -0
  14. package/dist/components/conversation-detail/sticker-picker/StickerSection.d.ts +12 -0
  15. package/dist/components/conversation-detail/sticker-picker/StickerSection.d.ts.map +1 -0
  16. package/dist/components/conversation-detail/sticker-picker/StickerSection.js +46 -0
  17. package/dist/components/conversation-detail/sticker-picker/constants.d.ts +8 -0
  18. package/dist/components/conversation-detail/sticker-picker/constants.d.ts.map +1 -0
  19. package/dist/components/conversation-detail/sticker-picker/constants.js +1 -0
  20. package/dist/components/message/item/StickerMessage.d.ts.map +1 -1
  21. package/dist/components/message/item/StickerMessage.js +3 -5
  22. package/dist/components/message/reply/ReplyPreviewMedia.d.ts.map +1 -1
  23. package/dist/components/message/reply/ReplyPreviewMedia.js +8 -2
  24. package/dist/constants/images.d.ts +1 -0
  25. package/dist/constants/images.d.ts.map +1 -1
  26. package/dist/constants/images.js +2 -0
  27. package/dist/context/ChatContext.d.ts.map +1 -1
  28. package/dist/context/ChatContext.js +1 -1
  29. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  30. package/dist/hooks/global/useGlobalEvent.js +10 -1
  31. package/dist/hooks/message/useReportTypingState.d.ts +10 -0
  32. package/dist/hooks/message/useReportTypingState.d.ts.map +1 -0
  33. package/dist/hooks/message/useReportTypingState.js +73 -0
  34. package/dist/hooks/message/useSendMessage.d.ts +7 -1
  35. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  36. package/dist/hooks/message/useSendMessage.js +45 -2
  37. package/dist/hooks/message/useTypingStatus.d.ts +11 -0
  38. package/dist/hooks/message/useTypingStatus.d.ts.map +1 -0
  39. package/dist/hooks/message/useTypingStatus.js +120 -0
  40. package/dist/hooks/sticker/useStickers.d.ts +5 -0
  41. package/dist/hooks/sticker/useStickers.d.ts.map +1 -0
  42. package/dist/hooks/sticker/useStickers.js +39 -0
  43. package/dist/hooks/user/useAuth.d.ts.map +1 -1
  44. package/dist/hooks/user/useAuth.js +2 -0
  45. package/dist/locales/vi/conversation-inbox.json +11 -0
  46. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  47. package/dist/screens/conversation-detail/index.js +2 -1
  48. package/dist/screens/conversation-inbox/index.d.ts +2 -0
  49. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  50. package/dist/screens/conversation-inbox/index.js +2 -2
  51. package/dist/services/query.d.ts +3 -0
  52. package/dist/services/query.d.ts.map +1 -1
  53. package/dist/services/query.js +3 -0
  54. package/dist/services/routes.d.ts +3 -0
  55. package/dist/services/routes.d.ts.map +1 -1
  56. package/dist/services/routes.js +3 -0
  57. package/dist/store/typing.d.ts +13 -0
  58. package/dist/store/typing.d.ts.map +1 -0
  59. package/dist/store/typing.js +63 -0
  60. package/dist/styles/global.css +1 -1
  61. package/dist/types/chat.d.ts +1 -0
  62. package/dist/types/chat.d.ts.map +1 -1
  63. package/dist/types/sticker.d.ts +20 -0
  64. package/dist/types/sticker.d.ts.map +1 -0
  65. package/dist/types/sticker.js +1 -0
  66. package/package.json +1 -1
@@ -5,7 +5,9 @@ export interface ConversationDetailInputProps {
5
5
  onInputChange?: (value: string) => void;
6
6
  moreOptions?: MoreOptionConfig[];
7
7
  onOpenBotMenu?: () => void;
8
+ /** Shows the sticker picker entry in the composer. Defaults to true. */
9
+ showSticker?: boolean;
8
10
  }
9
- declare const ConversationDetailInput: ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, }: ConversationDetailInputProps) => import("react/jsx-runtime").JSX.Element;
11
+ declare const ConversationDetailInput: ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, showSticker, }: ConversationDetailInputProps) => import("react/jsx-runtime").JSX.Element;
10
12
  export default ConversationDetailInput;
11
13
  //# sourceMappingURL=ConversationDetailInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAmB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,QAAA,MAAM,uBAAuB,GAAI,4EAM9B,4BAA4B,4CA8rB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAoB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,QAAA,MAAM,uBAAuB,GAAI,yFAO9B,4BAA4B,4CAkvB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -7,6 +7,7 @@ import clsx from "clsx";
7
7
  import { Button, message as toastMessage } from "antd";
8
8
  import { Icon } from "../../components/icon";
9
9
  import { useSendMessage } from "../../hooks/message/useSendMessage";
10
+ import { useReportTypingState } from "../../hooks/message/useReportTypingState";
10
11
  import { useJoinPublicGroup } from "../../hooks/group/useJoinPublicGroup";
11
12
  import useAuthStore from "../../store/auth";
12
13
  import useConversationStore from "../../store/conversation";
@@ -19,6 +20,7 @@ import { FilePreviewList } from "../../components/message/footer/FilePreview";
19
20
  import LinkInputPreview from "../../components/message/footer/LinkInputPreview";
20
21
  import { ReplyPreviewBar } from "../../components/message/reply/ReplyPreviewBar";
21
22
  import ConversationMoreOptionPanel from "./ConversationMoreOptionPanel";
23
+ import StickerPicker from "./sticker-picker/StickerPicker";
22
24
  import { useMentionTrigger } from "./mention/useMentionTrigger";
23
25
  import { useMentionOptions } from "../../components/message/footer/mention/useMentionOptions";
24
26
  import { useMentionTokens } from "./mention/useMentionTokens";
@@ -30,7 +32,7 @@ const MAX_LINES = 5;
30
32
  const CONTAINER_MIN_HEIGHT = 48;
31
33
  const FALLBACK_LINE_HEIGHT = 25.6;
32
34
  const MAX_MENTIONED_USERS = 10;
33
- const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, }) => {
35
+ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, showSticker = true, }) => {
34
36
  var _a;
35
37
  const { t } = useTranslation("conversation-inbox");
36
38
  const { t: tCommon } = useTranslation("common");
@@ -74,7 +76,8 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
74
76
  const sendMessageOptions = useMemo(() => ({
75
77
  scrollEvent: "CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM",
76
78
  }), []);
77
- const { sendMergeMessage } = useSendMessage(sendMessageOptions);
79
+ const { sendMergeMessage, sendStickerMessage } = useSendMessage(sendMessageOptions);
80
+ const { reportActivity: reportTyping, stopTyping } = useReportTypingState(conversationId);
78
81
  const userID = useAuthStore((state) => state.userID);
79
82
  const setDraft = useMessageDraftStore((state) => state.setDraft);
80
83
  const clearDraft = useMessageDraftStore((state) => state.clearDraft);
@@ -93,6 +96,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
93
96
  const [isSending, setIsSending] = useState(false);
94
97
  const [previewDismissed, setPreviewDismissed] = useState(false);
95
98
  const [isMoreOptionOpen, { setFalse: closeMoreOption, toggle: toggleMoreOption },] = useBoolean(false);
99
+ const [isStickerOpen, { setFalse: closeSticker, toggle: toggleSticker },] = useBoolean(false);
96
100
  const [caretIndex, setCaretIndex] = useState(0);
97
101
  const rawMentionTrigger = useMentionTrigger(value, caretIndex);
98
102
  // Escape dismisses the menu for the CURRENT trigger span without erasing
@@ -167,10 +171,11 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
167
171
  }, [getTextareaHeights]);
168
172
  useEffect(() => {
169
173
  closeMoreOption();
174
+ closeSticker();
170
175
  setListUploadFiles([]);
171
176
  setPendingVideoUids([]);
172
177
  resetTokens();
173
- }, [conversationId, userID, closeMoreOption, resetTokens]);
178
+ }, [conversationId, userID, closeMoreOption, closeSticker, resetTokens]);
174
179
  useEffect(() => {
175
180
  setSelectedMentionIndex(0);
176
181
  }, [mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.triggerStart, mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.query]);
@@ -284,6 +289,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
284
289
  if (conversationId && userID) {
285
290
  setDraft(userID, conversationId, nextValue);
286
291
  }
292
+ reportTyping(nextValue);
287
293
  onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(nextValue);
288
294
  };
289
295
  // Covers caret-only movement (click, arrow keys) with no value change,
@@ -294,6 +300,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
294
300
  setCaretIndex((_a = event.currentTarget.selectionStart) !== null && _a !== void 0 ? _a : 0);
295
301
  };
296
302
  const resetComposer = () => {
303
+ stopTyping();
297
304
  setValue("");
298
305
  setListUploadFiles([]);
299
306
  setPendingVideoUids([]);
@@ -345,18 +352,35 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
345
352
  const handleFocus = () => {
346
353
  setIsFocused(true);
347
354
  closeMoreOption();
355
+ closeSticker();
348
356
  emit("CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM");
349
357
  };
350
358
  const handleBlur = () => {
351
359
  setIsFocused(false);
360
+ stopTyping();
352
361
  };
353
362
  const handleToggleMoreOption = () => {
354
363
  var _a;
355
364
  if (!isMoreOptionOpen) {
356
365
  (_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.blur();
366
+ closeSticker();
357
367
  }
358
368
  toggleMoreOption();
359
369
  };
370
+ const handleToggleSticker = () => {
371
+ var _a;
372
+ if (!isStickerOpen) {
373
+ (_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.blur();
374
+ closeMoreOption();
375
+ }
376
+ toggleSticker();
377
+ };
378
+ // Picker stays open after a send so several stickers can go out in a row.
379
+ const handleSelectSticker = (sticker) => {
380
+ if (!isConversationReady)
381
+ return;
382
+ void sendStickerMessage(sticker);
383
+ };
360
384
  const insertNewline = (textarea) => {
361
385
  const { selectionStart, selectionEnd } = textarea;
362
386
  const nextValue = value.slice(0, selectionStart) + "\n" + value.slice(selectionEnd);
@@ -388,6 +412,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
388
412
  if (conversationId && userID) {
389
413
  setDraft(userID, conversationId, nextValue);
390
414
  }
415
+ reportTyping(nextValue);
391
416
  onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(nextValue);
392
417
  const nextCaret = mentionTrigger.triggerStart + insertedText.length + 1;
393
418
  // Update caretIndex synchronously (not deferred to the RAF below) so
@@ -461,8 +486,10 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
461
486
  : "border-gray-50"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsxs("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: [onOpenBotMenu && (_jsx("button", { type: "button", onClick: onOpenBotMenu, "aria-label": t("thread_detail.bot.menu.open_menu"), "data-testid": "conversation-detail-open-bot-menu", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: "chat-suggest-o", size: 24, className: "text-gray-600" }) })), _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => {
462
487
  var _a;
463
488
  return handleChange(event.target.value, (_a = event.target.selectionStart) !== null && _a !== void 0 ? _a : event.target.value.length);
464
- }, onSelect: handleSelectionChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "conversation-detail-message-input", className: "conversation-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-gray-400 focus:border-0 focus:outline-none focus:ring-0" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, limitReached: mentionLimitReached, limitMax: MAX_MENTIONED_USERS, visible: true }))] }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "conversation-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend
489
+ }, onSelect: handleSelectionChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "conversation-detail-message-input", className: "conversation-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-gray-400 focus:border-0 focus:outline-none focus:ring-0" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, limitReached: mentionLimitReached, limitMax: MAX_MENTIONED_USERS, visible: true })), showSticker && (_jsx("button", { type: "button", onClick: handleToggleSticker, "aria-label": t("thread_detail.sticker.toggle"), "aria-expanded": isStickerOpen, "data-testid": "conversation-detail-sticker-toggle", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: isStickerOpen ? "sticker-b" : "sticker-o", size: 24, className: isStickerOpen
490
+ ? "text-[var(--dc-color-primary)]"
491
+ : "text-gray-600" }) }))] }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "conversation-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend
465
492
  ? "bg-[var(--dc-color-primary)]"
466
- : "bg-[var(--dc-color-primary)] opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }))] }));
493
+ : "bg-[var(--dc-color-primary)] opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } })), showSticker && isStickerOpen && (_jsx(StickerPicker, { onSelectSticker: handleSelectSticker }))] }));
467
494
  };
468
495
  export default ConversationDetailInput;
@@ -0,0 +1,10 @@
1
+ export interface TypingIndicatorBarProps {
2
+ conversationId?: string;
3
+ }
4
+ /**
5
+ * Overlays the bottom edge of the message list rather than taking layout
6
+ * space, so showing/hiding it never shifts the viewer's scroll position.
7
+ */
8
+ declare const TypingIndicatorBar: ({ conversationId }: TypingIndicatorBarProps) => import("react/jsx-runtime").JSX.Element | null;
9
+ export default TypingIndicatorBar;
10
+ //# sourceMappingURL=TypingIndicatorBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypingIndicatorBar.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/TypingIndicatorBar.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAqBD;;;GAGG;AACH,QAAA,MAAM,kBAAkB,GAAI,oBAAoB,uBAAuB,mDAkCtE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Fragment } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useTypingState } from "../../hooks/message/useTypingStatus";
6
+ const TYPING_DOT_DELAYS_MS = [0, 150, 300];
7
+ // Sampled from the design: name + text share one gray, dots are lighter.
8
+ const TEXT_COLOR = "text-[#747B7E]";
9
+ const TypingDots = () => (_jsx("span", { "aria-hidden": "true", className: "ml-1 inline-flex items-end gap-[2px] pb-[4px]", children: TYPING_DOT_DELAYS_MS.map((delay) => (_jsx("span", { className: "h-[3px] w-[3px] animate-bounce rounded-full bg-[#8D8E90]", style: { animationDelay: `${delay}ms` } }, delay))) }));
10
+ /**
11
+ * Overlays the bottom edge of the message list rather than taking layout
12
+ * space, so showing/hiding it never shifts the viewer's scroll position.
13
+ */
14
+ const TypingIndicatorBar = ({ conversationId }) => {
15
+ const { t } = useTranslation("conversation-inbox");
16
+ const { names, count } = useTypingState(conversationId);
17
+ if (count === 0)
18
+ return null;
19
+ // Names are rendered as JSX (not interpolated markup) so user-supplied
20
+ // nicknames can never be parsed as formatting tags.
21
+ const content = count > names.length ? (t("typing.many_label")) : (_jsxs(_Fragment, { children: [names.map((name, index) => (_jsxs(Fragment, { children: [index > 0 && ` ${t("typing.and")} `, _jsx("strong", { className: "font-semibold", children: name })] }, `${index}-${name}`))), " ", t("typing.suffix")] }));
22
+ return (_jsxs("div", { "data-testid": "conversation-typing-indicator", "data-cp-name": "TypingIndicatorBar", "aria-live": "polite", className: `pointer-events-none absolute inset-x-0 bottom-0 z-10 truncate bg-white/90 px-4 py-1 text-[13px] leading-[1.6] ${TEXT_COLOR}`, children: [content, _jsx(TypingDots, {})] }));
23
+ };
24
+ export default TypingIndicatorBar;
@@ -0,0 +1,9 @@
1
+ import { type StickerTab } from "./constants";
2
+ interface StickerCategoryTabsProps {
3
+ tabs: StickerTab[];
4
+ activeTabId: string | null;
5
+ onSelectTab: (tabId: string) => void;
6
+ }
7
+ declare const StickerCategoryTabs: ({ tabs, activeTabId, onSelectTab, }: StickerCategoryTabsProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default StickerCategoryTabs;
9
+ //# sourceMappingURL=StickerCategoryTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StickerCategoryTabs.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerCategoryTabs.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErE,UAAU,wBAAwB;IAChC,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAID,QAAA,MAAM,mBAAmB,GAAI,qCAI1B,wBAAwB,4CAiE1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import clsx from "clsx";
5
+ import { Icon } from "../../../components/icon";
6
+ import { RECENT_STICKER_TAB_ID } from "./constants";
7
+ const TAB_ROW_PADDING = 12;
8
+ const StickerCategoryTabs = ({ tabs, activeTabId, onSelectTab, }) => {
9
+ const rowRef = useRef(null);
10
+ const tabRefs = useRef(new Map());
11
+ // Scrolls only the tab row: scrollIntoView would also drag the host page
12
+ // when the picker sits partly off-screen.
13
+ useEffect(() => {
14
+ const row = rowRef.current;
15
+ const tab = activeTabId ? tabRefs.current.get(activeTabId) : undefined;
16
+ if (!row || !tab)
17
+ return;
18
+ const tabRight = tab.offsetLeft + tab.offsetWidth;
19
+ if (tab.offsetLeft < row.scrollLeft) {
20
+ row.scrollLeft = tab.offsetLeft - TAB_ROW_PADDING;
21
+ }
22
+ else if (tabRight > row.scrollLeft + row.clientWidth) {
23
+ row.scrollLeft = tabRight - row.clientWidth + TAB_ROW_PADDING;
24
+ }
25
+ }, [activeTabId]);
26
+ return (_jsx("div", { ref: rowRef, role: "tablist", "data-testid": "sticker-picker-tabs", className: "relative flex h-[52px] shrink-0 items-center gap-3 overflow-x-auto px-3 py-2 [scrollbar-width:none]", children: tabs.map((tab) => {
27
+ const isActive = tab.id === activeTabId;
28
+ return (_jsx("button", { ref: (element) => {
29
+ if (element)
30
+ tabRefs.current.set(tab.id, element);
31
+ else
32
+ tabRefs.current.delete(tab.id);
33
+ }, type: "button", role: "tab", "aria-selected": isActive, "aria-label": tab.name, "data-testid": "sticker-picker-tab", "data-tab-id": tab.id, onClick: () => onSelectTab(tab.id), className: clsx("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border p-1", isActive
34
+ ? "border-[#505759] bg-[#393E40]/10"
35
+ : "border-transparent"), children: tab.id === RECENT_STICKER_TAB_ID ? (_jsx(Icon, { icon: "time-circle-b", size: 24, className: "text-[#393E40]" })) : tab.iconUrl ? (_jsx("img", { src: tab.iconUrl, alt: "", className: "h-7 w-7 object-contain" })) : (_jsx("span", { className: "h-7 w-7 rounded bg-gray-100" })) }, tab.id));
36
+ }) }));
37
+ };
38
+ export default StickerCategoryTabs;
@@ -0,0 +1,7 @@
1
+ import type { StickerItem } from "../../../types/sticker";
2
+ interface StickerPickerProps {
3
+ onSelectSticker: (sticker: StickerItem) => void;
4
+ }
5
+ declare const StickerPicker: ({ onSelectSticker }: StickerPickerProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default StickerPicker;
7
+ //# sourceMappingURL=StickerPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StickerPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerPicker.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,UAAU,kBAAkB;IAC1B,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAMD,QAAA,MAAM,aAAa,GAAI,qBAAqB,kBAAkB,4CA0J7D,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,113 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useStickerCategories, useStickerHistory, } from "../../../hooks/sticker/useStickers";
6
+ import StickerCategoryTabs from "./StickerCategoryTabs";
7
+ import StickerSection from "./StickerSection";
8
+ import { RECENT_STICKER_TAB_ID } from "./constants";
9
+ // Long enough for a smooth scrollTo to finish, so the scroll-spy doesn't
10
+ // flick the active tab through every section passed on the way.
11
+ const TAB_SCROLL_LOCK_MS = 600;
12
+ const StickerPicker = ({ onSelectSticker }) => {
13
+ const { t } = useTranslation("conversation-inbox");
14
+ const categoriesQuery = useStickerCategories();
15
+ const historyQuery = useStickerHistory();
16
+ const recentStickers = useMemo(() => { var _a; return (_a = historyQuery.data) !== null && _a !== void 0 ? _a : []; }, [historyQuery.data]);
17
+ const tabs = useMemo(() => {
18
+ var _a;
19
+ const categoryTabs = ((_a = categoriesQuery.data) !== null && _a !== void 0 ? _a : []).map((category) => {
20
+ var _a;
21
+ return ({
22
+ id: category.id,
23
+ name: category.name,
24
+ iconUrl: (_a = category.icon) === null || _a === void 0 ? void 0 : _a.fullUrl,
25
+ stickerCount: category.stickerCount,
26
+ });
27
+ });
28
+ if (recentStickers.length === 0)
29
+ return categoryTabs;
30
+ return [
31
+ {
32
+ id: RECENT_STICKER_TAB_ID,
33
+ name: t("thread_detail.sticker.recent"),
34
+ stickerCount: recentStickers.length,
35
+ },
36
+ ...categoryTabs,
37
+ ];
38
+ }, [categoriesQuery.data, recentStickers.length, t]);
39
+ // Wait for history too: a Recent tab arriving after the categories would
40
+ // shove every section down and flip the default tab under the user.
41
+ const isLoading = categoriesQuery.isLoading ||
42
+ (historyQuery.isLoading && !historyQuery.isError);
43
+ const [activeTabId, setActiveTabId] = useState(null);
44
+ const [scrollRoot, setScrollRoot] = useState(null);
45
+ const sectionRefs = useRef(new Map());
46
+ const isTabScrollLockedRef = useRef(false);
47
+ const lockTimerRef = useRef(null);
48
+ useEffect(() => {
49
+ var _a, _b;
50
+ if (isLoading)
51
+ return;
52
+ if (!activeTabId || !tabs.some((tab) => tab.id === activeTabId)) {
53
+ setActiveTabId((_b = (_a = tabs[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
54
+ }
55
+ }, [activeTabId, isLoading, tabs]);
56
+ useEffect(() => () => {
57
+ if (lockTimerRef.current)
58
+ clearTimeout(lockTimerRef.current);
59
+ }, []);
60
+ const registerSectionRef = useCallback((tabId, element) => {
61
+ if (element)
62
+ sectionRefs.current.set(tabId, element);
63
+ else
64
+ sectionRefs.current.delete(tabId);
65
+ }, []);
66
+ const handleScroll = useCallback(() => {
67
+ var _a, _b;
68
+ if (!scrollRoot || isTabScrollLockedRef.current)
69
+ return;
70
+ const { scrollTop, clientHeight, scrollHeight } = scrollRoot;
71
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
72
+ let nextTabId = (_b = (_a = tabs[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
73
+ for (const tab of tabs) {
74
+ const section = sectionRefs.current.get(tab.id);
75
+ if (section && section.offsetTop <= scrollTop + 1)
76
+ nextTabId = tab.id;
77
+ }
78
+ if (isAtBottom && tabs.length > 0)
79
+ nextTabId = tabs[tabs.length - 1].id;
80
+ setActiveTabId(nextTabId);
81
+ }, [scrollRoot, tabs]);
82
+ const handleSelectTab = useCallback((tabId) => {
83
+ setActiveTabId(tabId);
84
+ const section = sectionRefs.current.get(tabId);
85
+ if (!scrollRoot || !section)
86
+ return;
87
+ isTabScrollLockedRef.current = true;
88
+ if (lockTimerRef.current)
89
+ clearTimeout(lockTimerRef.current);
90
+ lockTimerRef.current = setTimeout(() => {
91
+ isTabScrollLockedRef.current = false;
92
+ }, TAB_SCROLL_LOCK_MS);
93
+ if (typeof scrollRoot.scrollTo === "function") {
94
+ scrollRoot.scrollTo({ top: section.offsetTop, behavior: "smooth" });
95
+ }
96
+ else {
97
+ scrollRoot.scrollTop = section.offsetTop;
98
+ }
99
+ }, [scrollRoot]);
100
+ const renderBody = () => {
101
+ if (isLoading) {
102
+ return (_jsx("div", { className: "grid grid-cols-4 gap-2 px-3 py-3", children: Array.from({ length: 8 }, (_, index) => (_jsx("div", { "data-testid": "sticker-picker-skeleton", className: "aspect-square w-full animate-pulse rounded-lg bg-gray-100" }, index))) }));
103
+ }
104
+ if (categoriesQuery.isError || tabs.length === 0) {
105
+ return (_jsx("p", { className: "px-3 py-6 text-center text-sm text-gray-500", children: categoriesQuery.isError
106
+ ? t("thread_detail.sticker.load_failed")
107
+ : t("thread_detail.sticker.empty") }));
108
+ }
109
+ return tabs.map((tab) => (_jsx(StickerSection, { tab: tab, recentStickers: recentStickers, scrollRoot: scrollRoot, registerRef: registerSectionRef, onSelectSticker: onSelectSticker }, tab.id)));
110
+ };
111
+ return (_jsxs("div", { "data-testid": "sticker-picker", "data-cp-name": "StickerPicker", className: "flex h-[312px] flex-col bg-white", children: [!isLoading && tabs.length > 0 && (_jsx(StickerCategoryTabs, { tabs: tabs, activeTabId: activeTabId, onSelectTab: handleSelectTab })), _jsx("div", { ref: setScrollRoot, onScroll: handleScroll, "data-testid": "sticker-picker-scroll", className: "relative min-h-0 flex-1 overflow-y-auto", children: renderBody() })] }));
112
+ };
113
+ export default StickerPicker;
@@ -0,0 +1,12 @@
1
+ import type { StickerItem } from "../../../types/sticker";
2
+ import { type StickerTab } from "./constants";
3
+ interface StickerSectionProps {
4
+ tab: StickerTab;
5
+ recentStickers: StickerItem[];
6
+ scrollRoot: HTMLElement | null;
7
+ registerRef: (tabId: string, element: HTMLElement | null) => void;
8
+ onSelectSticker: (sticker: StickerItem) => void;
9
+ }
10
+ declare const StickerSection: ({ tab, recentStickers, scrollRoot, registerRef, onSelectSticker, }: StickerSectionProps) => import("react/jsx-runtime").JSX.Element;
11
+ export default StickerSection;
12
+ //# sourceMappingURL=StickerSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StickerSection.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerSection.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAIrE,UAAU,mBAAmB;IAC3B,GAAG,EAAE,UAAU,CAAC;IAChB,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAClE,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAyCD,QAAA,MAAM,cAAc,GAAI,oEAMrB,mBAAmB,4CAiErB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import clsx from "clsx";
5
+ import { images } from "../../../constants/images";
6
+ import { useStickersByCategory } from "../../../hooks/sticker/useStickers";
7
+ import { RECENT_STICKER_TAB_ID } from "./constants";
8
+ const PLACEHOLDER_CELL_COUNT = 4;
9
+ const StickerCell = ({ sticker, onSelect, }) => {
10
+ var _a;
11
+ const [isLoaded, setIsLoaded] = useState(false);
12
+ const [hasError, setHasError] = useState(false);
13
+ return (_jsx("button", { type: "button", "aria-label": sticker.name, "data-testid": "sticker-picker-item", onClick: () => onSelect(sticker), className: clsx("relative aspect-square w-full rounded-lg", !isLoaded && "animate-pulse bg-gray-100"), children: _jsx("img", { src: hasError ? images.stickerInvalid : (_a = sticker.file) === null || _a === void 0 ? void 0 : _a.fullUrl, alt: "", loading: "lazy", onLoad: () => setIsLoaded(true), onError: () => {
14
+ setHasError(true);
15
+ setIsLoaded(true);
16
+ }, className: clsx("h-full w-full object-contain", !isLoaded && "opacity-0") }) }));
17
+ };
18
+ const StickerSection = ({ tab, recentStickers, scrollRoot, registerRef, onSelectSticker, }) => {
19
+ const sectionRef = useRef(null);
20
+ const isRecent = tab.id === RECENT_STICKER_TAB_ID;
21
+ const [hasBeenVisible, setHasBeenVisible] = useState(() => typeof IntersectionObserver === "undefined");
22
+ // Categories load on first approach, like chat-mobile, so opening the
23
+ // picker doesn't fire one request per category up front.
24
+ useEffect(() => {
25
+ const element = sectionRef.current;
26
+ if (hasBeenVisible || isRecent || !element || !scrollRoot)
27
+ return;
28
+ const observer = new IntersectionObserver((entries) => {
29
+ if (entries.some((entry) => entry.isIntersecting)) {
30
+ setHasBeenVisible(true);
31
+ }
32
+ }, { root: scrollRoot, rootMargin: "200px 0px" });
33
+ observer.observe(element);
34
+ return () => observer.disconnect();
35
+ }, [hasBeenVisible, isRecent, scrollRoot]);
36
+ const { data: categoryStickers, isError } = useStickersByCategory(tab.id, !isRecent && hasBeenVisible);
37
+ const stickers = isRecent ? recentStickers : categoryStickers;
38
+ const placeholderCount = tab.stickerCount || PLACEHOLDER_CELL_COUNT;
39
+ return (_jsxs("div", { ref: (element) => {
40
+ sectionRef.current = element;
41
+ registerRef(tab.id, element);
42
+ }, "data-testid": "sticker-picker-section", "data-tab-id": tab.id, children: [_jsx("p", { className: "px-3 py-1 text-[14px] font-bold leading-[22px] text-[#747B7E]", children: tab.name }), isError ? null : (_jsx("div", { className: "grid grid-cols-4 gap-2 px-3 pb-3", children: stickers
43
+ ? stickers.map((sticker) => (_jsx(StickerCell, { sticker: sticker, onSelect: onSelectSticker }, sticker.id)))
44
+ : Array.from({ length: placeholderCount }, (_, index) => (_jsx("div", { "data-testid": "sticker-picker-skeleton", className: "aspect-square w-full animate-pulse rounded-lg bg-gray-100" }, index))) }))] }));
45
+ };
46
+ export default StickerSection;
@@ -0,0 +1,8 @@
1
+ export declare const RECENT_STICKER_TAB_ID = "recent";
2
+ export interface StickerTab {
3
+ id: string;
4
+ name: string;
5
+ iconUrl?: string;
6
+ stickerCount: number;
7
+ }
8
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ export const RECENT_STICKER_TAB_ID = "recent";
@@ -1 +1 @@
1
- {"version":3,"file":"StickerMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/StickerMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,aAAa,uBAAuB,4CAyB/D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"StickerMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/StickerMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,aAAa,uBAAuB,4CAe/D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,15 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import clsx from "clsx";
4
- import { Icon } from "../../../components/icon";
4
+ import { images } from "../../../constants/images";
5
5
  const STICKER_SIZE_CLASS = "h-[120px] w-[120px]";
6
6
  const StickerMessageItem = ({ message }) => {
7
7
  var _a;
8
8
  const [hasLoadError, setHasLoadError] = useState(false);
9
9
  const stickerUrl = (_a = message.stickerElem) === null || _a === void 0 ? void 0 : _a.content;
10
- if (!stickerUrl || hasLoadError) {
11
- return (_jsx("div", { className: clsx("flex items-center justify-center rounded-xl bg-black/5", STICKER_SIZE_CLASS), children: _jsx(Icon, { icon: "image-o", size: 32, className: "text-gray-400" }) }));
12
- }
13
- return (_jsx("img", { src: stickerUrl, alt: "", className: clsx("block object-cover", STICKER_SIZE_CLASS), onError: () => setHasLoadError(true) }));
10
+ const isInvalid = !stickerUrl || hasLoadError;
11
+ return (_jsx("img", { src: isInvalid ? images.stickerInvalid : stickerUrl, alt: "", "data-testid": "sticker-message-image", "data-invalid": isInvalid || undefined, className: clsx("block object-contain", STICKER_SIZE_CLASS), onError: () => setHasLoadError(true) }));
14
12
  };
15
13
  export default StickerMessageItem;
@@ -1 +1 @@
1
- {"version":3,"file":"ReplyPreviewMedia.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyPreviewMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,8BAA8B,CAAC;AAKxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAoD7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ReplyPreviewMedia.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyPreviewMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,8BAA8B,CAAC;AAKxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAgE7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -4,7 +4,7 @@ import { documentIcon } from "../../../assets/svg";
4
4
  import { Icon } from "../../../components/icon";
5
5
  import { parseUrlMetadata } from "../../../utils/common";
6
6
  const ReplyPreviewMedia = ({ message }) => {
7
- var _a, _b, _c, _d, _e, _f;
7
+ var _a, _b, _c, _d, _e, _f, _g;
8
8
  if (message.contentType === MessageType.FileMessage) {
9
9
  return (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden [&>svg]:h-6 [&>svg]:w-6", children: documentIcon }));
10
10
  }
@@ -19,8 +19,14 @@ const ReplyPreviewMedia = ({ message }) => {
19
19
  const posterUrl = (_e = message.videoElem) === null || _e === void 0 ? void 0 : _e.snapshotUrl;
20
20
  return (_jsxs("div", { className: "relative h-[42px] w-[42px] shrink-0 overflow-hidden rounded-lg bg-gray-900", children: [posterUrl && (_jsx("img", { src: posterUrl, alt: "", className: "h-full w-full object-cover" })), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-black/60", children: _jsx(Icon, { icon: "play-o", size: 12, className: "text-white" }) }) })] }));
21
21
  }
22
+ if (message.contentType === MessageType.StickerMessage) {
23
+ const stickerUrl = (_f = message.stickerElem) === null || _f === void 0 ? void 0 : _f.content;
24
+ if (!stickerUrl)
25
+ return null;
26
+ return (_jsx("img", { src: stickerUrl, alt: "", className: "h-[42px] w-[42px] shrink-0 object-contain" }));
27
+ }
22
28
  if (message.contentType === MessageType.UrlTextMessage) {
23
- const imageUrl = (_f = parseUrlMetadata(message.ex)) === null || _f === void 0 ? void 0 : _f.image;
29
+ const imageUrl = (_g = parseUrlMetadata(message.ex)) === null || _g === void 0 ? void 0 : _g.image;
24
30
  if (!imageUrl)
25
31
  return null;
26
32
  return (_jsx("img", { src: imageUrl, alt: "", className: "h-[42px] w-[42px] shrink-0 rounded-lg object-cover" }));
@@ -4,5 +4,6 @@ export declare const images: {
4
4
  emptyResources: string;
5
5
  muteNotificationWarning: string;
6
6
  searchEmpty: string;
7
+ stickerInvalid: string;
7
8
  };
8
9
  //# sourceMappingURL=images.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,MAAM;;;;;;;CAOlB,CAAC"}
@@ -4,10 +4,12 @@ const imageFailed = new URL("../assets/images/imageFailed.png", import.meta.url)
4
4
  const emptyResources = new URL("../assets/images/emptyResources.png", import.meta.url).href;
5
5
  const muteNotificationWarning = new URL("../assets/images/muteNotificationWarning.png", import.meta.url).href;
6
6
  const searchEmpty = new URL("../assets/images/searchEmpty.png", import.meta.url).href;
7
+ const stickerInvalid = new URL("../assets/images/stickerInvalid.png", import.meta.url).href;
7
8
  export const images = {
8
9
  conversationBg,
9
10
  imageFailed,
10
11
  emptyResources,
11
12
  muteNotificationWarning,
12
13
  searchEmpty,
14
+ stickerInvalid,
13
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAUtB,eAAO,MAAM,WAAW,0CAStB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAc5D,eAAO,MAAM,YAAY,GAAI,sIAS1B,iBAAiB,4CAsJnB,CAAC"}
1
+ {"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAUtB,eAAO,MAAM,WAAW,0CAStB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAc5D,eAAO,MAAM,YAAY,GAAI,sIAS1B,iBAAiB,4CA2JnB,CAAC"}
@@ -141,5 +141,5 @@ export const ChatProvider = ({ children, config, renderCustomMessage, onNavigate
141
141
  useUrlState,
142
142
  allowPinMessageWithoutPermission,
143
143
  renderGroupAccessModal,
144
- }, children: _jsx(ConfigProvider, { getPopupContainer: () => { var _a; return (_a = rootRef.current) !== null && _a !== void 0 ? _a : document.body; }, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx("div", { ref: rootRef, style: themeStyle, className: "contents", children: _jsx(MainLayout, { children: children }) }) }) }) }));
144
+ }, children: _jsx(ConfigProvider, { getPopupContainer: () => { var _a; return (_a = rootRef.current) !== null && _a !== void 0 ? _a : document.body; }, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx("div", { ref: rootRef, style: themeStyle, className: "contents", "data-dchat-root": "", children: _jsx(MainLayout, { children: children }) }) }) }) }));
145
145
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,cAAc,YA8iB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA+CA,eAAO,MAAM,cAAc,YA+jB1B,CAAC"}
@@ -6,6 +6,7 @@ import { BusinessNotificationType, ConnectStatus, CustomType, SyncStatus, } from
6
6
  import { pushNewMessage, updateOneMessage } from "../../hooks/message/useMessage";
7
7
  import { fetchPinnedMessages } from "../../hooks/message/usePinnedMessages";
8
8
  import { applyRevokedMessage } from "../../hooks/message/useRevokeMessage";
9
+ import { handleInputStatusChanged, removeTyperOnNewMessage, } from "../../hooks/message/useTypingStatus";
9
10
  import { useChatContext } from "../../context/ChatContext";
10
11
  import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
11
12
  import useConversationStore from "../../store/conversation";
@@ -152,7 +153,13 @@ export const useGlobalEvent = () => {
152
153
  };
153
154
  const newMessageHandler = ({ data }) => {
154
155
  var _a;
155
- (_a = data === null || data === void 0 ? void 0 : data.map) === null || _a === void 0 ? void 0 : _a.call(data, (message) => handleNewMessage(message));
156
+ (_a = data === null || data === void 0 ? void 0 : data.map) === null || _a === void 0 ? void 0 : _a.call(data, (message) => {
157
+ removeTyperOnNewMessage(message);
158
+ handleNewMessage(message);
159
+ });
160
+ };
161
+ const inputStatusChangedHandler = ({ data, }) => {
162
+ handleInputStatusChanged(data);
156
163
  };
157
164
  const userTokenHandler = () => {
158
165
  refetchChatToken(undefined, {
@@ -387,6 +394,7 @@ export const useGlobalEvent = () => {
387
394
  DChatSDK.on(CbEvents.OnRecvNewMessages, newMessageHandler);
388
395
  DChatSDK.on(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler);
389
396
  DChatSDK.on(CbEvents.OnRecvMessagePinned, messagePinnedHandler);
397
+ DChatSDK.on(CbEvents.OnConversationUserInputStatusChanged, inputStatusChangedHandler);
390
398
  // conversation
391
399
  DChatSDK.on(CbEvents.OnConversationChanged, conversationChangeHandler);
392
400
  DChatSDK.on(CbEvents.OnNewConversation, newConversationHandler);
@@ -421,6 +429,7 @@ export const useGlobalEvent = () => {
421
429
  DChatSDK.off(CbEvents.OnRecvNewMessages, newMessageHandler);
422
430
  DChatSDK.off(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler);
423
431
  DChatSDK.off(CbEvents.OnRecvMessagePinned, messagePinnedHandler);
432
+ DChatSDK.off(CbEvents.OnConversationUserInputStatusChanged, inputStatusChangedHandler);
424
433
  // conversation
425
434
  DChatSDK.off(CbEvents.OnConversationChanged, conversationChangeHandler);
426
435
  DChatSDK.off(CbEvents.OnNewConversation, newConversationHandler);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Reports the viewer's own composer activity for `conversationId` so other
3
+ * participants see a typing indicator. `reportActivity` on every edit,
4
+ * `stopTyping` when the composer is cleared, blurred or the message is sent.
5
+ */
6
+ export declare const useReportTypingState: (conversationId: string) => {
7
+ reportActivity: (composerValue: string) => void;
8
+ stopTyping: () => void;
9
+ };
10
+ //# sourceMappingURL=useReportTypingState.d.ts.map