Skip to content

Commit

Permalink
Feat: textarea value event emission (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
HusseinSerag authored Sep 13, 2024
1 parent 2d7ad85 commit 3036947
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 31 deletions.
23 changes: 5 additions & 18 deletions src/components/ChatBotInput/ChatBotInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -147,26 +151,9 @@ const ChatBotInput = ({ buttons }: { buttons: JSX.Element[] }) => {
* @param event textarea change event
*/
const handleTextAreaValueChange = (event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
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);
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/constants/RcbEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
7 changes: 5 additions & 2 deletions src/context/BotRefsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useContext, useRef } from "react";
import React, { createContext, useContext, useRef } from "react";

import { Flow } from "../types/Flow";

Expand All @@ -9,6 +9,7 @@ type BotRefsContextType = {
botIdRef: React.RefObject<string>;
flowRef: React.RefObject<Flow>;
inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>;
prevInputRef: React.MutableRefObject<string>;
streamMessageMap: React.MutableRefObject<Map<string, string>>;
chatBodyRef: React.RefObject<HTMLDivElement>;
paramsInputRef: React.MutableRefObject<string>;
Expand All @@ -32,6 +33,7 @@ const BotRefsProvider = ({
const botIdRef = useRef<string>(id);
const flowRef = useRef<Flow>(initialFlow);
const inputRef = useRef<HTMLTextAreaElement | HTMLInputElement>(null);
const prevInputRef = useRef<string>("");
const streamMessageMap = useRef<Map<string, string>>(new Map());
const chatBodyRef = useRef<HTMLDivElement>(null);
const paramsInputRef = useRef<string>("");
Expand All @@ -45,7 +47,8 @@ const BotRefsProvider = ({
streamMessageMap,
chatBodyRef,
paramsInputRef,
keepVoiceOnRef
keepVoiceOnRef,
prevInputRef
}}>
{children}
</BotRefsContext.Provider>
Expand Down
44 changes: 35 additions & 9 deletions src/hooks/internal/useTextAreaInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -23,24 +25,48 @@ 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.
*
* @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;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/services/RcbEventService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand All @@ -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<typeof data, EventDetail>;

event.data = data;
Expand Down
1 change: 1 addition & 0 deletions src/types/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,6 @@ export type Settings = {
rcbDismissToast?: boolean;
rcbUserSubmitText?: boolean;
rcbUserUploadFile?: boolean;
rcbTextareaChangeValue?: boolean;
}
}
9 changes: 9 additions & 0 deletions src/types/events/RcbTextareaChangeValue.ts
Original file line number Diff line number Diff line change
@@ -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;
}>;
4 changes: 4 additions & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}

0 comments on commit 3036947

Please sign in to comment.