@progress/kendo-angular-conversational-ui 24.0.0-develop.3 → 24.0.0-develop.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,11 +7,11 @@ import { InjectionToken, Input, ViewChild, HostBinding, Inject, Directive, Injec
7
7
  import { IconWrapperComponent, IconsService } from '@progress/kendo-angular-icons';
8
8
  import * as i1$2 from '@progress/kendo-angular-popup';
9
9
  import { PopupComponent, KENDO_POPUP, PopupService } from '@progress/kendo-angular-popup';
10
- import { isPresent, normalizeKeys, Keys, focusableSelector, guid, getter, isDocumentAvailable, hasObservers, EventsOutsideAngularDirective, closest as closest$1, ResizeSensorComponent, isChanged, processCssValue, ResizeBatchService } from '@progress/kendo-angular-common';
10
+ import { isPresent, normalizeKeys, Keys, focusableSelector, guid, getter, isDocumentAvailable, hasObservers, EventsOutsideAngularDirective, closest as closest$1, ResizeSensorComponent, getLicenseMessage, shouldShowValidationUI, isChanged, processCssValue, WatermarkOverlayComponent, ResizeBatchService } from '@progress/kendo-angular-common';
11
11
  import { DialogContainerService, DialogService, WindowService, WindowContainerService } from '@progress/kendo-angular-dialog';
12
12
  import { NgTemplateOutlet, NgClass } from '@angular/common';
13
13
  import { Subject, Subscription, fromEvent } from 'rxjs';
14
- import { moreHorizontalIcon, commentIcon, sparklesIcon, stopSmIcon, thumbUpIcon, thumbDownOutlineIcon, thumbDownIcon, thumbUpOutlineIcon, copyIcon, arrowRotateCwIcon, chevronUpIcon, chevronDownIcon, arrowUpOutlineIcon, paperclipOutlineAltRightIcon, undoIcon, downloadIcon, xIcon, moreVerticalIcon, chevronLeftIcon, chevronRightIcon, arrowRotateCwOutlineIcon, warningTriangleIcon, pinOutlineIcon, arrowDownOutlineIcon, cancelOutlineIcon, menuIcon, paperPlaneIcon } from '@progress/kendo-svg-icons';
14
+ import { moreHorizontalIcon, commentIcon, sparklesIcon, stopIcon, thumbUpIcon, thumbDownIcon, copyIcon, arrowRotateCwIcon, chevronUpIcon, chevronDownIcon, arrowUpIcon, paperclipIcon, undoIcon, downloadIcon, xIcon, moreVerticalIcon, chevronLeftIcon, chevronRightIcon, warningTriangleIcon, pinIcon, arrowDownIcon, cancelIcon, menuIcon, paperPlaneIcon } from '@progress/kendo-svg-icons';
15
15
  import * as i1 from '@progress/kendo-angular-l10n';
16
16
  import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
17
17
  import { validatePackage } from '@progress/kendo-licensing';
@@ -215,8 +215,8 @@ const packageMetadata = {
215
215
  productName: 'Kendo UI for Angular',
216
216
  productCode: 'KENDOUIANGULAR',
217
217
  productCodes: ['KENDOUIANGULAR'],
218
- publishDate: 1776940514,
219
- version: '24.0.0-develop.3',
218
+ publishDate: 1778748700,
219
+ version: '24.0.0-develop.30',
220
220
  licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/'
221
221
  };
222
222
 
@@ -820,7 +820,7 @@ class AIPromptComponent {
820
820
  /**
821
821
  * @hidden
822
822
  */
823
- fabStopGenerationSVGIcon = stopSmIcon;
823
+ fabStopGenerationSVGIcon = stopIcon;
824
824
  /**
825
825
  * @hidden
826
826
  */
@@ -964,7 +964,7 @@ class AIPromptComponent {
964
964
  </div>
965
965
  </div>
966
966
  }
967
- `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$3, selector: "[kendoAIPromptLocalizedMessages]" }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: AIPromptToolbarFocusableDirective, selector: "[kendoAIPromptToolbarFocusable]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FloatingActionButtonComponent, selector: "kendo-floatingactionbutton", inputs: ["themeColor", "size", "rounded", "disabled", "align", "offset", "positionMode", "icon", "svgIcon", "iconClass", "buttonClass", "dialClass", "text", "dialItemAnimation", "tabIndex", "dialItems"], outputs: ["blur", "focus", "dialItemClick", "open", "close"] }] });
967
+ `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$3, selector: "[kendoAIPromptLocalizedMessages]" }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: AIPromptToolbarFocusableDirective, selector: "[kendoAIPromptToolbarFocusable]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FloatingActionButtonComponent, selector: "kendo-floatingactionbutton", inputs: ["themeColor", "size", "rounded", "disabled", "align", "offset", "positionMode", "icon", "svgIcon", "iconClass", "buttonClass", "dialClass", "text", "dialItemAnimation", "tabIndex", "dialItems"], outputs: ["blur", "focus", "dialItemClick", "open", "close"] }] });
968
968
  }
