From c5cab0cb46e040934a6c37d4986c946183ce49ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Wed, 16 Oct 2024 18:51:01 +0200 Subject: [PATCH] Chat: Implement Typing --- .../devextreme/js/__internal/ui/chat/chat.ts | 57 +++++++++- .../js/__internal/ui/chat/messagebox.ts | 100 +++++++++++++++++- packages/devextreme/playground/jquery.html | 11 +- 3 files changed, 161 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index 008235cbbbaf..fc96d0e56501 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -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 { _chatHeader?: ChatHeader; @@ -32,6 +39,12 @@ class Chat extends Widget { _messageSendAction?: (e: Partial) => 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(), @@ -43,6 +56,8 @@ class Chat extends Widget { dataSource: null, user: { id: new Guid().toString() }, onMessageSend: undefined, + onTypingStart: undefined, + onTypingEnd: undefined, showDayHeaders: true, }; } @@ -57,6 +72,8 @@ class Chat extends Widget { this._refreshDataSource(); this._createMessageSendAction(); + this._createTypingStartAction(); + this._createTypingEndAction(); } _dataSourceLoadErrorHandler(): void { @@ -131,6 +148,12 @@ class Chat extends Widget { onMessageSend: (e) => { this._messageSendHandler(e); }, + onTypingStart: () => { + this._typingStartHandler(); + }, + onTypingEnd: () => { + this._typingEndHandler(); + }, }; this._messageBox = this._createComponent($messageBox, MessageBox, configuration); @@ -158,6 +181,20 @@ class Chat extends Widget { ); } + _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(); @@ -171,6 +208,18 @@ class Chat extends Widget { 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}`); @@ -216,6 +265,12 @@ class Chat extends Widget { case 'onMessageSend': this._createMessageSendAction(); break; + case 'onTypingStart': + this._createTypingStartAction(); + break; + case 'onTypingEnd': + this._createTypingEndAction(); + break; case 'showDayHeaders': this._messageList.option(name, value); break; diff --git a/packages/devextreme/js/__internal/ui/chat/messagebox.ts b/packages/devextreme/js/__internal/ui/chat/messagebox.ts index 2f030e6b7b08..eff7bc9bd44a 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagebox.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagebox.ts @@ -15,6 +15,26 @@ 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 & { text?: string }; @@ -22,6 +42,12 @@ export type MessageSendEvent = export interface Properties extends DOMComponentProperties { 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; @@ -36,6 +62,18 @@ class MessageBox extends DOMComponent { _messageSendAction?: (e: Partial) => 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(), @@ -50,6 +88,17 @@ class MessageBox extends DOMComponent { 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 { @@ -82,9 +131,8 @@ class MessageBox extends DOMComponent { 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) { @@ -133,6 +181,46 @@ class MessageBox extends DOMComponent { ); } + _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; @@ -171,6 +259,12 @@ class MessageBox extends DOMComponent { case 'onMessageSend': this._createMessageSendAction(); break; + case 'onTypingStart': + this._createTypingStartAction(); + break; + case 'onTypingEnd': + this._createTypingEndAction(); + break; default: super._optionChanged(args); } diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index fde766f17218..22e4d3f25c04 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -52,9 +52,14 @@

Te