diff --git a/packages/devextreme-angular/src/ui/chat/index.ts b/packages/devextreme-angular/src/ui/chat/index.ts index 3b12df31c558..9b32cfefc9bc 100644 --- a/packages/devextreme-angular/src/ui/chat/index.ts +++ b/packages/devextreme-angular/src/ui/chat/index.ts @@ -22,6 +22,8 @@ import { } from '@angular/core'; +import { Store } from 'devextreme/data'; +import DataSource, { Options as DataSourceOptions } from 'devextreme/data/data_source'; import { DisposingEvent, InitializedEvent, Message, MessageSendEvent, OptionChangedEvent, User } from 'devextreme/ui/chat'; import DxChat from 'devextreme/ui/chat'; @@ -88,6 +90,19 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges } + /** + * [descr:dxChatOptions.dataSource] + + */ + @Input() + get dataSource(): Store | DataSource | DataSourceOptions | null | string | Array { + return this._getOption('dataSource'); + } + set dataSource(value: Store | DataSource | DataSourceOptions | null | string | Array) { + this._setOption('dataSource', value); + } + + /** * [descr:WidgetOptions.disabled] @@ -276,6 +291,13 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges */ @Output() activeStateEnabledChange: EventEmitter; + /** + + * This member supports the internal infrastructure and is not intended to be used directly from your code. + + */ + @Output() dataSourceChange: EventEmitter>; + /** * This member supports the internal infrastructure and is not intended to be used directly from your code. @@ -383,6 +405,7 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges { subscribe: 'optionChanged', emit: 'onOptionChanged' }, { emit: 'accessKeyChange' }, { emit: 'activeStateEnabledChange' }, + { emit: 'dataSourceChange' }, { emit: 'disabledChange' }, { emit: 'elementAttrChange' }, { emit: 'focusStateEnabledChange' }, @@ -412,6 +435,7 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); + this.setupChanges('dataSource', changes); this.setupChanges('items', changes); } @@ -422,6 +446,7 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges } ngDoCheck() { + this._idh.doCheck('dataSource'); this._idh.doCheck('items'); this._watcherHelper.checkWatchers(); super.ngDoCheck(); diff --git a/packages/devextreme-scss/scss/widgets/base/chat/_index.scss b/packages/devextreme-scss/scss/widgets/base/chat/_index.scss index 50631cc2b22b..41466058689e 100644 --- a/packages/devextreme-scss/scss/widgets/base/chat/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/chat/_index.scss @@ -43,13 +43,48 @@ $chat-text-area-height: 40px; padding: 24px 0; } +.dx-chat-message-bubble-container { + display: flex; + flex-direction: column; + row-gap: 4px; + max-width: 90%; +} + .dx-chat-message-group-alignment-start { justify-items: start; grid-template-columns: 44px 1fr; + grid-column: 2; + + .dx-chat-message-bubble { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + + &:first-child { + border-top-left-radius: $chat-bubble-border-radius; + } + + &:last-child { + border-bottom-left-radius: $chat-bubble-border-radius; + } + } } .dx-chat-message-group-alignment-end { justify-items: end; + + .dx-chat-message-bubble { + background-color: $chat-bubble-background-color-primary; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + + &:first-child { + border-top-right-radius: $chat-bubble-border-radius; + } + + &:last-child { + border-bottom-right-radius: $chat-bubble-border-radius; + } + } } .dx-chat-message-group-information { @@ -84,61 +119,10 @@ $chat-text-area-height: 40px; .dx-chat-message-bubble { padding: 8px 12px; - max-width: 90%; border-radius: $chat-bubble-border-radius; background-color: $chat-bubble-background-color-secondary; } -.dx-chat-message-group-alignment-start .dx-chat-message-bubble.dx-chat-message-bubble-first { - border-bottom-left-radius: 0; -} - -.dx-chat-message-group-alignment-start .dx-chat-message-bubble.dx-chat-message-bubble-last { - border-top-left-radius: 0; -} - -.dx-chat-message-group-alignment-start .dx-chat-message-bubble-first.dx-chat-message-bubble-last { - border-bottom-left-radius: $chat-bubble-border-radius; - border-top-left-radius: $chat-bubble-border-radius; -} - -.dx-chat-message-group-alignment-start .dx-chat-message-bubble:not( - .dx-chat-message-bubble-first.dx-chat-message-bubble-last, - .dx-chat-message-bubble-first, - .dx-chat-message-bubble-last) { - border-bottom-left-radius: 0; - border-top-left-radius: 0; -} - -.dx-chat-message-group-alignment-end .dx-chat-message-bubble.dx-chat-message-bubble-first { - border-bottom-right-radius: 0; -} - -.dx-chat-message-group-alignment-end .dx-chat-message-bubble.dx-chat-message-bubble-last { - border-top-right-radius: 0; -} - -.dx-chat-message-group-alignment-end .dx-chat-message-bubble-first.dx-chat-message-bubble-last { - border-bottom-right-radius: $chat-bubble-border-radius; - border-top-right-radius: $chat-bubble-border-radius; -} - -.dx-chat-message-group-alignment-end .dx-chat-message-bubble:not( - .dx-chat-message-bubble-first.dx-chat-message-bubble-last, - .dx-chat-message-bubble-first, - .dx-chat-message-bubble-last) { - border-bottom-right-radius: 0; - border-top-right-radius: 0; -} - -.dx-chat-message-group-alignment-start .dx-chat-message-bubble { - grid-column: 2; -} - -.dx-chat-message-group-alignment-end .dx-chat-message-bubble { - background-color: $chat-bubble-background-color-primary; -} - .dx-chat-message-box { display: flex; align-items: center; diff --git a/packages/devextreme-vue/src/chat.ts b/packages/devextreme-vue/src/chat.ts index a8ef6d018abc..ca9626be8229 100644 --- a/packages/devextreme-vue/src/chat.ts +++ b/packages/devextreme-vue/src/chat.ts @@ -5,6 +5,7 @@ import { createConfigurationComponent } from "./core/index"; type AccessibleOptions = Pick { hoverStateEnabled: true, title: '', items: [], + dataSource: null, user: { id: new Guid().toString() }, onMessageSend: undefined, }; @@ -138,7 +140,7 @@ class Chat extends Widget { case 'activeStateEnabled': case 'focusStateEnabled': case 'hoverStateEnabled': - this._messageBox.option({ [name]: value }); + this._messageBox.option(name, value as Properties[typeof name]); break; case 'title': { if (value) { @@ -154,10 +156,11 @@ class Chat extends Widget { break; } case 'user': - this._messageList.option('currentUserId', (value as Properties['user'])?.id); + this._messageList.option('currentUserId', (value as Properties[typeof name])?.id); break; case 'items': - this._messageList.option('items', (value as Properties['items']) ?? []); + case 'dataSource': + this._messageList.option(name, value as Properties[typeof name]); break; case 'onMessageSend': this._createMessageSendAction(); @@ -176,7 +179,6 @@ class Chat extends Widget { } } -// @ts-expect-error ts-error -registerComponent('dxChat', Chat); +registerComponent('dxChat', Chat as unknown as ComponentFactory); export default Chat; diff --git a/packages/devextreme/js/__internal/ui/chat/chat_message_group.ts b/packages/devextreme/js/__internal/ui/chat/chat_message_group.ts index 7b11a4ebbb44..ef502913013a 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat_message_group.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat_message_group.ts @@ -14,9 +14,7 @@ const CHAT_MESSAGE_GROUP_ALIGNMENT_END_CLASS = 'dx-chat-message-group-alignment- const CHAT_MESSAGE_GROUP_INFORMATION_CLASS = 'dx-chat-message-group-information'; const CHAT_MESSAGE_TIME_CLASS = 'dx-chat-message-time'; const CHAT_MESSAGE_AUTHOR_NAME_CLASS = 'dx-chat-message-author-name'; -const CHAT_MESSAGE_BUBBLE_CLASS = 'dx-chat-message-bubble'; -const CHAT_MESSAGE_BUBBLE_FIRST_CLASS = 'dx-chat-message-bubble-first'; -const CHAT_MESSAGE_BUBBLE_LAST_CLASS = 'dx-chat-message-bubble-last'; +const CHAT_MESSAGE_BUBBLE_CONTAINER_CLASS = 'dx-chat-message-bubble-container'; export type MessageGroupAlignment = 'start' | 'end'; @@ -28,6 +26,8 @@ export interface MessageGroupOptions extends WidgetOptions { class MessageGroup extends Widget { _avatar?: Avatar; + _$messageBubbleContainer!: dxElementWrapper; + _getDefaultOptions(): MessageGroupOptions { return { ...super._getDefaultOptions(), @@ -84,31 +84,24 @@ class MessageGroup extends Widget { }); } - _renderMessageBubble(message: Message, index: number, length: number): void { + _renderMessageBubble(message: Message): void { const $bubble = $('
'); - const isFirst = index === 0; - const isLast = index === length - 1; - - if (isFirst) { - $bubble.addClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS); - } - - if (isLast) { - $bubble.addClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS); - } - - $bubble.appendTo(this.element()); - this._createComponent($bubble, MessageBubble, { text: message.text, }); + + this._$messageBubbleContainer.append($bubble); } _renderMessageBubbles(items: Message[]): void { - items.forEach((message, index) => { - this._renderMessageBubble(message, index, items.length); + this._$messageBubbleContainer = $('
').addClass(CHAT_MESSAGE_BUBBLE_CONTAINER_CLASS); + + items.forEach((message) => { + this._renderMessageBubble(message); }); + + this._$messageBubbleContainer.appendTo(this.element()); } _renderName(name: string, $element: dxElementWrapper): void { @@ -147,13 +140,6 @@ class MessageGroup extends Widget { $information.appendTo(this.element()); } - _updateLastBubbleClasses(): void { - const $bubbles = $(this.element()).find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - const $lastBubble = $bubbles.eq($bubbles.length - 1); - - $lastBubble.removeClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS); - } - _optionChanged(args: Record): void { const { name } = args; @@ -174,8 +160,7 @@ class MessageGroup extends Widget { this._setOptionWithoutOptionChange('items', newItems); - this._updateLastBubbleClasses(); - this._renderMessageBubble(message, newItems.length - 1, newItems.length); + this._renderMessageBubble(message); } } diff --git a/packages/devextreme/js/core/component_registrator.d.ts b/packages/devextreme/js/core/component_registrator.d.ts index c9637a60890d..3676d7fcc739 100644 --- a/packages/devextreme/js/core/component_registrator.d.ts +++ b/packages/devextreme/js/core/component_registrator.d.ts @@ -1,7 +1,7 @@ import DOMComponent from './dom_component'; import { UserDefinedElement } from './element'; -type ComponentFactory = { +export type ComponentFactory = { new(element: UserDefinedElement, options?: Record): TComponent; getInstance(element: UserDefinedElement): TComponent; }; diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index 46ba1998c99c..b2481f188c32 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -5,6 +5,7 @@ import { InitializedEventInfo, ChangedOptionInfo, } from '../events/index'; +import DataSource, { DataSourceLike } from '../data/data_source'; /** * @docid _ui_chat_DisposingEvent @@ -138,6 +139,13 @@ export interface dxChatOptions extends WidgetOptions { * @public */ items?: Array; + /** + * @docid + * @type string | Array | Store | DataSource | DataSourceOptions | null + * @default null + * @public + */ + dataSource?: DataSourceLike | null; /** * @docid * @default null @@ -150,7 +158,7 @@ export interface dxChatOptions extends WidgetOptions { /** * @docid - * @inherits Widget + * @inherits Widget, DataHelperMixin * @namespace DevExpress.ui * @public */ @@ -161,6 +169,8 @@ export default class dxChat extends Widget { * @public */ renderMessage(message: Message): void; + + getDataSource(): DataSource; } /** @public */ diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageGroup.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageGroup.markup.tests.js index 29d87329362c..0a8bf51f1624 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageGroup.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageGroup.markup.tests.js @@ -11,8 +11,6 @@ const CHAT_MESSAGE_GROUP_INFORMATION_CLASS = 'dx-chat-message-group-information' const CHAT_MESSAGE_TIME_CLASS = 'dx-chat-message-time'; const CHAT_MESSAGE_AUTHOR_NAME_CLASS = 'dx-chat-message-author-name'; const CHAT_MESSAGE_BUBBLE_CLASS = 'dx-chat-message-bubble'; -const CHAT_MESSAGE_BUBBLE_FIRST_CLASS = 'dx-chat-message-bubble-first'; -const CHAT_MESSAGE_BUBBLE_LAST_CLASS = 'dx-chat-message-bubble-last'; const moduleConfig = { beforeEach: function() { @@ -219,76 +217,5 @@ QUnit.module('MessageGroup', moduleConfig, () => { assert.strictEqual($messageBubble.length, 4); }); - - QUnit.test('single message should have additional classes', function(assert) { - this.reinit({ - items: [{}], - }); - - const $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.length, 1); - assert.strictEqual($messageBubble.hasClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS), true); - assert.strictEqual($messageBubble.hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), true); - }); - - QUnit.test('first message bubble should have additional class', function(assert) { - this.reinit({ - items: [{}, {}], - }); - - const $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.eq(0).hasClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS), true); - assert.strictEqual($messageBubble.eq(0).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), false); - }); - - QUnit.test('last message bubble should have additional class', function(assert) { - this.reinit({ - items: [{}, {}], - }); - - const $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.eq(1).hasClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS), false); - assert.strictEqual($messageBubble.eq(1).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), true); - }); - - QUnit.test('middle message bubbles should not have additional classes', function(assert) { - this.reinit({ - items: [{}, {}, {}, {}], - }); - - const $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.eq(1).hasClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS), false); - assert.strictEqual($messageBubble.eq(1).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), false); - - assert.strictEqual($messageBubble.eq(2).hasClass(CHAT_MESSAGE_BUBBLE_FIRST_CLASS), false); - assert.strictEqual($messageBubble.eq(2).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), false); - }); - - QUnit.test('last class should be deleted from last bubble after renderMessage', function(assert) { - this.reinit({ - items: [{}, {}, {}], - }); - - let $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.eq(2).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), true); - - const newMessage = { - author: { id: 'MikeID' }, - timestamp: Date.now(), - text: 'NEW MESSAGE', - }; - - this.instance.renderMessage(newMessage); - - $messageBubble = this.$element.find(`.${CHAT_MESSAGE_BUBBLE_CLASS}`); - - assert.strictEqual($messageBubble.eq(2).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), false); - assert.strictEqual($messageBubble.eq(3).hasClass(CHAT_MESSAGE_BUBBLE_LAST_CLASS), true); - }); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui/defaultOptions.tests.js b/packages/devextreme/testing/tests/DevExpress.ui/defaultOptions.tests.js index f7ec1185cd1c..66fae23c6e33 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui/defaultOptions.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui/defaultOptions.tests.js @@ -1347,6 +1347,7 @@ testComponentDefaults(Chat, hoverStateEnabled: true, title: '', onMessageSend: undefined, + dataSource: undefined, } ); diff --git a/packages/devextreme/ts/dx.all.d.ts b/packages/devextreme/ts/dx.all.d.ts index e55b230e4769..03dc54094ee2 100644 --- a/packages/devextreme/ts/dx.all.d.ts +++ b/packages/devextreme/ts/dx.all.d.ts @@ -4837,7 +4837,7 @@ declare module DevExpress.core { /** * @deprecated Attention! This type is for internal purposes only. If you used it previously, please submit a ticket to our {@link https://supportcenter.devexpress.com/ticket/create Support Center}. We will check if there is an alternative solution. */ - type ComponentFactory = { + export type ComponentFactory = { new ( element: UserDefinedElement, options?: Record @@ -9462,6 +9462,8 @@ declare module DevExpress.ui { * [descr:dxChat.renderMessage(message)] */ renderMessage(message: DevExpress.ui.dxChat.Message): void; + + getDataSource(): DevExpress.data.DataSource; } module dxChat { /** @@ -9523,6 +9525,10 @@ declare module DevExpress.ui { * [descr:dxChatOptions.items] */ items?: Array; + /** + * [descr:dxChatOptions.dataSource] + */ + dataSource?: DevExpress.data.DataSource.DataSourceLike | null; /** * [descr:dxChatOptions.onMessageSend] */