969
969
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AIPromptComponent, decorators: [{
970
970
  type: Component,
@@ -1324,17 +1324,17 @@ class AIPromptOutputCardComponent {
1324
1324
  ngOnInit() {
1325
1325
  if (this.promptOutput?.rating === 'positive') {
1326
1326
  this.positiveRatingIcon = thumbUpIcon;
1327
- this.negativeRatingIcon = thumbDownOutlineIcon;
1327
+ this.negativeRatingIcon = thumbDownIcon;
1328
1328
  }
1329
1329
  else if (this.promptOutput?.rating === 'negative') {
1330
1330
  this.negativeRatingIcon = thumbDownIcon;
1331
- this.positiveRatingIcon = thumbUpOutlineIcon;
1331
+ this.positiveRatingIcon = thumbUpIcon;
1332
1332
  }
1333
1333
  }
1334
1334
  copyIcon = copyIcon;
1335
1335
  retryIcon = arrowRotateCwIcon;
1336
- positiveRatingIcon = thumbUpOutlineIcon;
1337
- negativeRatingIcon = thumbDownOutlineIcon;
1336
+ positiveRatingIcon = thumbUpIcon;
1337
+ negativeRatingIcon = thumbDownIcon;
1338
1338
  titleId = `k-output-card-${guid()}`;
1339
1339
  messageFor(text) {
1340
1340
  return this.localization.get(text);
@@ -1381,11 +1381,11 @@ class AIPromptOutputCardComponent {
1381
1381
  this.service.outputRatingChangeEvent.next(eventArgs);
1382
1382
  if (ratingType === 'positive') {
1383
1383
  this.positiveRatingIcon = thumbUpIcon;
1384
- this.negativeRatingIcon = thumbDownOutlineIcon;
1384
+ this.negativeRatingIcon = thumbDownIcon;
1385
1385
  }
1386
1386
  else {
1387
1387
  this.negativeRatingIcon = thumbDownIcon;
1388
- this.positiveRatingIcon = thumbUpOutlineIcon;
1388
+ this.positiveRatingIcon = thumbUpIcon;
1389
1389
  }
1390
1390
  }
1391
1391
  /**
@@ -1431,19 +1431,19 @@ class AIPromptOutputCardComponent {
1431
1431
  <span class="k-spacer"></span>
1432
1432
  <button kendoButton
1433
1433
  fillMode="flat"
1434
- icon="thumb-up-outline"
1434
+ icon="thumb-up"
1435
1435
  [svgIcon]="positiveRatingIcon"
1436
1436
  (click)="handleRating('positive')">
1437
1437
  </button>
1438
1438
  <button kendoButton
1439
1439
  fillMode="flat"
1440
- icon="thumb-down-outline"
1440
+ icon="thumb-down"
1441
1441
  [svgIcon]="negativeRatingIcon"
1442
1442
  (click)="handleRating('negative')">
1443
1443
  </button>
1444
1444
  }
1445
1445
  </div>
1446
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
1446
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
1447
1447
  }
1448
1448
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AIPromptOutputCardComponent, decorators: [{
1449
1449
  type: Component,
@@ -1485,13 +1485,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1485
1485
  <span class="k-spacer"></span>
1486
1486
  <button kendoButton
1487
1487
  fillMode="flat"
1488
- icon="thumb-up-outline"
1488
+ icon="thumb-up"
1489
1489
  [svgIcon]="positiveRatingIcon"
1490
1490
  (click)="handleRating('positive')">
1491
1491
  </button>
1492
1492
  <button kendoButton
1493
1493
  fillMode="flat"
1494
- icon="thumb-down-outline"
1494
+ icon="thumb-down"
1495
1495
  [svgIcon]="negativeRatingIcon"
1496
1496
  (click)="handleRating('negative')">
1497
1497
  </button>
@@ -1774,7 +1774,7 @@ class PromptViewComponent extends BaseView {
1774
1774
  </div>
1775
1775
  }
1776
1776
  </ng-template>
1777
- `, isInline: true, dependencies: [{ kind: "component", type: TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }] });
1777
+ `, isInline: true, dependencies: [{ kind: "component", type: TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }] });
1778
1778
  }
1779
1779
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PromptViewComponent, decorators: [{
1780
1780
  type: Component,
@@ -2349,6 +2349,86 @@ const defaultModelFields = {
2349
2349
  failedField: 'failed'
2350
2350
  };
2351
2351
 
2352
+ /**
2353
+ * @hidden
2354
+ *
2355
+ * Show the scroll-to-bottom button when the user is this many pixels from the bottom.
2356
+ */
2357
+ const scrollButtonThreshold = 100;
2358
+ /**
2359
+ * @hidden
2360
+ *
2361
+ * Pins a scroll container to a computed target across layout shifts.
2362
+ * Observers extend the idle window until layout has settled; the initial and
2363
+ * final pins bracket the settle window so the viewport lands exactly on target.
2364
+ */
2365
+ const settleScroll = (zone, el, computeTarget, onComplete, idleMs = 120, timeoutMs = 1000) => {
2366
+ const pin = () => {
2367
+ const t = computeTarget();
2368
+ if (t !== null) {
2369
+ el.scrollTop = t;
2370
+ }
2371
+ };
2372
+ pin();
2373
+ zone.runOutsideAngular(() => {
2374
+ let idleTimer = null;
2375
+ let safetyTimer = null;
2376
+ let mutationObserver = null;
2377
+ let resizeObserver = null;
2378
+ let finished = false;
2379
+ const finish = () => {
2380
+ if (finished) {
2381
+ return;
2382
+ }
2383
+ finished = true;
2384
+ if (mutationObserver) {
2385
+ mutationObserver.disconnect();
2386
+ }
2387
+ if (resizeObserver) {
2388
+ resizeObserver.disconnect();
2389
+ }
2390
+ clearTimeout(idleTimer);
2391
+ clearTimeout(safetyTimer);
2392
+ pin();
2393
+ zone.run(() => {
2394
+ onComplete();
2395
+ requestAnimationFrame(pin);
2396
+ });
2397
+ };
2398
+ const resetIdle = () => {
2399
+ clearTimeout(idleTimer);
2400
+ idleTimer = setTimeout(finish, idleMs);
2401
+ };
2402
+ const onChange = () => {
2403
+ pin();
2404
+ resetIdle();
2405
+ };
2406
+ if (typeof MutationObserver !== 'undefined') {
2407
+ mutationObserver = new MutationObserver(onChange);
2408
+ mutationObserver.observe(el, {
2409
+ childList: true,
2410
+ subtree: true,
2411
+ characterData: true,
2412
+ attributes: true,
2413
+ attributeFilter: ['style', 'class', 'hidden']
2414
+ });
2415
+ }
2416
+ if (typeof ResizeObserver !== 'undefined') {
2417
+ resizeObserver = new ResizeObserver(onChange);
2418
+ resizeObserver.observe(el);
2419
+ for (const child of Array.from(el.children)) {
2420
+ resizeObserver.observe(child);
2421
+ }
2422
+ }
2423
+ if (!mutationObserver && !resizeObserver) {
2424
+ finished = true;
2425
+ zone.run(onComplete);
2426
+ return;
2427
+ }
2428
+ safetyTimer = setTimeout(finish, timeoutMs);
2429
+ resetIdle();
2430
+ });
2431
+ };
2352
2432
  /**
2353
2433
  * @hidden
2354
2434
  */
@@ -2396,10 +2476,10 @@ const STB_DEFAULT_SETTINGS$1 = {
2396
2476
  const SEND_BTN_DEFAULT_SETTINGS = {
2397
2477
  rounded: 'full',
2398
2478
  size: 'small',
2399
- icon: 'arrow-up-outline',
2400
- svgIcon: arrowUpOutlineIcon,
2401
- loadingIcon: 'stop-sm',
2402
- loadingSVGIcon: stopSmIcon,
2479
+ icon: 'arrow-up',
2480
+ svgIcon: arrowUpIcon,
2481
+ loadingIcon: 'stop',
2482
+ loadingSVGIcon: stopIcon,
2403
2483
  };
2404
2484
  /**
2405
2485
  * @hidden
@@ -2408,8 +2488,8 @@ const FILESELECT_BUTTON_DEFAULT_SETTINGS$1 = {
2408
2488
  multiple: true,
2409
2489
  disabled: false,
2410
2490
  fillMode: 'clear',
2411
- icon: 'paperclip-outline-alt-right',
2412
- svgIcon: paperclipOutlineAltRightIcon,
2491
+ icon: 'paperclip',
2492
+ svgIcon: paperclipIcon,
2413
2493
  size: 'small',
2414
2494
  rounded: 'full',
2415
2495
  };
@@ -2514,15 +2594,17 @@ const transformActions = (actions) => {
2514
2594
  * @hidden
2515
2595
  */
2516
2596
  class ChatService {
2597
+ zone;
2517
2598
  authorId;
2518
2599
  messageWidthMode;
2519
- messageToolbarActions = [];
2520
2600
  messageContextMenuActions = [];
2521
2601
  calculatedContextMenuActions = [];
2522
2602
  fileActions = [];
2523
2603
  toggleMessageState = false;
2604
+ layoutChangeInProgress = false;
2524
2605
  reply;
2525
2606
  messages = [];
2607
+ repliedToMessages = [];
2526
2608
  chatElement;
2527
2609
  messageElementsMap = new Map();
2528
2610
  messagesContextMenu;
@@ -2540,6 +2622,7 @@ class ChatService {
2540
2622
  _messageBoxSettings = MESSAGE_BOX_SETTINGS;
2541
2623
  _suggestionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2542
2624
  _quickActionsLayout = SUGGESTIONS_LAYOUT_DEFAULT_SETTINGS;
2625
+ _messageToolbarActions = [];
2543
2626
  _authorMessageSettings;
2544
2627
  _receiverMessageSettings;
2545
2628
  _allowMessageCollapse;
@@ -2557,6 +2640,7 @@ class ChatService {
2557
2640
  authorMessageSettingsChange: new Subject(),
2558
2641
  receiverMessageSettingsChange: new Subject(),
2559
2642
  allowMessageCollapseChange: new Subject(),
2643
+ messageToolbarActionsChange: new Subject(),
2560
2644
  };
2561
2645
  toolbarAction$ = this.subjects.toolbarAction.asObservable();
2562
2646
  contextMenuAction$ = this.subjects.contextMenuAction.asObservable();
@@ -2571,6 +2655,10 @@ class ChatService {
2571
2655
  authorMessageSettingsChange$ = this.subjects.authorMessageSettingsChange.asObservable();
2572
2656
  receiverMessageSettingsChange$ = this.subjects.receiverMessageSettingsChange.asObservable();
2573
2657
  allowMessageCollapseChange$ = this.subjects.allowMessageCollapseChange.asObservable();
2658
+ messageToolbarActionsChange$ = this.subjects.messageToolbarActionsChange.asObservable();
2659
+ constructor(zone) {
2660
+ this.zone = zone;
2661
+ }
2574
2662
  set authorMessageSettings(settings) {
2575
2663
  const previousSettings = this._authorMessageSettings;
2576
2664
  if (JSON.stringify(previousSettings) !== JSON.stringify(settings)) {
@@ -2629,6 +2717,13 @@ class ChatService {
2629
2717
  get quickActionsLayout() {
2630
2718
  return this._quickActionsLayout;
2631
2719
  }
2720
+ set messageToolbarActions(value) {
2721
+ this._messageToolbarActions = value;
2722
+ this.emit('messageToolbarActionsChange', this._messageToolbarActions);
2723
+ }
2724
+ get messageToolbarActions() {
2725
+ return this._messageToolbarActions;
2726
+ }
2632
2727
  set allowMessageCollapse(value) {
2633
2728
  const previousValue = this._allowMessageCollapse;
2634
2729
  if (previousValue !== value) {
@@ -2651,7 +2746,8 @@ class ChatService {
2651
2746
  (this.subjects[subjectKey]).next(value);
2652
2747
  }
2653
2748
  getMessageById(id) {
2654
- return this.messages.find(message => message.id === id);
2749
+ return this.messages.find(message => message.id === id)
2750
+ ?? this.repliedToMessages.find(message => message.id === id);
2655
2751
  }
2656
2752
  registerMessageElement(messageId, elementRef) {
2657
2753
  this.messageElementsMap.set(messageId, elementRef);
@@ -2662,26 +2758,25 @@ class ChatService {
2662
2758
  this.reply = null;
2663
2759
  }
2664
2760
  }
2665
- scrollToMessage(messageId) {
2666
- const elementRef = this.messageElementsMap.get(messageId);
2667
- if (!elementRef?.nativeElement) {
2761
+ scrollToMessage(messageId, behavior = 'smooth') {
2762
+ if (!isDocumentAvailable()) {
2668
2763
  return;
2669
2764
  }
2670
- const scrollContainer = this.chatElement?.element?.nativeElement;
2765
+ const scrollContainer = this.getScrollContainer();
2671
2766
  if (!scrollContainer) {
2672
2767
  return;
2673
2768
  }
2674
- const targetElement = elementRef.nativeElement;
2675
- const pinnedElement = scrollContainer.querySelector('.k-message-pinned');
2676
- const pinnedHeight = pinnedElement?.offsetHeight || 0;
2677
- const containerRect = scrollContainer.getBoundingClientRect();
2678
- const targetRect = targetElement.getBoundingClientRect();
2679
- const targetTop = targetRect.top - containerRect.top + scrollContainer.scrollTop;
2680
- const scrollTo = targetTop - pinnedHeight;
2681
- scrollContainer.scrollTo({
2682
- top: scrollTo,
2683
- behavior: 'smooth'
2684
- });
2769
+ const computeTarget = () => this.computeScrollTarget(messageId);
2770
+ const scrollTop = computeTarget();
2771
+ if (scrollTop === null) {
2772
+ return;
2773
+ }
2774
+ if (behavior === 'auto') {
2775
+ settleScroll(this.zone, scrollContainer, computeTarget, () => { });
2776
+ }
2777
+ else {
2778
+ scrollContainer.scrollTo({ top: scrollTop, behavior });
2779
+ }
2685
2780
  }
2686
2781
  focusActiveMessageElement() {
2687
2782
  if (this.activeMessageElement) {
@@ -2691,6 +2786,25 @@ class ChatService {
2691
2786
  isOwnMessage(message) {
2692
2787
  return this.authorId === message.author.id;
2693
2788
  }
2789
+ getScrollContainer() {
2790
+ return this.chatElement?.element?.nativeElement ?? null;
2791
+ }
2792
+ computeScrollTarget(messageId) {
2793
+ const elementRef = this.messageElementsMap.get(messageId);
2794
+ if (!elementRef?.nativeElement) {
2795
+ return null;
2796
+ }
2797
+ const scrollContainer = this.getScrollContainer();
2798
+ if (!scrollContainer) {
2799
+ return null;
2800
+ }
2801
+ const targetElement = elementRef.nativeElement;
2802
+ const pinnedElement = scrollContainer.querySelector('.k-message-pinned');
2803
+ const pinnedHeight = pinnedElement?.offsetHeight || 0;
2804
+ const containerRect = scrollContainer.getBoundingClientRect();
2805
+ const targetRect = targetElement.getBoundingClientRect();
2806
+ return targetRect.top - containerRect.top + scrollContainer.scrollTop - pinnedHeight;
2807
+ }
2694
2808
  updateComponentSettings(property, settings, defaultSettings) {
2695
2809
  if (settings === true) {
2696
2810
  this[property] = defaultSettings;
@@ -2702,17 +2816,17 @@ class ChatService {
2702
2816
  this[property] = { ...defaultSettings, ...settings };
2703
2817
  }
2704
2818
  }
2705
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2819
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
2706
2820
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatService });
2707
2821
  }
2708
2822
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatService, decorators: [{
2709
2823
  type: Injectable
2710
- }] });
2824
+ }], ctorParameters: () => [{ type: i0.NgZone }] });
2711
2825
 
2712
2826
  // Consider scroll to be at the bottom when within this number of pixels from the container height.
2713
2827
  const maxDelta = 2;
2714
- // Show the scroll button when the user is this number of pixels away from the bottom.
2715
- const scrollButtonThreshold = 100;
2828
+ // Edge detection threshold for endless scroll fire loadMore when within this many pixels.
2829
+ const edgeThreshold = 100;
2716
2830
  /**
2717
2831
  * @hidden
2718
2832
  */
@@ -2722,13 +2836,53 @@ class ScrollAnchorDirective {
2722
2836
  renderer;
2723
2837
  cdr;
2724
2838
  autoScroll = true;
2839
+ autoScrollThreshold = '20%';
2840
+ set endlessMode(value) {
2841
+ const changed = this._endlessMode !== value;
2842
+ this._endlessMode = value;
2843
+ this._nearTopLocked = false;
2844
+ this._nearBottomLocked = false;
2845
+ this.scrolling = false;
2846
+ if (changed && !value && this._initialScrollDone && isDocumentAvailable()) {
2847
+ this.element.nativeElement.scrollTo({ top: 0, behavior: 'auto' });
2848
+ this._subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
2849
+ this.performScroll('smooth');
2850
+ }));
2851
+ }
2852
+ }
2853
+ get endlessMode() {
2854
+ return this._endlessMode;
2855
+ }
2856
+ rangeIsAtEnd = true;
2725
2857
  autoScrollChange = new EventEmitter();
2858
+ nearTop = new EventEmitter();
2859
+ nearBottom = new EventEmitter();
2726
2860
  overflowAnchor = 'none';
2861
+ get scrollBehaviorStyle() {
2862
+ return this._endlessMode ? 'auto' : null;
2863
+ }
2727
2864
  showScrollToBottomButton = false;
2728
2865
  showMessageBoxSeparator = false;
2729
2866
  scrolling = false;
2730
2867
  unsubscribe;
2731
2868
  scrollUpdate = null;
2869
+ _previousScrollHeight = 0;
2870
+ _previousScrollTop = 0;
2871
+ _pendingScrollPreservation = false;
2872
+ _endlessMode = false;
2873
+ _nearTopLocked = false;
2874
+ _nearBottomLocked = false;
2875
+ _scrollingToMessage = false;
2876
+ _initialScrollDone = false;
2877
+ _subs = new Subscription();
2878
+ // Need to ensure that the scroll follows a new streaming message
2879
+ // until the scroll threshold is reached.
2880
+ _streamingFollow = false;
2881
+ _thresholdScrollCap = null;
2882
+ _streamingPrevScrollTop = 0;
2883
+ _streamingPrevHeight = 0;
2884
+ _streamingPrevMsgCount = 0;
2885
+ _mutationObserver = null;
2732
2886
  constructor(element, zone, renderer, cdr) {
2733
2887
  this.element = element;
2734
2888
  this.zone = zone;
@@ -2736,15 +2890,54 @@ class ScrollAnchorDirective {
2736
2890
  this.cdr = cdr;
2737
2891
  }
2738
2892
  ngOnInit() {
2893
+ if (!isDocumentAvailable()) {
2894
+ return;
2895
+ }
2739
2896
  this.zone.runOutsideAngular(() => {
2740
- this.unsubscribe = this.renderer.listen(this.element.nativeElement, 'scroll', () => this.setupScrollUpdate(), { passive: true });
2897
+ const el = this.element.nativeElement;
2898
+ this.unsubscribe = this.renderer.listen(el, 'scroll', () => {
2899
+ this.detectStreamingScrollUp();
2900
+ this.setupScrollUpdate();
2901
+ }, { passive: true });
2902
+ this._mutationObserver = new MutationObserver(() => {
2903
+ if (!this._streamingFollow) {
2904
+ return;
2905
+ }
2906
+ const currentMsgCount = el.querySelectorAll('.k-message').length;
2907
+ const currentHeight = el.scrollHeight;
2908
+ if (currentMsgCount === this._streamingPrevMsgCount && currentHeight > this._streamingPrevHeight) {
2909
+ const bottom = currentHeight - el.clientHeight;
2910
+ const cap = this._thresholdScrollCap;
2911
+ const target = cap !== null ? Math.min(bottom, cap) : bottom;
2912
+ if (target > el.scrollTop) {
2913
+ el.scrollTop = target;
2914
+ }
2915
+ this._streamingPrevScrollTop = el.scrollTop;
2916
+ this.updateScrollToBottomButton(el);
2917
+ }
2918
+ this._streamingPrevMsgCount = currentMsgCount;
2919
+ this._streamingPrevHeight = currentHeight;
2920
+ });
2921
+ this._mutationObserver.observe(el, {
2922
+ childList: true,
2923
+ subtree: true,
2924
+ characterData: true,
2925
+ });
2741
2926
  });
2742
2927
  }
2743
2928
  ngAfterViewInit() {
2744
- this.autoScrollToBottom();
2745
- this.zone.onStable.pipe(take(1)).subscribe(() => {
2929
+ this._subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
2930
+ if (this.autoScroll) {
2931
+ if (this._endlessMode) {
2932
+ this.scrollToBottomWithSettle();
2933
+ }
2934
+ else {
2935
+ this.performScroll('smooth');
2936
+ }
2937
+ }
2746
2938
  this.calculateMessageBoxSeparator();
2747
- });
2939
+ this._initialScrollDone = true;
2940
+ }));
2748
2941
  }
2749
2942
  ngOnDestroy() {
2750
2943
  if (this.unsubscribe) {
@@ -2753,8 +2946,16 @@ class ScrollAnchorDirective {
2753
2946
  if (this.scrollUpdate !== null) {
2754
2947
  cancelAnimationFrame(this.scrollUpdate);
2755
2948
  }
2949
+ if (this._mutationObserver) {
2950
+ this._mutationObserver.disconnect();
2951
+ this._mutationObserver = null;
2952
+ }
2953
+ this._subs.unsubscribe();
2756
2954
  }
2757
2955
  onScroll() {
2956
+ if (!isDocumentAvailable() || this._scrollingToMessage) {
2957
+ return;
2958
+ }
2758
2959
  const el = this.element.nativeElement;
2759
2960
  const scrollPosition = el.scrollHeight - el.scrollTop - el.clientHeight;
2760
2961
  const atBottom = scrollPosition < maxDelta;
@@ -2764,7 +2965,8 @@ class ScrollAnchorDirective {
2764
2965
  }
2765
2966
  return;
2766
2967
  }
2767
- const showScrollButton = scrollPosition > scrollButtonThreshold;
2968
+ const showScrollButton = scrollPosition > scrollButtonThreshold
2969
+ || (this.endlessMode && !this.rangeIsAtEnd);
2768
2970
  const autoScrollChanged = this.autoScroll !== atBottom;
2769
2971
  const buttonChanged = this.showScrollToBottomButton !== showScrollButton;
2770
2972
  if (autoScrollChanged || buttonChanged) {
@@ -2779,8 +2981,30 @@ class ScrollAnchorDirective {
2779
2981
  }
2780
2982
  });
2781
2983
  }
2984
+ if (this.endlessMode) {
2985
+ const nearTopEdge = el.scrollTop < edgeThreshold;
2986
+ const nearBottomEdge = !this.rangeIsAtEnd && scrollPosition < edgeThreshold;
2987
+ if (!nearTopEdge) {
2988
+ this._nearTopLocked = false;
2989
+ }
2990
+ if (!nearBottomEdge) {
2991
+ this._nearBottomLocked = false;
2992
+ }
2993
+ if (nearTopEdge && !this._nearTopLocked) {
2994
+ this._nearTopLocked = true;
2995
+ this.zone.run(() => this.nearTop.emit());
2996
+ }
2997
+ if (nearBottomEdge && !this._nearBottomLocked) {
2998
+ this._nearBottomLocked = true;
2999
+ this.zone.run(() => this.nearBottom.emit());
3000
+ }
3001
+ }
2782
3002
  }
2783
3003
  autoScrollToBottom() {
3004
+ if (this._streamingFollow) {
3005
+ this.followStreamingContent();
3006
+ return;
3007
+ }
2784
3008
  if (!this.autoScroll) {
2785
3009
  return;
2786
3010
  }
@@ -2789,9 +3013,80 @@ class ScrollAnchorDirective {
2789
3013
  scrollToBottom() {
2790
3014
  this.autoScroll = true;
2791
3015
  this.showScrollToBottomButton = false;
3016
+ this._streamingFollow = false;
3017
+ this._thresholdScrollCap = null;
2792
3018
  this.performScroll();
2793
3019
  }
3020
+ scrollToBottomWithSettle(onComplete) {
3021
+ if (!isDocumentAvailable()) {
3022
+ onComplete?.();
3023
+ return;
3024
+ }
3025
+ const el = this.element.nativeElement;
3026
+ this._scrollingToMessage = true;
3027
+ this._streamingFollow = false;
3028
+ this._thresholdScrollCap = null;
3029
+ settleScroll(this.zone, el, () => el.scrollHeight - el.clientHeight, () => {
3030
+ this.autoScroll = true;
3031
+ this.showScrollToBottomButton = false;
3032
+ this.scrolling = false;
3033
+ this._scrollingToMessage = false;
3034
+ this.cdr.markForCheck();
3035
+ onComplete?.();
3036
+ });
3037
+ }
3038
+ scrollWithThreshold(messageEl) {
3039
+ if (!isDocumentAvailable() || !messageEl) {
3040
+ this.performScroll();
3041
+ return;
3042
+ }
3043
+ const container = this.element.nativeElement;
3044
+ const pinnedHeight = this.getPinnedBarHeight();
3045
+ const thresholdPx = Math.max(0, this.parseThreshold(pinnedHeight));
3046
+ const containerRect = container.getBoundingClientRect();
3047
+ const messageRect = messageEl.getBoundingClientRect();
3048
+ const msgTopInContent = messageRect.top - containerRect.top + container.scrollTop;
3049
+ const targetScrollTop = Math.max(0, msgTopInContent - thresholdPx - pinnedHeight);
3050
+ const bottomScrollTop = Math.max(0, container.scrollHeight - container.clientHeight);
3051
+ const finalScrollTop = Math.min(targetScrollTop, bottomScrollTop);
3052
+ if (finalScrollTop > container.scrollTop) {
3053
+ container.scrollTo({ top: finalScrollTop, behavior: this._endlessMode ? 'auto' : 'smooth' });
3054
+ }
3055
+ this._thresholdScrollCap = targetScrollTop;
3056
+ this._streamingFollow = true;
3057
+ this._streamingPrevScrollTop = container.scrollTop;
3058
+ this._streamingPrevHeight = container.scrollHeight;
3059
+ this._streamingPrevMsgCount = container.querySelectorAll('.k-message').length;
3060
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
3061
+ this.showScrollToBottomButton = distanceFromBottom > scrollButtonThreshold;
3062
+ this.cdr.markForCheck();
3063
+ }
3064
+ recordScrollHeight() {
3065
+ if (!isDocumentAvailable()) {
3066
+ return;
3067
+ }
3068
+ const el = this.element.nativeElement;
3069
+ this._previousScrollHeight = el.scrollHeight;
3070
+ this._previousScrollTop = el.scrollTop;
3071
+ this._pendingScrollPreservation = true;
3072
+ this.cdr.markForCheck();
3073
+ }
3074
+ preserveScrollPosition() {
3075
+ if (!this._pendingScrollPreservation || !isDocumentAvailable()) {
3076
+ return;
3077
+ }
3078
+ const el = this.element.nativeElement;
3079
+ const delta = el.scrollHeight - this._previousScrollHeight;
3080
+ if (delta > 0) {
3081
+ el.scrollTop = this._previousScrollTop + delta;
3082
+ }
3083
+ this._pendingScrollPreservation = false;
3084
+ this.cdr.markForCheck();
3085
+ }
2794
3086
  calculateMessageBoxSeparator() {
3087
+ if (!isDocumentAvailable()) {
3088
+ return;
3089
+ }
2795
3090
  const el = this.element.nativeElement;
2796
3091
  const shouldShow = el.scrollHeight > el.clientHeight;
2797
3092
  if (this.showMessageBoxSeparator !== shouldShow) {
@@ -2799,6 +3094,90 @@ class ScrollAnchorDirective {
2799
3094
  this.cdr.markForCheck();
2800
3095
  }
2801
3096
  }
3097
+ getDistanceFromBottom() {
3098
+ if (!isDocumentAvailable()) {
3099
+ return 0;
3100
+ }
3101
+ const el = this.element.nativeElement;
3102
+ return el.scrollHeight - el.scrollTop - el.clientHeight;
3103
+ }
3104
+ setAriaLive(value) {
3105
+ if (!isDocumentAvailable()) {
3106
+ return;
3107
+ }
3108
+ this.renderer.setAttribute(this.element.nativeElement, 'aria-live', value);
3109
+ }
3110
+ lockForMessageScroll() {
3111
+ this._scrollingToMessage = true;
3112
+ }
3113
+ unlockForMessageScroll() {
3114
+ this._scrollingToMessage = false;
3115
+ this.setupScrollUpdate();
3116
+ }
3117
+ getAutoScrollThresholdPx() {
3118
+ return Math.max(0, this.parseThreshold(this.getPinnedBarHeight()));
3119
+ }
3120
+ get isFollowingThreshold() {
3121
+ return this._streamingFollow;
3122
+ }
3123
+ followStreamingContent() {
3124
+ if (!isDocumentAvailable()) {
3125
+ return;
3126
+ }
3127
+ const el = this.element.nativeElement;
3128
+ const bottom = el.scrollHeight - el.clientHeight;
3129
+ const cap = this._thresholdScrollCap;
3130
+ const target = cap !== null ? Math.min(bottom, cap) : bottom;
3131
+ if (target > el.scrollTop) {
3132
+ el.scrollTop = target;
3133
+ this._streamingPrevScrollTop = el.scrollTop;
3134
+ }
3135
+ this.updateScrollToBottomButton(el);
3136
+ }
3137
+ updateScrollToBottomButton(el) {
3138
+ const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
3139
+ const shouldShow = distance > scrollButtonThreshold
3140
+ || (this.endlessMode && !this.rangeIsAtEnd);
3141
+ if (this.showScrollToBottomButton !== shouldShow) {
3142
+ this.zone.run(() => {
3143
+ this.showScrollToBottomButton = shouldShow;
3144
+ this.cdr.markForCheck();
3145
+ });
3146
+ }
3147
+ }
3148
+ detectStreamingScrollUp() {
3149
+ if (!this._streamingFollow) {
3150
+ return;
3151
+ }
3152
+ const st = this.element.nativeElement.scrollTop;
3153
+ if (st < this._streamingPrevScrollTop) {
3154
+ this._streamingFollow = false;
3155
+ this._thresholdScrollCap = null;
3156
+ }
3157
+ this._streamingPrevScrollTop = st;
3158
+ }
3159
+ parseThreshold(pinnedHeight = 0) {
3160
+ const threshold = this.autoScrollThreshold;
3161
+ if (typeof threshold === 'number') {
3162
+ return threshold;
3163
+ }
3164
+ if (typeof threshold === 'string') {
3165
+ if (threshold.endsWith('%')) {
3166
+ const percent = parseFloat(threshold) / 100;
3167
+ const visibleHeight = this.element.nativeElement.clientHeight - pinnedHeight;
3168
+ return visibleHeight * percent;
3169
+ }
3170
+ const parsed = parseFloat(threshold);
3171
+ if (!isNaN(parsed)) {
3172
+ return parsed;
3173
+ }
3174
+ }
3175
+ return 0;
3176
+ }
3177
+ getPinnedBarHeight() {
3178
+ const pinned = this.element.nativeElement.querySelector('.k-message-pinned');
3179
+ return pinned ? pinned.offsetHeight : 0;
3180
+ }
2802
3181
  setupScrollUpdate() {
2803
3182
  if (this.scrollUpdate !== null) {
2804
3183
  return;
@@ -2808,13 +3187,20 @@ class ScrollAnchorDirective {
2808
3187
  this.onScroll();
2809
3188
  });
2810
3189
  }
2811
- performScroll() {
3190
+ performScroll(behavior = 'auto') {
3191
+ if (!isDocumentAvailable()) {
3192
+ return;
3193
+ }
2812
3194
  const el = this.element.nativeElement;
2813
- el.scrollTop = el.scrollHeight - el.clientHeight;
3195
+ const target = el.scrollHeight - el.clientHeight;
3196
+ if (Math.abs(el.scrollTop - target) < maxDelta) {
3197
+ return;
3198
+ }
3199
+ el.scrollTo({ top: target, behavior });
2814
3200
  this.scrolling = true;
2815
3201
  }
2816
3202
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScrollAnchorDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
2817
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: ScrollAnchorDirective, isStandalone: true, selector: "[kendoChatScrollAnchor]", inputs: { autoScroll: "autoScroll" }, outputs: { autoScrollChange: "autoScrollChange" }, host: { properties: { "style.overflow-anchor": "this.overflowAnchor" } }, exportAs: ["scrollAnchor"], ngImport: i0 });
3203
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: ScrollAnchorDirective, isStandalone: true, selector: "[kendoChatScrollAnchor]", inputs: { autoScroll: "autoScroll", autoScrollThreshold: "autoScrollThreshold", endlessMode: "endlessMode", rangeIsAtEnd: "rangeIsAtEnd" }, outputs: { autoScrollChange: "autoScrollChange", nearTop: "nearTop", nearBottom: "nearBottom" }, host: { properties: { "style.overflow-anchor": "this.overflowAnchor", "style.scroll-behavior": "this.scrollBehaviorStyle" } }, exportAs: ["scrollAnchor"], ngImport: i0 });
2818
3204
  }
2819
3205
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScrollAnchorDirective, decorators: [{
2820
3206
  type: Directive,
@@ -2825,11 +3211,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
2825
3211
  }]
2826
3212
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }], propDecorators: { autoScroll: [{
2827
3213
  type: Input
3214
+ }], autoScrollThreshold: [{
3215
+ type: Input
3216
+ }], endlessMode: [{
3217
+ type: Input
3218
+ }], rangeIsAtEnd: [{
3219
+ type: Input
2828
3220
  }], autoScrollChange: [{
2829
3221
  type: Output
3222
+ }], nearTop: [{
3223
+ type: Output
3224
+ }], nearBottom: [{
3225
+ type: Output
2830
3226
  }], overflowAnchor: [{
2831
3227
  type: HostBinding,
2832
3228
  args: ['style.overflow-anchor']
3229
+ }], scrollBehaviorStyle: [{
3230
+ type: HostBinding,
3231
+ args: ['style.scroll-behavior']
2833
3232
  }] } });
