@botonic/react
Version:
Build Chatbots using React
621 lines • 28.5 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { INPUT, isMobile, params2queryString } from '@botonic/core';
import merge from 'lodash.merge';
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState, } from 'react';
import Textarea from 'react-textarea-autosize';
import styled, { StyleSheetManager } from 'styled-components';
import { useAsyncEffect } from 'use-async-effect';
import { v4 as uuidv4 } from 'uuid';
import { Audio, Document, Image, Text, Video } from '../components';
import { Handoff } from '../components/handoff';
import { normalizeWebchatSettings } from '../components/webchat-settings';
import { COLORS, MAX_ALLOWED_SIZE_MB, ROLES, SENDERS, WEBCHAT, } from '../constants';
import { RequestContext, WebchatContext } from '../contexts';
import { getFullMimeWhitelist, getMediaType, isAllowedSize, isAudio, isDocument, isImage, isMedia, isText, isVideo, readDataURL, } from '../message-utils';
import { msgToBotonic } from '../msg-to-botonic';
import { scrollToBottom } from '../util/dom';
import { isDev } from '../util/environment';
import { deserializeRegex, stringifyWithRegexs } from '../util/regexs';
import { _getThemeProperty, getServerErrorMessage, initSession, shouldKeepSessionOnReload, } from '../util/webchat';
import { Attachment } from './components/attachment';
import { EmojiPicker, OpenedEmojiPicker } from './components/emoji-picker';
import { OpenedPersistentMenu, PersistentMenu, } from './components/persistent-menu';
import { SendButton } from './components/send-button';
import { TypingIndicator } from './components/typing-indicator';
import { DeviceAdapter } from './devices/device-adapter';
import { StyledWebchatHeader } from './header';
import { useComponentWillMount, usePrevious, useTyping, useWebchat, } from './hooks';
import { WebchatMessageList } from './message-list';
import { WebchatReplies } from './replies';
import { TriggerButton } from './trigger-button';
import { useStorageState } from './use-storage-state-hook';
import { WebviewContainer } from './webview';
export const getParsedAction = botonicAction => {
const splittedAction = botonicAction.split('create_case:');
if (splittedAction.length <= 1)
return undefined;
return JSON.parse(splittedAction[1]);
};
const StyledWebchat = styled.div `
position: fixed;
right: 20px;
bottom: 20px;
width: ${props => props.width}px;
height: ${props => props.height}px;
margin: auto;
background-color: ${COLORS.SOLID_WHITE};
border-radius: 10px;
box-shadow: ${COLORS.SOLID_BLACK_ALPHA_0_2} 0px 0px 12px;
display: flex;
flex-direction: column;
`;
const UserInputContainer = styled.div `
min-height: 52px;
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 16px;
padding: 0px 16px;
z-index: 1;
border-top: 1px solid ${COLORS.SOLID_BLACK_ALPHA_0_5};
`;
const TextAreaContainer = styled.div `
display: flex;
flex: 1 1 auto;
align-items: center;
`;
const ErrorMessageContainer = styled.div `
position: relative;
display: flex;
z-index: 1;
justify-content: center;
width: 100%;
`;
const ErrorMessage = styled.div `
position: absolute;
top: 10px;
font-size: 14px;
line-height: 20px;
padding: 4px 11px;
display: flex;
background-color: ${COLORS.ERROR_RED};
color: ${COLORS.CONCRETE_WHITE};
border-radius: 5px;
align-items: center;
justify-content: center;
font-family: ${WEBCHAT.DEFAULTS.FONT_FAMILY};
`;
const DarkBackgroundMenu = styled.div `
background: ${COLORS.SOLID_BLACK};
opacity: 0.3;
z-index: 1;
right: 0;
bottom: 0;
border-radius: 10px;
position: absolute;
width: 100%;
height: 100%;
`;
// eslint-disable-next-line complexity, react/display-name
export const Webchat = forwardRef((props, ref) => {
const { webchatState, addMessage, addMessageComponent, updateMessage, updateReplies, updateLatestInput, updateTyping, updateWebview, updateSession, updateLastRoutePath, updateHandoff, updateTheme, updateDevSettings, toggleWebchat, toggleEmojiPicker, togglePersistentMenu, toggleCoverComponent, doRenderCustomComponent, setError, setOnline, clearMessages, openWebviewT, updateLastMessageDate, setCurrentAttachment,
// eslint-disable-next-line react-hooks/rules-of-hooks
} = props.webchatHooks || useWebchat();
const firstUpdate = useRef(true);
const isOnline = () => webchatState.online;
const currentDateString = () => new Date().toISOString();
const theme = merge(webchatState.theme, props.theme);
const { initialSession, initialDevSettings, onStateChange } = props;
const getThemeProperty = _getThemeProperty(theme);
const [customComponent, setCustomComponent] = useState(null);
const storage = props.storage;
const storageKey = typeof props.storageKey === 'function'
? props.storageKey()
: props.storageKey;
const [botonicState, saveState] = useStorageState(storage, storageKey);
const host = props.host || document.body;
const deviceAdapter = new DeviceAdapter();
const saveWebchatState = webchatState => {
storage &&
saveState(JSON.parse(stringifyWithRegexs({
messages: webchatState.messagesJSON,
session: webchatState.session,
lastRoutePath: webchatState.lastRoutePath,
devSettings: webchatState.devSettings,
lastMessageUpdate: webchatState.lastMessageUpdate,
themeUpdates: webchatState.themeUpdates,
})));
};
const handleAttachment = event => {
if (!isAllowedSize(event.target.files[0].size)) {
throw new Error(`The file is too large. A maximum of ${MAX_ALLOWED_SIZE_MB}MB is allowed.`);
}
setCurrentAttachment({
fileName: event.target.files[0].name,
file: event.target.files[0],
attachmentType: getMediaType(event.target.files[0].type),
});
};
useEffect(() => {
if (webchatState.currentAttachment)
sendAttachment(webchatState.currentAttachment);
}, [webchatState.currentAttachment]);
const sendUserInput = async (input) => {
props.onUserInput &&
props.onUserInput({
user: webchatState.session.user,
input: input,
session: webchatState.session,
lastRoutePath: webchatState.lastRoutePath,
});
};
const sendChatEvent = async (chatEvent) => {
const chatEventInput = {
id: uuidv4(),
type: INPUT.CHAT_EVENT,
data: chatEvent,
};
props.onUserInput &&
props.onUserInput({
user: webchatState.session.user,
input: chatEventInput,
session: webchatState.session,
lastRoutePath: webchatState.lastRoutePath,
});
};
// Load styles stored in window._botonicInsertStyles by Webpack
useComponentWillMount(() => {
if (window._botonicInsertStyles && window._botonicInsertStyles.length) {
for (const botonicStyle of window._botonicInsertStyles) {
// Injecting styles at head is needed even if we use shadowDOM
// as some dependencies like simplebar rely on creating ephemeral elements
// on document.body and assume styles will be available globally
document.head.appendChild(botonicStyle);
// injecting styles in host node too so that shadowDOM works
if (props.shadowDOM)
host.appendChild(botonicStyle.cloneNode(true));
}
delete window._botonicInsertStyles;
}
if (props.shadowDOM) {
// emoji-picker-react injects styles in head, so we need to
// re-inject them in our host node to make it work with shadowDOM
for (const style of document.querySelectorAll('style')) {
if (style.textContent &&
style.textContent.includes('emoji-picker-react'))
host.appendChild(style.cloneNode(true));
}
}
});
// Load initial state from storage
useEffect(() => {
let { messages, session, lastRoutePath, devSettings, lastMessageUpdate, themeUpdates, } = botonicState || {};
session = initSession(session);
updateSession(session);
if (shouldKeepSessionOnReload({ initialDevSettings, devSettings })) {
if (messages) {
messages.forEach(m => {
addMessage(m);
const newComponent = msgToBotonic(Object.assign(Object.assign({}, m), { delay: 0, typing: 0 }), (props.theme.message && props.theme.message.customTypes) ||
props.theme.customMessageTypes);
if (newComponent)
addMessageComponent(newComponent);
});
}
if (initialSession)
updateSession(merge(initialSession, session));
if (lastRoutePath)
updateLastRoutePath(lastRoutePath);
}
else
updateSession(merge(initialSession, session));
if (devSettings)
updateDevSettings(devSettings);
else if (initialDevSettings)
updateDevSettings(initialDevSettings);
if (lastMessageUpdate)
updateLastMessageDate(lastMessageUpdate);
if (themeUpdates !== undefined)
updateTheme(merge(props.theme, themeUpdates), themeUpdates);
if (props.onInit)
setTimeout(() => props.onInit(), 100);
}, []);
useEffect(() => {
if (!webchatState.isWebchatOpen)
return;
deviceAdapter.init(host);
scrollToBottom({ behavior: 'auto', host });
}, [webchatState.isWebchatOpen]);
useEffect(() => {
if (onStateChange && typeof onStateChange === 'function')
onStateChange(webchatState);
saveWebchatState(webchatState);
}, [
webchatState.messagesJSON,
webchatState.session,
webchatState.lastRoutePath,
webchatState.devSettings,
webchatState.lastMessageUpdate,
]);
useAsyncEffect(async () => {
if (!webchatState.online) {
setError({
message: getServerErrorMessage(props.server),
});
}
else {
if (!firstUpdate.current) {
setError(undefined);
}
}
}, [webchatState.online]);
useTyping({ webchatState, updateTyping, updateMessage, host });
useEffect(() => {
updateTheme(merge(props.theme, theme, webchatState.themeUpdates));
}, [props.theme, webchatState.themeUpdates]);
const openWebview = (webviewComponent, params) => updateWebview(webviewComponent, params);
const handleSelectedEmoji = event => {
textArea.current.value += event.emoji;
textArea.current.focus();
};
const closeWebview = options => {
updateWebview();
if (userInputEnabled) {
textArea.current.focus();
}
if (options && options.payload) {
sendPayload(options.payload);
}
else if (options && options.path) {
let params = '';
if (options.params)
params = params2queryString(options.params);
sendPayload(`__PATH_PAYLOAD__${options.path}?${params}`);
}
};
const handleMenu = () => {
togglePersistentMenu(!webchatState.isPersistentMenuOpen);
};
const handleEmojiClick = () => {
toggleEmojiPicker(!webchatState.isEmojiPickerOpen);
};
const persistentMenuOptions = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.persistentMenu, props.persistentMenu);
const darkBackgroundMenu = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.darkBackgroundMenu, false);
const getBlockInputs = (rule, inputData) => {
const processedInput = rule.preprocess
? rule.preprocess(inputData)
: inputData;
return rule.match.some(regex => {
if (typeof regex === 'string')
regex = deserializeRegex(regex);
return regex.test(processedInput);
});
};
const checkBlockInput = input => {
// if is a text we check if it is a serialized RE
const blockInputs = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.blockInputs, props.blockInputs);
if (!Array.isArray(blockInputs))
return false;
for (const rule of blockInputs) {
if (getBlockInputs(rule, input.data)) {
addMessageComponent(_jsx(Text, Object.assign({ id: input.id, from: SENDERS.user, blob: false, style: {
backgroundColor: COLORS.SCORPION_GRAY,
borderColor: COLORS.SCORPION_GRAY,
padding: '8px 12px',
} }, { children: rule.message })));
updateReplies(false);
return true;
}
}
return false;
};
const closeMenu = () => {
togglePersistentMenu(false);
};
const persistentMenu = () => {
return (_jsx(OpenedPersistentMenu, { onClick: closeMenu, options: persistentMenuOptions, borderRadius: webchatState.theme.style.borderRadius || '10px' }));
};
const getCoverComponent = () => {
return getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.coverComponent, props.coverComponent &&
(props.coverComponent.component || props.coverComponent));
};
const CoverComponent = getCoverComponent();
const closeCoverComponent = () => {
toggleCoverComponent(false);
};
useEffect(() => {
if (!CoverComponent)
return;
if (!botonicState ||
(botonicState.messages && botonicState.messages.length == 0))
toggleCoverComponent(true);
}, []);
const coverComponent = () => {
const coverComponentProps = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.coverComponentProps, props.coverComponent && props.coverComponent.props);
if (CoverComponent && webchatState.isCoverComponentOpen)
return (_jsx(CoverComponent, Object.assign({ closeComponent: closeCoverComponent }, coverComponentProps)));
return null;
};
const messageComponentFromInput = input => {
let messageComponent = null;
if (isText(input)) {
messageComponent = (_jsx(Text, Object.assign({ id: input.id, payload: input.payload, from: SENDERS.user }, { children: input.data })));
}
else if (isMedia(input)) {
const temporaryDisplayUrl = URL.createObjectURL(input.data);
const mediaProps = {
id: input.id,
from: SENDERS.user,
src: temporaryDisplayUrl,
};
if (isImage(input)) {
mediaProps.input = input;
messageComponent = _jsx(Image, Object.assign({}, mediaProps));
}
else if (isAudio(input))
messageComponent = _jsx(Audio, Object.assign({}, mediaProps));
else if (isVideo(input))
messageComponent = _jsx(Video, Object.assign({}, mediaProps));
else if (isDocument(input))
messageComponent = _jsx(Document, Object.assign({}, mediaProps));
}
return messageComponent;
};
const sendInput = async (input) => {
if (!input || Object.keys(input).length == 0)
return;
if (isText(input) && (!input.data || !input.data.trim()))
return; // in case trim() doesn't work in a browser we can use !/\S/.test(input.data)
if (isText(input) && checkBlockInput(input))
return;
if (!input.id)
input.id = uuidv4();
const messageComponent = messageComponentFromInput(input);
if (messageComponent)
addMessageComponent(messageComponent);
if (isMedia(input))
input.data = await readDataURL(input.data);
sendUserInput(input);
updateLatestInput(input);
isOnline() && updateLastMessageDate(currentDateString());
updateReplies(false);
togglePersistentMenu(false);
toggleEmojiPicker(false);
};
/* This is the public API this component exposes to its parents
https://stackoverflow.com/questions/37949981/call-child-method-from-parent
*/
const updateSessionWithUser = userToUpdate => updateSession(merge(webchatState.session, { user: userToUpdate }));
useImperativeHandle(ref, () => ({
addBotResponse: ({ response, session, lastRoutePath }) => {
updateTyping(false);
if (Array.isArray(response))
response.map(r => addMessageComponent(r));
else if (response)
addMessageComponent(response);
if (session) {
updateSession(merge(session, { user: webchatState.session.user }));
const action = session._botonic_action || '';
const handoff = action.startsWith('create_case');
if (handoff && isDev)
addMessageComponent(_jsx(Handoff, {}));
updateHandoff(handoff);
}
if (lastRoutePath)
updateLastRoutePath(lastRoutePath);
updateLastMessageDate(currentDateString());
},
setTyping: typing => updateTyping(typing),
addUserMessage: message => sendInput(message),
updateUser: updateSessionWithUser,
openWebchat: () => toggleWebchat(true),
closeWebchat: () => toggleWebchat(false),
toggleWebchat: () => toggleWebchat(!webchatState.isWebchatOpen),
openCoverComponent: () => toggleCoverComponent(true),
closeCoverComponent: () => toggleCoverComponent(false),
renderCustomComponent: _customComponent => {
setCustomComponent(_customComponent);
doRenderCustomComponent(true);
},
unmountCustomComponent: () => doRenderCustomComponent(false),
toggleCoverComponent: () => toggleCoverComponent(!webchatState.isCoverComponentOpen),
openWebviewApi: component => openWebviewT(component),
setError,
setOnline,
getMessages: () => webchatState.messagesJSON,
isOnline,
clearMessages: () => {
clearMessages();
updateReplies(false);
},
getLastMessageUpdate: () => webchatState.lastMessageUpdate,
updateMessageInfo: (msgId, messageInfo) => {
const messageToUpdate = webchatState.messagesJSON.filter(m => m.id == msgId)[0];
const updatedMsg = merge(messageToUpdate, messageInfo);
if (updatedMsg.ack === 1)
delete updatedMsg.unsentInput;
updateMessage(updatedMsg);
},
updateWebchatSettings: settings => {
const themeUpdates = normalizeWebchatSettings(settings);
updateTheme(merge(webchatState.theme, themeUpdates), themeUpdates);
},
}));
const resolveCase = () => {
updateHandoff(false);
updateSession(Object.assign(Object.assign({}, webchatState.session), { _botonic_action: null }));
};
const prevSession = usePrevious(webchatState.session);
useEffect(() => {
// Resume conversation after handoff
if (prevSession &&
prevSession._botonic_action &&
!webchatState.session._botonic_action) {
const action = getParsedAction(prevSession._botonic_action);
if (action && action.on_finish)
sendPayload(action.on_finish);
}
}, [webchatState.session._botonic_action]);
const sendText = async (text, payload) => {
if (!text)
return;
const input = { type: INPUT.TEXT, data: text, payload };
await sendInput(input);
};
const sendPayload = async (payload) => {
if (!payload)
return;
const input = { type: INPUT.POSTBACK, payload };
await sendInput(input);
};
const sendAttachment = async (attachment) => {
if (attachment.file) {
const attachmentType = getMediaType(attachment.file.type);
if (!attachmentType)
return;
const input = {
type: attachmentType,
data: attachment.file,
};
await sendInput(input);
setCurrentAttachment(undefined);
}
};
const sendTextAreaText = () => {
sendText(textArea.current.value);
textArea.current.value = '';
};
let isTyping = false;
let typingTimeout = null;
function clearTimeoutWithReset(reset) {
const waitTime = 20 * 1000;
if (typingTimeout)
clearTimeout(typingTimeout);
if (reset)
typingTimeout = setTimeout(stopTyping, waitTime);
}
function startTyping() {
isTyping = true;
sendChatEvent('typing_on');
}
function stopTyping() {
clearTimeoutWithReset(false);
isTyping = false;
sendChatEvent('typing_off');
}
const onKeyDown = event => {
if (event.keyCode === 13 && event.shiftKey === false) {
event.preventDefault();
sendTextAreaText();
stopTyping();
}
};
const onKeyUp = () => {
if (textArea.current.value === '') {
stopTyping();
return;
}
if (!isTyping) {
startTyping();
}
clearTimeoutWithReset(true);
};
const webviewRequestContext = {
getString: stringId => props.getString(stringId, webchatState.session),
setLocale: locale => props.getString(locale, webchatState.session),
session: webchatState.session || {},
params: webchatState.webviewParams || {},
closeWebview: closeWebview,
defaultDelay: props.defaultDelay || 0,
defaultTyping: props.defaultTyping || 0,
};
useEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
if (webchatState.isWebchatOpen && props.onOpen)
props.onOpen();
if (!webchatState.isWebchatOpen && props.onClose && !firstUpdate.current) {
props.onClose();
toggleEmojiPicker(false);
togglePersistentMenu(false);
}
}, [webchatState.isWebchatOpen]);
const webchatMessageList = () => (_jsx(WebchatMessageList, Object.assign({ style: { flex: 1 } }, { children: webchatState.typing && _jsx(TypingIndicator, {}) })));
const webchatReplies = () => _jsx(WebchatReplies, { replies: webchatState.replies });
const isUserInputEnabled = () => {
const isUserInputEnabled = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.enableUserInput, props.enableUserInput !== undefined ? props.enableUserInput : true);
return isUserInputEnabled && !webchatState.isCoverComponentOpen;
};
const userInputEnabled = isUserInputEnabled();
const textArea = useRef(null);
const userInputArea = () => {
return (userInputEnabled && (_jsxs(UserInputContainer, Object.assign({ style: Object.assign({}, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.userInputStyle)), className: 'user-input-container' }, { children: [webchatState.isEmojiPickerOpen && (_jsx(OpenedEmojiPicker, { height: webchatState.theme.style.height, onEmojiClick: handleSelectedEmoji, onClick: handleEmojiClick })), _jsx(PersistentMenu, { onClick: handleMenu, persistentMenu: props.persistentMenu }), _jsx(TextAreaContainer, { children: _jsx(Textarea, { inputRef: textArea, name: 'text', onFocus: () => deviceAdapter.onFocus(host), onBlur: () => deviceAdapter.onBlur(), maxRows: 4, wrap: 'soft', maxLength: '1000', placeholder: getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.textPlaceholder, WEBCHAT.DEFAULTS.PLACEHOLDER), autoFocus: true, onKeyDown: e => onKeyDown(e), onKeyUp: onKeyUp, style: Object.assign({ display: 'flex', fontSize: deviceAdapter.fontSize(14), width: '100%', border: 'none', resize: 'none', overflow: 'auto', outline: 'none', flex: '1 1 auto', padding: 10, paddingLeft: persistentMenuOptions ? 0 : 10, fontFamily: 'inherit' }, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.userInputBoxStyle)) }) }), _jsx(EmojiPicker, { enableEmojiPicker: props.enableEmojiPicker, onClick: handleEmojiClick }), _jsx(Attachment, { enableAttachments: props.enableAttachments, onChange: handleAttachment, accept: getFullMimeWhitelist().join(',') }), _jsx(SendButton, { onClick: sendTextAreaText })] }))));
};
const webchatWebview = () => (_jsx(RequestContext.Provider, Object.assign({ value: webviewRequestContext }, { children: _jsx(WebviewContainer, { style: Object.assign(Object.assign({}, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.webviewStyle)), mobileStyle), webview: webchatState.webview }) })));
let mobileStyle = {};
if (isMobile(getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.mobileBreakpoint))) {
mobileStyle = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.mobileStyle) || {
width: '100%',
height: '100%',
right: 0,
bottom: 0,
borderRadius: 0,
};
}
useEffect(() => {
// Prod mode
saveWebchatState(webchatState);
scrollToBottom({ host });
}, [webchatState.themeUpdates]);
// Only needed for dev/serve mode
const updateWebchatDevSettings = settings => {
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
const themeUpdates = normalizeWebchatSettings(settings);
updateTheme(merge(webchatState.theme, themeUpdates), themeUpdates);
}, [webchatState.messagesJSON]);
};
const DarkenBackground = ({ component }) => {
return (_jsxs("div", { children: [darkBackgroundMenu && (_jsx(DarkBackgroundMenu, { style: {
borderRadius: webchatState.theme.style.borderRadius,
} })), component] }));
};
const _renderCustomComponent = () => {
if (!customComponent)
return _jsx(_Fragment, {});
else
return customComponent;
};
const WebchatComponent = (_jsxs(WebchatContext.Provider, Object.assign({ value: {
addMessage,
getThemeProperty,
openWebview,
resolveCase,
sendAttachment,
sendInput,
sendPayload,
sendText,
toggleWebchat,
updateLatestInput,
updateMessage,
updateReplies,
updateUser: updateSessionWithUser,
updateWebchatDevSettings: updateWebchatDevSettings,
webchatState,
} }, { children: [!webchatState.isWebchatOpen && _jsx(TriggerButton, {}), webchatState.isWebchatOpen && (_jsxs(StyledWebchat
// TODO: Distinguis between multiple instances of webchat, e.g. `${uniqueId}-botonic-webchat`
, Object.assign({
// TODO: Distinguis between multiple instances of webchat, e.g. `${uniqueId}-botonic-webchat`
role: ROLES.WEBCHAT, id: WEBCHAT.DEFAULTS.ID, width: webchatState.width, height: webchatState.height, style: Object.assign(Object.assign({}, webchatState.theme.style), mobileStyle) }, { children: [_jsx(StyledWebchatHeader, { onCloseClick: () => {
toggleWebchat(false);
} }), webchatState.error.message && (_jsx(ErrorMessageContainer, { children: _jsx(ErrorMessage, { children: webchatState.error.message }) })), webchatMessageList(), webchatState.replies &&
Object.keys(webchatState.replies).length > 0 &&
webchatReplies(), webchatState.isPersistentMenuOpen && (_jsx(DarkenBackground, { component: persistentMenu() })), !webchatState.handoff && userInputArea(), webchatState.webview && webchatWebview(), webchatState.isCoverComponentOpen && coverComponent(), webchatState.isCustomComponentRendered &&
customComponent &&
_renderCustomComponent()] })))] })));
return props.shadowDOM ? (_jsx(StyleSheetManager, Object.assign({ target: host }, { children: WebchatComponent }))) : (WebchatComponent);
});
//# sourceMappingURL=webchat.js.map