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