2834
3233
 
2835
3234
  const DEFAULT_SCROLL_BEHAVIOR = 'smooth';
@@ -2933,6 +3332,93 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
2933
3332
  type: Injectable
2934
3333
  }], ctorParameters: () => [{ type: i0.NgZone }, { type: i1.LocalizationService }] });
2935
3334
 
3335
+ /**
3336
+ * @hidden
3337
+ */
3338
+ class EndlessScrollState {
3339
+ startIndex = 0;
3340
+ endIndex = 0;
3341
+ isLoading = false;
3342
+ get isAtStart() {
3343
+ return this.startIndex === 0;
3344
+ }
3345
+ get isAtEnd() {
3346
+ return this.endIndex >= this._total;
3347
+ }
3348
+ _total = 0;
3349
+ _pageSize = 50;
3350
+ get pageSize() {
3351
+ return this._pageSize;
3352
+ }
3353
+ init(total, pageSize) {
3354
+ this._total = total;
3355
+ this._pageSize = pageSize;
3356
+ return this.jumpToEnd();
3357
+ }
3358
+ extendUp() {
3359
+ if (this.isAtStart || this.isLoading) {
3360
+ return null;
3361
+ }
3362
+ const newStart = Math.max(0, this.startIndex - this._pageSize);
3363
+ if (newStart === this.startIndex) {
3364
+ return null;
3365
+ }
3366
+ this.startIndex = newStart;
3367
+ return { start: this.startIndex, end: this.endIndex };
3368
+ }
3369
+ extendDown() {
3370
+ if (this.isAtEnd || this.isLoading) {
3371
+ return null;
3372
+ }
3373
+ const newEnd = Math.min(this._total, this.endIndex + this._pageSize);
3374
+ if (newEnd === this.endIndex) {
3375
+ return null;
3376
+ }
3377
+ this.endIndex = newEnd;
3378
+ return { start: this.startIndex, end: this.endIndex };
3379
+ }
3380
+ jumpTo(targetIndex) {
3381
+ const half = Math.floor(this._pageSize / 2);
3382
+ let start = targetIndex - half;
3383
+ let end = start + this._pageSize;
3384
+ if (start < 0) {
3385
+ start = 0;
3386
+ end = Math.min(this._pageSize, this._total);
3387
+ }
3388
+ if (end > this._total) {
3389
+ end = this._total;
3390
+ start = Math.max(0, end - this._pageSize);
3391
+ }
3392
+ this.startIndex = start;
3393
+ this.endIndex = end;
3394
+ return { start, end };
3395
+ }
3396
+ jumpToEnd() {
3397
+ const start = Math.max(0, this._total - this._pageSize);
3398
+ const end = this._total;
3399
+ this.startIndex = start;
3400
+ this.endIndex = end;
3401
+ return { start, end };
3402
+ }
3403
+ updateTotal(total) {
3404
+ this._total = total;
3405
+ }
3406
+ reset() {
3407
+ this.startIndex = 0;
3408
+ this.endIndex = 0;
3409
+ this.isLoading = false;
3410
+ this._total = 0;
3411
+ }
3412
+ contains(index) {
3413
+ return index >= this.startIndex && index < this.endIndex;
3414
+ }
3415
+ syncFromInputs(start, end, total) {
3416
+ this.startIndex = start;
3417
+ this.endIndex = end;
3418
+ this._total = total;
3419
+ }
3420
+ }
3421
+
2936
3422
  /**
2937
3423
  * @hidden
2938
3424
  */
@@ -3113,7 +3599,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3113
3599
  * ```html
3114
3600
  * <kendo-promptbox>
3115
3601
  * <kendo-promptbox-end-affix>
3116
- * <button kendoButton look="clear" icon="image"></button>
3602
+ * <button kendoButton fillMode="clear" [svgIcon]="imageIcon"></button>
3117
3603
  * </kendo-promptbox-end-affix>
3118
3604
  * </kendo-promptbox>
3119
3605
  * ```
@@ -3146,7 +3632,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3146
3632
  * ```html
3147
3633
  * <kendo-promptbox>
3148
3634
  * <kendo-promptbox-start-affix>
3149
- * <button kendoButton look="clear" icon="image"></button>
3635
+ * <button kendoButton fillMode="clear" [svgIcon]="imageIcon"></button>
3150
3636
  * </kendo-promptbox-start-affix>
3151
3637
  * </kendo-promptbox>
3152
3638
  * ```
@@ -3179,7 +3665,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3179
3665
  * ```html
3180
3666
  * <kendo-promptbox>
3181
3667
  * <kendo-promptbox-top-affix>
3182
- * <button kendoButton look="clear" icon="image"></button>
3668
+ * <button kendoButton fillMode="clear" [svgIcon]="imageIcon"></button>
3183
3669
  * </kendo-promptbox-top-affix>
3184
3670
  * </kendo-promptbox>
3185
3671
  * ```
@@ -3325,10 +3811,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3325
3811
  const ACTION_BUTTON_DEFAULT_SETTINGS = {
3326
3812
  rounded: 'full',
3327
3813
  size: 'small',
3328
- icon: 'arrow-up-outline',
3329
- svgIcon: arrowUpOutlineIcon,
3330
- loadingIcon: 'stop-sm',
3331
- loadingSVGIcon: stopSmIcon,
3814
+ icon: 'arrow-up',
3815
+ svgIcon: arrowUpIcon,
3816
+ loadingIcon: 'stop',
3817
+ loadingSVGIcon: stopIcon,
3332
3818
  };
3333
3819
  /**
3334
3820
  * @hidden
@@ -3352,8 +3838,8 @@ const FILESELECT_BUTTON_DEFAULT_SETTINGS = {
3352
3838
  fillMode: 'flat',
3353
3839
  size: 'small',
3354
3840
  rounded: 'full',
3355
- icon: 'paperclip-outline-alt-right',
3356
- svgIcon: paperclipOutlineAltRightIcon,
3841
+ icon: 'paperclip',
3842
+ svgIcon: paperclipIcon,
3357
3843
  multiple: true,
3358
3844
  restrictions: null,
3359
3845
  accept: null
@@ -3507,15 +3993,15 @@ class PromptBoxActionButtonComponent extends PromptBoxBaseTool {
3507
3993
  /**
3508
3994
  * Sets the icon to be displayed when the **Action** button is in loading state.
3509
3995
  *
3510
- * @default 'stop-sm'
3996
+ * @default 'stop'
3511
3997
  */
3512
- loadingIcon = 'stop-sm';
3998
+ loadingIcon = 'stop';
3513
3999
  /**
3514
4000
  * Sets the SVG icon to be displayed when the **Action** button is in loading state.
3515
4001
  *
3516
- * @default stopSmIcon
4002
+ * @default stopIcon
3517
4003
  */
