UNPKG

@progress/kendo-react-conversational-ui

Version:

React Chat component allows the user to participate in chat sessions with users or chat bots. KendoReact Conversational UI components

459 lines (458 loc) 12.9 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import { validatePackage as at, getLicenseMessage as rt, Keys as M, classNames as we, WatermarkOverlay as lt } from "@progress/kendo-react-common"; import o from "prop-types"; import * as e from "react"; import { convertMsgsToViewItems as it } from "../ViewItem.mjs"; import ct from "./ActionGroup.mjs"; import ut from "./AttachmentGroup.mjs"; import mt from "./DateMarker.mjs"; import gt from "./MessageGroup.mjs"; import pt from "./NewMessage.mjs"; import ft from "./Header.mjs"; import dt from "./elements/PinnedMessage.mjs"; import ht from "./Notification.mjs"; import yt from "./ChatContext.mjs"; import { ChatMessage as Ct } from "./ChatMessage.mjs"; import { packageMetadata as Fe } from "../../package-metadata.mjs"; import { ariaLabelMessageList as ve, messages as Le, chatCopyNotification as Ie } from "../../messages/index.mjs"; import { useLocalization as Tt } from "@progress/kendo-react-intl"; import { isStandardMessageFormat as Mt, mapDataArrayToMessages as St } from "../utils/fieldMapping.mjs"; import { useChatScroll as kt } from "../utils/scroll.mjs"; import { resolveMessageSettings as bt } from "../utils/messageSettings.mjs"; const Ne = e.forwardRef((De, y) => { const { messages: m = [], messageTemplate: v, messageContentTemplate: L, attachmentTemplate: S, width: Ue, height: Oe, onSendMessage: d, onSuggestionClick: I, onInputValueChange: N, onActionExecute: k, dir: g, messageBox: Pe, placeholder: Ve, className: D, style: Be, message: U = Ct, authorId: r, suggestions: O, suggestionTemplate: je, suggestionsLayout: qe = "scroll", quickActionsLayout: P = "scroll", inputValue: We, headerTemplate: p, noDataTemplate: f, timestampTemplate: V, statusTemplate: B, userStatusTemplate: j, allowMessageCollapse: q, messageToolbarActions: W, messageContextMenuActions: G, fileActions: C, onUnpin: Ge, messageWidthMode: z, showAvatar: K, showUsername: H, timestampVisibility: $, authorMessageSettings: J, receiverMessageSettings: Q, onToolbarAction: X, onContextMenuAction: Y, onFileAction: Z, onDownload: _, speechToTextConfig: ee, uploadConfig: te, sendButtonConfig: ze, messageFilesLayout: se, textField: oe, statusField: ne, authorIdField: ae, authorNameField: re, authorImageUrlField: le, authorImageAltTextField: ie, idField: ce, timestampField: ue, filesField: me, attachmentsField: ge, replyToIdField: pe, isDeletedField: fe, isPinnedField: de, typingField: he, suggestedActionsField: ye, ...Ke } = De, [i, b] = e.useState(null), [x, He] = e.useState(!1), [$e, Ce] = e.useState(!1), [Te, Me] = e.useState(null), [c, Se] = e.useState(null), E = e.useRef(null), ke = e.useRef(null), A = e.useRef(null), be = e.useRef(null), R = e.useRef(void 0), w = e.useRef(!1), F = e.useRef(null), xe = e.useRef(""), a = e.useMemo(() => { if (!m || m.length === 0) return []; const s = { textField: oe, statusField: ne, authorIdField: ae, authorNameField: re, authorImageUrlField: le, authorImageAltTextField: ie, idField: ce, timestampField: ue, filesField: me, attachmentsField: ge, replyToIdField: pe, isDeletedField: fe, isPinnedField: de, typingField: he, suggestedActionsField: ye }; return !Object.values(s).some((n) => n !== void 0) && Mt(m[0]) ? m.map((n) => ({ ...n, timestamp: n.timestamp ? new Date(n.timestamp) : n.timestamp })) : St(m, s); }, [ m, oe, ne, ae, re, le, ie, ce, ue, me, ge, pe, fe, de, he, ye ]); e.useEffect(() => { if (r === xe.current && F.current) { Se(F.current); return; } if (r && a.length > 0) { const s = a.find( (n) => n.author && (n.author.id === r || n.author.id === String(r)) ), t = (s == null ? void 0 : s.author) || { id: r }; F.current = t, xe.current = r, Se(t); } }, [r, a]); const u = e.useMemo(() => a.length > 0 ? it(a) : [], [a]), Je = e.useMemo(() => !at(Fe, { component: "Chat" }), []), Qe = rt(Fe), Ee = e.useMemo(() => a.find((s) => s.isPinned), [a]), Ae = Tt(); kt({ viewItemsWrapperRef: A, isKeyboardNavigationActiveRef: w, processedMessages: a, suggestions: O }); const Xe = e.useCallback(() => { clearTimeout(R.current); }, []), Ye = e.useCallback(() => { R.current = window.setTimeout(() => { b(null); }, 0); }, []), T = e.useCallback((s) => { b(s); }, []), Ze = e.useCallback( (s) => { let t = null; const n = i !== null ? i : u.lastSelectionIndex; s.keyCode === M.up ? n === null ? t = 0 : n > 0 && (t = n - 1) : s.keyCode === M.down ? n === null ? t = 0 : n < u.lastSelectionIndex && (t = n + 1) : s.keyCode === M.home ? t = 0 : s.keyCode === M.end && (t = u.lastSelectionIndex), t !== null && (b(t), w.current = !0, s.preventDefault()); }, [i, u] ), Re = e.useCallback( (s, t, n) => { var h; if (k && k({ action: { value: s.value, title: s.title, type: s.type }, syntheticEvent: t, nativeEvent: t.nativeEvent, target: t.currentTarget }), !t.isDefaultPrevented()) { switch (s.type) { case "reply": d && d({ message: { id: "", author: c, text: s.value, timestamp: /* @__PURE__ */ new Date() }, syntheticEvent: t, nativeEvent: t.nativeEvent, target: t.currentTarget }); break; case "call": window.open("tel:" + s.value); break; case "openUrl": window.open(s.value); break; } (h = be.current) == null || h.focusInput(); } }, [k, d, c] ), _e = e.useCallback(() => we("k-chat", D, { "k-rtl": x }), [D, x]), et = e.useCallback(() => { const s = u.length - 1; return u.map((t, n) => { var h; if (t.type === "date-marker") return /* @__PURE__ */ e.createElement(mt, { item: t, key: t.trackBy, timestampTemplate: V }); if (t.type === "message-group") { const nt = { allowMessageCollapse: q, messageWidthMode: z, showAvatar: K, showUsername: H, messageTemplate: v, messageContentTemplate: L, messageToolbarActions: W, messageContextMenuActions: G, fileActions: C }, l = bt( (h = t == null ? void 0 : t.messages) == null ? void 0 : h[0], r, nt, J, Q ); return /* @__PURE__ */ e.createElement( gt, { group: t, itemTemplate: l.messageTemplate, contentTemplate: l.messageContentTemplate, attachmentTemplate: S, user: c, selectedItemIndex: i, onRequestSelection: T, isLastGroup: n === s, key: t.messages[0].selectionIndex, message: U, allowMessageCollapse: l.allowMessageCollapse, messageToolbarActions: l.messageToolbarActions, messageContextMenuActions: l.messageContextMenuActions, fileActions: l.fileActions, messageWidthMode: l.messageWidthMode, showAvatar: l.showAvatar, showUsername: l.showUsername } ); } else { if (t.type === "attachment-group") return /* @__PURE__ */ e.createElement( ut, { group: t, itemTemplate: S, onRequestSelection: T, selected: t.selectionIndex === i, isLastGroup: n === s, key: t.selectionIndex } ); if (t.type === "action-group") return /* @__PURE__ */ e.createElement( ct, { group: t, onActionExecute: Re, onRequestSelection: T, selected: t.selectionIndex === i, key: t.selectionIndex, quickActionsLayout: P, dir: g } ); } return null; }); }, [ u, V, v, L, S, c, i, T, U, q, W, G, C, z, K, H, r, J, Q, Re, P, g ]), tt = e.useCallback(() => p ? typeof p == "function" ? p() : p : null, [p]), st = e.useCallback(() => f ? typeof f == "function" ? f() : f : null, [f]); e.useEffect(() => { const s = g !== void 0 ? g === "rtl" : E.current && getComputedStyle(E.current).direction === "rtl"; He(!!s); }, [g]), e.useEffect(() => () => { clearTimeout(R.current); }, []); const ot = e.useMemo(() => ({ replyToMessage: Te, setReplyToMessage: Me, messages: a, user: c, internalScrollContainerRef: ke, messageListScrollContainerRef: A, onToolbarAction: X, onContextMenuAction: Y, onFileAction: Z, setShowCopyNotification: Ce, onDownload: _, fileActions: C, speechToTextConfig: ee, uploadConfig: te, statusTemplate: B, userStatusTemplate: j, messageFilesLayout: se, timestampVisibility: $ }), [ Te, Me, a, c, X, Y, Z, Ce, _, C, ee, te, B, j, se, $ ]); return /* @__PURE__ */ e.createElement(yt.Provider, { value: ot }, /* @__PURE__ */ e.createElement( "div", { style: { width: Ue, height: Oe, position: "relative", overflow: "hidden", ...Be }, onKeyDown: Ze, className: _e(), ref: (s) => { E.current = s, ke.current = s, typeof y == "function" ? y(s) : y && (y.current = s); }, ...Ke }, p !== void 0 && /* @__PURE__ */ e.createElement(ft, null, tt()), Ee && /* @__PURE__ */ e.createElement(dt, { message: Ee, onUnpin: Ge, user: c }), /* @__PURE__ */ e.createElement( "div", { className: "k-message-list", onBlur: Ye, onFocus: Xe, role: "log", "aria-label": Ae.toLanguageString( ve, Le[ve] ), "aria-live": "polite", ref: A }, /* @__PURE__ */ e.createElement( "div", { className: we("k-message-list-content", { "k-message-list-content-empty": a.length === 0 }) }, a.length === 0 && f !== void 0 && st(), a.length > 0 && et() ) ), /* @__PURE__ */ e.createElement( pt, { onSendMessage: (s, t) => { d && d({ message: s, syntheticEvent: t, nativeEvent: t.nativeEvent, target: t.currentTarget }); }, onSuggestionClick: (s) => { I && I(s); }, isDirectionRightToLeft: x, ref: be, placeholder: Ve, MessageBox: Pe, suggestions: O, suggestionTemplate: je, inputValue: We, onInputValueChange: (s) => { N && N(s); }, onInputClick: () => { w.current = !1; }, sendButtonConfig: ze, suggestionsLayout: qe, dir: g } ), /* @__PURE__ */ e.createElement( ht, { show: $e, text: Ae.toLanguageString( Ie, Le[Ie] ) } ), Je && /* @__PURE__ */ e.createElement(lt, { message: Qe }) )); }); Ne.displayName = "Chat"; Ne.propTypes = { messages: o.arrayOf(o.object), user: o.object, messageTemplate: o.any, messageContentTemplate: o.any, attachmentTemplate: o.any, width: o.oneOfType([o.string, o.number]), style: o.object, onSendMessage: o.func, onActionExecute: o.func, dir: o.string, messageBox: o.any, noDataTemplate: o.oneOfType([o.element, o.func]), suggestionsLayout: o.oneOf(["scroll", "wrap", "scrollbuttons"]), quickActionsLayout: o.oneOf(["scroll", "wrap", "scrollbuttons"]), userStatusTemplate: o.elementType, messageFilesLayout: o.oneOf(["vertical", "wrap", "horizontal"]), showAvatar: o.bool, showUsername: o.bool, authorMessageSettings: o.object, receiverMessageSettings: o.object }; export { Ne as Chat };