diff --git a/src/components/ChatBotInput/ChatBotInput.tsx b/src/components/ChatBotInput/ChatBotInput.tsx index 0a80cefb..32ee1c3f 100644 --- a/src/components/ChatBotInput/ChatBotInput.tsx +++ b/src/components/ChatBotInput/ChatBotInput.tsx @@ -16,6 +16,7 @@ import { useSettingsContext } from "../../context/SettingsContext"; import { useStylesContext } from "../../context/StylesContext"; import "./ChatBotInput.css"; +import { useTextArea } from "../../hooks/useTextArea"; /** * Contains chat input field for user to enter messages. @@ -48,6 +49,9 @@ const ChatBotInput = ({ buttons }: { buttons: JSX.Element[] }) => { // handles user input submission const { handleSubmitText } = useSubmitInputInternal(); + //handle textarea functionality + const { setTextAreaValue } = useTextArea(); + // styles for text area const textAreaStyle: React.CSSProperties = { boxSizing: isDesktop ? "content-box" : "border-box", @@ -147,26 +151,9 @@ const ChatBotInput = ({ buttons }: { buttons: JSX.Element[] }) => { * @param event textarea change event */ const handleTextAreaValueChange = (event: ChangeEvent) => { - if (textAreaDisabled && inputRef.current) { - // prevent input and keep current value - inputRef.current.value = ""; - return; - } if (inputRef.current) { - const characterLimit = settings.chatInput?.characterLimit - /* - * @params allowNewline Boolean - * allowNewline [true] Allow input values to contain line breaks "\n" - * allowNewline [false] Replace \n with a space - * */ - const allowNewline = settings.chatInput?.allowNewline - const newInput = allowNewline ? event.target.value : event.target.value.replace(/\n/g, " "); - if (characterLimit != null && characterLimit >= 0 && newInput.length > characterLimit) { - inputRef.current.value = newInput.slice(0, characterLimit); - } else { - inputRef.current.value = newInput - } + setTextAreaValue(event.target.value) setInputLength(inputRef.current.value.length); } }; diff --git a/src/constants/RcbEvent.ts b/src/constants/RcbEvent.ts index 4bf627f2..4e0ba790 100644 --- a/src/constants/RcbEvent.ts +++ b/src/constants/RcbEvent.ts @@ -33,6 +33,9 @@ const RcbEvent = { // user input submission USER_SUBMIT_TEXT: "rcb-user-submit-text", USER_UPLOAD_FILE: "rcb-user-upload-file", + + // text area value change + TEXTAREA_CHANGE_VALUE: "rcb-textarea-change-value" } export { RcbEvent }; diff --git a/src/context/BotRefsContext.tsx b/src/context/BotRefsContext.tsx index be1942a7..91df76f8 100644 --- a/src/context/BotRefsContext.tsx +++ b/src/context/BotRefsContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useRef } from "react"; +import React, { createContext, useContext, useRef } from "react"; import { Flow } from "../types/Flow"; @@ -9,6 +9,7 @@ type BotRefsContextType = { botIdRef: React.RefObject; flowRef: React.RefObject; inputRef: React.RefObject; + prevInputRef: React.MutableRefObject; streamMessageMap: React.MutableRefObject>; chatBodyRef: React.RefObject; paramsInputRef: React.MutableRefObject; @@ -32,6 +33,7 @@ const BotRefsProvider = ({ const botIdRef = useRef(id); const flowRef = useRef(initialFlow); const inputRef = useRef(null); + const prevInputRef = useRef(""); const streamMessageMap = useRef>(new Map()); const chatBodyRef = useRef(null); const paramsInputRef = useRef(""); @@ -45,7 +47,8 @@ const BotRefsProvider = ({ streamMessageMap, chatBodyRef, paramsInputRef, - keepVoiceOnRef + keepVoiceOnRef, + prevInputRef }}> {children} diff --git a/src/hooks/internal/useTextAreaInternal.ts b/src/hooks/internal/useTextAreaInternal.ts index a330e6c4..72728cae 100644 --- a/src/hooks/internal/useTextAreaInternal.ts +++ b/src/hooks/internal/useTextAreaInternal.ts @@ -4,6 +4,8 @@ import { isChatBotVisible } from "../../utils/displayChecker"; import { useBotStatesContext } from "../../context/BotStatesContext"; import { useSettingsContext } from "../../context/SettingsContext"; import { useBotRefsContext } from "../../context/BotRefsContext"; +import { useRcbEventInternal } from "./useRcbEventInternal"; +import { RcbEvent } from "../../constants/RcbEvent"; /** * Internal custom hook for managing input text area. @@ -23,7 +25,10 @@ export const useTextAreaInternal = () => { } = useBotStatesContext(); // handles bot refs - const { inputRef, chatBodyRef } = useBotRefsContext(); + const { inputRef, chatBodyRef, prevInputRef } = useBotRefsContext(); + + // handles rcb events + const { callRcbEvent } = useRcbEventInternal(); /** * Sets the text area value. @@ -31,16 +36,37 @@ export const useTextAreaInternal = () => { * @param value value to set */ const setTextAreaValue = (value: string) => { - // todo: Checks are currently not performed and input length is also not set. - // It should be similar to what the handleTextAreaValueChange function is doing - // inside ChatBotInput component - a recommended approach is to centralize the - // setting of input values into this function and then include logic checks here. - // All other parts of the project setting input value should then call this function. + + if (textAreaDisabled && inputRef.current) { + // prevent input and keep current value + inputRef.current.value = ""; + return; + } - // todo: emit rcb event once the checks above passed + if (inputRef.current && prevInputRef.current !== null) { + const characterLimit = settings.chatInput?.characterLimit + /* + * @params allowNewline Boolean + * allowNewline [true] Allow input values to contain line breaks "\n" + * allowNewline [false] Replace \n with a space + * */ + const allowNewline = settings.chatInput?.allowNewline + const newInput = allowNewline ? value : value.replace(/\n/g, " "); + if (characterLimit != null && characterLimit >= 0 && newInput.length > characterLimit) { + inputRef.current.value = newInput.slice(0, characterLimit); + } else { + inputRef.current.value = newInput + } + if(settings.event?.rcbTextareaChangeValue) { - if (inputRef.current) { - inputRef.current.value = value; + const event = callRcbEvent(RcbEvent.TEXTAREA_CHANGE_VALUE, + {currValue: inputRef.current.value, prevValue: prevInputRef.current}); + if (event.defaultPrevented) { + inputRef.current.value = prevInputRef.current; + return + } + } + prevInputRef.current = inputRef.current.value; } } diff --git a/src/services/RcbEventService.tsx b/src/services/RcbEventService.tsx index 5298af75..f33821c8 100644 --- a/src/services/RcbEventService.tsx +++ b/src/services/RcbEventService.tsx @@ -21,7 +21,8 @@ const cancellableMap = { [RcbEvent.SHOW_TOAST]: true, [RcbEvent.DISMISS_TOAST]: true, [RcbEvent.USER_SUBMIT_TEXT]: true, - [RcbEvent.USER_UPLOAD_FILE]: true + [RcbEvent.USER_UPLOAD_FILE]: true, + [RcbEvent.TEXTAREA_CHANGE_VALUE]: true } /** @@ -36,7 +37,7 @@ export const emitRcbEvent = (eventName: typeof RcbEvent[keyof typeof RcbEvent], // Create a custom event with the provided name and detail const event: RcbBaseEvent = new CustomEvent(eventName, { detail: eventDetail, - cancelable: cancellableMap.eventName, + cancelable: cancellableMap[eventName], }) as RcbBaseEvent; event.data = data; diff --git a/src/types/Settings.ts b/src/types/Settings.ts index 71f3d533..7e271a11 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -148,5 +148,6 @@ export type Settings = { rcbDismissToast?: boolean; rcbUserSubmitText?: boolean; rcbUserUploadFile?: boolean; + rcbTextareaChangeValue?: boolean; } } diff --git a/src/types/events/RcbTextareaChangeValue.ts b/src/types/events/RcbTextareaChangeValue.ts new file mode 100644 index 00000000..876b880c --- /dev/null +++ b/src/types/events/RcbTextareaChangeValue.ts @@ -0,0 +1,9 @@ +import { RcbBaseEvent } from "../internal/events/RcbBaseEvent"; + +/** + * Defines the data available for stop stream message event. + */ +export type RcbTextareaChangeValueEvent = RcbBaseEvent<{ + currValue: string; + prevValue: string; +}>; \ No newline at end of file diff --git a/types/global.d.ts b/types/global.d.ts index 014ab383..23f9ef56 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -16,6 +16,7 @@ import { RcbDismissToastEvent } from "../src/types/events/RcbDismissToastEvent"; import { RcbUserSubmitTextEvent } from "../src/types/events/RcbUserSubmitTextEvent"; import { RcbUserUploadFileEvent } from "../src/types/events/RcbUserUploadFileEvent"; import { RcbEvent } from "../src/constants/RcbEvent"; +import { RcbTextareaChangeValueEvent } from "../src/types/events/RcbTextareaChangeValue"; declare global { interface Navigator { @@ -72,5 +73,8 @@ declare global { // user input submission "rcb-user-submit-text": RcbUserSubmitTextEvent; "rcb-user-upload-file": RcbUserUploadFileEvent; + + // textarea change value + "rcb-textarea-change-value": RcbTextareaChangeValueEvent; } }