3518
- loadingSVGIcon = stopSmIcon;
4004
+ loadingSVGIcon = stopIcon;
3519
4005
  /**
3520
4006
  * Fires when the user clicks the Action button.
3521
4007
  * @hidden
@@ -3527,8 +4013,8 @@ class PromptBoxActionButtonComponent extends PromptBoxBaseTool {
3527
4013
  this.disabled = true;
3528
4014
  this.rounded = 'full';
3529
4015
  this.size = 'small';
3530
- this.icon = 'arrow-up-outline';
3531
- this.svgIcon = arrowUpOutlineIcon;
4016
+ this.icon = 'arrow-up';
4017
+ this.svgIcon = arrowUpIcon;
3532
4018
  }
3533
4019
  /**
3534
4020
  * @hidden
@@ -3566,7 +4052,7 @@ class PromptBoxActionButtonComponent extends PromptBoxBaseTool {
3566
4052
  [themeColor]="themeColor"
3567
4053
  (click)="handleClick()"
3568
4054
  ></button>
3569
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
4055
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
3570
4056
  }
3571
4057
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PromptBoxActionButtonComponent, decorators: [{
3572
4058
  type: Component,
@@ -3656,8 +4142,8 @@ class PromptBoxFileSelectButtonComponent extends PromptBoxBaseTool {
3656
4142
  this.disabled = false;
3657
4143
  this.fillMode = 'flat';
3658
4144
  this.size = 'small';
3659
- this.icon = 'paperclip-outline-alt-right';
3660
- this.svgIcon = paperclipOutlineAltRightIcon;
4145
+ this.icon = 'paperclip';
4146
+ this.svgIcon = paperclipIcon;
3661
4147
  }
3662
4148
  /**
3663
4149
  * @hidden
@@ -3712,7 +4198,7 @@ class PromptBoxFileSelectButtonComponent extends PromptBoxBaseTool {
3712
4198
  [accept]="accept"
3713
4199
  (select)="handleSelect($event)"
3714
4200
  ></kendo-fileselect>
3715
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: FileSelectComponent, selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }] });
4201
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: FileSelectComponent, selector: "kendo-fileselect", inputs: ["name"], outputs: ["valueChange"], exportAs: ["kendoFileSelect"] }] });
3716
4202
  }
3717
4203
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PromptBoxFileSelectButtonComponent, decorators: [{
3718
4204
  type: Component,
@@ -3813,12 +4299,13 @@ class PromptBoxFileComponent {
3813
4299
  [attr.title]="textFor('removeFileTitle')"
3814
4300
  [svgIcon]="deleteIcon"
3815
4301
  icon="x"
4302
+ size="xsmall"
3816
4303
  [disabled]="disabled"
3817
4304
  (click)="remove.emit(promptBoxFile)"
3818
4305
  fillMode="flat"
3819
4306
  ></button>
3820
4307
  }
3821
- `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
4308
+ `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
3822
4309
  }
3823
4310
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PromptBoxFileComponent, decorators: [{
3824
4311
  type: Component,
@@ -3841,6 +4328,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3841
4328
  [attr.title]="textFor('removeFileTitle')"
3842
4329
  [svgIcon]="deleteIcon"
3843
4330
  icon="x"
4331
+ size="xsmall"
3844
4332
  [disabled]="disabled"
3845
4333
  (click)="remove.emit(promptBoxFile)"
3846
4334
  fillMode="flat"
@@ -5413,6 +5901,7 @@ class ChatFileComponent extends ChatItem {
5413
5901
  [attr.title]="textFor('removeFileTitle')"
5414
5902
  [svgIcon]="deleteIcon"
5415
5903
  icon="x"
5904
+ size="xsmall"
5416
5905
  (click)="remove.emit(chatFile)"
5417
5906
  fillMode="flat"
5418
5907
  ></button>
@@ -5422,6 +5911,7 @@ class ChatFileComponent extends ChatItem {
5422
5911
  [data]="fileActions"
5423
5912
  [attr.title]="textFor('fileActionsTitle')"
5424
5913
  fillMode="flat"
5914
+ size="small"
5425
5915
  icon="more-vertical"
5426
5916
  [svgIcon]="moreIcon"
5427
5917
  (itemClick)="actionClick.emit($event)"
@@ -5430,7 +5920,7 @@ class ChatFileComponent extends ChatItem {
5430
5920
  (close)="actionsToggle.emit(false)"
5431
5921
  ></kendo-dropdownbutton>
5432
5922
  }
5433
- `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }] });
5923
+ `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }] });
5434
5924
  }
5435
5925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatFileComponent, decorators: [{
5436
5926
  type: Component,
@@ -5457,6 +5947,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
5457
5947
  [attr.title]="textFor('removeFileTitle')"
5458
5948
  [svgIcon]="deleteIcon"
5459
5949
  icon="x"
5950
+ size="xsmall"
5460
5951
  (click)="remove.emit(chatFile)"
5461
5952
  fillMode="flat"
5462
5953
  ></button>
@@ -5466,6 +5957,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
5466
5957
  [data]="fileActions"
5467
5958
  [attr.title]="textFor('fileActionsTitle')"
5468
5959
  fillMode="flat"
5960
+ size="small"
5469
5961
  icon="more-vertical"
5470
5962
  [svgIcon]="moreIcon"
5471
5963
  (itemClick)="actionClick.emit($event)"
@@ -5538,7 +6030,7 @@ const groupMessages = (acc, msg, isLastMessage) => {
5538
6030
  messages: [msg],
5539
6031
  author: msg.author,
5540
6032
  timestamp: msg.timestamp,
5541
- trackBy: msg
6033
+ trackBy: msg.id
5542
6034
  });
5543
6035
  }
5544
6036
  };
@@ -6557,6 +7049,7 @@ class MessageBoxComponent {
6557
7049
  icon="x"
6558
7050
  (click)="removeReply()"
6559
7051
  fillMode="flat"
7052
+ size="xsmall"
6560
7053
  ></button>
6561
7054
  </div>
6562
7055
  </ng-template>
@@ -6595,7 +7088,7 @@ class MessageBoxComponent {
6595
7088
  } @if (messageBoxTemplate?.templateRef) {
6596
7089
  <ng-template [ngTemplateOutlet]="messageBoxTemplate?.templateRef"></ng-template>
6597
7090
  }
6598
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PromptBoxComponent, selector: "kendo-promptbox", inputs: ["disabled", "focusableId", "loading", "placeholder", "readonly", "title", "mode", "rows", "maxTextAreaHeight", "value", "actionButton", "fileSelectButton", "speechToTextButton"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur", "promptAction", "selectAttachments", "fileRemove", "speechToTextClick", "speechToTextStart", "speechToTextEnd", "speechToTextError", "speechToTextResult"], exportAs: ["kendoPromptBox"] }, { kind: "component", type: PromptBoxCustomMessagesComponent, selector: "kendo-promptbox-messages" }, { kind: "directive", type: PromptBoxHeaderTemplateDirective, selector: "[kendoPromptBoxHeaderTemplate]" }, { kind: "component", type: PromptBoxStartAffixComponent, selector: "kendo-promptbox-start-affix", exportAs: ["kendoPromptBoxStartAffix"] }, { kind: "component", type: PromptBoxTopAffixComponent, selector: "kendo-promptbox-top-affix", exportAs: ["kendoPromptBoxTopAffix"] }, { kind: "component", type: PromptBoxEndAffixComponent, selector: "kendo-promptbox-end-affix", exportAs: ["kendoPromptBoxEndAffix"] }, { kind: "component", type: PromptBoxFileSelectButtonComponent, selector: "kendo-promptbox-fileselect-button", inputs: ["restrictions", "multiple", "accept"], outputs: ["selectAttachments"], exportAs: ["kendoPromptBoxFileSelectButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "type", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }] });
7091
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PromptBoxComponent, selector: "kendo-promptbox", inputs: ["disabled", "focusableId", "loading", "placeholder", "readonly", "title", "mode", "rows", "maxTextAreaHeight", "value", "actionButton", "fileSelectButton", "speechToTextButton"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur", "promptAction", "selectAttachments", "fileRemove", "speechToTextClick", "speechToTextStart", "speechToTextEnd", "speechToTextError", "speechToTextResult"], exportAs: ["kendoPromptBox"] }, { kind: "component", type: PromptBoxCustomMessagesComponent, selector: "kendo-promptbox-messages" }, { kind: "directive", type: PromptBoxHeaderTemplateDirective, selector: "[kendoPromptBoxHeaderTemplate]" }, { kind: "component", type: PromptBoxStartAffixComponent, selector: "kendo-promptbox-start-affix", exportAs: ["kendoPromptBoxStartAffix"] }, { kind: "component", type: PromptBoxTopAffixComponent, selector: "kendo-promptbox-top-affix", exportAs: ["kendoPromptBoxTopAffix"] }, { kind: "component", type: PromptBoxEndAffixComponent, selector: "kendo-promptbox-end-affix", exportAs: ["kendoPromptBoxEndAffix"] }, { kind: "component", type: PromptBoxFileSelectButtonComponent, selector: "kendo-promptbox-fileselect-button", inputs: ["restrictions", "multiple", "accept"], outputs: ["selectAttachments"], exportAs: ["kendoPromptBoxFileSelectButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "component", type: SuggestedActionsComponent, selector: "kendo-chat-suggested-actions", inputs: ["actions", "suggestions", "tabbable", "type", "suggestionTemplate"], outputs: ["dispatchAction", "dispatchSuggestion"] }] });
6599
7092
  }
6600
7093
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MessageBoxComponent, decorators: [{
6601
7094
  type: Component,
@@ -6653,6 +7146,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
6653
7146
  icon="x"
6654
7147
  (click)="removeReply()"
6655
7148
  fillMode="flat"
7149
+ size="xsmall"
6656
7150
  ></button>
6657
7151
  </div>
6658
7152
  </ng-template>
@@ -6763,8 +7257,16 @@ class MessageComponent extends ChatItem {
6763
7257
  chatService;
6764
7258
  localization;
6765
7259
  cdr;
7260
+ zone;
6766
7261
  set message(value) {
7262
+ const textChanged = this._message && value && this._message.text !== value.text;
6767
7263
  this._message = value;
7264
+ if (textChanged) {
7265
+ this.parts = this.getFormattedTextParts(value.text);
7266
+ if (this.isMessageExpandable && !this.showExpandCollapseIcon) {
7267
+ this.updateExpandCollapseIconAfterStable();
7268
+ }
7269
+ }
6768
7270
  }
6769
7271
  get message() {
6770
7272
  return this._message;
@@ -6802,7 +7304,7 @@ class MessageComponent extends ChatItem {
6802
7304
  expandIcon = chevronDownIcon;
6803
7305
  collapseIcon = chevronUpIcon;
6804
7306
  downloadIcon = downloadIcon;
6805
- resendIcon = arrowRotateCwOutlineIcon;
7307
+ resendIcon = arrowRotateCwIcon;
6806
7308
  messageFailedIcon = warningTriangleIcon;
6807
7309
  isMessageExpanded = false;
6808
7310
  showExpandCollapseIcon = false;
@@ -6856,13 +7358,14 @@ class MessageComponent extends ChatItem {
6856
7358
  }
6857
7359
  subs = new Subscription();
6858
7360
  _message;
6859
- constructor(element, intl, chatService, localization, cdr) {
7361
+ constructor(element, intl, chatService, localization, cdr, zone) {
6860
7362
  super();
6861
7363
  this.element = element;
6862
7364
  this.intl = intl;
6863
7365
  this.chatService = chatService;
6864
7366
  this.localization = localization;
6865
7367
  this.cdr = cdr;
7368
+ this.zone = zone;
6866
7369
  }
6867
7370
  ngOnInit() {
6868
7371
  this.fileActions = this.getFileActions();
@@ -6873,14 +7376,20 @@ class MessageComponent extends ChatItem {
6873
7376
  this.subs.add(settingsChange$.subscribe(() => {
6874
7377
  this.fileActions = this.getFileActions();
6875
7378
  this.toolbarActions = this.getToolbarActions();
6876
- setTimeout(() => {
6877
- this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
6878
- });
7379
+ if (this.isMessageExpandable) {
7380
+ this.updateExpandCollapseIconAfterStable();
7381
+ }
6879
7382
  }));
6880
7383
  this.subs.add(this.chatService.allowMessageCollapseChange$.subscribe(() => {
6881
- setTimeout(() => {
6882
- this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
6883
- });
7384
+ if (this.isMessageExpandable) {
7385
+ this.updateExpandCollapseIconAfterStable();
7386
+ }
7387
+ else {
7388
+ this.showExpandCollapseIcon = false;
7389
+ }
7390
+ }));
7391
+ this.subs.add(this.chatService.messageToolbarActionsChange$.subscribe(() => {
7392
+ this.toolbarActions = this.getToolbarActions();
6884
7393
  }));
6885
7394
  if (this.message.id) {
6886
7395
  this.chatService.registerMessageElement(this.message.id, this.element);
@@ -6888,8 +7397,9 @@ class MessageComponent extends ChatItem {
6888
7397
  this.parts = this.getFormattedTextParts(this.message.text);
6889
7398
  }
6890
7399
  ngAfterViewInit() {
6891
- this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
6892
- this.cdr.detectChanges();
7400
+ if (this.isMessageExpandable) {
7401
+ this.updateExpandCollapseIconAfterStable();
7402
+ }
6893
7403
  }
6894
7404
  ngOnDestroy() {
6895
7405
  if (this.message.id) {
@@ -6960,6 +7470,7 @@ class MessageComponent extends ChatItem {
6960
7470
  event.stopImmediatePropagation();
6961
7471
  this.isMessageExpanded = !this.isMessageExpanded;
6962
7472
  this.chatService.toggleMessageState = false;
7473
+ this.cdr.detectChanges();
6963
7474
  }
6964
7475
  onExpandableKeydown(event) {
6965
7476
  const key = normalizeKeys(event);
@@ -7074,7 +7585,13 @@ class MessageComponent extends ChatItem {
7074
7585
  : this.chatService.fileActions || [];
7075
7586
  return transformActions(actions);
7076
7587
  }
7077
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.IntlService }, { token: ChatService }, { token: i1.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
7588
+ updateExpandCollapseIconAfterStable() {
7589
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
7590
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
7591
+ this.cdr.detectChanges();
7592
+ }));
7593
+ }
7594
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.IntlService }, { token: ChatService }, { token: i1.LocalizationService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
7078
7595
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", authorMessageContentTemplate: "authorMessageContentTemplate", receiverMessageContentTemplate: "receiverMessageContentTemplate", messageContentTemplate: "messageContentTemplate", authorMessageTemplate: "authorMessageTemplate", receiverMessageTemplate: "receiverMessageTemplate", messageTemplate: "messageTemplate", statusTemplate: "statusTemplate", showMessageTime: "showMessageTime", authorId: "authorId" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "class.k-message": "this.cssClass", "class.k-message-failed": "this.failedClass", "class.k-message-removed": "this.removedClass", "attr.tabIndex": "this.tabIndex" } }, providers: [
7079
7596
  {
7080
7597
  provide: ChatItem,
@@ -7094,7 +7611,7 @@ class MessageComponent extends ChatItem {
7094
7611
  </div>
7095
7612
  </div>
7096
7613
  } @if (!message.typing) { @if (useCustomContentTemplate) { @if (message.failed) {
7097
- <button kendoButton class="k-resend-button" size="small" fillMode="clear" icon="arrow-rotate-cw-outline" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7614
+ <button kendoButton class="k-resend-button" size="xsmall" fillMode="clear" icon="arrow-rotate-cw" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7098
7615
  }
7099
7616
  <div
7100
7617
  class="k-chat-bubble k-bubble"
@@ -7130,7 +7647,7 @@ class MessageComponent extends ChatItem {
7130
7647
  }
7131
7648
  </div>
7132
7649
  } @if (!useCustomContentTemplate && hasMessageContent) { @if (message.failed) {
7133
- <button kendoButton class="k-resend-button" size="small" fillMode="clear" icon="arrow-rotate-cw-outline" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7650
+ <button kendoButton class="k-resend-button" size="xsmall" fillMode="clear" icon="arrow-rotate-cw" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7134
7651
  }
7135
7652
  <div
7136
7653
  class="k-chat-bubble k-bubble"
@@ -7159,7 +7676,7 @@ class MessageComponent extends ChatItem {
7159
7676
  </span>
7160
7677
  } @if (!message.isDeleted && parts.length > 0) {
7161
7678
  <span class="k-chat-bubble-text">
7162
- @for (part of parts; track part) { @if (part.type === 'text') {{{part.content}}} @if (part.type ===
7679
+ @for (part of parts; track $index) { @if (part.type === 'text') {{{part.content}}} @if (part.type ===
7163
7680
  'link') {<a [href]="part.href" target="_blank">{{ part.content }}</a
7164
7681
  >} }
7165
7682
  </span>
@@ -7172,7 +7689,7 @@ class MessageComponent extends ChatItem {
7172
7689
  'k-files-vertical': chatService.messageFilesLayout === 'vertical'
7173
7690
  }"
7174
7691
  >
7175
- @for (file of message.files; track file) {
7692
+ @for (file of message.files; track file.id) {
7176
7693
  <li
7177
7694
  class="k-file-box"
7178
7695
  [chatFile]="file"
@@ -7186,7 +7703,7 @@ class MessageComponent extends ChatItem {
7186
7703
  } @else {
7187
7704
  <ul class="k-file-box-wrapper">
7188
7705
  <div class="k-files-scroll">
7189
- @for (file of message.files; track file) {
7706
+ @for (file of message.files; track file.id) {
7190
7707
  <li
7191
7708
  class="k-file-box"
7192
7709
  [chatFile]="file"
@@ -7271,8 +7788,8 @@ class MessageComponent extends ChatItem {
7271
7788
  }
7272
7789
  </div>
7273
7790
  } } @if (showToolbar) {
7274
- <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat">
7275
- @for (action of toolbarActions; track action) {
7791
+ <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat" size="small">
7792
+ @for (action of toolbarActions; track action.id) {
7276
7793
  <kendo-toolbar-button
7277
7794
  fillMode="flat"
7278
7795
  [icon]="action.icon"
@@ -7285,7 +7802,7 @@ class MessageComponent extends ChatItem {
7285
7802
  }
7286
7803
  </kendo-toolbar>
7287
7804
  }
7288
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
7805
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
7289
7806
  }
7290
7807
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MessageComponent, decorators: [{
7291
7808
  type: Component,
@@ -7311,7 +7828,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7311
7828
  </div>
7312
7829
  </div>
7313
7830
  } @if (!message.typing) { @if (useCustomContentTemplate) { @if (message.failed) {
7314
- <button kendoButton class="k-resend-button" size="small" fillMode="clear" icon="arrow-rotate-cw-outline" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7831
+ <button kendoButton class="k-resend-button" size="xsmall" fillMode="clear" icon="arrow-rotate-cw" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7315
7832
  }
7316
7833
  <div
7317
7834
  class="k-chat-bubble k-bubble"
@@ -7347,7 +7864,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7347
7864
  }
7348
7865
  </div>
7349
7866
  } @if (!useCustomContentTemplate && hasMessageContent) { @if (message.failed) {
7350
- <button kendoButton class="k-resend-button" size="small" fillMode="clear" icon="arrow-rotate-cw-outline" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7867
+ <button kendoButton class="k-resend-button" size="xsmall" fillMode="clear" icon="arrow-rotate-cw" [svgIcon]="resendIcon" (click)="onResendMessage($event)"></button>
7351
7868
  }
7352
7869
  <div
7353
7870
  class="k-chat-bubble k-bubble"
@@ -7376,7 +7893,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7376
7893
  </span>
7377
7894
  } @if (!message.isDeleted && parts.length > 0) {
7378
7895
  <span class="k-chat-bubble-text">
7379
- @for (part of parts; track part) { @if (part.type === 'text') {{{part.content}}} @if (part.type ===
7896
+ @for (part of parts; track $index) { @if (part.type === 'text') {{{part.content}}} @if (part.type ===
7380
7897
  'link') {<a [href]="part.href" target="_blank">{{ part.content }}</a
7381
7898
  >} }
7382
7899
  </span>
@@ -7389,7 +7906,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7389
7906
  'k-files-vertical': chatService.messageFilesLayout === 'vertical'
7390
7907
  }"
7391
7908
  >
7392
- @for (file of message.files; track file) {
7909
+ @for (file of message.files; track file.id) {
7393
7910
  <li
7394
7911
  class="k-file-box"
7395
7912
  [chatFile]="file"
@@ -7403,7 +7920,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7403
7920
  } @else {
7404
7921
  <ul class="k-file-box-wrapper">
7405
7922
  <div class="k-files-scroll">
7406
- @for (file of message.files; track file) {
7923
+ @for (file of message.files; track file.id) {
7407
7924
  <li
7408
7925
  class="k-file-box"
7409
7926
  [chatFile]="file"
@@ -7488,8 +8005,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7488
8005
  }
7489
8006
  </div>
7490
8007
  } } @if (showToolbar) {
7491
- <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat">
7492
- @for (action of toolbarActions; track action) {
8008
+ <kendo-toolbar class="k-chat-message-toolbar" fillMode="flat" size="small">
8009
+ @for (action of toolbarActions; track action.id) {
7493
8010
  <kendo-toolbar-button
7494
8011
  fillMode="flat"
7495
8012
  [icon]="action.icon"
@@ -7516,7 +8033,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
7516
8033
  ButtonComponent,
7517
8034
  ],
7518
8035
  }]
7519
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { message: [{
8036
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.IntlService }, { type: ChatService }, { type: i1.LocalizationService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }], propDecorators: { message: [{
7520
8037
  type: Input
7521
8038
  }], tabbable: [{
7522
8039
  type: Input
@@ -7816,7 +8333,7 @@ class MessageAttachmentsComponent extends ChatItem {
7816
8333
  >
7817
8334
  </button>
7818
8335
  }
7819
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: AttachmentComponent, selector: "kendo-chat-attachment", inputs: ["attachment", "template"] }] });
8336
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: AttachmentComponent, selector: "kendo-chat-attachment", inputs: ["attachment", "template"] }] });
7820
8337
  }
7821
8338
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MessageAttachmentsComponent, decorators: [{
7822
8339
  type: Component,
@@ -8074,6 +8591,7 @@ class MessageListComponent {
8074
8591
  item.selected = true;
8075
8592
  this.selectedItem = item;
8076
8593
  }
8594
+ this.chatService.layoutChangeInProgress = true;
8077
8595
  this.cdr.detectChanges();
8078
8596
  }
8079
8597
  this.chatService.toggleMessageState = false;
@@ -8238,7 +8756,7 @@ class MessageListComponent {
8238
8756
  <div class="k-message-group-content">
8239
8757
  @if (showGroupAuthor(group)) {
8240
8758
  <p class="k-message-author">{{ group.author.name }}</p>
8241
- } @for (msg of group.messages; track msg; let firstMessage = $first; let lastMessage = $last) { @if
8759
+ } @for (msg of group.messages; track msg.id; let firstMessage = $first; let lastMessage = $last) { @if
8242
8760
  (msg.user?.avatarUrl) {
8243
8761
  <div class="k-avatar">
8244
8762
  <span class="k-avatar-image">
@@ -8346,7 +8864,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
8346
8864
  <div class="k-message-group-content">
8347
8865
  @if (showGroupAuthor(group)) {
8348
8866
  <p class="k-message-author">{{ group.author.name }}</p>
8349
- } @for (msg of group.messages; track msg; let firstMessage = $first; let lastMessage = $last) { @if
8867
+ } @for (msg of group.messages; track msg.id; let firstMessage = $first; let lastMessage = $last) { @if
8350
8868
  (msg.user?.avatarUrl) {
8351
8869
  <div class="k-avatar">
8352
8870
  <span class="k-avatar-image">
@@ -8666,6 +9184,62 @@ class ChatComponent {
8666
9184
  get sendButtonSettings() {
8667
9185
  return this.sendButton;
8668
9186
  }
9187
+ /**
9188
+ * Controls the scrolling behavior of the message list.
9189
+ *
9190
+ * @default 'scrollable'
9191
+ */
9192
+ scrollMode = 'scrollable';
9193
+ /**
9194
+ * Sets the number of messages loaded per page in endless scroll mode.
9195
+ * Ignored in `scrollable` mode.
9196
+ *
9197
+ * @default 50
9198
+ */
9199
+ pageSize = 50;
9200
+ /**
9201
+ * Sets the minimum distance from the top of the visible message area that prevents auto-scrolling when a new receiver message arrives.
9202
+ * Accepts a percentage string (for example, `'30%'`) or a pixel value as a number. Set to `0` to always auto-scroll to new messages.
9203
+ *
9204
+ * Has no effect on author messages, which always scroll to the bottom.
9205
+ *
9206
+ * For more details, refer to the [Auto-Scroll Threshold](slug:scroll_modes_chat#auto-scroll-threshold) article.
9207
+ *
9208
+ * @default '20%'
9209
+ */
9210
+ autoScrollThreshold = '20%';
9211
+ /**
9212
+ * Sets the total number of messages in the conversation when using endless scrolling with remote data source. For more details, refer to the [Endless Scrolling with Remote Data](slug:scroll_modes_chat#remote-data) section in the documentation.
9213
+ */
9214
+ total;
9215
+ /**
9216
+ * Sets the index of the first message in the currently loaded batch within the full conversation.
9217
+ * Used with remote data sources in endless scroll mode to compute the range for the next [`loadMore`](slug:api_conversational-ui_chatcomponent#loadMore) call.
9218
+ * For more details, refer to the [Endless Scrolling with Remote Data](slug:scroll_modes_chat#remote-data) section in the documentation.
9219
+ */
9220
+ startIndex;
9221
+ /**
9222
+ * Sets the exclusive end index of the currently loaded batch within the full conversation.
9223
+ * Used with remote data sources in endless scroll mode to determine whether more messages exist beyond the current batch.
9224
+ * For more details, refer to the [Endless Scrolling with Remote Data](slug:scroll_modes_chat#remote-data) section in the documentation.
9225
+ */
9226
+ endIndex;
9227
+ /**
9228
+ * Sets the full set of pinned messages in the conversation.
9229
+ * Used with remote data sources in endless scroll mode to render the pinned message indicator when the pinned message is outside the currently loaded batch.
9230
+ * For more details, refer to the [Endless Scrolling with Remote Data](slug:scroll_modes_chat#remote-data) section in the documentation.
9231
+ *
9232
+ * @default []
9233
+ */
9234
+ pinnedMessages = [];
9235
+ /**
9236
+ * Sets the messages that serve as reply targets for messages in the currently loaded batch but exist outside it.
9237
+ * Used with remote data sources in endless scroll mode to render reply previews when the replied-to message is not in the current batch.
9238
+ * For more details, refer to the [Endless Scrolling with Remote Data](slug:scroll_modes_chat#remote-data) section in the documentation.
9239
+ *
9240
+ * @default []
9241
+ */
9242
+ repliedToMessages = [];
8669
9243
  /**
8670
9244
  * Sets the names of the model fields from which the Chat reads its data.
8671
9245
  * Lets you map custom data types to the expected `Message` format.
@@ -8743,6 +9317,14 @@ class ChatComponent {
8743
9317
  * Fires when the user types in the message input box.
8744
9318
  */
