UNPKG

@progress/kendo-angular-conversational-ui

Version:

Kendo UI for Angular Conversational UI components

444 lines (435 loc) 19.1 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ import { Component, ElementRef, EventEmitter, forwardRef, HostBinding, Input, Output, QueryList, ViewChildren, ViewChild, NgZone, Renderer2 } from '@angular/core'; import { NgTemplateOutlet } from '@angular/common'; import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import { Keys, normalizeKeys } from '@progress/kendo-angular-common'; import { ChatItem } from './chat-item'; import { ChatSuggestionTemplateDirective } from './templates/suggestion-template.directive'; import { ChatService } from './common/chat.service'; import { ChatScrollableButtonComponent } from './common/scroll-button.component'; import { LocalizationService } from '@progress/kendo-angular-l10n'; import { SuggestionsScrollService } from './common/scroll.service'; import * as i0 from "@angular/core"; import * as i1 from "./common/chat.service"; import * as i2 from "@progress/kendo-angular-l10n"; import * as i3 from "./common/scroll.service"; /** * @hidden */ export class SuggestedActionsComponent extends ChatItem { chatService; localization; scrollService; ngZone; renderer; get defaultClass() { if (this.type === 'suggestion') { return this.chatService.suggestionsLayout === 'wrap' || this.chatService.suggestionsLayout === 'scroll'; } else if (this.type === 'action') { return this.chatService.quickActionsLayout === 'wrap' || this.chatService.quickActionsLayout === 'scroll'; } } get scrollableClass() { if (this.type === 'suggestion') { return this.chatService.suggestionsLayout === 'scroll'; } else if (this.type === 'action') { return this.chatService.quickActionsLayout === 'scroll'; } } get scrollButtonsClass() { if (this.type === 'suggestion') { return this.chatService.suggestionsLayout === 'scrollbuttons'; } else if (this.type === 'action') { return this.chatService.quickActionsLayout === 'scrollbuttons'; } } get role() { if (!this.hasScrollButtons) { return 'group'; } return null; } actions; suggestions; tabbable; type; suggestionTemplate; dispatchAction = new EventEmitter(); dispatchSuggestion = new EventEmitter(); items; suggestionsContainer; prevScrollButton; nextScrollButton; selectedIndex = 0; activeIndex = -1; active = false; get hasScrollButtons() { return this.type === 'suggestion' ? this.chatService.suggestionsLayout === 'scrollbuttons' : this.chatService.quickActionsLayout === 'scrollbuttons'; } subscriptions = new Subscription(); resizeObserver = null; actionKeyHandlers = { [Keys.Tab]: (e) => this.changeSelectedIndex(e), [Keys.Enter]: (_, action) => this.actionClick(action), [Keys.Space]: (_, action) => this.actionClick(action), }; suggestionKeyHandlers = { [Keys.Tab]: (e) => this.changeSelectedIndex(e), [Keys.Enter]: (_, suggestion) => this.suggestionClick(suggestion), [Keys.Space]: (_, suggestion) => this.suggestionClick(suggestion), }; constructor(chatService, localization, scrollService, ngZone, renderer) { super(); this.chatService = chatService; this.localization = localization; this.scrollService = scrollService; this.ngZone = ngZone; this.renderer = renderer; this.scrollService.owner = this; } ngAfterViewInit() { const layoutChangeObservable = this.type === 'suggestion' ? this.chatService.suggestionsLayoutChange$ : this.chatService.quickActionsLayoutChange$; this.subscriptions.add(layoutChangeObservable.subscribe((layoutMode) => { if (layoutMode === 'scrollbuttons') { this.ngZone.onStable.pipe(take(1)).subscribe(() => { if (this.suggestionsContainer) { this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement); } this.scrollService.toggleScrollButtonsState(); }); } })); this.subscriptions.add(this.scrollService.scrollButtonActiveStateChange.subscribe((change) => { this.toggleScrollButtonState(change.buttonType, change.active); })); if (this.hasScrollButtons && this.suggestionsContainer) { this.ngZone.runOutsideAngular(() => { this.resizeObserver = new ResizeObserver(() => { this.ngZone.run(() => { this.scrollService.toggleScrollButtonsState(); }); }); this.resizeObserver.observe(this.suggestionsContainer.nativeElement); }); } if (this.hasScrollButtons) { this.ngZone.onStable.pipe(take(1)).subscribe(() => { this.scrollService.toggleScrollButtonsState(); }); if (this.suggestionsContainer) { this.scrollService.updateScrollPosition(this.suggestionsContainer.nativeElement); } } } ngOnDestroy() { this.subscriptions.unsubscribe(); if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } } isSelected(index) { return this.selected && this.selectedIndex === index; } isActive(index) { return this.activeIndex === index; } actionClick(action, index) { if (index >= 0) { this.selectedIndex = index; } this.dispatchAction.next(action); } suggestionClick(suggestion, index) { if (index >= 0) { this.selectedIndex = index; } this.dispatchSuggestion.next(suggestion); } toggleActiveState(apply, index) { this.activeIndex = apply ? index : -1; } actionKeydown(e, action) { const handler = this.actionKeyHandlers[normalizeKeys(e)]; if (handler) { handler(e, action); } } suggestionKeydown(e, suggestion) { const handler = this.suggestionKeyHandlers[normalizeKeys(e)]; if (handler) { handler(e, suggestion); } } getScrollButtonTitle(direction) { let currentButton; if (this.localization.rtl) { currentButton = direction === 'prev' ? 'nextSuggestionsButtonTitle' : 'previousSuggestionsButtonTitle'; } else { currentButton = direction === 'prev' ? 'previousSuggestionsButtonTitle' : 'nextSuggestionsButtonTitle'; } return this.localization.get(currentButton); } onScroll(event) { this.scrollService.onScroll(event); } scrollSuggestions(direction) { this.scrollService.scrollSuggestions(direction); } focus() { } toggleScrollButtonState(buttonType, active) { const button = this[`${buttonType}ScrollButton`]; if (button?.nativeElement) { if (active) { this.renderer.removeClass(button.nativeElement, 'k-disabled'); } else { this.renderer.addClass(button.nativeElement, 'k-disabled'); } } } changeSelectedIndex(e) { const offset = e.shiftKey ? -1 : 1; const prevIndex = this.selectedIndex; this.selectedIndex = Math.max(0, Math.min(prevIndex + offset, this.items.length - 1)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, deps: [{ token: i1.ChatService }, { token: i2.LocalizationService }, { token: i3.SuggestionsScrollService }, { token: i0.NgZone }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SuggestedActionsComponent, isStandalone: true, selector: "kendo-chat-suggested-actions", inputs: { actions: "actions", suggestions: "suggestions", tabbable: "tabbable", type: "type", suggestionTemplate: "suggestionTemplate" }, outputs: { dispatchAction: "dispatchAction", dispatchSuggestion: "dispatchSuggestion" }, host: { properties: { "class.k-suggestion-group": "this.defaultClass", "class.k-suggestion-group-scrollable": "this.scrollableClass", "class.k-suggestion-scrollwrap": "this.scrollButtonsClass", "attr.role": "this.role" } }, providers: [{ provide: ChatItem, useExisting: forwardRef(() => SuggestedActionsComponent) }, SuggestionsScrollService ], viewQueries: [{ propertyName: "suggestionsContainer", first: true, predicate: ["suggestionsContainer"], descendants: true, read: ElementRef }, { propertyName: "prevScrollButton", first: true, predicate: ["prevScrollButton"], descendants: true, read: ElementRef }, { propertyName: "nextScrollButton", first: true, predicate: ["nextScrollButton"], descendants: true, read: ElementRef }, { propertyName: "items", predicate: ["item"], descendants: true }], usesInheritance: true, ngImport: i0, template: ` @if (hasScrollButtons) { <span #prevScrollButton kendoChatScrollableButton [prev]="true" [title]="getScrollButtonTitle('prev')" class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button" (onClick)="scrollSuggestions($event)" > </span> } @if (hasScrollButtons) { <div class="k-suggestion-group" #suggestionsContainer role="group" (scroll)="onScroll($event)"> <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container> </div> } @else { <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container> } <ng-template #suggestionsContent> @if (actions) { @for (action of actions; track action; let index = $index; let first = $first; let last = $last) { <span #item class="k-suggestion k-suggestion-primary" role="button" [class.k-selected]="isSelected(index)" [class.k-focus]="isSelected(index)" [class.k-active]="isActive(index)" [attr.tabindex]="0" (click)="actionClick(action, index)" (keydown)="actionKeydown($event, action)" (mousedown)="toggleActiveState(true, index)" (mouseup)="toggleActiveState(false, index)" > {{ action.title || action.value }} </span> } } @if (suggestions) { @if (!suggestionTemplate?.templateRef) { @for (suggestion of suggestions; track suggestion; let index = $index; let first = $first; let last = $last) { <span #item class="k-suggestion" role="button" [class.k-selected]="isSelected(index)" [class.k-focus]="isSelected(index)" [class.k-active]="isActive(index)" [attr.tabindex]="0" (click)="suggestionClick(suggestion, index)" (keydown)="suggestionKeydown($event, suggestion)" (mousedown)="toggleActiveState(true, index)" (mouseup)="toggleActiveState(false, index)" > {{ suggestion.text }} </span> } } @if (suggestionTemplate?.templateRef) { @for (suggestion of suggestions; track suggestion) { <ng-template [ngTemplateOutlet]="suggestionTemplate.templateRef" [ngTemplateOutletContext]="{ $implicit: suggestion }" > </ng-template> } } } </ng-template> @if (hasScrollButtons) { <span #nextScrollButton kendoChatScrollableButton [prev]="false" [title]="getScrollButtonTitle('next')" class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button" (onClick)="scrollSuggestions($event)" > </span> } `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatScrollableButtonComponent, selector: "[kendoChatScrollableButton]", inputs: ["prev"], outputs: ["onClick"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SuggestedActionsComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-chat-suggested-actions', providers: [{ provide: ChatItem, useExisting: forwardRef(() => SuggestedActionsComponent) }, SuggestionsScrollService], template: ` @if (hasScrollButtons) { <span #prevScrollButton kendoChatScrollableButton [prev]="true" [title]="getScrollButtonTitle('prev')" class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button" (onClick)="scrollSuggestions($event)" > </span> } @if (hasScrollButtons) { <div class="k-suggestion-group" #suggestionsContainer role="group" (scroll)="onScroll($event)"> <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container> </div> } @else { <ng-container *ngTemplateOutlet="suggestionsContent"></ng-container> } <ng-template #suggestionsContent> @if (actions) { @for (action of actions; track action; let index = $index; let first = $first; let last = $last) { <span #item class="k-suggestion k-suggestion-primary" role="button" [class.k-selected]="isSelected(index)" [class.k-focus]="isSelected(index)" [class.k-active]="isActive(index)" [attr.tabindex]="0" (click)="actionClick(action, index)" (keydown)="actionKeydown($event, action)" (mousedown)="toggleActiveState(true, index)" (mouseup)="toggleActiveState(false, index)" > {{ action.title || action.value }} </span> } } @if (suggestions) { @if (!suggestionTemplate?.templateRef) { @for (suggestion of suggestions; track suggestion; let index = $index; let first = $first; let last = $last) { <span #item class="k-suggestion" role="button" [class.k-selected]="isSelected(index)" [class.k-focus]="isSelected(index)" [class.k-active]="isActive(index)" [attr.tabindex]="0" (click)="suggestionClick(suggestion, index)" (keydown)="suggestionKeydown($event, suggestion)" (mousedown)="toggleActiveState(true, index)" (mouseup)="toggleActiveState(false, index)" > {{ suggestion.text }} </span> } } @if (suggestionTemplate?.templateRef) { @for (suggestion of suggestions; track suggestion) { <ng-template [ngTemplateOutlet]="suggestionTemplate.templateRef" [ngTemplateOutletContext]="{ $implicit: suggestion }" > </ng-template> } } } </ng-template> @if (hasScrollButtons) { <span #nextScrollButton kendoChatScrollableButton [prev]="false" [title]="getScrollButtonTitle('next')" class="k-button k-button-md k-button-solid k-button-solid-base k-rounded-md k-icon-button" (onClick)="scrollSuggestions($event)" > </span> } `, standalone: true, imports: [NgTemplateOutlet, ChatScrollableButtonComponent] }] }], ctorParameters: () => [{ type: i1.ChatService }, { type: i2.LocalizationService }, { type: i3.SuggestionsScrollService }, { type: i0.NgZone }, { type: i0.Renderer2 }], propDecorators: { defaultClass: [{ type: HostBinding, args: ['class.k-suggestion-group'] }], scrollableClass: [{ type: HostBinding, args: ['class.k-suggestion-group-scrollable'] }], scrollButtonsClass: [{ type: HostBinding, args: ['class.k-suggestion-scrollwrap'] }], role: [{ type: HostBinding, args: ['attr.role'] }], actions: [{ type: Input }], suggestions: [{ type: Input }], tabbable: [{ type: Input }], type: [{ type: Input }], suggestionTemplate: [{ type: Input }], dispatchAction: [{ type: Output }], dispatchSuggestion: [{ type: Output }], items: [{ type: ViewChildren, args: ['item'] }], suggestionsContainer: [{ type: ViewChild, args: ['suggestionsContainer', { read: ElementRef, static: false }] }], prevScrollButton: [{ type: ViewChild, args: ['prevScrollButton', { read: ElementRef }] }], nextScrollButton: [{ type: ViewChild, args: ['nextScrollButton', { read: ElementRef }] }] } });