@progress/kendo-angular-conversational-ui
Version:
Kendo UI for Angular Conversational UI components
444 lines (435 loc) • 19.1 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* 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: `
(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>
}
(hasScrollButtons) {
<div class="k-suggestion-group"
#suggestionsContainer
role="group"
(scroll)="onScroll($event)">
<ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
</div>
} {
<ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
}
<ng-template #suggestionsContent>
(actions) {
(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>
}
}
(suggestions) {
(!suggestionTemplate?.templateRef) {
(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>
}
}
(suggestionTemplate?.templateRef) {
(suggestion of suggestions; track suggestion) {
<ng-template
[ngTemplateOutlet]="suggestionTemplate.templateRef"
[ngTemplateOutletContext]="{ $implicit: suggestion }"
>
</ng-template>
}
}
}
</ng-template>
(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: `
(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>
}
(hasScrollButtons) {
<div class="k-suggestion-group"
#suggestionsContainer
role="group"
(scroll)="onScroll($event)">
<ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
</div>
} {
<ng-container *ngTemplateOutlet="suggestionsContent"></ng-container>
}
<ng-template #suggestionsContent>
(actions) {
(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>
}
}
(suggestions) {
(!suggestionTemplate?.templateRef) {
(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>
}
}
(suggestionTemplate?.templateRef) {
(suggestion of suggestions; track suggestion) {
<ng-template
[ngTemplateOutlet]="suggestionTemplate.templateRef"
[ngTemplateOutletContext]="{ $implicit: suggestion }"
>
</ng-template>
}
}
}
</ng-template>
(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 }]
}] } });