8745
9319
  inputValueChange = new EventEmitter();
9320
+ /**
9321
+ * Fires when the user scrolls near the edge of the rendered message window in endless scroll mode.
9322
+ */
9323
+ loadMore = new EventEmitter();
9324
+ /**
9325
+ * Fires when the user clicks a referenced message (pinned indicator or reply preview).
9326
+ */
9327
+ referencedMessageClick = new EventEmitter();
8746
9328
  get className() {
8747
9329
  return 'k-chat';
8748
9330
  }
@@ -8793,6 +9375,43 @@ class ChatComponent {
8793
9375
  }
8794
9376
  return this.messages;
8795
9377
  }
9378
+ /**
9379
+ * @hidden
9380
+ * Returns `true` when the Chat is in remote endless scroll mode.
9381
+ * Remote mode is active whenever the consumer provides a `total`.
9382
+ */
9383
+ get isRemoteMode() {
9384
+ return this.scrollMode === 'endless'
9385
+ && isPresent(this.total);
9386
+ }
9387
+ /**
9388
+ * @hidden
9389
+ * Returns the messages to render — sliced in endless mode, full in scrollable mode.
9390
+ * In remote mode, returns processedMessages as-is (consumer already provides only the current batch).
9391
+ */
9392
+ get renderedMessages() {
9393
+ const all = this.processedMessages;
9394
+ if (!all || all.length === 0) {
9395
+ return [];
9396
+ }
9397
+ if (this.scrollMode === 'endless') {
9398
+ if (this.isRemoteMode) {
9399
+ return all;
9400
+ }
9401
+ const start = this.endlessState.startIndex;
9402
+ const end = this.endlessState.endIndex;
9403
+ if (all !== this._renderedMessagesSource ||
9404
+ start !== this._renderedMessagesStart ||
9405
+ end !== this._renderedMessagesEnd) {
9406
+ this._cachedRenderedMessages = all.slice(start, end);
9407
+ this._renderedMessagesSource = all;
9408
+ this._renderedMessagesStart = start;
9409
+ this._renderedMessagesEnd = end;
9410
+ }
9411
+ return this._cachedRenderedMessages;
9412
+ }
9413
+ return all;
9414
+ }
8796
9415
  /**
8797
9416
  * @hidden
8798
9417
  */
