@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
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 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
};