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

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