From b46070454f435a99367d6894e61153469d52527d Mon Sep 17 00:00:00 2001 From: Andrei Vorobev Date: Wed, 24 Jun 2026 14:30:26 +0300 Subject: [PATCH 1/3] Chat: added clear button behaviour --- .../stories/chat/Chat.stories.tsx | 31 ++++ packages/devextreme-vue/src/chat.ts | 6 + .../devextreme/js/__internal/ui/chat/chat.ts | 33 +++++ packages/devextreme/js/ui/chat.d.ts | 13 ++ packages/devextreme/playground/index.html | 5 +- packages/devextreme/playground/index.ts | 137 +++++++++++++++--- packages/devextreme/ts/dx.all.d.ts | 8 + 7 files changed, 207 insertions(+), 26 deletions(-) diff --git a/apps/react-storybook/stories/chat/Chat.stories.tsx b/apps/react-storybook/stories/chat/Chat.stories.tsx index 76bc58f640d2..b0ee7ba0da79 100644 --- a/apps/react-storybook/stories/chat/Chat.stories.tsx +++ b/apps/react-storybook/stories/chat/Chat.stories.tsx @@ -988,6 +988,37 @@ export const ControlledMode: Story = { } } +export const ClearButton: Story = { + args: { + showClearButton: true, + }, + argTypes: { + showClearButton: { + control: 'boolean', + }, + }, + render: ({ showClearButton }) => { + const [messages, setMessages] = useState([...initialMessages]); + + const onMessageEntered = useCallback(({ message }: ChatTypes.MessageEnteredEvent) => { + setMessages((prev) => [...prev, message]); + }, []); + + return ( +
+ +
+ ); + }, +}; + export const SendButtonOptions: Story = { args: { action: 'send', diff --git a/packages/devextreme-vue/src/chat.ts b/packages/devextreme-vue/src/chat.ts index 7036b1e3115e..c5bbc8e94fa7 100644 --- a/packages/devextreme-vue/src/chat.ts +++ b/packages/devextreme-vue/src/chat.ts @@ -110,6 +110,7 @@ type AccessibleOptions = Pick string)) | Record | string>, onAttachmentDownloadClick: Function as PropType<((e: AttachmentDownloadClickEvent) => void)>, + onClearButtonClick: Function as PropType<(() => void)>, onDisposing: Function as PropType<((e: DisposingEvent) => void)>, onInitialized: Function as PropType<((e: InitializedEvent) => void)>, onInputFieldTextChanged: Function as PropType<((e: InputFieldTextChangedEvent) => void)>, @@ -181,6 +184,7 @@ const componentConfig = { rtlEnabled: Boolean, sendButtonOptions: Object as PropType>, showAvatar: Boolean, + showClearButton: Boolean, showDayHeaders: Boolean, showMessageTimestamp: Boolean, showUserName: Boolean, @@ -214,6 +218,7 @@ const componentConfig = { "update:messageTemplate": null, "update:messageTimestampFormat": null, "update:onAttachmentDownloadClick": null, + "update:onClearButtonClick": null, "update:onDisposing": null, "update:onInitialized": null, "update:onInputFieldTextChanged": null, @@ -231,6 +236,7 @@ const componentConfig = { "update:rtlEnabled": null, "update:sendButtonOptions": null, "update:showAvatar": null, + "update:showClearButton": null, "update:showDayHeaders": null, "update:showMessageTimestamp": null, "update:showUserName": null, diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index f94ffa754d43..a179332507b3 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -27,6 +27,7 @@ import { getGlobalFormatByDataType } from '@ts/core/m_global_format_config'; import { invokeConditionally } from '@ts/core/utils/conditional_invoke'; import type { OptionChanged } from '@ts/core/widget/types'; import Widget from '@ts/core/widget/widget'; +import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import AlertList from '@ts/ui/chat/alertlist'; import ConfirmationPopup from '@ts/ui/chat/confirmationpopup'; import type { @@ -45,6 +46,8 @@ import MessageList from '@ts/ui/chat/messagelist'; import Suggestions, { type SuggestionsOptions } from '@ts/ui/chat/suggestions'; import type { DataChange } from '@ts/ui/collection/collection_widget.base'; +import Toolbar from '../toolbar/toolbar'; + const CHAT_CLASS = 'dx-chat'; const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input'; @@ -53,6 +56,8 @@ class Chat extends Widget { _messageList!: MessageList; + _toolbar?: Toolbar; + _alertList!: AlertList; _suggestions?: Suggestions; @@ -130,6 +135,8 @@ class Chat extends Widget { onTypingStart: undefined, onAttachmentDownloadClick: undefined, onInputFieldTextChanged: undefined, + showClearButton: false, + onClearButtonClick: undefined, }; } @@ -184,10 +191,15 @@ class Chat extends Widget { } _initMarkup(): void { + const { showClearButton } = this.option(); $(this.element()).addClass(CHAT_CLASS); super._initMarkup(); + if (showClearButton) { + this._renderToolbar(); + } + this._renderMessageList(); this._renderAlertList(); this._renderSuggestions(); @@ -504,6 +516,27 @@ class Chat extends Widget { return config; } + _getClearButtonConfig(): ToolbarItem { + const { onClearButtonClick } = this.option(); + return { + widget: 'dxButton', + location: 'after', + options: { + icon: 'trash', + onClick: (): void => { + onClearButtonClick?.(); + }, + }, + } as ToolbarItem; + } + + _renderToolbar(): void { + // @ts-expect-error Toolbar typings? + this._toolbar = new Toolbar(this.$element(), { + items: [this._getClearButtonConfig()], + }); + } + _renderSuggestions(): void { this._suggestions = new Suggestions(this.$element(), this._getSuggestionsConfiguration()); } diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index d46916502ccb..d69d4f13a9d8 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -644,6 +644,19 @@ export interface dxChatOptions extends WidgetOptions { * @public */ onTypingStart?: ((e: TypingStartEvent) => void) | undefined; + /** + * @docid + * @default false + * @public + */ + showClearButton?: boolean; + /** + * @docid + * @default undefined + * @action + * @public + */ + onClearButtonClick?: (() => void) | undefined; } /** diff --git a/packages/devextreme/playground/index.html b/packages/devextreme/playground/index.html index ba4497ebcec5..b68b551744e3 100644 --- a/packages/devextreme/playground/index.html +++ b/packages/devextreme/playground/index.html @@ -6,9 +6,10 @@ DevExtreme Playground -
+
-
+
+
diff --git a/packages/devextreme/playground/index.ts b/packages/devextreme/playground/index.ts index 68ff30f0d976..5c1d11f8b977 100644 --- a/packages/devextreme/playground/index.ts +++ b/packages/devextreme/playground/index.ts @@ -1,32 +1,121 @@ import '../js/__internal/integration/jquery'; -import '../js/ui/card_view'; +import '../js/ui/chat'; import $ from 'jquery'; import { setupThemeSelector } from './themeSelector.ts'; -const customers = [ - { ID: 1, Company: 'Super Mart of the West', Address: '702 SW 8th Street', City: 'Bentonville', State: 'Arkansas', Zipcode: 72716, Phone: '(800) 555-2797' }, - { ID: 2, Company: 'Electronics Depot', Address: '2455 Paces Ferry Road NW', City: 'Atlanta', State: 'Georgia', Zipcode: 30339, Phone: '(800) 595-3232' }, - { ID: 3, Company: 'K&S Music', Address: '1000 Nicllet Mall', City: 'Minneapolis', State: 'Minnesota', Zipcode: 55403, Phone: '(612) 304-6073' }, - { ID: 4, Company: "Tom's Club", Address: '999 Lake Drive', City: 'Issaquah', State: 'Washington', Zipcode: 98027, Phone: '(800) 955-2292' }, - { ID: 5, Company: 'E-Mart', Address: '3333 Beverly Rd', City: 'Hoffman Estates', State: 'Illinois', Zipcode: 60179, Phone: '(847) 286-2500' }, - { ID: 6, Company: 'Walters', Address: '200 Wilmot Rd', City: 'Deerfield', State: 'Illinois', Zipcode: 60015, Phone: '(847) 940-2500' }, - { ID: 7, Company: 'StereoShack', Address: '400 Commerce S', City: 'Fort Worth', State: 'Texas', Zipcode: 76102, Phone: '(817) 820-0741' }, - { ID: 8, Company: 'Circuit Town', Address: '2200 Kensington Court', City: 'Oak Brook', State: 'Illinois', Zipcode: 60523, Phone: '(800) 955-2929' }, - { ID: 9, Company: 'Premier Buy', Address: '7601 Penn Avenue South', City: 'Richfield', State: 'Minnesota', Zipcode: 55423, Phone: '(612) 291-1000' }, - { ID: 10, Company: 'ElectrixMax', Address: '263 Shuman Blvd', City: 'Naperville', State: 'Illinois', Zipcode: 60563, Phone: '(630) 438-7800' }, - { ID: 11, Company: 'Video Emporium', Address: '1201 Elm Street', City: 'Dallas', State: 'Texas', Zipcode: 75270, Phone: '(214) 854-3000' }, - { ID: 12, Company: 'Screen Shop', Address: '1000 Lowes Blvd', City: 'Mooresville', State: 'North Carolina', Zipcode: 28117, Phone: '(800) 445-6937' }, +//data.js +const getTimestamp = function (date, offsetMinutes = 0) { + return date.getTime() + offsetMinutes * 60000; +}; + +const date = new Date(); +date.setHours(0, 0, 0, 0); + +const currentUser = { + id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', + name: 'John Doe', +}; + +const supportAgent = { + id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3', + name: 'Support Agent', + avatarUrl: 'images/petersmith.png', +}; + +let initialMessages = [ + { + timestamp: getTimestamp(date, -9), + author: supportAgent, + text: 'Hello, John!\nHow can I assist you today?', + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: "Hi, I'm having trouble accessing my account.", + }, + { + timestamp: getTimestamp(date, -7), + author: currentUser, + text: 'It says my password is incorrect.', + }, + { + timestamp: getTimestamp(date, -7), + author: supportAgent, + text: 'I can help you with that. Can you please confirm your UserID for security purposes?', + }, + { + timestamp: getTimestamp(date, 1), + author: currentUser, + text: 'john.doe1357', + }, + { + timestamp: getTimestamp(date, 1), + author: supportAgent, + text: '✅ Instructions to restore access have been sent to the email address associated with your account.', + }, ]; -window.addEventListener('load', () => +const initialMessagesForSupport = initialMessages.slice(); + + + + +function onWindowLoad() { + $(() => { + let userChat; + + function onMessageEntered({ message }) { + userChat.renderMessage(message); + supportChat.renderMessage(message); + } + + function userChatTypingStart() { + supportChat.option('typingUsers', [currentUser]); + } + + function userChatTypingEnd() { + supportChat.option('typingUsers', []); + } + + function supportChatTypingStart() { + userChat.option('typingUsers', [supportAgent]); + } + + function supportChatTypingEnd() { + userChat.option('typingUsers', []); + } + + const removeMessages = () => { + initialMessages = []; + userChat.option('items', []); + } + + + userChat = $('#user-chat').dxChat({ + items: initialMessages, + user: currentUser, + speechToTextEnabled: true, + onMessageEntered, + onTypingStart: userChatTypingStart, + onTypingEnd: userChatTypingEnd, + showClearButton: true, + onClearButtonClick: removeMessages, + }).dxChat('instance'); + + const supportChat = $('#support-chat').dxChat({ + items: initialMessagesForSupport, + user: supportAgent, + speechToTextEnabled: true, + onMessageEntered, + onTypingStart: supportChatTypingStart, + onTypingEnd: supportChatTypingEnd, + }).dxChat('instance'); + }); + +} + +window.addEventListener('load', () => { setupThemeSelector('theme-selector') .catch((err) => console.error('Theme loading failed:', err)) - .then(() => { - $('#widget-container').dxCardView({ - dataSource: customers, - keyExpr: 'ID', - cardsPerRow: 'auto', - cardMinWidth: 320, - columns: ['Company', 'Address', 'City', 'State', 'Zipcode', 'Phone'], - }); - })); \ No newline at end of file + .then(() => onWindowLoad()) +}); \ No newline at end of file diff --git a/packages/devextreme/ts/dx.all.d.ts b/packages/devextreme/ts/dx.all.d.ts index 9a2eacec574a..0d51710b0f18 100644 --- a/packages/devextreme/ts/dx.all.d.ts +++ b/packages/devextreme/ts/dx.all.d.ts @@ -11851,6 +11851,14 @@ declare module DevExpress.ui { onTypingStart?: | ((e: DevExpress.ui.dxChat.TypingStartEvent) => void) | undefined; + /** + * [descr:dxChatOptions.showClearButton] + */ + showClearButton?: boolean; + /** + * [descr:dxChatOptions.onClearButtonClick] + */ + onClearButtonClick?: (() => void) | undefined; } /** * [descr:dxCheckBox] From 4a3cb2fe8fa50ecb6f83b4d63635a5708353866c Mon Sep 17 00:00:00 2001 From: Andrei Vorobev Date: Wed, 24 Jun 2026 14:45:48 +0300 Subject: [PATCH 2/3] Chat: added clear button behaviour --- .../devextreme-angular/src/ui/chat/index.ts | 21 +++++++++++++++++++ packages/devextreme-vue/src/chat.ts | 3 --- packages/devextreme/js/ui/chat.d.ts | 3 ++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/devextreme-angular/src/ui/chat/index.ts b/packages/devextreme-angular/src/ui/chat/index.ts index bd20da79ff2f..3a2d8d96d99f 100644 --- a/packages/devextreme-angular/src/ui/chat/index.ts +++ b/packages/devextreme-angular/src/ui/chat/index.ts @@ -395,6 +395,19 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges } + /** + * [descr:dxChatOptions.showClearButton] + + */ + @Input() + get showClearButton(): boolean { + return this._getOption('showClearButton'); + } + set showClearButton(value: boolean) { + this._setOption('showClearButton', value); + } + + /** * [descr:dxChatOptions.showDayHeaders] @@ -784,6 +797,13 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges */ @Output() showAvatarChange: EventEmitter; + /** + + * This member supports the internal infrastructure and is not intended to be used directly from your code. + + */ + @Output() showClearButtonChange: EventEmitter; + /** * This member supports the internal infrastructure and is not intended to be used directly from your code. @@ -903,6 +923,7 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges { emit: 'rtlEnabledChange' }, { emit: 'sendButtonOptionsChange' }, { emit: 'showAvatarChange' }, + { emit: 'showClearButtonChange' }, { emit: 'showDayHeadersChange' }, { emit: 'showMessageTimestampChange' }, { emit: 'showUserNameChange' }, diff --git a/packages/devextreme-vue/src/chat.ts b/packages/devextreme-vue/src/chat.ts index c5bbc8e94fa7..468114ac28cd 100644 --- a/packages/devextreme-vue/src/chat.ts +++ b/packages/devextreme-vue/src/chat.ts @@ -110,7 +110,6 @@ type AccessibleOptions = Pick string)) | Record | string>, onAttachmentDownloadClick: Function as PropType<((e: AttachmentDownloadClickEvent) => void)>, - onClearButtonClick: Function as PropType<(() => void)>, onDisposing: Function as PropType<((e: DisposingEvent) => void)>, onInitialized: Function as PropType<((e: InitializedEvent) => void)>, onInputFieldTextChanged: Function as PropType<((e: InputFieldTextChangedEvent) => void)>, @@ -218,7 +216,6 @@ const componentConfig = { "update:messageTemplate": null, "update:messageTimestampFormat": null, "update:onAttachmentDownloadClick": null, - "update:onClearButtonClick": null, "update:onDisposing": null, "update:onInitialized": null, "update:onInputFieldTextChanged": null, diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index d69d4f13a9d8..46808b89da7f 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -646,13 +646,14 @@ export interface dxChatOptions extends WidgetOptions { onTypingStart?: ((e: TypingStartEvent) => void) | undefined; /** * @docid - * @default false + * @default undefined * @public */ showClearButton?: boolean; /** * @docid * @default undefined + * @type_function_param1 undefined * @action * @public */ From a48d5972e157d8a94204f296018630f4c1d360d8 Mon Sep 17 00:00:00 2001 From: Andrei Vorobev Date: Wed, 24 Jun 2026 16:24:47 +0300 Subject: [PATCH 3/3] Chat: added clear button behaviour story --- apps/react-storybook/stories/chat/Chat.stories.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/react-storybook/stories/chat/Chat.stories.tsx b/apps/react-storybook/stories/chat/Chat.stories.tsx index b0ee7ba0da79..510cd8eeb36d 100644 --- a/apps/react-storybook/stories/chat/Chat.stories.tsx +++ b/apps/react-storybook/stories/chat/Chat.stories.tsx @@ -1004,8 +1004,16 @@ export const ClearButton: Story = { setMessages((prev) => [...prev, message]); }, []); + const onClearButtonClick = useCallback(() => { + setMessages([]); + }, []); + return ( -
+
);