@@ -8825,7 +9444,7 @@ class ChatComponent {
8825
9444
  /**
8826
9445
  * @hidden
8827
9446
  */
8828
- pinIcon = pinOutlineIcon;
9447
+ pinIcon = pinIcon;
8829
9448
  /**
8830
9449
  * @hidden
8831
9450
  */
@@ -8837,7 +9456,20 @@ class ChatComponent {
8837
9456
  /**
8838
9457
  * @hidden
8839
9458
  */
8840
- scrollToBottomIcon = arrowDownOutlineIcon;
9459
+ scrollToBottomIcon = arrowDownIcon;
9460
+ /**
9461
+ * @hidden
9462
+ */
9463
+ endlessState = new EndlessScrollState();
9464
+ /**
9465
+ * @hidden
9466
+ */
9467
+ showLicenseWatermark = false;
9468
+ /**
9469
+ * @hidden
9470
+ */
9471
+ licenseMessage;
9472
+ anchor;
8841
9473
  direction;
8842
9474
  subs = new Subscription();
8843
9475
  _modelFields = defaultModelFields;
@@ -8848,13 +9480,27 @@ class ChatComponent {
8848
9480
  _lastModelFields = null;
8849
9481
  _cachedContextMenuActions = [];
8850
9482
  _lastContextMenuActionsReference = null;
9483
+ _previousMessagesLength = 0;
9484
+ _previousLastMessageId = null;
9485
+ _hasProcessedMessages = false;
9486
+ _pendingScrollAction = null;
9487
+ _scrollHandledBeforePaint = false;
9488
+ _lastNewMessageId = null;
9489
+ _cachedRenderedMessages = [];
9490
+ _renderedMessagesSource = null;
9491
+ _renderedMessagesStart = -1;
9492
+ _renderedMessagesEnd = -1;
9493
+ _pendingRemoteScrollToMessageId = null;
9494
+ _remoteScrollToBottom = false;
8851
9495
  constructor(localization, zone, renderer, element, chatService) {
8852
9496
  this.localization = localization;
8853
9497
  this.zone = zone;
8854
9498
  this.renderer = renderer;
8855
9499
  this.element = element;
8856
9500
  this.chatService = chatService;
8857
- validatePackage(packageMetadata);
9501
+ const isValid = validatePackage(packageMetadata);
9502
+ this.licenseMessage = getLicenseMessage(packageMetadata);
9503
+ this.showLicenseWatermark = shouldShowValidationUI(isValid);
8858
9504
  this.direction = localization.rtl ? 'rtl' : 'ltr';
8859
9505
  this.subs.add(localization.changes.subscribe(({ rtl }) => {
8860
9506
  this.direction = rtl ? 'rtl' : 'ltr';
@@ -8900,17 +9546,53 @@ class ChatComponent {
8900
9546
  }));
8901
9547
  this.pinnedMessage = this.findLastPinnedMessage();
8902
9548
  this.chatService.authorId = this.authorId;
9549
+ this.chatService.repliedToMessages = this.repliedToMessages || [];
9550
+ this.initEndlessScroll();
9551
+ this.subs.add(this.chatService.replyReferenceClick$.subscribe((messageId) => {
9552
+ this.handleReferencedMessageClick(messageId, 'reply');
9553
+ }));
8903
9554
  }
8904
9555
  /**
8905
9556
  * @hidden
8906
9557
  */
8907
9558
  ngOnChanges(changes) {
8908
- this.zone.runOutsideAngular(() => setTimeout(() => {
8909
- this.messageList.element.nativeElement.style.flex = '1 1 auto';
8910
- }));
8911
9559
  if (isChanged('messages', changes, false)) {
8912
9560
  this.pinnedMessage = this.findLastPinnedMessage();
8913
9561
  this.chatService.messages = this.processedMessages;
9562
+ if (this.isRemoteMode) {
9563
+ const skipDetectAndSchedule = this.endlessState.isLoading || !!this._pendingRemoteScrollToMessageId;
9564
+ if (!skipDetectAndSchedule) {
9565
+ this.detectNewMessageScrollAction();
9566
+ }
9567
+ this.handleRemoteMessagesChange();
9568
+ if (!skipDetectAndSchedule) {
9569
+ this.scheduleNewMessageScroll();
9570
+ }
9571
+ }
9572
+ else {
9573
+ this.detectNewMessageScrollAction();
9574
+ this.handleMessagesChange();
9575
+ this.scheduleNewMessageScroll();
9576
+ }
9577
+ this.handlePendingRemoteScroll();
9578
+ }
9579
+ if (isChanged('pinnedMessages', changes, false)) {
9580
+ this.pinnedMessage = this.findLastPinnedMessage();
9581
+ }
9582
+ if (isChanged('repliedToMessages', changes, false)) {
9583
+ this.chatService.repliedToMessages = this.repliedToMessages || [];
9584
+ }
9585
+ this.validateRemoteInputs();
9586
+ if (isChanged('scrollMode', changes, false)) {
9587
+ this.initEndlessScroll();
9588
+ }
9589
+ if (isChanged('pageSize', changes, false) && this.scrollMode === 'endless') {
9590
+ this.endlessState.init(this.processedMessages?.length || 0, this.pageSize);
9591
+ }
9592
+ if (this.isRemoteMode && (isChanged('startIndex', changes, false) ||
9593
+ isChanged('endIndex', changes, false) ||
9594
+ isChanged('total', changes, false))) {
9595
+ this.endlessState.syncFromInputs(this.startIndex, this.endIndex, this.total);
8914
9596
  }
8915
9597
  if (isChanged('height', changes, false)) {
8916
9598
  this.renderer.setStyle(this.element.nativeElement, 'height', `${processCssValue(this.height)}`);
@@ -8980,9 +9662,130 @@ class ChatComponent {
8980
9662
  /**
8981
9663
  * @hidden
8982
9664
  */
8983
- scrollToPinnedMessage() {
9665
+ scrollToPinnedMessage(event) {
9666
+ if (event && event.target?.closest('button')) {
9667
+ return;
9668
+ }
8984
9669
  if (this.pinnedMessage) {
8985
- this.chatService.scrollToMessage(this.pinnedMessage?.id);
9670
+ this.handleReferencedMessageClick(this.pinnedMessage.id, 'pinned');
9671
+ }
9672
+ }
9673
+ /**
9674
+ * @hidden
9675
+ */
9676
+ onNearTop() {
9677
+ if (this.scrollMode !== 'endless' || this.endlessState.isLoading) {
9678
+ return;
9679
+ }
9680
+ if (this.isRemoteMode) {
9681
+ if (this.startIndex <= 0) {
9682
+ return;
9683
+ }
9684
+ this.endlessState.isLoading = true;
9685
+ this.anchor?.recordScrollHeight();
9686
+ this.anchor?.setAriaLive('off');
9687
+ const reqStart = Math.max(0, this.startIndex - this.pageSize);
9688
+ this.loadMore.emit({ startIndex: reqStart, endIndex: this.endIndex });
9689
+ return;
9690
+ }
9691
+ const range = this.endlessState.extendUp();
9692
+ if (range) {
9693
+ this.endlessState.isLoading = true;
9694
+ this.anchor?.recordScrollHeight();
9695
+ this.anchor?.setAriaLive('off');
9696
+ this.loadMore.emit({ startIndex: range.start, endIndex: range.end });
9697
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9698
+ this.anchor?.preserveScrollPosition();
9699
+ this.anchor?.setAriaLive('polite');
9700
+ this.endlessState.isLoading = false;
9701
+ }));
9702
+ }
9703
+ }
9704
+ /**
9705
+ * @hidden
9706
+ */
9707
+ onNearBottom() {
9708
+ if (this.scrollMode !== 'endless' || this.endlessState.isLoading) {
9709
+ return;
9710
+ }
9711
+ if (this.isRemoteMode) {
9712
+ if (this.endIndex >= this.total) {
9713
+ return;
9714
+ }
9715
+ this.endlessState.isLoading = true;
9716
+ this.anchor?.setAriaLive('off');
9717
+ const reqEnd = Math.min(this.total, this.endIndex + this.pageSize);
9718
+ this.loadMore.emit({ startIndex: this.startIndex, endIndex: reqEnd });
9719
+ return;
9720
+ }
9721
+ const range = this.endlessState.extendDown();
9722
+ if (range) {
9723
+ this.endlessState.isLoading = true;
9724
+ this.loadMore.emit({ startIndex: range.start, endIndex: range.end });
9725
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9726
+ this.endlessState.isLoading = false;
9727
+ }));
9728
+ }
9729
+ }
9730
+ /**
9731
+ * @hidden
9732
+ */
9733
+ onScrollToBottomClick() {
9734
+ if (this.scrollMode === 'endless' && !this.endlessState.isAtEnd) {
9735
+ if (this.isRemoteMode) {
9736
+ this.endlessState.isLoading = true;
9737
+ this._remoteScrollToBottom = true;
9738
+ this.autoScroll = true;
9739
+ const reqStart = Math.max(0, this.total - this.pageSize);
9740
+ this.loadMore.emit({ startIndex: reqStart, endIndex: this.total });
9741
+ return;
9742
+ }
9743
+ this.anchor?.lockForMessageScroll();
9744
+ const range = this.endlessState.jumpToEnd();
9745
+ this.loadMore.emit({ startIndex: range.start, endIndex: range.end });
9746
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9747
+ this.anchor?.scrollToBottomWithSettle(() => {
9748
+ this.anchor?.unlockForMessageScroll();
9749
+ });
9750
+ }));
9751
+ }
9752
+ else {
9753
+ this.anchor?.scrollToBottom();
9754
+ }
9755
+ }
9756
+ /**
9757
+ * @hidden
9758
+ */
9759
+ handleReferencedMessageClick(messageId, type) {
9760
+ this.referencedMessageClick.emit({ id: messageId, type });
9761
+ this.autoScroll = false;
9762
+ if (this.scrollMode === 'endless') {
9763
+ if (this.isRemoteMode) {
9764
+ this.handleRemoteReferencedMessageClick(messageId);
9765
+ return;
9766
+ }
9767
+ const allMessages = this.processedMessages;
9768
+ const targetIndex = allMessages.findIndex(m => m.id === messageId);
9769
+ if (targetIndex === -1) {
9770
+ return;
9771
+ }
9772
+ if (this.endlessState.contains(targetIndex)) {
9773
+ this.anchor?.lockForMessageScroll();
9774
+ this.chatService.scrollToMessage(messageId, 'auto');
9775
+ this.anchor?.unlockForMessageScroll();
9776
+ }
9777
+ else {
9778
+ this.anchor?.lockForMessageScroll();
9779
+ const range = this.endlessState.jumpTo(targetIndex);
9780
+ this.loadMore.emit({ startIndex: range.start, endIndex: range.end });
9781
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9782
+ this.chatService.scrollToMessage(messageId, 'auto');
9783
+ this.anchor?.unlockForMessageScroll();
9784
+ }));
9785
+ }
9786
+ }
9787
+ else {
9788
+ this.chatService.scrollToMessage(messageId);
8986
9789
  }
8987
9790
  }
8988
9791
  /**
@@ -9022,8 +9825,219 @@ class ChatComponent {
9022
9825
  this.chatService.selectOnMenuClose = false;
9023
9826
  }
9024
9827
  }
9828
+ /**
9829
+ * @hidden
9830
+ */
9831
+ onMessageListResize() {
9832
+ this.anchor?.calculateMessageBoxSeparator();
9833
+ const layoutChange = this.chatService.layoutChangeInProgress;
9834
+ const skipAutoScroll = !!this._pendingScrollAction || this._scrollHandledBeforePaint || layoutChange;
9835
+ if (skipAutoScroll) {
9836
+ this._scrollHandledBeforePaint = false;
9837
+ if (layoutChange) {
9838
+ this.chatService.layoutChangeInProgress = false;
9839
+ this.anchor?.onScroll();
9840
+ }
9841
+ return;
9842
+ }
9843
+ this.anchor?.autoScrollToBottom();
9844
+ }
9845
+ /**
9846
+ * Schedules the new-message scroll adjustment on zone.onStable so it runs
9847
+ * after Angular renders the new DOM but before the browser paints,
9848
+ * preventing a visible intermediate frame.
9849
+ */
9850
+ scheduleNewMessageScroll() {
9851
+ if (!this._pendingScrollAction) {
9852
+ return;
9853
+ }
9854
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9855
+ this.zone.run(() => {
9856
+ const action = this._pendingScrollAction;
9857
+ this._pendingScrollAction = null;
9858
+ this._scrollHandledBeforePaint = true;
9859
+ if (action === 'author') {
9860
+ this._lastNewMessageId = null;
9861
+ this.autoScroll = true;
9862
+ this.anchor?.scrollToBottomWithSettle();
9863
+ return;
9864
+ }
9865
+ if (action === 'receiver') {
9866
+ const messageEl = this.getLastNewMessageElement();
9867
+ this._lastNewMessageId = null;
9868
+ this.autoScroll = true;
9869
+ if (messageEl) {
9870
+ this.anchor?.scrollWithThreshold(messageEl);
9871
+ }
9872
+ else {
9873
+ this.anchor?.scrollToBottom();
9874
+ }
9875
+ }
9876
+ });
9877
+ }));
9878
+ }
9879
+ detectNewMessageScrollAction() {
9880
+ const messages = this.processedMessages;
9881
+ const prevLength = this._previousMessagesLength;
9882
+ const hasNoMessages = !messages;
9883
+ const hasNoNewMessages = messages && messages.length <= prevLength;
9884
+ const isInitialLoad = prevLength === 0 && !this._hasProcessedMessages;
9885
+ const lastMessage = messages?.length > 0 ? messages[messages.length - 1] : null;
9886
+ const currentLastId = lastMessage?.id ?? null;
9887
+ const isLastMessageReplaced = messages?.length === prevLength
9888
+ && currentLastId !== this._previousLastMessageId
9889
+ && this._previousLastMessageId !== null && currentLastId !== null;
9890
+ if (hasNoMessages || (hasNoNewMessages && !isLastMessageReplaced) || isInitialLoad) {
9891
+ this._pendingScrollAction = null;
9892
+ this._lastNewMessageId = null;
9893
+ return;
9894
+ }
9895
+ this._lastNewMessageId = currentLastId;
9896
+ const isAuthor = this.isOwnMessage(lastMessage);
9897
+ if (isAuthor) {
9898
+ this._pendingScrollAction = 'author';
9899
+ return;
9900
+ }
9901
+ if (!this.anchor) {
9902
+ this._pendingScrollAction = null;
9903
+ return;
9904
+ }
9905
+ const distance = this.anchor.getDistanceFromBottom();
9906
+ const threshold = this.anchor.getAutoScrollThresholdPx();
9907
+ const isNearBottom = distance <= Math.max(threshold, scrollButtonThreshold);
9908
+ this._pendingScrollAction = isNearBottom ? 'receiver' : null;
9909
+ }
9910
+ getLastNewMessageElement() {
9911
+ if (this._lastNewMessageId == null) {
9912
+ return null;
9913
+ }
9914
+ const ref = this.chatService.messageElementsMap.get(this._lastNewMessageId);
9915
+ return ref?.nativeElement ?? null;
9916
+ }
9025
9917
  findLastPinnedMessage() {
9026
- return [...this.processedMessages].reverse().find((message) => message.isPinned);
9918
+ const fromMessages = [...this.processedMessages].reverse().find((message) => message.isPinned);
9919
+ if (fromMessages) {
9920
+ return fromMessages;
9921
+ }
9922
+ if (this.isRemoteMode && this.pinnedMessages?.length) {
9923
+ return [...this.pinnedMessages].reverse().find((message) => message.isPinned);
9924
+ }
9925
+ return undefined;
9926
+ }
9927
+ initEndlessScroll() {
9928
+ if (this.scrollMode === 'endless') {
9929
+ if (this.isRemoteMode) {
9930
+ this.endlessState.syncFromInputs(this.startIndex ?? 0, this.endIndex ?? 0, this.total ?? 0);
9931
+ }
9932
+ else {
9933
+ this.endlessState.init(this.processedMessages?.length || 0, this.pageSize);
9934
+ }
9935
+ this._previousMessagesLength = this.processedMessages?.length || 0;
9936
+ }
9937
+ else {
9938
+ this.endlessState.reset();
9939
+ this._remoteScrollToBottom = false;
9940
+ this._pendingRemoteScrollToMessageId = null;
9941
+ }
9942
+ }
9943
+ handleMessagesChange() {
9944
+ if (this.scrollMode !== 'endless') {
9945
+ const msgs = this.processedMessages;
9946
+ this._previousMessagesLength = msgs?.length || 0;
9947
+ this._previousLastMessageId = msgs?.length > 0 ? msgs[msgs.length - 1]?.id ?? null : null;
9948
+ this._hasProcessedMessages = true;
9949
+ return;
9950
+ }
9951
+ const allMessages = this.processedMessages || [];
9952
+ const wasAtEnd = this.endlessState.isAtEnd;
9953
+ const isNewMessageAppended = allMessages.length > this._previousMessagesLength && this._previousMessagesLength > 0;
9954
+ const isSameLength = allMessages.length === this._previousMessagesLength;
9955
+ const shouldScroll = this._pendingScrollAction !== null;
9956
+ if (isNewMessageAppended) {
9957
+ this.endlessState.updateTotal(allMessages.length);
9958
+ if (shouldScroll) {
9959
+ if (!wasAtEnd) {
9960
+ const range = this.endlessState.jumpToEnd();
9961
+ this.loadMore.emit({ startIndex: range.start, endIndex: range.end });
9962
+ }
9963
+ else {
9964
+ this.endlessState.endIndex = allMessages.length;
9965
+ }
9966
+ }
9967
+ }
9968
+ else if (!isSameLength) {
9969
+ this.endlessState.init(allMessages.length, this.pageSize);
9970
+ }
9971
+ this._previousMessagesLength = allMessages.length;
9972
+ this._previousLastMessageId = allMessages.length > 0 ? allMessages[allMessages.length - 1]?.id ?? null : null;
9973
+ this._hasProcessedMessages = true;
9974
+ }
9975
+ handleRemoteMessagesChange() {
9976
+ const wasLoading = this.endlessState.isLoading;
9977
+ const prevLength = this._previousMessagesLength;
9978
+ const currentLength = this.processedMessages?.length || 0;
9979
+ const isInitialBatch = prevLength === 0 && currentLength > 0;
9980
+ if (wasLoading) {
9981
+ this.endlessState.isLoading = false;
9982
+ if (this._remoteScrollToBottom) {
9983
+ this._remoteScrollToBottom = false;
9984
+ this._scrollHandledBeforePaint = true;
9985
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9986
+ this.anchor?.scrollToBottomWithSettle();
9987
+ }));
9988
+ }
9989
+ else {
9990
+ this._scrollHandledBeforePaint = true;
9991
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
9992
+ this.anchor?.preserveScrollPosition();
9993
+ this.anchor?.setAriaLive('polite');
9994
+ }));
9995
+ }
9996
+ }
9997
+ else if (isInitialBatch && !this._pendingScrollAction) {
9998
+ this.autoScroll = true;
9999
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
10000
+ this.anchor?.scrollToBottom();
10001
+ this.anchor?.calculateMessageBoxSeparator();
10002
+ }));
10003
+ }
10004
+ else if (currentLength > 0 && !this._pendingScrollAction) {
10005
+ const lastMessage = this.processedMessages[currentLength - 1];
10006
+ const isNowAtEnd = this.endIndex >= this.total;
10007
+ if (isNowAtEnd && this.isOwnMessage(lastMessage)) {
10008
+ this._pendingScrollAction = 'author';
10009
+ }
10010
+ }
10011
+ this._previousMessagesLength = currentLength;
10012
+ this._previousLastMessageId = currentLength > 0
10013
+ ? this.processedMessages[currentLength - 1]?.id ?? null
10014
+ : null;
10015
+ if (currentLength > 0) {
10016
+ this._hasProcessedMessages = true;
10017
+ }
10018
+ }
10019
+ handleRemoteReferencedMessageClick(messageId) {
10020
+ const messageEl = this.chatService.messageElementsMap.get(messageId);
10021
+ if (messageEl) {
10022
+ this.anchor?.lockForMessageScroll();
10023
+ this.chatService.scrollToMessage(messageId, 'auto');
10024
+ this.anchor?.unlockForMessageScroll();
10025
+ return;
10026
+ }
10027
+ this._pendingRemoteScrollToMessageId = messageId;
10028
+ }
10029
+ handlePendingRemoteScroll() {
10030
+ if (!this._pendingRemoteScrollToMessageId) {
10031
+ return;
10032
+ }
10033
+ const messageId = this._pendingRemoteScrollToMessageId;
10034
+ this._pendingRemoteScrollToMessageId = null;
10035
+ this._scrollHandledBeforePaint = true;
10036
+ this.subs.add(this.zone.onStable.pipe(take(1)).subscribe(() => {
10037
+ this.anchor?.lockForMessageScroll();
10038
+ this.chatService.scrollToMessage(messageId, 'auto');
10039
+ this.anchor?.unlockForMessageScroll();
10040
+ }));
9027
10041
  }
