Skip to content

Commit

Permalink
Chat: Implement Typing
Browse files Browse the repository at this point in the history
  • Loading branch information
marker dao ® committed Oct 16, 2024
1 parent ad8f0c8 commit c5cab0c
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 7 deletions.
57 changes: 56 additions & 1 deletion packages/devextreme/js/__internal/ui/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ import MessageList from './messagelist';
const CHAT_CLASS = 'dx-chat';
const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input';

type Properties = ChatProperties & { title: string; showDayHeaders: boolean };
type Properties = ChatProperties & {
title: string;
showDayHeaders: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onTypingStart: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onTypingEnd: any;
};

class Chat extends Widget<Properties> {
_chatHeader?: ChatHeader;
Expand All @@ -32,6 +39,12 @@ class Chat extends Widget<Properties> {

_messageSendAction?: (e: Partial<MessageSendEvent>) => void;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_typingStartAction?: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_typingEndAction?: any;

_getDefaultOptions(): Properties {
return {
...super._getDefaultOptions(),
Expand All @@ -43,6 +56,8 @@ class Chat extends Widget<Properties> {
dataSource: null,
user: { id: new Guid().toString() },
onMessageSend: undefined,
onTypingStart: undefined,
onTypingEnd: undefined,
showDayHeaders: true,
};
}
Expand All @@ -57,6 +72,8 @@ class Chat extends Widget<Properties> {
this._refreshDataSource();

this._createMessageSendAction();
this._createTypingStartAction();
this._createTypingEndAction();
}

_dataSourceLoadErrorHandler(): void {
Expand Down Expand Up @@ -131,6 +148,12 @@ class Chat extends Widget<Properties> {
onMessageSend: (e) => {
this._messageSendHandler(e);
},
onTypingStart: () => {
this._typingStartHandler();
},
onTypingEnd: () => {
this._typingEndHandler();
},
};

this._messageBox = this._createComponent($messageBox, MessageBox, configuration);
Expand Down Expand Up @@ -158,6 +181,20 @@ class Chat extends Widget<Properties> {
);
}

_createTypingStartAction(): void {
this._typingStartAction = this._createActionByOption(
'onTypingStart',
{ excludeValidators: ['disabled', 'readOnly'] },
);
}

_createTypingEndAction(): void {
this._typingEndAction = this._createActionByOption(
'onTypingEnd',
{ excludeValidators: ['disabled', 'readOnly'] },
);
}

_messageSendHandler(e: MessageBoxMessageSendEvent): void {
const { text, event } = e;
const { user } = this.option();
Expand All @@ -171,6 +208,18 @@ class Chat extends Widget<Properties> {
this._messageSendAction?.({ message, event });
}

_typingStartHandler(): void {
const { user } = this.option();

this._typingStartAction?.({ user });
}

_typingEndHandler(): void {
const { user } = this.option();

this._typingEndAction?.({ user });
}

_focusTarget(): dxElementWrapper {
const $input = $(this.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`);

Expand Down Expand Up @@ -216,6 +265,12 @@ class Chat extends Widget<Properties> {
case 'onMessageSend':
this._createMessageSendAction();
break;
case 'onTypingStart':
this._createTypingStartAction();
break;
case 'onTypingEnd':
this._createTypingEndAction();
break;
case 'showDayHeaders':
this._messageList.option(name, value);
break;
Expand Down
100 changes: 97 additions & 3 deletions packages/devextreme/js/__internal/ui/chat/messagebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,39 @@ const CHAT_MESSAGEBOX_CLASS = 'dx-chat-messagebox';
const CHAT_MESSAGEBOX_TEXTAREA_CLASS = 'dx-chat-messagebox-textarea';
const CHAT_MESSAGEBOX_BUTTON_CLASS = 'dx-chat-messagebox-button';

const TYPING_START_DELAY = 1500;
const TYPING_END_DELAY = 2000;

// eslint-disable-next-line @typescript-eslint/no-explicit-any, spellcheck/spell-checker
const debounce = (func: any, delay: number): any => {
let timestamp = 0;

// eslint-disable-next-line func-names, @typescript-eslint/no-explicit-any
return function (...args: any) {
const now = Date.now();

if (now - timestamp >= delay) {
// eslint-disable-next-line @typescript-eslint/no-invalid-this
func.apply(this, args);

timestamp = now;
}
};
};

export type MessageSendEvent =
NativeEventInfo<MessageBox, KeyboardEvent | PointerEvent | MouseEvent | TouchEvent> &
{ text?: string };

export interface Properties extends DOMComponentProperties<MessageBox> {
onMessageSend?: (e: MessageSendEvent) => void;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
onTypingStart?: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
onTypingEnd?: any;

activeStateEnabled?: boolean;

focusStateEnabled?: boolean;
Expand All @@ -36,6 +62,18 @@ class MessageBox extends DOMComponent<MessageBox, Properties> {

_messageSendAction?: (e: Partial<MessageSendEvent>) => void;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_typingStartAction?: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_typingEndAction?: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any, spellcheck/spell-checker
_debouncedTriggerTypingStartEvent: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_typingStartEventTimeout: any;

_getDefaultOptions(): Properties {
return {
...super._getDefaultOptions(),
Expand All @@ -50,6 +88,17 @@ class MessageBox extends DOMComponent<MessageBox, Properties> {
super._init();

this._createMessageSendAction();
this._createTypingStartAction();
this._createTypingEndAction();
this._wrapTriggerTypingStartEvent();
}

_wrapTriggerTypingStartEvent(): void {
// eslint-disable-next-line spellcheck/spell-checker
this._debouncedTriggerTypingStartEvent = debounce(
this._triggerTypingStartEvent,
TYPING_START_DELAY,
);
}

_initMarkup(): void {
Expand Down Expand Up @@ -82,9 +131,8 @@ class MessageBox extends DOMComponent<MessageBox, Properties> {
valueChangeEvent: 'input',
maxHeight: '8em',
onInput: (): void => {
const shouldButtonBeDisabled = !this._isValuableTextEntered();

this._toggleButtonDisableState(shouldButtonBeDisabled);
this._onInputToggleButtonStateHandler();
this._onInputTriggerTypingEventsHandler();
},
onEnterKey: (e: EnterKeyEvent): void => {
if (!e.event?.shiftKey) {
Expand Down Expand Up @@ -133,6 +181,46 @@ class MessageBox extends DOMComponent<MessageBox, Properties> {
);
}

_createTypingStartAction(): void {
this._typingStartAction = this._createActionByOption(
'onTypingStart',
{ excludeValidators: ['disabled', 'readOnly'] },
);
}

_createTypingEndAction(): void {
this._typingEndAction = this._createActionByOption(
'onTypingEnd',
{ excludeValidators: ['disabled', 'readOnly'] },
);
}

_onInputToggleButtonStateHandler(): void {
const shouldButtonBeDisabled = !this._isValuableTextEntered();

this._toggleButtonDisableState(shouldButtonBeDisabled);
}

_onInputTriggerTypingEventsHandler(): void {
// eslint-disable-next-line spellcheck/spell-checker
this._debouncedTriggerTypingStartEvent();
}

_triggerTypingStartEvent(): void {
clearTimeout(this._typingStartEventTimeout);

// eslint-disable-next-line no-restricted-globals
this._typingStartEventTimeout = setTimeout(() => {
this._triggerTypingEndEvent();
}, TYPING_END_DELAY);

this._typingStartAction?.();
}

_triggerTypingEndEvent(): void {
this._typingEndAction?.();
}

_sendHandler(e: ClickEvent | EnterKeyEvent): void {
if (!this._isValuableTextEntered()) {
return;
Expand Down Expand Up @@ -171,6 +259,12 @@ class MessageBox extends DOMComponent<MessageBox, Properties> {
case 'onMessageSend':
this._createMessageSendAction();
break;
case 'onTypingStart':
this._createTypingStartAction();
break;
case 'onTypingEnd':
this._createTypingEndAction();
break;
default:
super._optionChanged(args);
}
Expand Down
11 changes: 8 additions & 3 deletions packages/devextreme/playground/jquery.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ <h1 style="position: fixed; left: 0; top: 0; clip: rect(1px, 1px, 1px, 1px);">Te
<div id="button"></div>
<script>
$(function() {
$("#button").dxButton({
text: 'Click me!',
onClick: function() { alert("clicked"); }
$("#button").dxChat({
height: 300,
onTypingStart: (e) => {
console.log('Typing has begun');
},
onTypingEnd: (e) => {
console.log('Typing finished');
},
});
});
</script>
Expand Down

0 comments on commit c5cab0c

Please sign in to comment.