UNPKG

igniteui-webcomponents

Version:

Ignite UI for Web Components is a complete library of UI components, giving you the ability to build modern web applications using encapsulation and the concept of reusable components in a dependency-free approach.

301 lines (299 loc) 11.4 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { ContextProvider } from '@lit/context'; import { html, LitElement, nothing } from 'lit'; import { property, query } from 'lit/decorators.js'; import { cache } from 'lit/directives/cache.js'; import { repeat } from 'lit/directives/repeat.js'; import { addThemingController } from '../../theming/theming-controller.js'; import IgcButtonComponent from '../button/button.js'; import { chatContext, chatUserInputContext } from '../common/context.js'; import { addSlotController, setSlots } from '../common/controllers/slot.js'; import { registerComponent } from '../common/definitions/register.js'; import { IgcChatResourceStringEN } from '../common/i18n/EN/chat.resources.js'; import { EventEmitterMixin } from '../common/mixins/event-emitter.js'; import { isEmpty } from '../common/util.js'; import IgcIconComponent from '../icon/icon.js'; import IgcListComponent from '../list/list.js'; import IgcToastComponent from '../toast/toast.js'; import IgcTooltipComponent from '../tooltip/tooltip.js'; import IgcChatInputComponent from './chat-input.js'; import IgcChatMessageComponent from './chat-message.js'; import { ChatState } from './chat-state.js'; import { styles } from './themes/chat.base.css.js'; import { styles as shared } from './themes/shared/chat.common.css.js'; import { all } from './themes/themes.js'; const Slots = setSlots('prefix', 'title', 'actions', 'suggestions-header', 'suggestions', 'suggestions-actions', 'suggestion', 'empty-state', 'typing-indicator'); export default class IgcChatComponent extends EventEmitterMixin(LitElement) { static { this.tagName = 'igc-chat'; } static { this.styles = [styles, shared]; } static register() { registerComponent(IgcChatComponent, IgcChatInputComponent, IgcChatMessageComponent, IgcButtonComponent, IgcIconComponent, IgcListComponent, IgcTooltipComponent, IgcToastComponent); } _updateContext() { this._context.setValue(this._state, true); } _updateUserInputContext() { this._userInputContext.setValue(this._state, true); } set messages(value) { this._state.messages = value; } get messages() { return this._state.messages; } set draftMessage(value) { if (this._state && value) { this._state.inputValue = value.text; this._state.inputAttachments = value.attachments || []; this.requestUpdate(); } } get draftMessage() { return { text: this._state.inputValue, attachments: this._state.inputAttachments, }; } set options(value) { this._state.options = value; } get options() { return this._state.options; } constructor() { super(); this._state = new ChatState(this, this._updateContext, this._updateUserInputContext); this._defaults = Object.freeze({ typingIndicator: () => this._renderLoadingTemplate(), suggestionPrefix: () => this._renderSuggestionPrefix(), }); this._slots = addSlotController(this, { slots: Slots, }); this._context = new ContextProvider(this, { context: chatContext, initialValue: this._state, }); this._userInputContext = new ContextProvider(this, { context: chatUserInputContext, initialValue: this._state, }); this.resourceStrings = IgcChatResourceStringEN; addThemingController(this, all); } _getRenderer(name) { return this._state.options?.renderers ? (this._state.options.renderers[name] ?? this._defaults[name]) : this._defaults[name]; } _handleSuggestionClick(text) { this._state.addMessageWithEvent({ text }); this._input?.focusInput(); } scrollToMessage(messageId) { if (!isEmpty(this.messages)) { const message = this.renderRoot.querySelector(`#message-${messageId}`); message?.scrollIntoView({ block: 'end', inline: 'end' }); } } updated(properties) { if ((properties.has('messages') || this._typingIndicator || this._suggestionsContainer) && !this._state.disableAutoScroll) { this._scrollToBottom(); } } _scrollToBottom() { const current = this._scrollContainer.scrollTop; requestAnimationFrame(() => { const scrollHeight = this._scrollContainer.scrollHeight; if (current < scrollHeight) { this._scrollContainer.scrollBy({ top: Math.abs(scrollHeight - current), }); } }); } _renderHeader() { const hasContent = this._slots.hasAssignedElements('prefix') || this._slots.hasAssignedElements('title') || this._slots.hasAssignedElements('actions') || this._state.options?.headerText; return html ` <div part="header" ?hidden=${!hasContent}> <slot name="prefix" ?hidden=${!this._slots.hasAssignedElements('prefix')} ></slot> <slot name="title">${this._state.options?.headerText}</slot> <slot name="actions"></slot> </div> `; } _renderMessages() { const ctx = { instance: this }; return html ` <div part="message-list" tabindex="0"> ${repeat(this._state.messages, (message) => message.id, (message) => { return html ` <igc-chat-message id=${`message-${message.id}`} part="message-item" .message=${message} exportparts=" message-container, message-list, message-header, plain-text: message-content, message-attachments: message-attachments-container, attachment: message-attachment, message-actions: message-actions-container, sent: message-sent, attachment-header, attachment-content, attachment-icon, file-name, " > </igc-chat-message> `; })} ${this._state.options?.isTyping ? html ` <div part="typing-indicator"> <slot name="typing-indicator" >${this._getRenderer('typingIndicator')(ctx)}</slot > </div> ` : nothing} </div> `; } _renderLoadingTemplate() { return html ` <div part="typing-dot"></div> <div part="typing-dot"></div> <div part="typing-dot"></div> <div part="typing-dot"></div> `; } _renderSuggestionPrefix() { return html `<igc-icon name="auto_suggest"></igc-icon>`; } _renderSuggestions() { const hasContent = this._slots.hasAssignedElements('suggestions-header'); const suggestions = this._state.options?.suggestions ?? []; const ctx = { instance: this }; return html ` <div part="suggestions-container"> <igc-list> <igc-list-header part="suggestions-header"> <span ?hidden=${hasContent}> ${this.resourceStrings.suggestionsHeader} </span> <slot name="suggestions-header"></slot> </igc-list-header> <slot name="suggestions"> ${suggestions.map((suggestion) => html ` <slot name="suggestion"> <igc-list-item part="suggestion" @click=${() => this._handleSuggestionClick(suggestion)} > <span slot="start" part="suggestion-prefix"> ${this._getRenderer('suggestionPrefix')(ctx)} </span> <span slot="title" part="suggestion-title" >${suggestion}</span > </igc-list-item> </slot> `)} </slot> <slot name="suggestions-actions"></slot> </igc-list> </div> `; } _renderEmptyState() { return html ` <div part="empty-state"> <slot name="empty-state"></slot> </div> `; } render() { const hasMessages = !isEmpty(this.messages); const suggestions = isEmpty(this._state.options?.suggestions ?? []) ? nothing : this._renderSuggestions(); return html ` <div part="chat-container"> ${this._renderHeader()} <div part="message-area-container"> ${cache(hasMessages || this._state.options?.isTyping ? this._renderMessages() : this._renderEmptyState())} ${this._state.suggestionsPosition === 'below-messages' ? suggestions : nothing} </div> <div part="input-area-container"> <igc-chat-input exportparts=" input-container: input-area, input-wrapper: input-container, attachments: input-attachments-container, attachment-wrapper: input-attachment-container, attachment-name: input-attachment-name, attachment-icon: input-attachment-icon, text-input, actions-container: input-actions-container, input-actions-start, input-actions-end, file-upload-container, file-upload, send-button-container, send-button" > </igc-chat-input> ${this._state.suggestionsPosition === 'below-input' ? suggestions : nothing} </div> </div> `; } } __decorate([ query(IgcChatInputComponent.tagName) ], IgcChatComponent.prototype, "_input", void 0); __decorate([ query('[part="typing-indicator"]') ], IgcChatComponent.prototype, "_typingIndicator", void 0); __decorate([ query('[part="suggestions-container"]') ], IgcChatComponent.prototype, "_suggestionsContainer", void 0); __decorate([ query('[part="message-area-container"]', true) ], IgcChatComponent.prototype, "_scrollContainer", void 0); __decorate([ property({ attribute: false }) ], IgcChatComponent.prototype, "messages", null); __decorate([ property({ attribute: false }) ], IgcChatComponent.prototype, "draftMessage", null); __decorate([ property({ attribute: false }) ], IgcChatComponent.prototype, "options", null); __decorate([ property({ attribute: false }) ], IgcChatComponent.prototype, "resourceStrings", void 0); //# sourceMappingURL=chat.js.map