9028
10042
  updateChatServiceProperties(propNames, changes) {
9029
10043
  propNames.forEach(propName => {
@@ -9032,6 +10046,34 @@ class ChatComponent {
9032
10046
  }
9033
10047
  });
9034
10048
  }
10049
+ validateRemoteInputs() {
10050
+ if (!isDevMode()) {
10051
+ return;
10052
+ }
10053
+ const hasRemoteInputs = isPresent(this.total) || isPresent(this.startIndex) || isPresent(this.endIndex);
10054
+ if (hasRemoteInputs && this.scrollMode !== 'endless') {
10055
+ console.warn('[kendo-chat] [total], [startIndex], and [endIndex] are only used when [scrollMode] is \'endless\'.');
10056
+ return;
10057
+ }
10058
+ if (isPresent(this.total) && (!isPresent(this.startIndex) || !isPresent(this.endIndex))) {
10059
+ console.warn('[kendo-chat] Remote mode requires [startIndex] and [endIndex] when [total] is set.');
10060
+ }
10061
+ if (isPresent(this.startIndex) && this.startIndex < 0) {
10062
+ console.warn('[kendo-chat] [startIndex] must not be negative.');
10063
+ }
10064
+ if (isPresent(this.startIndex) && isPresent(this.endIndex) && this.startIndex > this.endIndex) {
10065
+ console.warn(`[kendo-chat] [startIndex] (${this.startIndex}) must not exceed [endIndex] (${this.endIndex}).`);
10066
+ }
10067
+ if (isPresent(this.endIndex) && isPresent(this.total) && this.endIndex > this.total) {
10068
+ console.warn(`[kendo-chat] [endIndex] (${this.endIndex}) must not exceed [total] (${this.total}).`);
10069
+ }
10070
+ if (isPresent(this.startIndex) && isPresent(this.endIndex) && this.messages) {
10071
+ const expectedLength = this.endIndex - this.startIndex;
10072
+ if (this.messages.length !== expectedLength) {
10073
+ console.warn(`[kendo-chat] messages.length (${this.messages.length}) does not match the range [startIndex]–[endIndex] (expected ${expectedLength}).`);
10074
+ }
10075
+ }
10076
+ }
9035
10077
  mergeWithDefaultActions(actions, defaultActions) {
9036
10078
  if (!actions || actions.length === 0) {
9037
10079
  return [];
@@ -9045,7 +10087,7 @@ class ChatComponent {
9045
10087
  });
9046
10088
  }
9047
10089
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatComponent, deps: [{ token: i1.LocalizationService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: ChatService }], target: i0.ɵɵFactoryTarget.Component });
9048
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: ChatComponent, isStandalone: true, selector: "kendo-chat", inputs: { messages: "messages", authorId: "authorId", messageBoxType: "messageBoxType", height: "height", width: "width", placeholder: "placeholder", messageWidthMode: "messageWidthMode", timestampVisibility: "timestampVisibility", showUsername: "showUsername", showAvatar: "showAvatar", allowMessageCollapse: "allowMessageCollapse", speechToTextButton: "speechToTextButton", fileSelectButton: "fileSelectButton", messageBoxSettings: "messageBoxSettings", messageToolbarActions: "messageToolbarActions", inputValue: "inputValue", authorMessageSettings: "authorMessageSettings", receiverMessageSettings: "receiverMessageSettings", messageContextMenuActions: "messageContextMenuActions", fileActions: "fileActions", messageFilesLayout: "messageFilesLayout", suggestionsLayout: "suggestionsLayout", quickActionsLayout: "quickActionsLayout", suggestions: "suggestions", sendButton: "sendButton", sendButtonSettings: "sendButtonSettings", modelFields: "modelFields", scrollToBottomButton: "scrollToBottomButton", loading: "loading" }, outputs: { sendMessage: "sendMessage", resendMessage: "resendMessage", toolbarActionClick: "toolbarActionClick", contextMenuActionClick: "contextMenuActionClick", fileActionClick: "fileActionClick", download: "download", executeAction: "executeAction", suggestionExecute: "suggestionExecute", fileSelect: "fileSelect", fileRemove: "fileRemove", unpin: "unpin", inputValueChange: "inputValueChange" }, host: { properties: { "class": "this.className", "attr.dir": "this.dirAttr" } }, providers: [
10090
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: ChatComponent, isStandalone: true, selector: "kendo-chat", inputs: { messages: "messages", authorId: "authorId", messageBoxType: "messageBoxType", height: "height", width: "width", placeholder: "placeholder", messageWidthMode: "messageWidthMode", timestampVisibility: "timestampVisibility", showUsername: "showUsername", showAvatar: "showAvatar", allowMessageCollapse: "allowMessageCollapse", speechToTextButton: "speechToTextButton", fileSelectButton: "fileSelectButton", messageBoxSettings: "messageBoxSettings", messageToolbarActions: "messageToolbarActions", inputValue: "inputValue", authorMessageSettings: "authorMessageSettings", receiverMessageSettings: "receiverMessageSettings", messageContextMenuActions: "messageContextMenuActions", fileActions: "fileActions", messageFilesLayout: "messageFilesLayout", suggestionsLayout: "suggestionsLayout", quickActionsLayout: "quickActionsLayout", suggestions: "suggestions", sendButton: "sendButton", sendButtonSettings: "sendButtonSettings", scrollMode: "scrollMode", pageSize: "pageSize", autoScrollThreshold: "autoScrollThreshold", total: "total", startIndex: "startIndex", endIndex: "endIndex", pinnedMessages: "pinnedMessages", repliedToMessages: "repliedToMessages", modelFields: "modelFields", scrollToBottomButton: "scrollToBottomButton", loading: "loading" }, outputs: { sendMessage: "sendMessage", resendMessage: "resendMessage", toolbarActionClick: "toolbarActionClick", contextMenuActionClick: "contextMenuActionClick", fileActionClick: "fileActionClick", download: "download", executeAction: "executeAction", suggestionExecute: "suggestionExecute", fileSelect: "fileSelect", fileRemove: "fileRemove", unpin: "unpin", inputValueChange: "inputValueChange", loadMore: "loadMore", referencedMessageClick: "referencedMessageClick" }, host: { properties: { "class": "this.className", "attr.dir": "this.dirAttr" } }, providers: [
9049
10091
  LocalizationService,
9050
10092
  ChatService,
9051
10093
  SuggestionsScrollService,
@@ -9053,7 +10095,7 @@ class ChatComponent {
9053
10095
  provide: L10N_PREFIX,
9054
10096
  useValue: 'kendo.chat',
9055
10097
  },
9056
- ], queries: [{ propertyName: "attachmentTemplate", first: true, predicate: AttachmentTemplateDirective, descendants: true }, { propertyName: "chatHeaderTemplate", first: true, predicate: ChatHeaderTemplateDirective, descendants: true }, { propertyName: "chatNoDataTemplate", first: true, predicate: NoDataTemplateDirective, descendants: true }, { propertyName: "authorMessageContentTemplate", first: true, predicate: AuthorMessageContentTemplateDirective, descendants: true }, { propertyName: "receiverMessageContentTemplate", first: true, predicate: ReceiverMessageContentTemplateDirective, descendants: true }, { propertyName: "messageContentTemplate", first: true, predicate: MessageContentTemplateDirective, descendants: true }, { propertyName: "authorMessageTemplate", first: true, predicate: AuthorMessageTemplateDirective, descendants: true }, { propertyName: "receiverMessageTemplate", first: true, predicate: ReceiverMessageTemplateDirective, descendants: true }, { propertyName: "messageTemplate", first: true, predicate: MessageTemplateDirective, descendants: true }, { propertyName: "timestampTemplate", first: true, predicate: ChatTimestampTemplateDirective, descendants: true }, { propertyName: "suggestionTemplate", first: true, predicate: ChatSuggestionTemplateDirective, descendants: true }, { propertyName: "statusTemplate", first: true, predicate: ChatStatusTemplateDirective, descendants: true }, { propertyName: "messageBoxTemplate", first: true, predicate: ChatMessageBoxTemplateDirective, descendants: true }, { propertyName: "messageBoxStartAffixTemplate", first: true, predicate: ChatMessageBoxStartAffixTemplateDirective, descendants: true }, { propertyName: "messageBoxEndAffixTemplate", first: true, predicate: ChatMessageBoxEndAffixTemplateDirective, descendants: true }, { propertyName: "messageBoxTopAffixTemplate", first: true, predicate: ChatMessageBoxTopAffixTemplateDirective, descendants: true }, { propertyName: "userStatusTemplate", first: true, predicate: ChatUserStatusTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "messagesContextMenu", first: true, predicate: ["messagesContextMenu"], descendants: true }, { propertyName: "messageBox", first: true, predicate: ["messageBox"], descendants: true }, { propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
10098
+ ], queries: [{ propertyName: "attachmentTemplate", first: true, predicate: AttachmentTemplateDirective, descendants: true }, { propertyName: "chatHeaderTemplate", first: true, predicate: ChatHeaderTemplateDirective, descendants: true }, { propertyName: "chatNoDataTemplate", first: true, predicate: NoDataTemplateDirective, descendants: true }, { propertyName: "authorMessageContentTemplate", first: true, predicate: AuthorMessageContentTemplateDirective, descendants: true }, { propertyName: "receiverMessageContentTemplate", first: true, predicate: ReceiverMessageContentTemplateDirective, descendants: true }, { propertyName: "messageContentTemplate", first: true, predicate: MessageContentTemplateDirective, descendants: true }, { propertyName: "authorMessageTemplate", first: true, predicate: AuthorMessageTemplateDirective, descendants: true }, { propertyName: "receiverMessageTemplate", first: true, predicate: ReceiverMessageTemplateDirective, descendants: true }, { propertyName: "messageTemplate", first: true, predicate: MessageTemplateDirective, descendants: true }, { propertyName: "timestampTemplate", first: true, predicate: ChatTimestampTemplateDirective, descendants: true }, { propertyName: "suggestionTemplate", first: true, predicate: ChatSuggestionTemplateDirective, descendants: true }, { propertyName: "statusTemplate", first: true, predicate: ChatStatusTemplateDirective, descendants: true }, { propertyName: "messageBoxTemplate", first: true, predicate: ChatMessageBoxTemplateDirective, descendants: true }, { propertyName: "messageBoxStartAffixTemplate", first: true, predicate: ChatMessageBoxStartAffixTemplateDirective, descendants: true }, { propertyName: "messageBoxEndAffixTemplate", first: true, predicate: ChatMessageBoxEndAffixTemplateDirective, descendants: true }, { propertyName: "messageBoxTopAffixTemplate", first: true, predicate: ChatMessageBoxTopAffixTemplateDirective, descendants: true }, { propertyName: "userStatusTemplate", first: true, predicate: ChatUserStatusTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "messagesContextMenu", first: true, predicate: ["messagesContextMenu"], descendants: true }, { propertyName: "messageBox", first: true, predicate: ["messageBox"], descendants: true }, { propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
9057
10099
  <ng-container
9058
10100
  kendoChatLocalizedMessages
9059
10101
  i18n-deletedMessageSenderText="
@@ -9123,9 +10165,7 @@ class ChatComponent {
9123
10165
  kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions
9124
10166
  "
9125
10167
  nextSuggestionsButtonTitle="Scroll right"
9126
- i18n-unpinMessageTitle="
9127
- kendo.chat.unpinMessageTitle|The title of the button that unpins a pinned message
9128
- "
10168
+ i18n-unpinMessageTitle="kendo.chat.unpinMessageTitle|The title of the button that unpins a pinned message"
9129
10169
  unpinMessageTitle="Unpin message"
9130
10170
  >
9131
10171
  </ng-container>
@@ -9144,18 +10184,31 @@ class ChatComponent {
9144
10184
  [attr.aria-label]="textFor('messageListLabel')"
9145
10185
  #anchor="scrollAnchor"
9146
10186
  [(autoScroll)]="autoScroll"
10187
+ [autoScrollThreshold]="autoScrollThreshold"
10188
+ [endlessMode]="scrollMode === 'endless'"
10189
+ [rangeIsAtEnd]="endlessState.isAtEnd"
10190
+ (nearTop)="onNearTop()"
10191
+ (nearBottom)="onNearBottom()"
9147
10192
  >
9148
10193
  @if (pinnedMessage) {
9149
10194
  <div
9150
10195
  class="k-message-reference k-message-pinned"
9151
10196
  [class.k-message-reference-receiver]="!isOwnMessage(pinnedMessage)"
9152
10197
  [class.k-message-reference-sender]="isOwnMessage(pinnedMessage)"
9153
- (click)="scrollToPinnedMessage()"
10198
+ (click)="scrollToPinnedMessage($event)"
9154
10199
  >
9155
10200
  <kendo-icon-wrapper name="pin" [svgIcon]="pinIcon"> </kendo-icon-wrapper>
9156
10201
  <chat-message-reference-content [message]="pinnedMessage"></chat-message-reference-content>
9157
10202
  <span class="k-spacer"></span>
9158
- <button kendoButton icon="x" [svgIcon]="deleteIcon" [attr.title]="textFor('unpinMessageTitle')" (click)="unpin.emit(pinnedMessage)" fillMode="flat"></button>
10203
+ <button
10204
+ kendoButton
10205
+ icon="x"
10206
+ [svgIcon]="deleteIcon"
10207
+ [attr.title]="textFor('unpinMessageTitle')"
10208
+ (click)="unpin.emit(pinnedMessage)"
10209
+ fillMode="flat"
10210
+ size="xsmall"
10211
+ ></button>
9159
10212
  </div>
9160
10213
  } @if (processedMessages && processedMessages.length === 0) {
9161
10214
  <div class="k-message-list-content k-message-list-content-empty">
@@ -9163,7 +10216,7 @@ class ChatComponent {
9163
10216
  </div>
9164
10217
  } @else {
9165
10218
  <kendo-chat-message-list
9166
- [messages]="processedMessages"
10219
+ [messages]="renderedMessages"
9167
10220
  [authorMessageContentTemplate]="authorMessageContentTemplate"
9168
10221
  [receiverMessageContentTemplate]="receiverMessageContentTemplate"
9169
10222
  [messageContentTemplate]="messageContentTemplate"
@@ -9177,7 +10230,7 @@ class ChatComponent {
9177
10230
  [attachmentTemplate]="attachmentTemplate"
9178
10231
  [authorId]="authorId"
9179
10232
  (executeAction)="dispatchAction($event)"
9180
- (resize)="anchor.autoScrollToBottom(); anchor.calculateMessageBoxSeparator()"
10233
+ (resize)="onMessageListResize()"
9181
10234
  (navigate)="this.autoScroll = false"
9182
10235
  >
9183
10236
  </kendo-chat-message-list>
@@ -9186,11 +10239,11 @@ class ChatComponent {
9186
10239
  <kendo-floatingactionbutton
9187
10240
  positionMode="absolute"
9188
10241
  [size]="'small'"
9189
- [themeColor]="'light'"
10242
+ [themeColor]="'base'"
9190
10243
  [align]="{ horizontal: 'center' }"
9191
10244
  icon="arrow-down-outline"
9192
10245
  [svgIcon]="scrollToBottomIcon"
9193
- (click)="anchor.scrollToBottom()"
10246
+ (click)="onScrollToBottomClick()"
9194
10247
  ></kendo-floatingactionbutton>
9195
10248
  </div>
9196
10249
  }
@@ -9227,7 +10280,11 @@ class ChatComponent {
9227
10280
  (popupClose)="handleMenuClose($event)"
9228
10281
  (select)="onContextMenuAction($event.item.originalAction)"
9229
10282
  ></kendo-contextmenu>
9230
- `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$2, selector: "[kendoChatLocalizedMessages]" }, { kind: "directive", type: ScrollAnchorDirective, selector: "[kendoChatScrollAnchor]", inputs: ["autoScroll"], outputs: ["autoScrollChange"], exportAs: ["scrollAnchor"] }, { kind: "component", type: MessageListComponent, selector: "kendo-chat-message-list", inputs: ["messages", "attachmentTemplate", "authorMessageContentTemplate", "receiverMessageContentTemplate", "messageContentTemplate", "authorMessageTemplate", "receiverMessageTemplate", "messageTemplate", "timestampTemplate", "statusTemplate", "userStatusTemplate", "localization", "authorId"], outputs: ["executeAction", "navigate", "resize"] }, { kind: "component", type: MessageBoxComponent, selector: "kendo-message-box", inputs: ["authorId", "autoScroll", "suggestions", "placeholder", "inputValue", "localization", "messageBoxTemplate", "messageBoxStartAffixTemplate", "messageBoxEndAffixTemplate", "messageBoxTopAffixTemplate", "suggestionTemplate", "loading"], outputs: ["sendMessage", "executeSuggestion", "fileSelect", "fileRemove"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: FloatingActionButtonComponent, selector: "kendo-floatingactionbutton", inputs: ["themeColor", "size", "rounded", "disabled", "align", "offset", "positionMode", "icon", "svgIcon", "iconClass", "buttonClass", "dialClass", "text", "dialItemAnimation", "tabIndex", "dialItems"], outputs: ["blur", "focus", "dialItemClick", "open", "close"] }] });
10283
+
10284
+ @if (showLicenseWatermark) {
10285
+ <div kendoWatermarkOverlay [licenseMessage]="licenseMessage"></div>
10286
+ }
10287
+ `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective$2, selector: "[kendoChatLocalizedMessages]" }, { kind: "directive", type: ScrollAnchorDirective, selector: "[kendoChatScrollAnchor]", inputs: ["autoScroll", "autoScrollThreshold", "endlessMode", "rangeIsAtEnd"], outputs: ["autoScrollChange", "nearTop", "nearBottom"], exportAs: ["scrollAnchor"] }, { kind: "component", type: MessageListComponent, selector: "kendo-chat-message-list", inputs: ["messages", "attachmentTemplate", "authorMessageContentTemplate", "receiverMessageContentTemplate", "messageContentTemplate", "authorMessageTemplate", "receiverMessageTemplate", "messageTemplate", "timestampTemplate", "statusTemplate", "userStatusTemplate", "localization", "authorId"], outputs: ["executeAction", "navigate", "resize"] }, { kind: "component", type: MessageBoxComponent, selector: "kendo-message-box", inputs: ["authorId", "autoScroll", "suggestions", "placeholder", "inputValue", "localization", "messageBoxTemplate", "messageBoxStartAffixTemplate", "messageBoxEndAffixTemplate", "messageBoxTopAffixTemplate", "suggestionTemplate", "loading"], outputs: ["sendMessage", "executeSuggestion", "fileSelect", "fileRemove"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: FloatingActionButtonComponent, selector: "kendo-floatingactionbutton", inputs: ["themeColor", "size", "rounded", "disabled", "align", "offset", "positionMode", "icon", "svgIcon", "iconClass", "buttonClass", "dialClass", "text", "dialItemAnimation", "tabIndex", "dialItems"], outputs: ["blur", "focus", "dialItemClick", "open", "close"] }, { kind: "component", type: WatermarkOverlayComponent, selector: "div[kendoWatermarkOverlay], kendo-watermark-overlay", inputs: ["licenseMessage"] }] });
9231
10288
  }
9232
10289
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ChatComponent, decorators: [{
9233
10290
  type: Component,
@@ -9312,9 +10369,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9312
10369
  kendo.chat.nextSuggestionsButtonTitle|The title of the button that scrolls to the next suggestions
9313
10370
  "
9314
10371
  nextSuggestionsButtonTitle="Scroll right"
9315
- i18n-unpinMessageTitle="
9316
- kendo.chat.unpinMessageTitle|The title of the button that unpins a pinned message
9317
- "
10372
+ i18n-unpinMessageTitle="kendo.chat.unpinMessageTitle|The title of the button that unpins a pinned message"
9318
10373
  unpinMessageTitle="Unpin message"
9319
10374
  >
9320
10375
  </ng-container>
@@ -9333,18 +10388,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9333
10388
  [attr.aria-label]="textFor('messageListLabel')"
9334
10389
  #anchor="scrollAnchor"
9335
10390
  [(autoScroll)]="autoScroll"
10391
+ [autoScrollThreshold]="autoScrollThreshold"
10392
+ [endlessMode]="scrollMode === 'endless'"
10393
+ [rangeIsAtEnd]="endlessState.isAtEnd"
10394
+ (nearTop)="onNearTop()"
10395
+ (nearBottom)="onNearBottom()"
9336
10396
  >
9337
10397
  @if (pinnedMessage) {
9338
10398
  <div
9339
10399
  class="k-message-reference k-message-pinned"
9340
10400
  [class.k-message-reference-receiver]="!isOwnMessage(pinnedMessage)"
9341
10401
  [class.k-message-reference-sender]="isOwnMessage(pinnedMessage)"
9342
- (click)="scrollToPinnedMessage()"
10402
+ (click)="scrollToPinnedMessage($event)"
9343
10403
  >
9344
10404
  <kendo-icon-wrapper name="pin" [svgIcon]="pinIcon"> </kendo-icon-wrapper>
9345
10405
  <chat-message-reference-content [message]="pinnedMessage"></chat-message-reference-content>
9346
10406
  <span class="k-spacer"></span>
9347
- <button kendoButton icon="x" [svgIcon]="deleteIcon" [attr.title]="textFor('unpinMessageTitle')" (click)="unpin.emit(pinnedMessage)" fillMode="flat"></button>
10407
+ <button
10408
+ kendoButton
10409
+ icon="x"
10410
+ [svgIcon]="deleteIcon"
10411
+ [attr.title]="textFor('unpinMessageTitle')"
10412
+ (click)="unpin.emit(pinnedMessage)"
10413
+ fillMode="flat"
10414
+ size="xsmall"
10415
+ ></button>
9348
10416
  </div>
9349
10417
  } @if (processedMessages && processedMessages.length === 0) {
9350
10418
  <div class="k-message-list-content k-message-list-content-empty">
@@ -9352,7 +10420,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9352
10420
  </div>
9353
10421
  } @else {
9354
10422
  <kendo-chat-message-list
9355
- [messages]="processedMessages"
10423
+ [messages]="renderedMessages"
9356
10424
  [authorMessageContentTemplate]="authorMessageContentTemplate"
9357
10425
  [receiverMessageContentTemplate]="receiverMessageContentTemplate"
9358
10426
  [messageContentTemplate]="messageContentTemplate"
@@ -9366,7 +10434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9366
10434
  [attachmentTemplate]="attachmentTemplate"
9367
10435
  [authorId]="authorId"
9368
10436
  (executeAction)="dispatchAction($event)"
9369
- (resize)="anchor.autoScrollToBottom(); anchor.calculateMessageBoxSeparator()"
10437
+ (resize)="onMessageListResize()"
9370
10438
  (navigate)="this.autoScroll = false"
9371
10439
  >
9372
10440
  </kendo-chat-message-list>
@@ -9375,11 +10443,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9375
10443
  <kendo-floatingactionbutton
9376
10444
  positionMode="absolute"
9377
10445
  [size]="'small'"
9378
- [themeColor]="'light'"
10446
+ [themeColor]="'base'"
9379
10447
  [align]="{ horizontal: 'center' }"
9380
10448
  icon="arrow-down-outline"
9381
10449
  [svgIcon]="scrollToBottomIcon"
9382
- (click)="anchor.scrollToBottom()"
10450
+ (click)="onScrollToBottomClick()"
9383
10451
  ></kendo-floatingactionbutton>
9384
10452
  </div>
9385
10453
  }
@@ -9416,6 +10484,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9416
10484
  (popupClose)="handleMenuClose($event)"
9417
10485
  (select)="onContextMenuAction($event.item.originalAction)"
9418
10486
  ></kendo-contextmenu>
10487
+
10488
+ @if (showLicenseWatermark) {
10489
+ <div kendoWatermarkOverlay [licenseMessage]="licenseMessage"></div>
10490
+ }
9419
10491
  `,
9420
10492
  standalone: true,
9421
10493
  imports: [
@@ -9429,6 +10501,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9429
10501
  ButtonComponent,
9430
10502
  ContextMenuComponent,
9431
10503
  FloatingActionButtonComponent,
10504
+ WatermarkOverlayComponent,
9432
10505
  ],
9433
10506
  }]
9434
10507
  }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: ChatService }], propDecorators: { messages: [{
@@ -9483,6 +10556,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9483
10556
  type: Input
9484
10557
  }], sendButtonSettings: [{
9485
10558
  type: Input
10559
+ }], scrollMode: [{
10560
+ type: Input
10561
+ }], pageSize: [{
10562
+ type: Input
10563
+ }], autoScrollThreshold: [{
10564
+ type: Input
10565
+ }], total: [{
10566
+ type: Input
10567
+ }], startIndex: [{
10568
+ type: Input
10569
+ }], endIndex: [{
10570
+ type: Input
10571
+ }], pinnedMessages: [{
10572
+ type: Input
10573
+ }], repliedToMessages: [{
10574
+ type: Input
9486
10575
  }], modelFields: [{
9487
10576
  type: Input
9488
10577
  }], scrollToBottomButton: [{
@@ -9513,6 +10602,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9513
10602
  type: Output
9514
10603
  }], inputValueChange: [{
9515
10604
  type: Output
10605
+ }], loadMore: [{
10606
+ type: Output
10607
+ }], referencedMessageClick: [{
10608
+ type: Output
9516
10609
  }], className: [{
9517
10610
  type: HostBinding,
9518
10611
  args: ['class']
@@ -9579,6 +10672,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
9579
10672
  }], messageList: [{
9580
10673
  type: ViewChild,
9581
10674
  args: ['messageList', { static: true, read: ViewContainerRef }]
10675
+ }], anchor: [{
10676
+ type: ViewChild,
10677
+ args: ['anchor']
9582
10678
  }] } });
9583
10679
 
9584
10680
  // eslint-disable no-forward-ref
@@ -9699,7 +10795,7 @@ class HeroCardComponent {
9699
10795
  </span>
9700
10796
  }
9701
10797
  </div>
9702
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
10798
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
9703
10799
  }
9704
10800
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: HeroCardComponent, decorators: [{
9705
10801
  type: Component,
@@ -9855,8 +10951,8 @@ const defaultOutputActions = [{
9855
10951
  {
9856
10952
  name: 'discard',
9857
10953
  type: 'button',
9858
- icon: 'cancel-outline',
9859
- svgIcon: cancelOutlineIcon,
10954
+ icon: 'cancel',
10955
+ svgIcon: cancelIcon,
9860
10956
  text: 'Discard',
9861
10957
  fillMode: 'flat',
9862
10958
  }
@@ -9928,7 +11024,7 @@ class InlineAIPromptContentComponent {
9928
11024
  calculateMeasurement = calculateMeasurement;
9929
11025
  commandMenuIcon = menuIcon;
9930
11026
  sendIcon = paperPlaneIcon;
9931
- stopGenerationIcon = stopSmIcon;
11027
+ stopGenerationIcon = stopIcon;
9932
11028
  isListening = false;
9933
11029
  commandMenuItems = [];
9934
11030
  messages = {};
@@ -10185,7 +11281,7 @@ class InlineAIPromptContentComponent {
10185
11281
  (click)="handlePromptRequest()"
10186
11282
  [disabled]="!streaming && (!promptValue?.trim() || isListening)"
10187
11283
  [svgIcon]="streaming ? stopGenerationIcon : sendIcon"
10188
- [icon]="streaming ? 'stop-sm' : 'paper-plane'"
11284
+ [icon]="streaming ? 'stop' : 'paper-plane'"
10189
11285
  ></button>
10190
11286
  </kendo-textarea-suffix>
10191
11287
  </kendo-textarea>
@@ -10199,7 +11295,7 @@ class InlineAIPromptContentComponent {
10199
11295
  class="k-hidden"
10200
11296
  (select)="onCommandClick($event)">
10201
11297
  </kendo-contextmenu>
10202
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoInlineAIPromptLocalizedMessages]" }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i2.SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: i3.TextAreaPrefixComponent, selector: "kendo-textarea-prefix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaPrefix"] }, { kind: "component", type: i3.TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: i4.ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: i5.CardComponent, selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i5.CardActionsComponent, selector: "kendo-card-actions", inputs: ["orientation", "layout", "actions"], outputs: ["action"] }, { kind: "component", type: i5.CardBodyComponent, selector: "kendo-card-body" }] });
11298
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoInlineAIPromptLocalizedMessages]" }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i2.SpeechToTextButtonComponent, selector: "button[kendoSpeechToTextButton]", inputs: ["disabled", "size", "rounded", "fillMode", "themeColor", "integrationMode", "lang", "continuous", "interimResults", "maxAlternatives"], outputs: ["start", "end", "result", "error", "click"], exportAs: ["kendoSpeechToTextButton"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "component", type: i3.TextAreaPrefixComponent, selector: "kendo-textarea-prefix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaPrefix"] }, { kind: "component", type: i3.TextAreaSuffixComponent, selector: "kendo-textarea-suffix", inputs: ["flow", "orientation"], exportAs: ["kendoTextAreaSuffix"] }, { kind: "component", type: i4.ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: i5.CardComponent, selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i5.CardActionsComponent, selector: "kendo-card-actions", inputs: ["orientation", "layout", "actions"], outputs: ["action"] }, { kind: "component", type: i5.CardBodyComponent, selector: "kendo-card-body" }] });
10203
11299
  }
10204
11300
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: InlineAIPromptContentComponent, decorators: [{
10205
11301
  type: Component,
@@ -10306,7 +11402,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
10306
11402
  (click)="handlePromptRequest()"
10307
11403
  [disabled]="!streaming && (!promptValue?.trim() || isListening)"
10308
11404
  [svgIcon]="streaming ? stopGenerationIcon : sendIcon"
10309
- [icon]="streaming ? 'stop-sm' : 'paper-plane'"
11405
+ [icon]="streaming ? 'stop' : 'paper-plane'"
10310
11406
  ></button>
10311
11407
  </kendo-textarea-suffix>
10312
11408
  </kendo-textarea>
@@ -10487,7 +11583,7 @@ class InlineAIPromptComponent {
10487
11583
  * The default actions are `copy`, `retry`, and `discard`.
10488
11584
  * To customize the appearance and order of the default actions, define them with the same `name`.
10489
11585
  *
10490
- * @default [{ name: 'copy', type: 'button', icon: 'copy', svgIcon: copyIcon, text: 'Copy', fillMode: 'flat', themeColor: 'primary', rounded: 'medium'}, { name: 'retry', type: 'button', icon: 'arrow-rotate-cw', svgIcon: arrowRotateCwIcon, text: 'Retry', fillMode: 'flat', themeColor: 'primary', rounded: 'medium'}, { name: 'discard', type: 'button', icon: 'cancel-outline', svgIcon: cancelOutlineIcon, text: 'Discard', fillMode: 'flat', themeColor: 'base', rounded: 'medium'}]
11586
+ * @default [{ name: 'copy', type: 'button', icon: 'copy', svgIcon: copyIcon, text: 'Copy', fillMode: 'flat', themeColor: 'primary', rounded: 'medium'}, { name: 'retry', type: 'button', icon: 'arrow-rotate-cw', svgIcon: arrowRotateCwIcon, text: 'Retry', fillMode: 'flat', themeColor: 'primary', rounded: 'medium'}, { name: 'discard', type: 'button', icon: 'cancel', svgIcon: cancelIcon, text: 'Discard', fillMode: 'flat', themeColor: 'base', rounded: 'medium'}]
10491
11587
  */
10492
11588
  outputActions = defaultOutputActions;
10493
11589
  /**